1<?php
2
3/**
4 * e107 website system
5 *
6 * Copyright (C) 2008-2017 e107 Inc (e107.org)
7 * Released under the terms and conditions of the
8 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
9 *
10 * @file
11 * JS Manager.
12 */
13
14
15/**
16 * Class e_jsmanager.
17 */
18class e_jsmanager
19{
20	/**
21	 * Supported Libraries (Front-End) - loaded on demand.
22	 */
23	protected $_libraries = array(
24		'prototype'	=> array(), // TODO remove prototype completely.
25		'jquery'	=> array(),
26	);
27
28	/**
29	 * Dynamic List of files to be cached.
30	 * @var array
31	 */
32	protected $_cache_list = array();
33
34
35
36	protected $_core_prefs = array();
37
38	/**
39	 * Array to store JavaScript options will be rendered in footer as JSON object.
40	 *
41	 * @var array
42	 */
43	protected $_e_js_settings = array(
44		'basePath' => e_HTTP,
45	);
46
47    /**
48     * Core JS library files, loaded via e_jslib.php
49     *
50     * @var array
51     */
52    protected $_e_jslib_core = array();
53
54    /**
55     * Plugin JS library files, loaded via e_jslib.php
56     *
57     * @var array
58     */
59    protected $_e_jslib_plugin = array();
60
61    /**
62     * Theme JS library files, loaded via e_jslib.php
63     *
64     * @var array
65     */
66    protected $_e_jslib_theme = array();
67
68    /**
69     * JS files array - loaded in page header
70     *
71     * @var array
72     */
73    protected $_runtime_header = array();
74
75    /**
76     * JS code array - loaded in page header
77     * after all registered JS header files
78     *
79     * @var array
80     */
81    protected $_runtime_header_src = array();
82
83    /**
84     * Current Header zone (under development)
85     *
86     * @var array
87     */
88    protected $_zone_header = 0;
89
90    /**
91     * Current Footer zone (under development)
92     *
93     * @var array
94     */
95    protected $_zone_footer = 0;
96
97    /**
98     * JS files array - loaded in page footer
99     *
100     * @var array
101     */
102    protected $_runtime_footer = array();
103
104    /**
105     * JS code array - loaded in page footer
106     *
107     * @var array
108     */
109    protected $_runtime_footer_src = array();
110
111    /**
112     * Index of all registered JS/CSS files - for faster searching
113     *
114     * @var array
115     */
116    protected $_index_all = array();
117
118   /**
119     * Registered link tags files by type (core|theme|plugin|other)
120     *
121     * @var array
122     */
123	protected $_e_link = array();
124
125
126    /**
127     * Registered CSS files by type (core|theme|plugin|other)
128     *
129     * @var array
130     */
131    protected $_e_css = array();
132
133    /**
134     * Inline CSS
135     *
136     * @var array
137     */
138    protected $_e_css_src = array();
139
140
141    /**
142     * Runtime location
143     *
144     * @var boolean
145     */
146    protected $_in_admin = false;
147
148	/**
149	 * Browser cache id
150	 *
151	 * @var integer
152	 */
153	protected $_browser_cache_id = 0;
154
155	/**
156	 * @var array
157	 */
158	protected $_lastModified = array();
159
160	/**
161	 * Singleton instance
162	 * Allow class extends - override {@link getInstance()}
163	 *
164	 * @var e_jsmanager
165	 */
166	protected static $_instance = null;
167
168	/**
169	 * Current Framework Dependency
170	 *
171	 * @var string null | prototype | jquery
172	 */
173	protected $_dependence = null;
174
175	/**
176	 * Loaded Framework Dependency
177	 *
178	 * @var array
179	 */
180	protected $_dependenceLoaded = array();
181
182
183	protected $_cache_enabled = false;
184
185
186	protected $_sep = '#|#';
187
188	/**
189	 * Constructor
190	 *
191	 * Use {@link getInstance()}, direct instantiating
192	 * is not possible for signleton objects
193	 */
194	protected function __construct()
195	{
196	}
197
198	/**
199	 * Cloning is not allowed
200	 *
201	 */
202	private function __clone()
203	{
204	}
205
206	/**
207	 * Get singleton instance
208	 *
209	 * @return e_jsmanager
210	 */
211	public static function getInstance()
212	{
213		if(null === self::$_instance)
214		{
215		    self::$_instance = new self();
216			self::$_instance->_init();
217		}
218	  	return self::$_instance;
219	}
220
221	/**
222	 * Get and parse core preference values (if available)
223	 *
224	 * @return void
225	 */
226	protected function _init()
227	{
228		// Try to auto-detect runtime location
229		$this->setInAdmin(defset('e_ADMIN_AREA', false));
230
231		if($this->isInAdmin()) // Admin Area.
232		{
233			e107::library('load', 'jquery');
234			// jQuery Once is used in e107.behaviors.
235			e107::library('load', 'jquery.once');
236			e107::library('load', 'jquery.ui');
237		}
238		else // Front-End.
239		{
240			e107::library('load', 'jquery');
241			// jQuery Once is used in e107.behaviors.
242			e107::library('load', 'jquery.once');
243		}
244
245		// TODO
246		// jQuery is the only JS framework, and it is always loaded. So remove
247		// unnecessary code here below.
248
249		$customJqueryUrls = e107::getPref('library-jquery-urls');
250		$this->_cache_enabled = e107::getPref('jscsscachestatus',false);
251
252		if(vartrue($customJqueryUrls) && $this->_in_admin === false)
253		{
254			$this->_libraries['jquery'] = explode("\n", $customJqueryUrls);
255		}
256
257		// Try to load browser cache id from core preferences
258		$this->setCacheId(e107::getPref('e_jslib_browser_cache', 0));
259
260		// Load stored in preferences core lib paths ASAP
261		$this->_core_prefs = e107::getPref('e_jslib_core');
262		$core = array();
263
264		if(is_array($this->_core_prefs))
265		{
266			foreach($this->_core_prefs as $id=>$vis)
267			{
268				$this->_dependence = $id;
269
270				if(!$this->libDisabled($id,$vis))
271				{
272					if(vartrue($this->_libraries[$id]))
273					{
274						foreach($this->_libraries[$id] as $path)
275						{
276							$core[$path] = $vis;
277						}
278					}
279
280				}
281
282			}
283		}
284		$this->_dependence = null;
285
286		if($vis != 'auto')
287		{
288			$this->checkLibDependence(null, $core);
289		}
290
291		// Load stored in preferences plugin lib paths ASAP
292		$plug_libs = e107::getPref('e_jslib_plugin');
293		if(!$plug_libs)
294		{
295			$plug_libs = array();
296		}
297		foreach ($plug_libs as $plugname => $lib_paths)
298		{
299			$this->pluginLib($plugname, $lib_paths);
300		}
301
302		// Load stored in preferences theme lib paths ASAP
303		// TODO - decide if THEME should directly use themeLib() or
304		// we store paths in 'e_jslib_theme' on theme installation only (theme.xml)!
305		$theme_libs = e107::getPref('e_jslib_theme');
306		if(!$theme_libs)
307		{
308			$theme_libs = array();
309		}
310		$this->themeLib($theme_libs);
311	}
312
313	/**
314	 * Add Core CSS file for inclusion in site header, shorthand of headerFile() method
315	 *
316	 * @param string|array $file_path relative to {e_JS} folder
317	 * @param string $media any valid media attribute string - http://www.w3schools.com/TAGS/att_link_media.asp
318	 * @return e_jsmanager
319	 */
320	public function coreCSS($file_path, $media = 'all', $preComment = '', $postComment = '')
321	{
322		$this->addJs('core_css', $file_path, $media);
323		return $this;
324	}
325
326	/**
327	 * Add Plugin CSS file(s) for inclusion in site header
328	 *
329	 * @param string $plugname
330	 * @param string|array $file_path relative to e107_plugins/myplug/ folder or array in format 'path - media'
331	 * @param string $media any valid media attribute string - http://www.w3schools.com/TAGS/att_link_media.asp
332	 * @return e_jsmanager
333	 */
334	public function pluginCSS($plugname, $file_path, $media = 'all', $preComment = '', $postComment = '')
335	{
336		if(is_array($file_path))
337		{
338			foreach ($file_path as $fpath => $media_attr)
339			{
340				$this->addJs('plugin_css', $plugname.':'.$fpath, $media_attr, $preComment, $postComment);
341			}
342			return $this;
343		}
344		$this->addJs('plugin_css', $plugname.':'.$file_path, $media, $preComment, $postComment);
345		return $this;
346	}
347
348	/**
349	 * Add Theme CSS file(s) for inclusion in site header
350	 *
351	 * @param string|array $file_path relative to e107_themes/current_theme/ folder
352	 * @param string $media any valid media attribute string - http://www.w3schools.com/TAGS/att_link_media.asp
353	 * @return e_jsmanager
354	 */
355	public function themeCSS($file_path, $media = 'all', $preComment = '', $postComment = '')
356	{
357		$this->addJs('theme_css', $file_path, $media, $preComment, $postComment);
358		return $this;
359	}
360
361	/**
362	 * Add CSS file(s) for inclusion in site header in the 'library' category.
363	 *
364	 * @param string|array $file_path path, shortcodes usage is prefered
365	 * @param string $media any valid media attribute string - http://www.w3schools.com/TAGS/att_link_media.asp
366	 * @return e_jsmanager
367	 */
368	public function libraryCSS($file_path, $media = 'all', $preComment = '', $postComment = '')
369	{
370		$this->addJs('library_css', $file_path, $media, $preComment, $postComment);
371		return $this;
372	}
373
374
375
376	/**
377	 * Add CSS file(s) for inclusion in site header
378	 *
379	 * @param string|array $file_path path, shortcodes usage is prefered
380	 * @param string $media any valid media attribute string - http://www.w3schools.com/TAGS/att_link_media.asp
381	 * @return e_jsmanager
382	 */
383	public function otherCSS($file_path, $media = 'all', $preComment = '', $postComment = '')
384	{
385		$this->addJs('other_css', $file_path, $media, $preComment, $postComment);
386		return $this;
387	}
388
389	/**
390	 * Add CSS code to site header
391	 *
392	 * @param string|array $js_content
393	 * @param string $media (not implemented yet) any valid media attribute string - http://www.w3schools.com/TAGS/att_link_media.asp
394	 * @return e_jsmanager
395	 */
396	public function inlineCSS($css_content, $media = 'all')
397	{
398		$this->addJs('inline_css', $css_content, $media);
399		return $this;
400	}
401
402
403	/**
404	 * Add Core JS library file(s) for inclusion from e_jslib routine
405	 *
406	 * @param string|array $file_path relative to e107_files/jslib/ folder or array in format 'path - runtime location'
407	 * @param string $runtime_location  admin|front|all - where should be JS used
408	 * @return e_jsmanager
409	 */
410	protected function coreLib($file_path, $runtime_location = 'front')
411	{
412		$this->addJs('core', $file_path, $runtime_location);
413		return $this;
414	}
415
416	/**
417	 * Add Plugin JS library file(s) for inclusion from e_jslib routine
418	 *
419	 * @param string $plugname
420	 * @param string|array $file_path relative to e107_plugins/myplug/ folder or array in format 'path - runtime location'
421	 * @param string $runtime_location admin|front|all - where should be JS used
422	 * @return e_jsmanager
423	 */
424	protected function pluginLib($plugname, $file_path, $runtime_location = 'front')
425	{
426		if(is_array($file_path))
427		{
428			foreach ($file_path as $fpath => $rlocation)
429			{
430				$this->addJs('plugin', $plugname.':'.$fpath, $rlocation);
431			}
432			return $this;
433		}
434		$this->addJs('plugin', $plugname.':'.$file_path, $runtime_location);
435		return $this;
436	}
437
438	/**
439	 * Add Theme JS library file(s) for inclusion from e_jslib routine
440	 *
441	 * @param string|array $file_path relative to e107_themes/current_theme/ folder or array in format 'path - runtime location'
442	 * @param string $runtime_location admin|front|all - where should be JS used
443	 * @return e_jsmanager
444	 */
445	public function themeLib($file_path, $runtime_location = 'front')
446	{
447		$this->addJs('theme', $file_path, $runtime_location);
448		return $this;
449	}
450
451	/**
452	 * Add Core JS library file(s) for inclusion in site header or site footer (in this order) if not
453	 * already loaded by e_jslib routine. This should avoid dependency problems.
454	 * Extremely useful for shortcodes and menus.
455	 *
456	 * @param string $file_path relative to e107_files/jslib/ folder
457	 * @param integer $zone 1-5 (see header.php)
458	 * @return e_jsmanager
459	 */
460	public function requireCoreLib($file_path, $zone = 2)
461	{
462		if(is_array($file_path))
463		{
464			foreach ($file_path as $fpath => $z)
465			{
466				$this->tryHeaderFile('{e_WEB_JS}'.trim($fpath, '/'), $z);
467			}
468			return $this;
469		}
470		$this->tryHeaderFile('{e_WEB_JS}'.trim($file_path, '/'), $zone);
471		return $this;
472	}
473
474	/**
475	 * Add Plugin JS library file(s) for inclusion in site header if not
476	 * already loaded by e_jslib routine. This should avoid dependency problems.
477	 *
478	 * @param string $plugname
479	 * @param string $file_path relative to e107_plugins/myplug/ folder
480	 * @param integer $zone 1-5 (see header.php)
481	 * @return e_jsmanager
482	 */
483	public function requirePluginLib($plugname, $file_path, $zone = 5)
484	{
485		if(is_array($file_path))
486		{
487			foreach ($file_path as $fpath => $z)
488			{
489				$this->tryHeaderFile('{e_PLUGIN}'.$plugname.'/'.trim($fpath, '/'), $z);
490			}
491			return $this;
492		}
493		$this->tryHeaderFile('{e_PLUGIN}'.$plugname.'/'.trim($file_path, '/'), $zone);
494		return $this;
495	}
496
497	/**
498	 * Add JS file(s) for inclusion in site header
499	 *
500	 * @param string|array $file_path path shortcodes usage is prefered
501	 * @param integer $zone 1-5 (see header.php)
502	 * @return e_jsmanager
503	 */
504	public function headerFile($file_path, $zone = 5, $pre = '', $post = '')
505	{
506		$this->addJs('header', $file_path, $zone, $pre, $post);
507		return $this;
508	}
509
510	/**
511	 * Add Core JS file for inclusion in site header, shorthand of headerFile() method
512	 *
513	 * @param string $file_path relative to {e_JS} folder
514	 * @param integer $zone 1-5 (see header.php)
515	 * @return e_jsmanager
516	 */
517	public function headerCore($file_path, $zone = 2, $pre = '', $post = '')
518	{
519		$this->headerFile('{e_WEB_JS}'.trim($file_path, '/'), $zone, $pre, $post);
520		return $this;
521	}
522
523	/**
524	 * Add Theme JS file for inclusion in site header, shorthand of headerFile() method
525	 *
526	 * @param string $file_path relative to theme root folder
527	 * @param integer $zone 1-5 (see header.php)
528	 * @return e_jsmanager
529	 */
530	public function headerTheme($file_path, $zone = 5, $pre = '', $post = '')
531	{
532		$this->headerFile(THEME.trim($file_path, '/'), $zone, $pre, $post);
533		return $this;
534	}
535
536
537	/**
538	 * Add Theme JS file for inclusion in site footer (preferred), shorthand of footerFile() method
539	 *
540	 * @param string $file_path relative to theme root folder
541	 * @param integer $zone 1-5 (see header.php)
542	 * @return e_jsmanager
543	 */
544	public function footerTheme($file_path, $zone = 5, $pre = '', $post = '')
545	{
546		$this->footerFile(THEME.trim($file_path, '/'), $zone, $pre, $post);
547		return $this;
548	}
549
550	/**
551	 * Add Plugin JS file for inclusion in site header, shorthand of headerFile() method
552	 *
553	 * @param string $plugname
554	 * @param string $file_path relative to plugin root folder
555	 * @param integer $zone 1-5 (see header.php) - REMOVED, actually we need to prevent zone change
556	 * @return e_jsmanager
557	 */
558	public function headerPlugin($plugname, $file_path, $pre, $post)
559	{
560		$this->headerFile('{e_PLUGIN}'.$plugname.'/'.trim($file_path, '/'), 2, $pre, $post);	// Zone 2 - after libraries
561		return $this;
562	}
563
564	/**
565	 * Add JS file(s) for inclusion in site header if possible, else
566	 * use {@link footerFile()}
567	 *
568	 * @param string|array $file_path path shortcodes usage is prefered
569	 * @param integer $zone 1-5 (see header.php and footer.php)
570	 * @return e_jsmanager
571	 */
572	public function tryHeaderFile($file_path, $zone = 5)
573	{
574		if(!defined('HEADER_INIT'))
575		{
576			$this->headerFile($file_path, $zone);
577			return $this;
578		}
579
580		$this->footerFile($file_path, $zone);
581		return $this;
582	}
583
584	/**
585	 * Add JS file(s) for inclusion in site footer
586	 *
587	 * @param string|array $file_path path shortcodes usage is prefered
588	 * @param integer $priority 1-5 (see footer.php)
589	 * @return e_jsmanager
590	 */
591	public function footerFile($file_path, $priority = 5, $pre = '', $post = '')
592	{
593		$this->addJs('footer', $file_path, $priority, $pre, $post);
594		return $this;
595	}
596
597	/**
598	 * Add JS code to site header
599	 *
600	 * @param string|array $js_content
601	 * @param integer $zone 1-5 (see header.php)
602	 * @return e_jsmanager
603	 */
604	public function headerInline($js_content, $zone = 5)
605	{
606		$this->addJs('header_inline', $js_content, $zone);
607		return $this;
608	}
609
610	/**
611	 * Add JS code to site site header if possible, else
612	 * use {@link footerInline()}
613	 *
614	 * @param string $js_content
615	 * @param integer $zone 1-5 (see header.php and footer.php)
616	 * @return e_jsmanager
617	 */
618	public function tryHeaderInline($js_content, $zone = 5)
619	{
620		if(!defined('HEADER_INIT'))
621		{
622			$this->headerInline($js_content, $zone);
623			return $this;
624		}
625
626		$this->footerInline($js_content, $zone);
627		return $this;
628	}
629
630	/**
631	 * Add JS file(s) for inclusion in site footer
632	 *
633	 * @param string|array $js_content path shortcodes usage is prefered
634	 * @param integer $priority 1-5 (see footer.php)
635	 * @return e_jsmanager
636	 */
637	public function footerInline($js_content, $priority = 5)
638	{
639		$this->addJs('footer_inline', $js_content, $priority);
640		return $this;
641	}
642
643	/**
644	 * Add JS settings to site footer
645	 *
646	 * @param array $js_settings
647	 * @return e_jsmanager
648	 */
649	public function jsSettings(array $js_settings)
650	{
651		$this->addJs('settings', $js_settings);
652		return $this;
653	}
654
655
656	function setDependency($dep)
657	{
658		$this->_dependence = $dep;
659	}
660
661	public function resetDependency()
662	{
663		$this->_dependence = null;
664	}
665
666	/**
667	 * Return TRUE if the library is disabled. ie. prototype or jquery.
668	 * FIXME - remove $type & $loc
669	 */
670	public function libDisabled($type = null, $loc = null)
671	{
672		if($type == 'core' && ($loc == 'none'))
673		{
674			return true;
675		}
676
677		if($this->_dependence != null && isset($this->_libraries[$this->_dependence]))
678		{
679			$status = $this->_core_prefs[$this->_dependence];
680
681			switch ($status)
682			{
683				case 'auto':
684				case 'all':
685					return false;
686				break;
687
688				case 'admin':
689					return ($this->isInAdmin()) ? false : true;
690				break;
691
692				case 'front':
693					return ($this->isInAdmin()) ? true : false;
694				break;
695
696				case 'none':
697					return true;
698				break;
699
700				default:
701					return true;
702				break;
703			}
704		}
705
706		return false;
707
708	}
709
710	public function checkLibDependence($rlocation, $libs = null)
711	{
712		// Load Required Library (prototype | jquery)
713		// called from addJs(), make isDisabled checks for smart runtime library detection
714		if($rlocation && $libs === null && $this->_dependence != null && isset($this->_libraries[$this->_dependence]) && !isset($this->_dependenceLoaded[$this->_dependence][$rlocation])) // load framework
715		{
716			if($this->libDisabled())
717			{
718				$this->_dependenceLoaded[$this->_dependence][$rlocation] = array();
719				return;
720			}
721
722			foreach($this->_libraries[$this->_dependence] as $inc)
723			{
724				if(strpos($inc,".css")!==false)
725				{
726					if(strpos($inc,"://")!==false) // cdn
727					{
728						$this->addJs('other_css', $inc, 'all', '<!-- AutoLoad -->');
729					}
730					else
731					{
732						$this->addJs('core_css', $inc, 'all', '<!-- AutoLoad -->');
733					}
734				}
735				else
736				{
737					$this->addJs('core', $inc, $rlocation, '<!-- AutoLoad -->');
738				}
739				$this->_dependenceLoaded[$this->_dependence][$rlocation][] = $inc;
740			}
741			return $this;
742		}
743		// called on init time, isDisabled checks already done, just add stuff
744		if($rlocation === null && is_array($libs))
745		{
746			foreach ($libs as $inc => $rlocation)
747			{
748				if(isset($this->_dependenceLoaded[$this->_dependence][$rlocation]) && in_array($inc, $this->_dependenceLoaded[$this->_dependence][$rlocation]))
749				{
750					continue;
751				}
752				if(strpos($inc,".css")!==false)
753				{
754					if(strpos($inc,"://")!==false) // cdn
755					{
756						$this->addJs('other_css', $inc, 'all', '<!-- AutoLoad -->');
757					}
758					else
759					{
760						$this->addJs('core_css', $inc, 'all', '<!-- AutoLoad -->');
761					}
762				}
763				else
764				{
765					$this->addJs('core', $inc, $rlocation, '<!-- AutoLoad -->');
766				}
767				$this->_dependenceLoaded[$this->_dependence][$rlocation][] = $inc;
768			}
769		}
770	}
771
772
773	/**
774	 * Add a <link> tag to the head.
775	 * @param array $attributes key>value pairs
776	 * @example addLink(array('rel'=>'prefetch', 'href'=>THEME.'images/browsers.png'));
777	 */
778	public function addLink($attributes=array())
779	{
780		if(!empty($attributes))
781		{
782			$this->_e_link[] = $attributes;
783		}
784	}
785
786
787	/**
788	 * Render all link tags. (other than css)
789	 * @return null
790	 */
791	public function renderLinks()
792	{
793
794		if(empty($this->_e_link))
795		{
796			return null;
797		}
798
799		$text = '';
800
801		foreach($this->_e_link as $v)
802		{
803			if(!empty($v['type']))
804			{
805				if($v['type'] == 'text/css' || $v['rel'] == 'stylesheet') // not for this purpose. use e107::css();
806				{
807					continue;
808				}
809			}
810
811
812			$text .= "\n<link";
813			foreach($v as $key=>$val)
814			{
815				if(!empty($val))
816				{
817					$text .= " ".$key."=\"".$val."\"";
818				}
819			}
820			$text .= " />";
821
822		}
823
824		echo $text;
825	}
826
827
828	/**
829	 * Require JS file(s). Used by corresponding public proxy methods.
830	 *
831	 * @see themeLib()
832	 * @see pluginLib()
833	 * @see coreLib()
834	 * @see headerFile()
835	 * @see footerFile()
836	 * @see headerInline()
837	 * @see footerInline()
838	 * @param string $type core|plugin - jslib.php, header|footer|header_inline|footer_inline|core_css|plugin_css|theme_css|other_css|inline_css - runtime
839	 * @param string|array $file_path
840	 * @param string|integer $runtime_location admin|front|all|none (jslib), 0-5 (runtime inclusion), 'media' attribute (CSS)
841	 * @return object $this
842	 */
843	protected function addJs($type, $file_path, $runtime_location = '', $pre = '', $post = '')
844	{
845		// TODO FIXME - remove JS framework dependency from front-end and backend.
846		// ie. no JS errors when prototype.js is completely disabled.
847		// no JS error with only 'e107 Core Minimum' is enabled.
848		// e107 Core Minimum should function independently of framework.
849		// ie. e107 Core Minimum: JS similar to e107 v1.0 should be loaded  "e_js.php" (no framwork dependency)
850		// with basic functions like SyncWithServerTime() and expandit(), externalLinks() etc.
851
852		if(empty($file_path))
853		{
854			return $this;
855		}
856
857		// prevent loop of death
858		if($pre != '<!-- AutoLoad -->')
859		{
860			$rlocation = $runtime_location;
861			if(is_numeric($runtime_location)) $rlocation = $this->isInAdmin() ? 'admin' : 'front';
862
863			$this->checkLibDependence($rlocation);
864
865
866			// FIXME - better performance - executed on every addJs call - BAD
867			//libraries handled only by checkLibDependence()
868			if(!is_array($file_path))
869			{
870				foreach ($this->_libraries as $l)
871				{
872					if(in_array($file_path, $l))
873					{
874						return $this;
875					}
876				}
877			}
878		}
879
880		// if($type == 'core' && !is_array($file_path) && substr($file_path,0,4)=='http' ) // Core using CDN.
881		// {
882			// $type = 'header';
883			// $runtime_location = 1;
884		// }
885
886		// Possibly no longer needed.
887		// FIXME - this could break something after CSS support was added, move it to separate method(s), recursion by type!
888		// Causes the css error on jquery-ui as a css file is loaded as a js.
889
890		if(is_array($file_path) && $type != 'settings')
891		{
892		// 	print_a($file_path);
893			foreach ($file_path as $fp => $loc)
894			{
895				if(is_numeric($fp))
896				{
897					$fp = $loc;
898					$loc = $runtime_location;
899				}
900
901				$type = (strpos($fp,".css")!==false && $type == 'core') ? "core_css" : $type;
902
903
904				 $this->addJs($type, $fp, $loc);
905			}
906			return $this;
907		}
908
909		if($this->libDisabled($type,$runtime_location))
910		{
911			//echo $this->_dependence." :: DISABLED<br />";
912			// echo $this->_dependence."::".$file_path." : DISABLED<br />";
913			return $this;
914
915		}
916		else
917		{
918			// echo $this->_dependence." :: ENABLED<br />";
919			 // echo $this->_dependence."::".$file_path." : DISABLED<br />";
920		}
921
922
923
924		$tp = e107::getParser();
925		$runtime = false;
926		switch($type)
927		{
928			case 'core':
929				// added direct CDN support
930				$file_path = (strpos($file_path, 'http') !== 0 && strpos($file_path, '//') !== 0 ? '{e_WEB_JS}'.trim($file_path, '/') : $file_path).$this->_sep.$pre.$this->_sep.$post;
931				$registry = &$this->_e_jslib_core;
932			break;
933
934			case 'plugin':
935				$file_path = explode(':', $file_path);
936				$file_path = '{e_PLUGIN}'.$file_path[0].'/'.trim($file_path[1], '/').$this->_sep.$pre.$this->_sep.$post;
937				$registry = &$this->_e_jslib_plugin;
938			break;
939
940			case 'theme':
941				$file_path = '{e_THEME}'.$this->getCurrentTheme().'/'.trim($file_path, '/').$this->_sep.$pre.$this->_sep.$post;
942				//echo "file-Path = ".$file_path;
943				$registry = &$this->_e_jslib_theme;
944			break;
945
946			case 'core_css': //FIXME - core CSS should point to new e_WEB/css; add one more case - js_css -> e_WEB/jslib/
947				// added direct CDN support
948				$file_path = $runtime_location.$this->_sep.(strpos($file_path, 'http') !== 0 && strpos($file_path, '//') !== 0 ? '{e_WEB_JS}'.trim($file_path, '/') : $file_path).$this->_sep.$pre.$this->_sep.$post;
949				if(!isset($this->_e_css['core'])) $this->_e_css['core'] = array();
950				$registry = &$this->_e_css['core'];
951				$runtime = true;
952			break;
953
954			case 'plugin_css':
955				$pfile_path = explode(':', $file_path,2);
956				$plugfile_path = $runtime_location.$this->_sep.'{e_PLUGIN}'.$pfile_path[0].'/'.trim($pfile_path[1], '/').$this->_sep.$pre.$this->_sep.$post;
957
958				// allow for URLs to be attributed to plugins. (loads after theme css in admin area header)
959				$file_path = ((strpos($pfile_path[1], 'http') !== 0 && strpos($pfile_path[1], '//') !== 0)) ? $plugfile_path : 'all'.$this->_sep.$pfile_path[1].$this->_sep.$pre.$this->_sep.$post;;
960				if(!isset($this->_e_css['plugin'])) $this->_e_css['plugin'] = array();
961				$registry = &$this->_e_css['plugin'];
962				$runtime = true;
963
964			break;
965
966			case 'theme_css':
967				$file_path = $runtime_location.$this->_sep.'{e_THEME}'.$this->getCurrentTheme().'/'.trim($file_path, '/').$this->_sep.$pre.$this->_sep.$post;
968				if(!isset($this->_e_css['theme'])) $this->_e_css['theme'] = array();
969				$registry = &$this->_e_css['theme'];
970				$runtime = true;
971			break;
972
973			case 'other_css':
974				$file_path = $runtime_location.$this->_sep.$tp->createConstants($file_path, 'mix').$this->_sep.$pre.$this->_sep.$post;
975				if(!isset($this->_e_css['other'])) $this->_e_css['other'] = array();
976				$registry = &$this->_e_css['other'];
977				$runtime = true;
978			break;
979
980			case 'library_css':
981				$file_path = $runtime_location.$this->_sep.$tp->createConstants($file_path, 'mix').$this->_sep.$pre.$this->_sep.$post;
982				// 	e107::getDebug()->log($file_path);
983				if(!isset($this->_e_css['library'])) $this->_e_css['library'] = array();
984				$registry = &$this->_e_css['library'];
985				$runtime = true;
986			break;
987
988			case 'inline_css': // no zones, TODO - media?
989				$this->_e_css_src[] = $file_path;
990				return $this;
991				break;
992			break;
993
994
995			case 'header':
996				$file_path = $tp->createConstants($file_path, 'mix').$this->_sep.$pre.$this->_sep.$post;
997				$zone = intval($runtime_location);
998				if($zone > 5 || $zone < 1)
999				{
1000					$zone = 5;
1001				}
1002				if(!isset($this->_runtime_header[$zone]))
1003				{
1004					$this->_runtime_header[$zone] = array();
1005				}
1006				$registry = &$this->_runtime_header[$zone];
1007				$runtime = true;
1008			break;
1009
1010			case 'footer':
1011				$file_path = $tp->createConstants($file_path, 'mix').$this->_sep.$pre.$this->_sep.$post;
1012				$zone = intval($runtime_location);
1013				if($zone > 5 || $zone < 1)
1014				{
1015					$zone = 5;
1016				}
1017				if(!isset($this->_runtime_footer[$zone]))
1018				{
1019					$this->_runtime_footer[$zone] = array();
1020				}
1021				$registry = &$this->_runtime_footer[$zone];
1022				$runtime = true;
1023			break;
1024
1025			case 'header_inline':
1026				$zone = intval($runtime_location);
1027				if($zone > 5 || $zone < 1)
1028				{
1029					$zone = 5;
1030				}
1031				$this->_runtime_header_src[$zone][] = $file_path;
1032				return $this;
1033				break;
1034			break;
1035
1036			case 'footer_inline':
1037				$zone = intval($runtime_location);
1038				if($zone > 5 || $zone < 1)
1039				{
1040					$zone = 5;
1041				}
1042				$this->_runtime_footer_src[$zone][] = $file_path;
1043				return $this;
1044			break;
1045
1046			case 'settings':
1047				$this->_e_js_settings = $this->arrayMergeDeepArray(array($this->_e_js_settings, $file_path));
1048				return $this;
1049			break;
1050
1051			default:
1052				return $this;
1053			break;
1054		}
1055
1056		if(in_array($file_path, $this->_index_all) || (!$runtime && $runtime_location != 'all' && $runtime_location != $this->getCurrentLocation()))
1057		{
1058			return $this;
1059		}
1060
1061		$this->_index_all[] = $file_path;
1062		$registry[] = $file_path;
1063
1064		return $this;
1065	}
1066
1067	/**
1068	 * Render registered JS
1069	 *
1070	 * @param string $mod core|plugin|theme|header|footer|header_inline|footer_inline|core_css|plugin_css|theme_css|other_css|inline_css
1071	 * @param integer $zone 1-5 - only used when in 'header','footer','header_inline' and 'footer_inline' render mod
1072	 * @param boolean $external external file calls, only used when NOT in 'header_inline' and 'footer_inline' render mod
1073	 * @param boolean $return
1074	 * @return string JS content - only if $return is true
1075	 */
1076	public function renderJs($mod, $zone = null, $external = true, $return = false)
1077	{
1078		if($return)
1079		{
1080			ob_start();
1081		}
1082
1083		switch($mod)
1084		{
1085			case 'settings':
1086				$tp = e107::getParser();
1087				$json = $tp->toJSON($this->_e_js_settings);
1088				echo "<script>\n";
1089				echo "var e107 = e107 || {'settings': {}, 'behaviors': {}};\n";
1090				echo "jQuery.extend(e107.settings, " . $json . ");\n";
1091				echo "</script>\n";
1092			break;
1093
1094			case 'framework': // CDN frameworks - rendered before consolidation script (if enabled)
1095				$fw = array();
1096				foreach ($this->_libraries as $lib)
1097				{
1098					foreach ($lib as $path)
1099					{
1100						$erase = array_search($path, $this->_e_jslib_core);
1101						if($erase !== false && strpos($path, 'http') === 0)
1102						{
1103							unset($this->_e_jslib_core[$erase]);
1104							$fw[] = $path;
1105						}
1106					}
1107				}
1108				$this->renderFile($fw, $external, 'CDN Framework', $mod, false);
1109			break;
1110
1111			case 'core': //e_jslib
1112				$this->setLastModfied($mod, $this->renderFile($this->_e_jslib_core, $external, 'Core libraries', $mod));
1113				$this->_e_jslib_core = array();
1114			break;
1115
1116			case 'plugin': //e_jslib
1117				/*foreach($this->_e_jslib_plugin as $plugname => $paths)
1118				{
1119					$this->setLastModfied($mod, $this->renderFile($paths, $external, $plugname.' libraries'));
1120				}*/
1121				$this->setLastModfied($mod, $this->renderFile($this->_e_jslib_plugin, $external, 'Plugin libraries', $mod));
1122				$this->_e_jslib_plugin = array();
1123			break;
1124
1125			case 'theme': //e_jslib
1126				$this->setLastModfied($mod, $this->renderFile($this->_e_jslib_theme, $external, 'Theme libraries', $mod));
1127				$this->_e_jslib_theme = array();
1128			break;
1129
1130			case 'header':
1131				$this->renderFile(vartrue($this->_runtime_header[$zone], array()), $external, 'Header JS include - zone #'.$zone, $mod);
1132				unset($this->_runtime_header[$zone]);
1133			break;
1134
1135			case 'core_css': //e_jslib
1136				$this->renderFile(varset($this->_e_css['core'], array()), $external, 'Core CSS', $mod, false);
1137				unset($this->_e_css['core']);
1138			break;
1139
1140			case 'plugin_css': //e_jslib
1141				$this->renderFile(varset($this->_e_css['plugin'], array()), $external, 'Plugin CSS', $mod, false);
1142				unset($this->_e_css['plugin']);
1143			break;
1144
1145			case 'theme_css': //e_jslib
1146				$this->renderFile(varset($this->_e_css['theme'], array()), $external, 'Theme CSS', $mod, false);
1147				unset($this->_e_css['theme']);
1148			break;
1149
1150			case 'other_css':
1151				$this->renderFile(varset($this->_e_css['other'], array()), $external, 'Other CSS', $mod, false);
1152				unset($this->_e_css['other']);
1153			break;
1154
1155			case 'library_css':
1156				$this->renderFile(varset($this->_e_css['library'], array()), $external, 'Library CSS', $mod, false);
1157				unset($this->_e_css['library']);
1158			break;
1159
1160			case 'inline_css':
1161				$this->renderInline($this->_e_css_src, 'Inline CSS', 'css');
1162				$this->_e_css_src = array();
1163			break;
1164
1165
1166			case 'footer':
1167				if(true === $zone)
1168				{
1169					ksort($this->_runtime_footer, SORT_NUMERIC);
1170					foreach ($this->_runtime_footer as $priority => $path_array)
1171					{
1172						$this->renderFile($path_array, $external, 'Footer JS include - priority #'.$priority, $mod);
1173					}
1174					$this->_runtime_footer = array();
1175				}
1176				else
1177				{
1178					$this->renderFile(vartrue($this->_runtime_footer[$zone], array()), $external, 'Footer JS include - priority #'.$zone, $mod);
1179					unset($this->_runtime_footer[$zone]);
1180				}
1181			break;
1182
1183			case 'header_inline':
1184				$this->renderInline(vartrue($this->_runtime_header_src[$zone], array()), 'Header JS - zone #'.$zone);
1185				unset($this->_runtime_header_src[$zone]);
1186			break;
1187
1188			case 'footer_inline':
1189				if(true === $zone)
1190				{
1191					ksort($this->_runtime_footer_src, SORT_NUMERIC);
1192					foreach ($this->_runtime_footer_src as $priority => $src_array)
1193					{
1194						$this->renderInline($src_array, 'Footer JS - priority #'.$priority);
1195					}
1196					$this->_runtime_footer_src = array();
1197				}
1198				else
1199				{
1200					$this->renderInline(vartrue($this->_runtime_footer_src[$zone], array()), 'Footer JS - priority #'.$zone);
1201					unset($this->_runtime_footer_src[$zone]);
1202				}
1203			break;
1204		}
1205
1206		if($return)
1207		{
1208			$ret = ob_get_contents();
1209			ob_end_clean();
1210			return $ret;
1211		}
1212	}
1213
1214
1215	/**
1216	 * Merges multiple arrays, recursively, and returns the merged array.
1217	 */
1218	public function arrayMergeDeepArray($arrays) {
1219		$result = array();
1220
1221		foreach ($arrays as $array) {
1222			foreach ($array as $key => $value) {
1223				// Renumber integer keys as array_merge_recursive() does. Note that PHP
1224				// automatically converts array keys that are integer strings (e.g., '1')
1225				// to integers.
1226				if (is_integer($key)) {
1227					$result[] = $value;
1228				}
1229				// Recurse when both values are arrays.
1230				elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
1231					$result[$key] = $this->arrayMergeDeepArray(array($result[$key], $value));
1232				}
1233				// Otherwise, use the latter value, overriding any previous value.
1234				else {
1235					$result[$key] = $value;
1236				}
1237			}
1238		}
1239
1240		return $result;
1241	}
1242
1243
1244	/**
1245	 * Render JS/CSS file array
1246	 *
1247	 * @param array $file_path_array
1248	 * @param string|boolean $external if true - external js file calls, if js|css - external js|css file calls, else output file contents
1249	 * @param string $label added as comment if non-empty
1250	 * @return void
1251	 */
1252	public function renderFile($file_path_array, $external = false, $label = '', $mod = null, $checkModified = true)
1253	{
1254		if(empty($file_path_array))
1255		{
1256			return '';
1257		}
1258
1259
1260		$tp = e107::getParser();
1261		echo "\n";
1262		if($label && E107_DEBUG_LEVEL > 0)
1263		{
1264			echo $external ? "<!-- [JSManager] ".$label." -->\n" : "/* [JSManager] ".$label." */\n\n";
1265			e107::getDebug()->logTime("Load JS/CSS: ".$label);
1266		}
1267
1268
1269
1270
1271		$lmodified = 0;
1272		foreach ($file_path_array as $path)
1273		{
1274            if (substr($path, - 4) == '.php')
1275            {
1276            	if('css' === $external)
1277				{
1278					$path = explode($this->_sep, $path, 4);
1279					$media = $path[0] ? $path[0] : 'all';
1280					// support of IE checks
1281					$pre = varset($path[2]) ? $path[2]."\n" : '';
1282					$post = varset($path[3]) ? "\n".$path[3] : '';
1283					$path = $path[1];
1284					if(strpos($path, 'http') !== 0)
1285					{
1286						$path = $tp->replaceConstants($path, 'abs').'?external=1'; // &amp;'.$this->getCacheId();
1287						$path = $this->url($path);
1288					}
1289
1290					echo $pre.'<link rel="stylesheet" media="'.$media.'" type="text/css" href="'.$path.'" />'.$post;
1291					echo "\n";
1292				//	$this->cacheList['css'][] = $path;
1293					continue;
1294				}
1295				elseif($external) //true or 'js'
1296				{
1297					if(strpos($path, 'http') === 0 || strpos($path, '//') === 0) continue; // not allowed
1298
1299					$path = explode($this->_sep, $path, 3);
1300					$pre = varset($path[1], '');
1301					if($pre) $pre .= "\n";
1302					$post = varset($path[2], '');
1303					if($post) $post = "\n".$post;
1304					$path = $path[0];
1305
1306					$path = $tp->replaceConstants($path, 'abs').'?external=1'; // &amp;'.$this->getCacheId();
1307					$path = $this->url($path);
1308					echo $pre.'<script type="text/javascript" src="'.$path.'"></script>'.$post;
1309					echo "\n";
1310					continue;
1311				}
1312
1313				$path = $tp->replaceConstants($path, '');
1314				if($checkModified) $lmodified = max($lmodified, filemtime($path));
1315                include_once($path);
1316                echo "\n";
1317            }
1318            else
1319            {
1320            	// CDN fix, ignore URLs inside consolidation script, render as external scripts
1321            	$isExternal = false;
1322				if(strpos($path, 'http') === 0 || strpos($path, '//') === 0)
1323				{
1324					if($external !== 'css') $isExternal = true;
1325				}
1326
1327
1328
1329
1330
1331
1332				if('css' === $external)
1333				{
1334					$path = explode($this->_sep, $path, 4);
1335
1336
1337
1338					$media = $path[0];
1339					// support of IE checks
1340					$pre = varset($path[2]) ? $path[2]."\n" : '';
1341					$post = varset($path[3]) ? "\n".$path[3] : '';
1342					$path = $path[1];
1343
1344					$insertID ='';
1345
1346					// issue #3390 Fix for protocol-less path
1347					if(strpos($path, 'http') !== 0 && strpos($path, '//') !== 0) // local file.
1348					{
1349
1350						if($label === 'Theme CSS') // add an id for local theme stylesheets.
1351						{
1352							$insertID = 'id="stylesheet-'. eHelper::secureIdAttr(str_replace(array('{e_THEME}','.css'),'',$path)).'"' ;
1353						}
1354
1355						if($this->addCache($external,$path) === true) // if cache enabled, then skip and continue.
1356						{
1357							continue;
1358						}
1359						$path = $tp->replaceConstants($path, 'abs'); // .'?'.$this->getCacheId();
1360						$path = $this->url($path);
1361					}
1362					elseif($this->isValidUrl($path) === false)
1363					{
1364						continue;
1365					}
1366
1367
1368					echo $pre.'<link '.$insertID.' rel="stylesheet" media="'.$media.'" property="stylesheet" type="text/css" href="'.$path.'" />'.$post;
1369					echo "\n";
1370
1371					continue;
1372				}
1373
1374				$path = explode($this->_sep, $path, 4);
1375				$pre = varset($path[1], '');
1376				if($pre) $pre .= "\n";
1377				$post = varset($path[2], '');
1378				if($post) $post = "\n".$post;
1379				$inline = isset($path[3]) ? $path[3] : '';
1380				if($inline) $inline = " ".$inline;
1381				$path = $path[0];
1382
1383	            if(!$isExternal && $this->addCache('js',$path)===true)
1384	            {
1385		            continue;
1386	            }
1387
1388
1389            	if($external)
1390				{
1391					// Never use CacheID on a CDN script, always render if it's CDN
1392					if(!$isExternal)
1393					{
1394						// don't render non CDN libs as external script calls when script consolidation is enabled
1395						if($mod === 'core' || $mod === 'plugin' || $mod === 'theme')
1396						{
1397							if(!e107::getPref('e_jslib_nocombine')) continue;
1398						}
1399
1400						$path = $tp->replaceConstants($path, 'abs'); //.'?'.$this->getCacheId();
1401						$path = $this->url($path, 'js');
1402					}
1403
1404					if($isExternal === true && $this->isValidUrl($path) == false)
1405					{
1406						continue;
1407					}
1408
1409					echo $pre.'<script type="text/javascript" src="'.$path.'"'.$inline.'></script>'.$post;
1410					echo "\n";
1411					continue;
1412				}
1413
1414
1415
1416
1417
1418				// never try to consolidate external scripts
1419				if($isExternal) continue;
1420				$path = $tp->replaceConstants($path, '');
1421				if($checkModified) $lmodified = max($lmodified, filemtime($path));
1422                echo file_get_contents($path);
1423                echo "\n";
1424            }
1425		}
1426
1427
1428		return $lmodified;
1429	}
1430
1431
1432	/**
1433	 * Return the URL while checking for staticUrl configuration.
1434	 * @param      $path
1435	 * @param bool $cacheId
1436	 * @return mixed|string
1437	 */
1438	private function url($path, $cacheId = true)
1439	{
1440
1441		if(/*(e_MOD_REWRITE_STATIC === true || defined('e_HTTP_STATIC')) &&*/ $this->isInAdmin() !== true)
1442		{
1443
1444		/*	$srch = array(
1445				e_PLUGIN_ABS,
1446				e_THEME_ABS,
1447				e_WEB_ABS
1448			);
1449
1450
1451			$http = deftrue('e_HTTP_STATIC', e_HTTP);
1452
1453			$base = (e_MOD_REWRITE_STATIC === true) ? 'static/'.$this->getCacheId().'/' : '';
1454
1455			$repl = array(
1456				$http.$base.e107::getFolder('plugins'),
1457				$http.$base.e107::getFolder('themes'),
1458				$http.$base.e107::getFolder('web')
1459			);
1460
1461			$folder = str_replace($srch,$repl,$path);
1462
1463			if(e_MOD_REWRITE_STATIC === true)
1464			{
1465				return trim($folder);
1466			}
1467
1468			$path = $folder;*/
1469
1470			$path = e107::getParser()->staticUrl($path);
1471		}
1472
1473
1474		if(!defined('e_HTTP_STATIC'))
1475		{
1476			if(strpos($path,'?')!==false)
1477			{
1478				$path .= "&amp;".$this->getCacheId();
1479			}
1480			else
1481			{
1482				$path .= "?".$this->getCacheId();
1483			}
1484		}
1485
1486		return $path;
1487
1488	}
1489
1490
1491
1492	/**
1493	 * Check CDN Url is valid.
1494	 * Experimental.
1495	 * @param $url
1496	 * @return resource
1497	 */
1498	private function isValidUrl($url)
1499	{
1500		return true;
1501		/*
1502
1503
1504		$connected = e107::getFile()->isValidURL($url);
1505
1506		if($connected == false)
1507		{
1508		//	echo "<br />Skipping: ".$url ." : ".$port;
1509			e107::getDebug()->log("Couldn't reach ".$url);
1510		}
1511
1512		return $connected;
1513		*/
1514	}
1515
1516
1517	/**
1518	 * @param $type string css|js
1519	 * @param $path
1520	 * @return bool
1521	 */
1522	private function addCache($type,$path)
1523	{
1524		if($this->_cache_enabled != true  || $this->isInAdmin() || substr($path,0,2) == '//' || strpos($path, 'wysiwyg.php')!==false )
1525		{
1526			return false;
1527		}
1528
1529		if(e_REQUEST_HTTP == e_ADMIN_ABS."menus.php") // disabled in menu-manager.
1530		{
1531			return false;
1532		}
1533
1534
1535		$localPath = e107::getParser()->replaceConstants($path);
1536		$this->_cache_list[$type][] = $localPath;
1537
1538		return true;
1539	}
1540
1541
1542
1543
1544	/**
1545	 * Render Cached JS or CSS file.
1546	 * @param $type
1547	 */
1548	public function renderCached($type)
1549	{
1550		if($this->_cache_enabled != true || $this->isInAdmin() || deftrue('e_MENUMANAGER_ACTIVE'))
1551		{
1552			return false;
1553		}
1554
1555		if(!empty($this->_cache_list[$type]))
1556		{
1557			$content = '';
1558			$cacheId = $this->getCacheFileId($this->_cache_list[$type]);
1559
1560			$fileName = $cacheId.".".$type;
1561			$saveFilePath = e_WEB.'cache/'.$fileName;
1562
1563			if(!is_readable($saveFilePath))
1564			{
1565
1566				foreach($this->_cache_list[$type] as $k=>$path)
1567				{
1568					$content .= "/* File: ".str_replace("../",'',$path)." */\n";
1569					$content .= $this->getCacheFileContent($path, $type);
1570					$content .= "\n\n";
1571				}
1572
1573				if(!@file_put_contents($saveFilePath, $content))
1574				{
1575					e107::getMessage()->addDebug("Couldn't save js/css cache file: ".$saveFilePath);
1576				}
1577
1578			}
1579
1580			echo "\n\n<!-- Cached ".$type." -->\n";
1581
1582			if($type == 'js')
1583			{
1584				echo "<script type='text/javascript' src='".$this->url(e_WEB_ABS."cache/".$fileName,'js','cache')."'></script>\n\n";
1585			}
1586			else
1587			{
1588				echo "<link type='text/css' href='".$this->url(e_WEB_ABS."cache/".$fileName,'cache')."' rel='stylesheet' property='stylesheet'  />\n\n";
1589				if(!empty($this->_cache_list['css_inline']))
1590				{
1591					echo $this->_cache_list['css_inline'];
1592					unset($this->_cache_list['css_inline']);
1593				}
1594			}
1595
1596			// Remove from list, anything we have added.
1597			foreach($this->_cache_list[$type] as $k=>$path)
1598			{
1599				unset($this->_cache_list[$type][$k]);
1600			}
1601
1602
1603		}
1604
1605
1606
1607	}
1608
1609
1610	/**
1611	 * Get js/css file to be cached and update url links.
1612	 * @param $path string
1613	 * @param $type string (js|css)
1614	 * @return mixed|string
1615	 */
1616	private function getCacheFileContent($path, $type)
1617	{
1618		$content = @file_get_contents($path);
1619
1620		if($type == 'js')
1621		{
1622			return $this->compress($content, 'js');
1623		}
1624
1625		// Correct relative paths in css files.
1626		preg_match_all('/url\([\'"]?([^\'"\) ]*)[\'"]?\)/',$content, $match);
1627		$newpath = array();
1628
1629		if(empty($match[0]))
1630		{
1631			return $this->compress($content, 'css');
1632		}
1633
1634		$path = str_replace("../",'',$path);
1635
1636		$basePath = dirname($path)."/";
1637
1638		$tp = e107::getParser();
1639
1640		foreach($match[1] as $k=>$v)
1641		{
1642			if(strpos($v,'data:') === 0 || strpos($v,'http') === 0)
1643			{
1644				unset($match[0][$k]);
1645				continue;
1646			}
1647
1648			$http = $tp->staticUrl(null, array('full'=>1)); // returns SITEURL or Static URL if enabled.
1649			$path = $this->normalizePath($basePath.$v);
1650			$dir = "url(".$http.$path.")";
1651
1652			$newpath[$k] = $dir;
1653		}
1654
1655		$result = str_replace($match[0], $newpath, $content);
1656
1657		return $this->compress($result, 'css');
1658	}
1659
1660
1661	/**
1662	 * Normalize a path.
1663	 * Replacement for realpath (move to core functions?)
1664	 * It will _only_ normalize the path and resolve indirections (.. and .)
1665	 * Normalization includes:
1666	 * - directiory separator is always /
1667	 * - there is never a trailing directory separator
1668	 * @param  $path
1669	 * @return String
1670	 */
1671	private function normalizePath($path)
1672	{
1673	    $parts = preg_split(":[\\\/]:", $path); // split on known directory separators
1674
1675	    // resolve relative paths
1676	    for ($i = 0; $i < count($parts); $i +=1)
1677	    {
1678	        if ($parts[$i] === "..")   // resolve ..
1679	        {
1680	            if ($i === 0)
1681	            {
1682	                throw new Exception("Cannot resolve path, path seems invalid: `" . $path . "`");
1683	            }
1684
1685	            unset($parts[$i - 1]);
1686	            unset($parts[$i]);
1687	            $parts = array_values($parts);
1688	            $i -= 2;
1689	        }
1690	        elseif ($parts[$i] === ".")   // resolve .
1691	        {
1692	            unset($parts[$i]);
1693	            $parts = array_values($parts);
1694	            $i -= 1;
1695	        }
1696
1697	        if ($i > 0 && $parts[$i] === "")  // remove empty parts
1698	        {
1699	            unset($parts[$i]);
1700	            $parts = array_values($parts);
1701	        }
1702	    }
1703
1704	    return implode("/", $parts);
1705	}
1706
1707
1708
1709	/**
1710	 * Minify JS/CSS for output
1711	 * @param string $minify
1712	 * @param string $type (js|css)
1713	 * @return string
1714	 */
1715	private function compress($minify, $type = 'js' )
1716    {
1717
1718        if($type == 'js')
1719        {
1720            return e107::minify($minify);
1721        }
1722
1723		// css
1724
1725		/* remove comments */
1726    	$minify = preg_replace( '!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $minify );
1727
1728        /* remove tabs, spaces, newlines, etc. */
1729    	$minify = str_replace( array("\r\n", "\r", "\n", "\t", '  ', '    ', '    '), '', $minify );
1730		$minify = str_replace("}","} ",$minify);
1731
1732        return $minify;
1733    }
1734
1735
1736	/**
1737	 * Generate a Cache-File ID from path list.
1738	 * @param array $paths
1739	 * @return string
1740	 */
1741	private function getCacheFileId($paths)
1742	{
1743		$id = '';
1744		foreach($paths as $p)
1745		{
1746			$id .= str_replace("../","",$p);
1747		}
1748
1749		return hash('crc32', $id) ;
1750
1751	}
1752
1753	/**
1754	 * Render JS/CSS source array
1755	 *
1756	 * @param array $js_content_array
1757	 * @param string $label added as comment if non-empty
1758	 * @return void
1759	 */
1760	function renderInline($content_array, $label = '', $type = 'js')
1761	{
1762		if(empty($content_array))
1763		{
1764			return '';
1765		}
1766
1767		$content_array = array_unique($content_array); //TODO quick fix, we need better control!
1768		echo "\n";
1769
1770		$raw = array();
1771
1772		if($type == 'js') // support for raw html as inline code. (eg. google/bing/yahoo analytics)
1773		{
1774			$script = array();
1775			foreach($content_array as $code)
1776			{
1777				$start = substr($code,0,7);
1778				if($start == '<script' || $start == '<iframe')
1779				{
1780					$raw[] = $code;
1781				}
1782				else
1783				{
1784					$script[] = $code;
1785				}
1786			}
1787
1788			$content_array = $script;
1789		}
1790
1791
1792		switch ($type)
1793		{
1794			case 'js':
1795				if($label && E107_DEBUG_LEVEL > 0)
1796				{
1797					echo "<!-- [JSManager] ".$label." -->\n";
1798				}
1799				echo '<script type="text/javascript">';
1800				echo "\n//<![CDATA[\n";
1801				echo implode("\n\n", $content_array);
1802				echo "\n//]]>\n";
1803				echo '</script>';
1804				echo "\n";
1805
1806				if(!empty($raw))
1807				{
1808					if($label) //TODO - print comments only if site debug is on
1809					{
1810						echo "\n<!-- [JSManager] (Raw) ".$label." -->\n";
1811					}
1812					echo implode("\n\n", $raw);
1813					echo "\n\n";
1814				}
1815			break;
1816
1817			case 'css':
1818				$text = '';
1819				if($label && E107_DEBUG_LEVEL > 0)
1820				{
1821					$text .= "<!-- [CSSManager] ".$label." -->\n";
1822				}
1823				$text .= '<style rel="stylesheet" type="text/css" property="stylesheet">';
1824				$text .= implode("\n\n", $content_array);
1825				$text .= '</style>';
1826				$text .= "\n";
1827
1828				if($this->_cache_enabled != true || $this->isInAdmin() || deftrue('e_MENUMANAGER_ACTIVE'))
1829				{
1830					echo $text;
1831				}
1832				else
1833				{
1834					$this->_cache_list['css_inline'] = $text;
1835				}
1836
1837
1838			break;
1839		}
1840	}
1841
1842	/**
1843	 * Returns true if currently running in
1844	 * administration area.
1845	 *
1846	 * @return boolean
1847	 */
1848	public function isInAdmin()
1849	{
1850		return $this->_in_admin;
1851	}
1852
1853	/**
1854	 * Set current script location
1855	 *
1856	 * @param object $is true - back-end, false - front-end
1857	 * @return e_jsmanager
1858	 */
1859	public function setInAdmin($is)
1860	{
1861		$this->_in_admin = (boolean) $is;
1862		return $this;
1863	}
1864
1865	/**
1866	 * Get current location as a string (admin|front)
1867	 *
1868	 * @return string
1869	 */
1870	public function getCurrentLocation()
1871	{
1872		return ($this->isInAdmin() ? 'admin' : 'front');
1873	}
1874
1875	/**
1876	 * Get current theme name
1877	 *
1878	 * @return string
1879	 */
1880	public function getCurrentTheme()
1881	{
1882		// XXX - USERTHEME is defined only on user session init
1883		return ($this->isInAdmin() ? e107::getPref('admintheme') : deftrue('USERTHEME', e107::getPref('sitetheme')));
1884	}
1885
1886	/**
1887	 * Get browser cache id
1888	 *
1889	 * @return integer
1890	 */
1891	public function getCacheId()
1892	{
1893		return $this->_browser_cache_id;
1894	}
1895
1896	/**
1897	 * Set browser cache id
1898	 *
1899	 * @return e_jsmanager
1900	 */
1901	public function setCacheId($cacheid)
1902	{
1903		$this->_browser_cache_id = intval($cacheid);
1904		return $this;
1905	}
1906
1907	/**
1908	 * Set last modification timestamp for given namespace
1909	 *
1910	 * @param string $what
1911	 * @param integer $when [optional]
1912	 * @return e_jsmanager
1913	 */
1914	public function setLastModfied($what, $when = 0)
1915	{
1916		$this->_lastModified[$what] = $when;
1917		return $this;
1918	}
1919
1920	/**
1921	 * Get last modification timestamp for given namespace
1922	 *
1923	 * @param string $what
1924	 * @return integer
1925	 */
1926	public function getLastModfied($what)
1927	{
1928		return (isset($this->_lastModified[$what]) ? $this->_lastModified[$what] : 0);
1929	}
1930
1931	public function addLibPref($mod, $array_newlib)
1932	{
1933
1934		if(!$array_newlib || !is_array($array_newlib))
1935		{
1936			return $this;
1937		}
1938		$core = e107::getConfig();
1939		$plugname = '';
1940		if(strpos($mod, 'plugin:') === 0)
1941		{
1942			$plugname = str_replace('plugin:', '', $mod);
1943			$mod = 'plugin';
1944		}
1945
1946		switch($mod)
1947		{
1948			case 'core':
1949			case 'theme':
1950				$key = 'e_jslib_'.$mod;
1951			break;
1952
1953			case 'plugin':
1954				$key = 'e_jslib_plugin/'.$plugname;
1955			break;
1956
1957			default:
1958				return $this;
1959			break;
1960		}
1961
1962
1963		$libs = $core->getPref($key);
1964		if(!$libs) $libs = array();
1965		foreach ($array_newlib as $path => $location)
1966		{
1967			$path = trim($path, '/');
1968
1969			if(!$path) continue;
1970
1971			$newlocation = $location == 'all' || (varset($libs[$path]) && $libs[$path] != $location) ? 'all' : $location;
1972			$libs[$path] = $newlocation;
1973		}
1974
1975		$core->setPref($key, $libs);
1976		return $this;
1977	}
1978
1979	public function removeLibPref($mod, $array_removelib)
1980	{
1981
1982		if(!$array_removelib || !is_array($array_removelib))
1983		{
1984			return $this;
1985		}
1986		$core = e107::getConfig();
1987		$plugname = '';
1988		if(strpos($mod, 'plugin:') === 0)
1989		{
1990			$plugname = str_replace('plugin:', '', $mod);
1991			$mod = 'plugin';
1992		}
1993
1994		switch($mod)
1995		{
1996			case 'core':
1997			case 'theme':
1998				$key = 'e_jslib_'.$mod;
1999			break;
2000
2001			case 'plugin':
2002				$key = 'e_jslib_plugin/'.$plugname;
2003			break;
2004
2005			default:
2006				return $this;
2007			break;
2008		}
2009
2010
2011		$libs = $core->getPref($key);
2012		if(!$libs) $libs = array();
2013		foreach ($array_removelib as $path => $location)
2014		{
2015			$path = trim($path, '/');
2016			if(!$path) continue;
2017
2018			unset($libs[$path]);
2019		}
2020
2021		$core->setPref($key, $libs);
2022		return $this;
2023	}
2024
2025	/**
2026	 * Get current object data
2027	 * @return array
2028	 */
2029	public function getData()
2030	{
2031		$data = get_class_vars(__CLASS__);
2032		unset($data['_instance'], $data['_in_admin']);
2033		$kdata = array_keys($data);
2034		$instance = self::getInstance();
2035		$data = array();
2036		foreach ($kdata as $prop)
2037		{
2038			$data[$prop] = $this->$prop;
2039		}
2040		return $data;
2041	}
2042
2043	/**
2044	 * Set all current object data
2045	 * @param $data
2046	 * @return e_jsmanager
2047	 */
2048	public function setData($data)
2049	{
2050		if(!is_array($data)) return $this;
2051		foreach ($data as $prop => $val)
2052		{
2053			if('_instance' == $prop || '_in_admin' == $prop) continue;
2054			$this->$prop = $val;
2055		}
2056		return $this;
2057	}
2058
2059}
2060