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 * External library handling for e107 core/plugins/themes.
12 *
13 * TODO:
14 * - Provide the ability to use third-party callbacks (are defined in e_library.php files) for groups:
15 *   'info', 'pre_detect', 'post_detect', 'pre_dependencies_load', 'pre_load', 'post_load'
16 */
17
18// [e_LANGUAGEDIR]/[e_LANGUAGE]/lan_library_manager.php
19e107::lan('core', 'library_manager');
20
21
22
23/**
24 * Class core_library.
25 */
26class core_library
27{
28
29	/**
30	 * Provides information about external libraries.
31	 *
32	 * Provides information about:
33	 * - jQuery (CDN).
34	 * - jQuery (local).
35	 * - jQuery Once (CDN)
36	 * - jQuery Once (local)
37	 * - jQuery UI (CDN)
38	 * - jQuery UI (local)
39	 * - Bootstrap (CDN)
40	 * - Bootstrap (local)
41	 * - Bootstrap Editable (CDN)
42	 * - Bootstrap Editable (local)
43	 * - Font-Awesome (CDN)
44	 * - Font-Awesome (local)
45	 */
46	public function config()
47	{
48		$libraries = array();
49
50		// jQuery (CDN).
51		$libraries['cdn.jquery'] = array(
52			'name'              => 'jQuery (CDN)',
53			'vendor_url'        => 'https://jquery.com/',
54			'version_arguments' => array(
55				'file'    => 'jquery.min.js',
56				'pattern' => '/jQuery\s+v(\d\.\d\.\d+)/',
57				'lines'   => 5,
58			),
59			'files'             => array(
60				'js' => array(
61					'jquery.min.js' => array(
62						'zone' => 1,
63						'type' => 'url',
64					),
65				),
66			),
67			'variants'          => array(
68				// 'unminified' version for debugging.
69				'dev' => array(
70					'files' => array(
71						'js' => array(
72							'jquery.js' => array(
73								'zone' => 1,
74								'type' => 'url',
75							),
76						),
77					),
78				),
79			),
80			// Override library path to CDN.
81			'library_path'      => 'https://cdn.jsdelivr.net/jquery',
82			'path'              => '2.2.4',
83		);
84
85		// jQuery (local).
86		$libraries['jquery'] = array(
87			'name'              => 'jQuery (local)',
88			'vendor_url'        => 'https://jquery.com/',
89			'version_arguments' => array(
90				'file'    => 'dist/jquery.min.js',
91				'pattern' => '/v(\d\.\d\.\d+)/',
92				'lines'   => 5,
93			),
94			'files'             => array(
95				'js' => array(
96					'dist/jquery.min.js' => array(
97						'zone' => 1,
98						'type' => 'url',
99					),
100				),
101			),
102			'variants'          => array(
103				// 'unminified' version for debugging.
104				'dev' => array(
105					'files' => array(
106						'js' => array(
107							'dist/jquery.js' => array(
108								'zone' => 1,
109								'type' => 'url',
110							),
111						),
112					),
113				),
114			),
115			'library_path'      => '{e_WEB}lib/jquery',
116			'path'              => '2.2.4',
117		);
118
119		// jQuery Once (CDN).
120		$libraries['cdn.jquery.once'] = array(
121			'name'              => 'jQuery Once (CDN)',
122			'vendor_url'        => 'https://plugins.jquery.com/once/',
123			'version_arguments' => array(
124				'file'    => 'jquery.once.min.js',
125				'pattern' => '/jQuery\sOnce\s+v(\d\.\d\.\d+)/',
126				'lines'   => 5,
127			),
128			'files'             => array(
129				'js' => array(
130					'jquery.once.min.js' => array(
131						'zone' => 2,
132						'type' => 'footer',
133					),
134				),
135			),
136			'variants'          => array(
137				// 'unminified' version for debugging.
138				'dev' => array(
139					'files' => array(
140						'js' => array(
141							// There is no non-minified version.
142							'jquery.once.min.js' => array(
143								'zone' => 2,
144								'type' => 'footer',
145							),
146						),
147					),
148				),
149			),
150			// Override library path to CDN.
151			'library_path'      => 'https://cdn.jsdelivr.net/jquery.once',
152			'path'              => '2.1.2',
153		);
154
155		// jQuery Once (local).
156		$libraries['jquery.once'] = array(
157			'name'              => 'jQuery Once (local)',
158			'vendor_url'        => 'https://plugins.jquery.com/once/',
159			'version_arguments' => array(
160				'file'    => 'jquery.once.min.js',
161				'pattern' => '/jQuery\sOnce\s+v(\d\.\d\.\d+)/',
162				'lines'   => 5,
163			),
164			'files'             => array(
165				'js' => array(
166					'jquery.once.min.js' => array(
167						'zone' => 2,
168						'type' => 'footer',
169					),
170				),
171			),
172			'variants'          => array(
173				// 'unminified' version for debugging.
174				'dev' => array(
175					'files' => array(
176						'js' => array(
177							// There is no non-minified version.
178							'jquery.once.min.js' => array(
179								'zone' => 2,
180								'type' => 'footer',
181							),
182						),
183					),
184				),
185			),
186			// Override library path.
187			'library_path'      => '{e_WEB}lib/jquery-once',
188		);
189
190		// jQuery UI (CDN).
191		$libraries['cdn.jquery.ui'] = array(
192			'name'              => 'jQuery UI (CDN)',
193			'vendor_url'        => 'https://jqueryui.com/',
194			'version_arguments' => array(
195				'file'    => 'jquery-ui.min.js',
196				'pattern' => '/v(\d\.\d+\.\d+)/',
197				'lines'   => 5,
198			),
199			'files'             => array(
200				'js'  => array(
201					'jquery-ui.min.js' => array(
202						'zone' => 2,
203						'type' => 'footer',
204					),
205				),
206				'css' => array(
207					'jquery-ui.min.css' => array(
208						'zone' => 2,
209					),
210				),
211			),
212			'variants'          => array(
213				// 'unminified' version for debugging.
214				'dev' => array(
215					'files' => array(
216						'js'  => array(
217							// There is no non-minified version.
218							'jquery-ui.min.js' => array(
219								'zone' => 2,
220								'type' => 'footer',
221							),
222						),
223						'css' => array(
224							// There is no non-minified version.
225							'jquery-ui.min.css' => array(
226								'zone' => 2,
227							),
228						),
229					),
230				),
231			),
232			// Override library path to CDN.
233			'library_path'      => 'https://cdn.jsdelivr.net/jquery.ui',
234			'path'              => '1.11.4',
235		);
236
237		// jQuery UI (local).
238		$libraries['jquery.ui'] = array(
239			'name'              => 'jQuery UI (local)',
240			'vendor_url'        => 'https://jqueryui.com/',
241			'version_arguments' => array(
242				'file'    => 'jquery-ui.js',
243				'pattern' => '/v(\d\.\d+\.\d+)/',
244				'lines'   => 5,
245			),
246			'files'             => array(
247				'js'  => array(
248					'jquery-ui.min.js' => array(
249						'zone' => 2,
250						'type' => 'footer',
251					),
252				),
253				'css' => array(
254					'jquery-ui.min.css' => array(
255						'zone' => 2,
256					),
257				),
258			),
259			'variants'          => array(
260				// 'unminified' version for debugging.
261				'dev' => array(
262					'files' => array(
263						'js'  => array(
264							'jquery-ui.js' => array(
265								'zone' => 2,
266								'type' => 'footer',
267							),
268						),
269						'css' => array(
270							'jquery-ui.css' => array(
271								'zone' => 2,
272							),
273						),
274					),
275				),
276			),
277			// Override library path.
278			'library_path'      => '{e_WEB}lib/jquery-ui',
279		);
280
281
282
283		// ----------------- Bootstrap 4 ---------------------------//
284
285			// Bootstrap (CDN).
286		$libraries['cdn.bootstrap4'] = array(
287			'name'              => 'Bootstrap 4 (CDN)',
288			'vendor_url'        => 'http://getbootstrap.com/',
289			'version_arguments' => array(
290				'file'    => 'dist/js/bootstrap.min.js',
291				'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/',
292				'lines'   => 5,
293			),
294			'files'             => array(
295				'js'  => array(
296					'dist/js/bootstrap.bundle.min.js' => array(
297						'zone' => 2,
298						'type' => 'footer',
299					),
300				),
301				'css' => array(
302					'dist/css/bootstrap.min.css' => array(
303						'zone' => 1,
304					),
305				),
306			),
307			'variants'          => array(
308				// 'unminified' version for debugging.
309				/*'dev' => array(
310					'files' => array(
311						'js'  => array(
312							'js/bootstrap.js' => array(
313								'zone' => 2,
314								'type' => 'footer',
315							),
316						),
317						'css' => array(
318							'css/bootstrap.css' => array(
319								'zone' => 2,
320							),
321						),
322					),
323				),*/
324
325
326			),
327			// Override library path to CDN.
328		//	https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.bundle.min.js
329			'library_path'      => 'https://cdn.jsdelivr.net/npm/bootstrap@4.3.1',
330			'path'              => '',
331		);
332
333		// Bootstrap (local).
334		$libraries['bootstrap4'] = array(
335			'name'              => 'Bootstrap 4 (local)',
336			'vendor_url'        => 'http://getbootstrap.com/',
337			'version_arguments' => array(
338				'file'    => 'js/bootstrap.bundle.min.js',
339				'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/',
340				'lines'   => 5,
341			),
342			'files'             => array(
343				'js'  => array(
344					'js/bootstrap.bundle.min.js' => array(
345						'zone' => 2,
346						'type' => 'footer',
347					),
348				),
349				'css' => array(
350					'css/bootstrap.min.css' => array(
351						'zone' => 2,
352					),
353				),
354			),
355			'variants'          => array(
356				// 'unminified' version for debugging.
357				'dev' => array(
358					'files' => array(
359						'js'  => array(
360							'js/bootstrap.bundle.js' => array(
361								'zone' => 2,
362								'type' => 'footer',
363							),
364						),
365						'css' => array(
366							'css/bootstrap.css' => array(
367								'zone' => 2,
368							),
369						),
370					),
371				),
372			),
373			'library_path'      => '{e_WEB}lib/bootstrap',
374			'path'              => '4',
375		);
376
377
378		// ----------------------------------------------------- //
379
380
381
382		// Bootstrap (CDN).
383		$libraries['cdn.bootstrap'] = array(
384			'name'              => 'Bootstrap (CDN)',
385			'vendor_url'        => 'http://getbootstrap.com/',
386			'version_arguments' => array(
387				'file'    => 'js/bootstrap.min.js',
388				'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/',
389				'lines'   => 5,
390			),
391			'files'             => array(
392				'js'  => array(
393					'js/bootstrap.min.js' => array(
394						'zone' => 2,
395						'type' => 'footer',
396					),
397				),
398				'css' => array(
399					'css/bootstrap.min.css' => array(
400						'zone' => 2,
401					),
402				),
403			),
404			'variants'          => array(
405				// 'unminified' version for debugging.
406				'dev' => array(
407					'files' => array(
408						'js'  => array(
409							'js/bootstrap.js' => array(
410								'zone' => 2,
411								'type' => 'footer',
412							),
413						),
414						'css' => array(
415							'css/bootstrap.css' => array(
416								'zone' => 2,
417							),
418						),
419					),
420				),
421
422
423			),
424			// Override library path to CDN.
425		//	'library_path'      => 'https://cdn.jsdelivr.net/bootstrap',
426			'library_path'      => 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap',
427			'path'              => '3.4.1',
428		);
429
430		// Bootstrap (local).
431		$libraries['bootstrap'] = array(
432			'name'              => 'Bootstrap (local)',
433			'vendor_url'        => 'http://getbootstrap.com/',
434			'version_arguments' => array(
435				'file'    => 'js/bootstrap.min.js',
436				'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/',
437				'lines'   => 5,
438			),
439			'files'             => array(
440				'js'  => array(
441					'js/bootstrap.min.js' => array(
442						'zone' => 2,
443						'type' => 'footer',
444					),
445				),
446				'css' => array(
447					'css/bootstrap.min.css' => array(
448						'zone' => 2,
449					),
450				),
451			),
452			'variants'          => array(
453				// 'unminified' version for debugging.
454				'dev' => array(
455					'files' => array(
456						'js'  => array(
457							'js/bootstrap.js' => array(
458								'zone' => 2,
459								'type' => 'footer',
460							),
461						),
462						'css' => array(
463							'css/bootstrap.css' => array(
464								'zone' => 2,
465							),
466						),
467					),
468				),
469			),
470			'library_path'      => '{e_WEB}lib/bootstrap',
471			'path'              => '3',
472		);
473
474		// Bootstrap Editable (CDN).
475		$libraries['cdn.bootstrap.editable'] = array(
476			'name'              => 'Bootstrap Editable (CDN)',
477			'vendor_url'        => 'https://vitalets.github.io/bootstrap-editable/',
478			'version_arguments' => array(
479				'file'    => 'js/bootstrap-editable.min.js',
480				'pattern' => '/v(\d\.\d\.\d+)/',
481				'lines'   => 5,
482			),
483			'files'             => array(
484				'js'  => array(
485					'js/bootstrap-editable.min.js' => array(
486						'zone' => 2,
487						'type' => 'footer',
488					),
489				),
490				'css' => array(
491					'css/bootstrap-editable.min.css' => array(
492						'zone' => 2,
493					),
494				),
495			),
496			'variants'          => array(
497				// 'unminified' version for debugging.
498				'dev' => array(
499					'files' => array(
500						'js'  => array(
501							'js/bootstrap-editable.js' => array(
502								'zone' => 2,
503								'type' => 'footer',
504							),
505						),
506						'css' => array(
507							'css/bootstrap-editable.css' => array(
508								'zone' => 2,
509							),
510						),
511					),
512				),
513			),
514			// Override library path to CDN.
515			'library_path'      => 'https://cdn.jsdelivr.net/bootstrap.editable',
516			'path'              => '1.5.1',
517		);
518
519		// Bootstrap Editable (local).
520		$libraries['bootstrap.editable'] = array(
521			'name'              => 'Bootstrap Editable (local)',
522			'vendor_url'        => 'https://vitalets.github.io/bootstrap-editable/',
523			'version_arguments' => array(
524				'file'    => 'js/bootstrap-editable.min.js',
525				'pattern' => '/v(\d\.\d\.\d+)/',
526				'lines'   => 5,
527			),
528			'files'             => array(
529				'js'  => array(
530					'js/bootstrap-editable.min.js' => array(
531						'zone' => 2,
532						'type' => 'footer',
533					),
534				),
535				'css' => array(
536					'css/bootstrap-editable.min.css' => array(
537						'zone' => 2,
538					),
539				),
540			),
541			'variants'          => array(
542				// 'unminified' version for debugging.
543				'dev' => array(
544					'files' => array(
545						'js'  => array(
546							'js/bootstrap-editable.js' => array(
547								'zone' => 2,
548								'type' => 'footer',
549							),
550						),
551						'css' => array(
552							'css/bootstrap-editable.css' => array(
553								'zone' => 2,
554							),
555						),
556					),
557				),
558			),
559			// Override library path.
560			'library_path'      => '{e_WEB}js/bootstrap3-editable',
561		);
562
563		// Bootstrap Switch (CDN).
564		$libraries['cdn.bootstrap.switch'] = array(
565			'name'              => 'Bootstrap Switch (CDN)',
566			'vendor_url'        => 'http://www.bootstrap-switch.org',
567			'version_arguments' => array(
568				'file'    => 'js/bootstrap-switch.min.js',
569				'pattern' => '/v(\d\.\d\.\d)/',
570				'lines'   => 5,
571			),
572			'files'             => array(
573				'js'  => array(
574					'js/bootstrap-switch.min.js' => array(
575						'zone' => 2,
576						'type' => 'footer',
577					),
578				),
579				'css' => array(
580					'css/bootstrap3/bootstrap-switch.min.css' => array(
581						'zone' => 2,
582					),
583				),
584			),
585			'variants'          => array(
586				// 'unminified' version for debugging.
587				'dev' => array(
588					'files' => array(
589						'js'  => array(
590							'js/bootstrap-switch.js' => array(
591								'zone' => 2,
592								'type' => 'footer',
593							),
594						),
595						'css' => array(
596							'css/bootstrap3/bootstrap-switch.css' => array(
597								'zone' => 2,
598							),
599						),
600					),
601				),
602			),
603			// Override library path to CDN.
604			'library_path'      => 'https://cdn.jsdelivr.net/bootstrap.switch',
605			'path'              => '3.3.2',
606		);
607
608		// Bootstrap Switch (local).
609		$libraries['bootstrap.switch'] = array(
610			'name'              => 'Bootstrap Switch (local)',
611			'vendor_url'        => 'http://www.bootstrap-switch.org',
612			'version_arguments' => array(
613				'file'    => 'dist/js/bootstrap-switch.min.js',
614				'pattern' => '/v(\d\.\d\.\d)/',
615				'lines'   => 5,
616			),
617			'files'             => array(
618				'js'  => array(
619					'dist/js/bootstrap-switch.min.js' => array(
620						'zone' => 2,
621						'type' => 'footer',
622					),
623				),
624				'css' => array(
625					'dist/css/bootstrap3/bootstrap-switch.min.css' => array(
626						'zone' => 2,
627					),
628				),
629			),
630			'variants'          => array(
631				// 'unminified' version for debugging.
632				'dev' => array(
633					'files' => array(
634						'js'  => array(
635							'dist/js/bootstrap-switch.js' => array(
636								'zone' => 2,
637								'type' => 'footer',
638							),
639						),
640						'css' => array(
641							'dist/css/bootstrap3/bootstrap-switch.css' => array(
642								'zone' => 2,
643							),
644						),
645					),
646				),
647			),
648			// Override library path.
649			'library_path'      => '{e_WEB}lib/bootstrap-switch',
650		);
651
652		// Font-Awesome 4 (CDN).
653		$libraries['cdn.fontawesome'] = array(
654			'name'              => 'Font-Awesome 4 (CDN)',
655			'vendor_url'        => 'http://fontawesome.io/',
656			'version_arguments' => array(
657				'file'    => 'css/font-awesome.min.css',
658				'pattern' => '/(\d\.\d\.\d+)/',
659				'lines'   => 10,
660			),
661			'files'             => array(
662				'css' => array(
663					'css/font-awesome.min.css' => array(
664						'zone' => 2,
665					),
666				),
667			),
668			'variants'          => array(
669				// 'unminified' version for debugging.
670				'dev' => array(
671					'files' => array(
672						'css' => array(
673							'css/font-awesome.css' => array(
674								'zone' => 2,
675							),
676						),
677					),
678				),
679			),
680			// Override library path to CDN.
681			'library_path'      => 'https://cdn.jsdelivr.net/fontawesome',
682			'path'              => '4.7.0',
683		);
684
685		// Font-Awesome (local).
686		$libraries['fontawesome'] = array(
687			'name'              => 'Font-Awesome 4 (local)',
688			'vendor_url'        => 'http://fontawesome.io/',
689			'version_arguments' => array(
690				'file'    => 'css/font-awesome.min.css',
691				'pattern' => '/(\d\.\d\.\d+)/',
692				'lines'   => 10,
693			),
694			'files'             => array(
695				'css' => array(
696					'css/font-awesome.min.css' => array(
697						'zone' => 2,
698					),
699				),
700			),
701			'variants'          => array(
702				// 'unminified' version for debugging.
703				'dev' => array(
704					'files' => array(
705						'css' => array(
706							'css/font-awesome.css' => array(
707								'zone' => 2,
708							),
709						),
710					),
711				),
712			),
713			// Override library path.
714			'library_path'      => '{e_WEB}lib/font-awesome',
715			'path'              => '4.7.0',
716		);
717
718
719
720
721
722		// Font-Awesome 5 (CDN).
723		$libraries['cdn.fontawesome5'] = array(
724			'name'              => 'Font-Awesome 5 (CDN)',
725			'vendor_url'        => 'https://fontawesome.com/',
726			'version_arguments' => array(
727				'file'    => 'css/all.css',
728				'pattern' => '/(\d\.\d\.\d+)/',
729				'lines'   => 10,
730			),
731			'files'             => array(
732				'css' => array(
733					'css/all.css' => array(
734						'zone' => 2,
735					),
736					'css/v4-shims.css' => array(
737						'zone' => 2,
738					),
739				),
740			),
741		/*	'variants'          => array(
742				// 'unminified' version for debugging.
743				'dev' => array(
744					'files' => array(
745						'css' => array(
746							'css/font-awesome.css' => array(
747								'zone' => 2,
748							),
749						),
750					),
751				),
752			),*/
753			// Override library path to CDN.
754			'library_path'      => 'https://use.fontawesome.com/releases',
755			'path'              => 'v5.8.1',
756		);
757
758		// Font-Awesome (local).
759		$libraries['fontawesome5'] = array(
760			'name'              => 'Font-Awesome 5 (local)',
761			'vendor_url'        => 'https://fontawesome.com/',
762			'version_arguments' => array(
763				'file'    => 'css/all.css',
764				'pattern' => '/(\d\.\d\.\d+)/',
765				'lines'   => 3,
766			),
767			'files'             => array(
768				'css' => array(
769					'css/all.min.css' => array(
770						'zone' => 2,
771					),
772					'css/v4-shims.min.css' => array(
773						'zone' => 2,
774					),
775				),
776			),
777			'variants'          => array(
778				// 'unminified' version for debugging.
779				'dev' => array(
780					'files' => array(
781						'css' => array(
782							'css/all.css' => array(
783								'zone' => 2,
784							),
785							'css/v4-shims.css' => array(
786								'zone' => 2,
787							),
788						),
789					),
790				),
791			),
792			// Override library path.
793			'library_path'      => '{e_WEB}lib/font-awesome',
794			'path'              => '5',
795		);
796
797
798
799			// Animate (local).
800		$libraries['animate.css'] = array(
801			'name'              => 'Animate.css (local)',
802			'vendor_url'        => 'https://daneden.github.io/animate.css/',
803			'version_arguments' => array(
804				'file'    => 'animate.min.css',
805				'pattern' => '/(\d\.\d\.\d+)/',
806				'lines'   => 5,
807			),
808			'files'             => array(
809				'css' => array(
810					'animate.min.css' => array(
811						'zone' => 2,
812					),
813				),
814			),
815		/*	'variants'          => array(
816				// 'unminified' version for debugging.
817				'dev' => array(
818					'files' => array(
819						'css' => array(
820							'css/font-awesome.css' => array(
821								'zone' => 2,
822							),
823						),
824					),
825				),
826			),*/
827			// Override library path.
828			'library_path'      => '{e_WEB}lib/animate.css',
829		//	'path'              => '3.5.2',
830		);
831
832
833
834
835		return $libraries;
836	}
837
838	/**
839	 * Alters library information before detection and caching takes place.
840	 */
841	function config_alter(&$libraries)
842	{
843		$pref = e107::pref('core');
844		$cdnProvider = varset($pref['e_jslib_cdn_provider'], 'jsdelivr');
845
846		// If CDNJS is the selected provider, we alter core CDN libraries to use it
847		// instead of jsDelivr.
848		if($cdnProvider == 'cdnjs')
849		{
850			$libraries['cdn.jquery']['library_path'] = str_replace('https://cdn.jsdelivr.net/jquery', 'https://cdnjs.cloudflare.com/ajax/libs/jquery', $libraries['cdn.jquery']['library_path']);
851			$libraries['cdn.jquery.once']['library_path'] = str_replace('https://cdn.jsdelivr.net/jquery.once', 'https://cdnjs.cloudflare.com/ajax/libs/jquery-once', $libraries['cdn.jquery.once']['library_path']);
852			$libraries['cdn.jquery.ui']['library_path'] = str_replace('https://cdn.jsdelivr.net/jquery.ui', 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui', $libraries['cdn.jquery.ui']['library_path']);
853			$libraries['cdn.bootstrap']['library_path'] = str_replace('https://cdn.jsdelivr.net/bootstrap', 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap', $libraries['cdn.bootstrap']['library_path']);
854
855			$libraries['cdn.bootstrap.editable']['library_path'] = str_replace('https://cdn.jsdelivr.net/bootstrap.editable', 'https://cdnjs.cloudflare.com/ajax/libs/x-editable', $libraries['cdn.bootstrap.editable']['library_path']);
856			$libraries['cdn.bootstrap.editable']['path'] .= '/bootstrap-editable';
857
858			$libraries['cdn.bootstrap.switch']['library_path'] = str_replace('https://cdn.jsdelivr.net/bootstrap.switch', 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-switch', $libraries['cdn.bootstrap.switch']['library_path']);
859			$libraries['cdn.fontawesome']['library_path'] = str_replace('https://cdn.jsdelivr.net/fontawesome', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome', $libraries['cdn.fontawesome']['library_path']);
860		}
861	}
862
863}
864
865
866/**
867 * Class e_library_manager.
868 */
869class e_library_manager
870{
871
872	/**
873	 * Constructor
874	 * Use {@link getInstance()}, direct instantiating is not possible for signleton objects.
875	 */
876	public function __construct()
877	{
878	}
879
880	/**
881	 * @return void
882	 */
883	protected function _init()
884	{
885	}
886
887	/**
888	 * Cloning is not allowed.
889	 */
890	private function __clone()
891	{
892	}
893
894	/**
895	 * Tries to detect a library and its installed version.
896	 *
897	 * @param $name
898	 *   The machine name of a library to return registered information for.
899	 *
900	 * @return array|false
901	 *   An associative array containing registered information for the library specified by $name, or FALSE if the
902	 *   library $name is not registered. In addition to the keys returned by info(), the following keys are
903	 *   contained:
904	 *   - installed: A boolean indicating whether the library is installed. Note that not only the top-level library,
905	 *     but also each variant contains this key.
906	 *   - version: If the version could be detected, the full version string.
907	 *   - error: If an error occurred during library detection, one of the following error statuses:
908	 *     "not found", "not detected", "not supported".
909	 *   - error_message: If an error occurred during library detection, a detailed error_message.
910	 */
911	public function detect($name)
912	{
913		// Re-use the statically cached value of info() to save memory.
914		$library = &$this->info($name);
915
916		// Exit early if the library was not found.
917		if($library === false)
918		{
919			return $library;
920		}
921
922		// If 'installed' is set, library detection ran already.
923		if(isset($library['installed']))
924		{
925			return $library;
926		}
927
928		$library['installed'] = false;
929
930		// Check whether the library exists.
931		if(!isset($library['library_path']))
932		{
933			$library['library_path'] = $this->detectPath($library['machine_name']);
934		}
935
936		$libraryPath = e107::getParser()->replaceConstants($library['library_path']);
937		if($library['library_path'] === false || (!file_exists($libraryPath) && substr($libraryPath, 0, 4) != 'http'))
938		{
939			$library['error'] = LAN_NOT_FOUND;
940
941			$replace_with = array($library['name']);
942			$library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_03, $replace_with, true);
943
944			return $library;
945		}
946
947		// TODO:
948		// Invoke callbacks in the 'pre_detect' group.
949		$this->invoke('pre_detect', $library);
950
951		// Detect library version, if not hardcoded.
952		if(!isset($library['version']))
953		{
954			// If version_callback is a method in $this class.
955			if(method_exists($this, $library['version_callback']))
956			{
957				// We support both a single parameter, which is an associative array, and an indexed array of multiple
958				// parameters.
959				if(isset($library['version_arguments'][0]))
960				{
961					// Add the library as the first argument.
962					$classMethod = array($this, $library['version_callback']);
963					$params = array_merge(array($library), $library['version_arguments']);
964					$library['version'] = call_user_func_array($classMethod, $params);
965				}
966				else
967				{
968					$method = $library['version_callback'];
969					$library['version'] = $this->$method($library, $library['version_arguments']);
970				}
971			}
972			// If version_callback is a method in e_library.php file.
973			else
974			{
975				$library['version'] = '';
976				$class = false;
977
978				if(varset($library['plugin'], false))
979				{
980					$class = e107::getAddon($library['plugin'], 'e_library');
981				}
982				elseif(varset($library['theme'], false))
983				{
984					// e107::getAddon() does not support theme folders.
985					if(is_readable(e_THEME . $library['theme'] . '/theme_library.php'))
986					{
987						e107_require_once(e_THEME . $library['theme'] . '/theme_library.php');
988						$addonClass = 'theme_library';
989
990						if(class_exists($addonClass))
991						{
992							$class = new $addonClass();
993						}
994					}
995
996					// e107::getAddon() does not support theme folders.
997					if(is_readable(e_THEME . $library['theme'] . '/admin_theme_library.php'))
998					{
999						e107_require_once(e_THEME . $library['theme'] . '/admin_theme_library.php');
1000						$addonClass = 'admin_theme_library';
1001
1002						if(class_exists($addonClass))
1003						{
1004							$class = new $addonClass();
1005						}
1006					}
1007				}
1008
1009				// We support both a single parameter, which is an associative array, and an
1010				// indexed array of multiple parameters.
1011				if(isset($library['version_arguments'][0]))
1012				{
1013					if($class)
1014					{
1015						$params = array_merge(array($library), $library['version_arguments']);
1016						$library['version'] = e107::callMethod($class, $library['version_callback'], $params);
1017					}
1018				}
1019				else
1020				{
1021					if($class)
1022					{
1023						$library['version'] = e107::callMethod($class, $library['version_callback'], $library, $library['version_arguments']);
1024					}
1025				}
1026			}
1027
1028			if(empty($library['version']))
1029			{
1030				$library['error'] = LAN_LIBRARY_MANAGER_10;
1031
1032				$replace_with = array($library['name']);
1033				$library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_04, $replace_with, true);
1034
1035				return $library;
1036			}
1037		}
1038
1039		// Determine to which supported version the installed version maps.
1040		if(!empty($library['versions']))
1041		{
1042			ksort($library['versions']);
1043			$version = 0;
1044			foreach($library['versions'] as $supported_version => $version_properties)
1045			{
1046				if(version_compare($library['version'], $supported_version, '>='))
1047				{
1048					$version = $supported_version;
1049				}
1050			}
1051			if(!$version)
1052			{
1053				$library['error'] = LAN_LIBRARY_MANAGER_11;
1054
1055				$replace_with = array($library['version'], $library['name']);
1056				$library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_05, $replace_with, true);
1057
1058				return $library;
1059			}
1060
1061			// Apply version specific definitions and overrides.
1062			$library = array_merge($library, $library['versions'][$version]);
1063			unset($library['versions']);
1064		}
1065
1066		// Check each variant if it is installed.
1067		if(!empty($library['variants']))
1068		{
1069			foreach($library['variants'] as $variant_name => &$variant)
1070			{
1071				// If no variant callback has been set, assume the variant to be installed.
1072				if(!isset($variant['variant_callback']))
1073				{
1074					$variant['installed'] = true;
1075				}
1076				else
1077				{
1078					$variant['installed'] = false;
1079					$class = false;
1080
1081					if(varset($library['plugin'], false))
1082					{
1083						$class = e107::getAddon($library['plugin'], 'e_library');
1084					}
1085					elseif(varset($library['theme'], false))
1086					{
1087						// e107::getAddon() does not support theme folders.
1088						if(is_readable(e_THEME . $library['theme'] . '/theme_library.php'))
1089						{
1090							e107_require_once(e_THEME . $library['theme'] . '/theme_library.php');
1091							$addonClass = 'theme_library';
1092
1093							if(class_exists($addonClass))
1094							{
1095								$class = new $addonClass();
1096							}
1097						}
1098
1099						// e107::getAddon() does not support theme folders.
1100						if(is_readable(e_THEME . $library['theme'] . '/admin_theme_library.php'))
1101						{
1102							e107_require_once(e_THEME . $library['theme'] . '/admin_theme_library.php');
1103							$addonClass = 'admin_theme_library';
1104
1105							if(class_exists($addonClass))
1106							{
1107								$class = new $addonClass();
1108							}
1109						}
1110					}
1111
1112					// We support both a single parameter, which is an associative array, and an indexed array of
1113					// multiple parameters.
1114					if(isset($variant['variant_arguments'][0]))
1115					{
1116						if($class)
1117						{
1118							$params = array_merge(array($library, $variant_name), $variant['variant_arguments']);
1119							$variant['installed'] = e107::callMethod($class, $library['variant_callback'], $params);
1120						}
1121					}
1122					else
1123					{
1124						if($class)
1125						{
1126							// Can't use e107::callMethod(), because it only supports 2 params.
1127							if(method_exists($class, $variant['variant_callback']))
1128							{
1129								// Call PLUGIN/THEME_library::VARIANT_CALLBACK().
1130								$method = $variant['variant_callback'];
1131								$variant['installed'] = $class->$method($library, $variant_name, $variant['variant_arguments']);
1132							}
1133						}
1134					}
1135
1136					if(!$variant['installed'])
1137					{
1138						$variant['error'] = LAN_NOT_FOUND;
1139
1140						$replace_with = array($variant_name, $library['name']);
1141						$variant['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_06, $replace_with, true);
1142					}
1143				}
1144			}
1145		}
1146
1147		// If we end up here, the library should be usable.
1148		$library['installed'] = true;
1149
1150		// Invoke callbacks in the 'post_detect' group.
1151		$this->invoke('post_detect', $library);
1152
1153		return $library;
1154	}
1155
1156	/**
1157	 * Loads a library.
1158	 *
1159	 * @param $name
1160	 *   The name of the library to load.
1161	 * @param $variant
1162	 *   The name of the variant to load. Note that only one variant of a library can be loaded within a single
1163	 *   request. The variant that has been passed first is used; different variant names in subsequent calls are
1164	 *   ignored.
1165	 *
1166	 * @return mixed
1167	 *   An associative array of the library information as returned from config(). The top-level properties
1168	 *   contain the effective definition of the library (variant) that has been loaded. Additionally:
1169	 *   - installed: Whether the library is installed, as determined by detect().
1170	 *   - loaded: Either the amount of library files that have been loaded, or FALSE if the library could not be
1171	 *   loaded. See MYPLUGIN_library::config() for more information.
1172	 */
1173	public function load($name, $variant = null)
1174	{
1175		// Re-use the statically cached value to save memory.
1176		static $loaded;
1177
1178		if(!isset($loaded[$name]))
1179		{
1180			$cache = e107::getCache();
1181			$cache_context = (defset('e_ADMIN_AREA', false) == true) ? 'AdminArea' : 'UserArea';
1182			$cacheID = 'Library_' . $cache_context . '_' . e107::getParser()->filter($name, 'file');
1183			$cached = $cache->retrieve($cacheID, false, true, true);
1184
1185			if($cached)
1186			{
1187				$library = e107::unserialize($cached);
1188			}
1189
1190			if(!varset($library, false))
1191			{
1192				$library = $this->detect($name);
1193				$cacheData = e107::serialize($library, 'json');
1194				$cache->set($cacheID, $cacheData, true, true, true);
1195			}
1196
1197			// Exit early if the library was not found.
1198			if($library === false)
1199			{
1200				$loaded[$name] = $library;
1201				return $loaded[$name];
1202			}
1203
1204			// If a variant was specified, override the top-level properties with the variant properties.
1205			if(isset($variant))
1206			{
1207				// Ensure that the $variant key exists, and if it does not, set its 'installed' property to FALSE by
1208				// default. This will prevent the loading of the library files below.
1209				$library['variants'] += array($variant => array('installed' => false));
1210				$library = array_merge($library, $library['variants'][$variant]);
1211			}
1212			// Regardless of whether a specific variant was requested or not, there can only be one variant of a
1213			// library within a single request.
1214			unset($library['variants']);
1215
1216			// TODO:
1217			// Invoke callbacks in the 'pre_dependencies_load' group.
1218			$this->invoke('pre_dependencies_load', $library);
1219
1220			// If the library (variant) is installed, load it.
1221			$library['loaded'] = false;
1222			if($library['installed'])
1223			{
1224				// Load library dependencies.
1225				if(isset($library['dependencies']))
1226				{
1227					foreach($library['dependencies'] as $dependency)
1228					{
1229						$this->load($dependency);
1230					}
1231				}
1232
1233				// TODO:
1234				// Invoke callbacks in the 'pre_load' group.
1235				$this->invoke('pre_load', $library);
1236
1237				// Load all the files associated with the library.
1238				$library['loaded'] = $this->loadFiles($library);
1239
1240				// TODO:
1241				// Invoke callbacks in the 'post_load' group.
1242				$this->invoke('post_load', $library);
1243			}
1244			$loaded[$name] = $library;
1245		}
1246
1247		return $loaded[$name];
1248	}
1249
1250	/**
1251	 * Gets the path of a library.
1252	 *
1253	 * @param $name
1254	 *   The machine name of a library to return the path for.
1255	 *
1256	 * @return string
1257	 *   The path to the specified library or FALSE if the library wasn't found.
1258	 */
1259	private function detectPath($name)
1260	{
1261		static $libraries;
1262
1263		if(!isset($libraries))
1264		{
1265			$libraries = $this->getLibraries();
1266		}
1267
1268		$path = '';
1269		if(!isset($libraries[$name]))
1270		{
1271			return false;
1272		}
1273		else
1274		{
1275			$path .= $libraries[$name];
1276		}
1277
1278		return $path;
1279	}
1280
1281	/**
1282	 * Returns an array of library directories.
1283	 *
1284	 * @return array
1285	 *   A list of library directories.
1286	 */
1287	private function getLibraries()
1288	{
1289		$dir = e_WEB . 'lib';
1290		$directories = array();
1291
1292		// Retrieve list of directories.
1293		$file = e107::getFile();
1294		$dirs = $file->get_dirs($dir);
1295
1296		foreach($dirs as $dirName)
1297		{
1298			$directories[$dirName] = "{e_WEB}lib/$dirName";
1299		}
1300
1301		return $directories;
1302	}
1303
1304	/**
1305	 * Returns with the selected property of a library.
1306	 *
1307	 * @param string $library
1308	 *  Library machine name. For example: bootstrap
1309	 *
1310	 * @param string $property
1311	 *  The property name. For example: library_path
1312	 *
1313	 * @return mixed
1314	 */
1315	public function getProperty($library, $property)
1316	{
1317		$lib = self::info($library);
1318		return varset($lib[$property], false);
1319	}
1320
1321
1322	/**
1323	 * Return full path to a library in different formats.
1324	 * @param string $library
1325	 * The library name eg. bootstrap
1326	 *
1327	 * @param null $mode
1328	 * The mode: null | 'full' | 'abs'
1329	 *
1330	 * @return string
1331	 */
1332	public function getPath($library, $mode=null)
1333	{
1334		$path = self::getProperty($library, 'library_path').'/'. self::getProperty($library, 'path');
1335		return e107::getParser()->replaceConstants($path,$mode).'/';
1336	}
1337
1338
1339	/**
1340	 * Returns information about registered libraries.
1341	 *
1342	 * The returned information is unprocessed; i.e., as registered by plugins.
1343	 *
1344	 * @param $library
1345	 *   (optional) The machine name of a library to return registered information for. If omitted, information
1346	 *   about all registered libraries is returned.
1347	 *
1348	 * @return array|false
1349	 *   An associative array containing registered information for all libraries, the registered information for the
1350	 *   library specified by $name, or FALSE if the library $name is not registered.
1351	 */
1352	public function &info($library = null)
1353	{
1354		// This static cache is re-used by detect() to save memory.
1355		static $libraries;
1356
1357		if(!isset($libraries))
1358		{
1359			$libraries = array();
1360
1361			$coreLibrary = new core_library();
1362			$info = $coreLibrary->config();
1363			if(is_array($info))
1364			{
1365				foreach($info as $machine_name => $properties)
1366				{
1367					$properties['info_type'] = 'core';
1368					$libraries[$machine_name] = $properties;
1369				}
1370			}
1371
1372			$plugins = array();
1373			$themes = array();
1374
1375			// Gather information from PLUGIN_library::config().
1376			$pluginInfo = e107::getAddonConfig('e_library', 'library'); // 'config' is the default.
1377			foreach($pluginInfo as $plugin => $info)
1378			{
1379				foreach($info as $machine_name => $properties)
1380				{
1381					$properties['info_type'] = 'plugin';
1382					$properties['plugin'] = $plugin;
1383					$libraries[$machine_name] = $properties;
1384					$plugins[] = $plugin; // This plugin has a valid e_library implementation.
1385				}
1386			}
1387
1388			$themes[] = array(
1389				'name'  => e107::getPref('sitetheme'),
1390				'file'  => 'theme_library',
1391				'class' => 'theme_library',
1392			);
1393
1394			$themes[] = array(
1395				'name'  => e107::getPref('admintheme'),
1396				'file'  => 'admin_theme_library',
1397				'class' => 'admin_theme_library',
1398			);
1399
1400			foreach($themes as $theme)
1401			{
1402				if(is_readable(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php'))
1403				{
1404					e107_require_once(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php');
1405
1406					$info = e107::callMethod($theme['class'], 'config');
1407					if(is_array($info))
1408					{
1409						foreach($info as $machine_name => $properties)
1410						{
1411							$properties['info_type'] = 'theme';
1412							$properties['theme'] = $theme['name'];
1413							$libraries[$machine_name] = $properties;
1414						}
1415					}
1416				}
1417			}
1418
1419			// Provide defaults.
1420			foreach($libraries as $machine_name => &$properties)
1421			{
1422				$this->infoDefaults($properties, $machine_name);
1423			}
1424
1425			// Alter config array. For example, change CDN provider.
1426			$coreLibrary->config_alter($libraries);
1427
1428			// Allow enabled plugins (with e_library.php file) to alter the registered libraries.
1429			foreach($plugins as $plugin)
1430			{
1431				$class = e107::getAddon($plugin, 'e_library');
1432				if($class && method_exists($class, 'config_alter'))
1433				{
1434					// The library definitions are passed by reference.
1435					$class->config_alter($libraries);
1436				}
1437			}
1438
1439			// Allow enabled themes to alter the registered libraries.
1440			foreach($themes as $theme)
1441			{
1442				if(is_readable(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php'))
1443				{
1444					e107_require_once(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php');
1445
1446					if(class_exists($theme['class']))
1447					{
1448						$class = new $theme['class']();
1449						if(method_exists($class, 'config_alter'))
1450						{
1451							// We cannot use e107::callMethod() because need to pass variable by reference.
1452							$class->config_alter($libraries);
1453						}
1454					}
1455				}
1456			}
1457
1458			// TODO:
1459			// Invoke callbacks in the 'info' group.
1460			foreach($libraries as &$properties)
1461			{
1462				$this->invoke('info', $properties);
1463			}
1464		}
1465
1466		if(isset($library))
1467		{
1468			if(!empty($libraries[$library]))
1469			{
1470				return $libraries[$library];
1471			}
1472			else
1473			{
1474				$false = false;
1475				return $false;
1476			}
1477		}
1478
1479		return $libraries;
1480	}
1481
1482	/**
1483	 * Applies default properties to a library definition.
1484	 *
1485	 * @param array $library
1486	 *   An array of library information, passed by reference.
1487	 * @param string $name
1488	 *   The machine name of the passed-in library.
1489	 *
1490	 * @return array
1491	 */
1492	private function infoDefaults(&$library, $name)
1493	{
1494		$library += array(
1495			'machine_name'      => $name,
1496			'name'              => $name,
1497			'vendor_url'        => '',
1498			'download_url'      => '',
1499			'path'              => '',
1500			'library_path'      => null,
1501			'version_callback'  => 'getVersion',
1502			'version_arguments' => array(),
1503			'files'             => array(),
1504			'dependencies'      => array(),
1505			'variants'          => array(),
1506			'versions'          => array(),
1507			'integration_files' => array(),
1508			'callbacks'         => array(),
1509		);
1510
1511		$library['callbacks'] += array(
1512			'info'                  => array(),
1513			'pre_detect'            => array('preDetect'),
1514			'post_detect'           => array(),
1515			'pre_dependencies_load' => array(),
1516			'pre_load'              => array('preLoad'),
1517			'post_load'             => array(),
1518		);
1519
1520		// Add our own callbacks before any others.
1521		array_unshift($library['callbacks']['info'], 'prepareFiles');
1522		array_unshift($library['callbacks']['post_detect'], 'detectDependencies');
1523
1524		return $library;
1525	}
1526
1527	/**
1528	 * Library info callback to make all 'files' properties consistent.
1529	 *
1530	 * This turns libraries' file information declared as e.g.
1531	 * @code
1532	 * $library['files']['js'] = array('example_1.js', 'example_2.js');
1533	 * @endcode
1534	 * into
1535	 * @code
1536	 * $library['files']['js'] = array(
1537	 *   'example_1.js' => array(),
1538	 *   'example_2.js' => array(),
1539	 * );
1540	 * @endcode
1541	 * It does the same for the 'integration_files' property.
1542	 *
1543	 * @param $library
1544	 *   An associative array of library information or a part of it, passed by reference.
1545	 * @param $version
1546	 *   If the library information belongs to a specific version, the version string. NULL otherwise.
1547	 * @param $variant
1548	 *   If the library information belongs to a specific variant, the variant name. NULL otherwise.
1549	 */
1550	private function prepareFiles(&$library, $version = null, $variant = null)
1551	{
1552		// Both the 'files' property and the 'integration_files' property contain file declarations, and we want to make
1553		// both consistent.
1554		$file_types = array();
1555		if(isset($library['files']))
1556		{
1557			$file_types[] = &$library['files'];
1558		}
1559		if(isset($library['integration_files']))
1560		{
1561			// Integration files are additionally keyed by plugin.
1562			foreach($library['integration_files'] as &$integration_files)
1563			{
1564				$file_types[] = &$integration_files;
1565			}
1566		}
1567		foreach($file_types as &$files)
1568		{
1569			// Go through all supported types of files.
1570			foreach(array('js', 'css', 'php') as $type)
1571			{
1572				if(isset($files[$type]))
1573				{
1574					foreach($files[$type] as $key => $value)
1575					{
1576						// Unset numeric keys and turn the respective values into keys.
1577						if(is_numeric($key))
1578						{
1579							$files[$type][$value] = array();
1580							unset($files[$type][$key]);
1581						}
1582					}
1583				}
1584			}
1585		}
1586	}
1587
1588	/**
1589	 * Library post detect callback to process and detect dependencies.
1590	 *
1591	 * It checks whether each of the dependencies of a library are installed and available in a compatible version.
1592	 *
1593	 * @param $library
1594	 *   An associative array of library information or a part of it, passed by reference.
1595	 * @param $version
1596	 *   If the library information belongs to a specific version, the version string. NULL otherwise.
1597	 * @param $variant
1598	 *   If the library information belongs to a specific variant, the variant name. NULL otherwise.
1599	 */
1600	private function detectDependencies(&$library, $version = null, $variant = null)
1601	{
1602		if(isset($library['dependencies']))
1603		{
1604			foreach($library['dependencies'] as &$dependency_string)
1605			{
1606				$dependency_info = $this->parseDependency($dependency_string);
1607				$dependency = $this->detect($dependency_info['name']);
1608				if(!$dependency['installed'])
1609				{
1610					$library['installed'] = false;
1611					$library['error'] = LAN_LIBRARY_MANAGER_07;
1612
1613					$replace_with = array($dependency['name'], $library['name']);
1614					$library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_01, $replace_with, true);
1615				}
1616				elseif($this->checkIncompatibility($dependency_info, $dependency['version']))
1617				{
1618					$library['installed'] = false;
1619					$library['error'] = LAN_LIBRARY_MANAGER_08;
1620
1621					$replace_with = array($dependency['version'], $library['name'], $library['name']);
1622					$library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_02, $replace_with, true);
1623				}
1624
1625				// Remove the version string from the dependency, so load() can load the libraries directly.
1626				$dependency_string = $dependency_info['name'];
1627			}
1628		}
1629	}
1630
1631	/**
1632	 * Invokes library callbacks.
1633	 *
1634	 * @param $group
1635	 *   A string containing the group of callbacks that is to be applied. Should be either 'info', 'post_detect'.
1636	 * @param $library
1637	 *   An array of library information, passed by reference.
1638	 */
1639	private function invoke($group, &$library)
1640	{
1641		// When introducing new callback groups in newer versions, stale cached library information somehow reaches
1642		// this point during the database update before clearing the library cache.
1643		if(empty($library['callbacks'][$group]))
1644		{
1645			return;
1646		}
1647
1648		foreach($library['callbacks'][$group] as $callback)
1649		{
1650			$this->traverseLibrary($library, $callback);
1651		}
1652	}
1653
1654	/**
1655	 * Helper function to apply a callback to all parts of a library.
1656	 *
1657	 * Because library declarations can include variants and versions, and those version declarations can in turn
1658	 * include variants, modifying e.g. the 'files' property everywhere it is declared can be quite cumbersome, in
1659	 * which case this helper function is useful.
1660	 *
1661	 * @param $library
1662	 *   An array of library information, passed by reference.
1663	 * @param $callback
1664	 *   A string containing the callback to apply to all parts of a library.
1665	 */
1666	private function traverseLibrary(&$library, $callback)
1667	{
1668		// If callback belongs to $this class.
1669		if(method_exists($this, $callback))
1670		{
1671			// Always apply the callback to the top-level library.
1672			// Params: $library, $version, $variant
1673			$this->{$callback}($library, null, null);
1674
1675			// Apply the callback to versions.
1676			if(isset($library['versions']))
1677			{
1678				foreach($library['versions'] as $version_string => &$version)
1679				{
1680					$this->{$callback}($version, $version_string, null);
1681
1682					// Versions can include variants as well.
1683					if(isset($version['variants']))
1684					{
1685						foreach($version['variants'] as $version_variant_name => &$version_variant)
1686						{
1687							$this->{$callback}($version_variant, $version_string, $version_variant_name);
1688						}
1689					}
1690				}
1691			}
1692
1693			// Apply the callback to variants.
1694			if(isset($library['variants']))
1695			{
1696				foreach($library['variants'] as $variant_name => &$variant)
1697				{
1698					$this->$callback($variant, null, $variant_name);
1699				}
1700			}
1701		}
1702		else
1703		{
1704			// TODO: Provide the ability to use third-party callbacks (are defined in e_library.php files) for groups:
1705			// 'info', 'pre_detect', 'post_detect', 'pre_dependencies_load', 'pre_load', 'post_load'
1706		}
1707	}
1708
1709	/**
1710	 * Loads a library's files.
1711	 *
1712	 * @param $library
1713	 *   An array of library information as returned by info().
1714	 *
1715	 * @return int
1716	 *   The number of loaded files.
1717	 */
1718	private function loadFiles($library)
1719	{
1720		$siteTheme = e107::getPref('sitetheme');
1721		$adminTheme = e107::getPref('admintheme');
1722
1723		// Load integration_files.
1724		if(!empty($library['post_load_integration_files']) && !$library['post_load_integration_files'] && !empty($library['integration_files']))
1725		{
1726			foreach($library['integration_files'] as $provider => $files)
1727			{
1728				// If provider is an installed plugin.
1729				if(e107::isInstalled($provider))
1730				{
1731					$this->loadFiles(array(
1732						'files'                       => $files,
1733						'path'                        => '',
1734						'library_path'                => e_PLUGIN . $provider,
1735						'post_load_integration_files' => false,
1736					));
1737				}
1738				// If provider is the admin theme, we only allow it for admin pages.
1739				elseif(e_ADMIN_AREA && $provider == $adminTheme)
1740				{
1741					$this->loadFiles(array(
1742						'files'                       => $files,
1743						'path'                        => '',
1744						'library_path'                => e_THEME . $provider,
1745						'post_load_integration_files' => false,
1746					));
1747				}
1748				// If provider is the site theme, we only allow it on user areas.
1749				elseif(!deftrue(e_ADMIN_AREA, false) && $provider == $siteTheme)
1750				{
1751					$this->loadFiles(array(
1752						'files'                       => $files,
1753						'path'                        => '',
1754						'library_path'                => e_THEME . $provider,
1755						'post_load_integration_files' => false,
1756					));
1757				}
1758			}
1759		}
1760
1761		// Construct the full path to the library for later use.
1762		$path = e107::getParser()->replaceConstants($library['library_path']);
1763		$path = ($library['path'] !== '' ? rtrim($path, '/') . '/' . $library['path'] : $path);
1764		$path = rtrim($path, '/');
1765
1766		// Count the number of loaded files for the return value.
1767		$count = 0;
1768
1769		// Load both the JavaScript and the CSS files.
1770		foreach(array('js', 'css') as $type)
1771		{
1772			if(!empty($library['files'][$type]))
1773			{
1774				foreach($library['files'][$type] as $data => $options)
1775				{
1776					// If the value is not an array, it's a filename and passed as first (and only) argument.
1777					if(!is_array($options))
1778					{
1779						$data = $options;
1780						$options = array();
1781					}
1782					// In some cases, the first parameter ($data) is an array. Arrays can't be passed as keys in PHP,
1783					// so we have to get $data from the value array.
1784					if(is_numeric($data))
1785					{
1786						$data = $options['data'];
1787						unset($options['data']);
1788					}
1789					// Prepend the library_path to the file name.
1790					$data = "$path/$data";
1791					// Apply the default zone if the zone isn't explicitly given.
1792					if(!isset($options['zone']))
1793					{
1794						$options['zone'] = ($type == 'js') ? 2 : 2; // TODO: default zones.
1795					}
1796					// Apply the default type if the type isn't explicitly given.
1797					if(!isset($options['type']))
1798					{
1799						$options['type'] = 'url';
1800					}
1801					if($type == 'js')
1802					{
1803						e107::js($options['type'], $data, null, $options['zone']);
1804					}
1805					elseif($type == 'css')
1806					{
1807						e107::getJs()->libraryCSS($data); // load before others.
1808					//	e107::css($options['type'], $data, null);
1809					}
1810					$count++;
1811				}
1812			}
1813		}
1814
1815		// Load PHP files.
1816		if(!empty($library['files']['php']))
1817		{
1818			foreach($library['files']['php'] as $file => $array)
1819			{
1820				$file_path1 = $path . '/' . $file;
1821				$file_path2 = e_ROOT . $path . '/' . $file;
1822
1823				if(file_exists($file_path1))
1824				{
1825					$this->_requireOnce($file_path1);
1826					$count++;
1827				}
1828				elseif(file_exists($file_path2))
1829				{
1830					$this->_requireOnce($file_path2);
1831					$count++;
1832				}
1833			}
1834		}
1835
1836		// Load integration_files.
1837		if(!empty($library['post_load_integration_files']) && $library['post_load_integration_files'] && !empty($library['integration_files']))
1838		{
1839			foreach($library['integration_files'] as $provider => $files)
1840			{
1841				// If provider is an installed plugin.
1842				if(e107::isInstalled($provider))
1843				{
1844					$this->loadFiles(array(
1845						'files'                       => $files,
1846						'path'                        => '',
1847						'library_path'                => e_PLUGIN . $provider,
1848						'post_load_integration_files' => false,
1849					));
1850				}
1851				// If provider is the admin theme, we only allow it for admin pages.
1852				elseif(e_ADMIN_AREA && $provider == $adminTheme)
1853				{
1854					$this->loadFiles(array(
1855						'files'                       => $files,
1856						'path'                        => '',
1857						'library_path'                => e_THEME . $provider,
1858						'post_load_integration_files' => false,
1859					));
1860				}
1861				// If provider is the site theme, we only allow it on user areas.
1862				elseif(!deftrue(e_ADMIN_AREA, false) && $provider == $siteTheme)
1863				{
1864					$this->loadFiles(array(
1865						'files'                       => $files,
1866						'path'                        => '',
1867						'library_path'                => e_THEME . $provider,
1868						'post_load_integration_files' => false,
1869					));
1870				}
1871			}
1872		}
1873
1874		return $count;
1875	}
1876
1877	/**
1878	 * Wrapper function for require_once.
1879	 *
1880	 * A library file could set a $path variable in file scope. Requiring such a file directly in loadFiles()
1881	 * would lead to the local $path variable being overridden after the require_once statement. This would break
1882	 * loading further files. Therefore we use this trivial wrapper which has no local state that can be tampered with.
1883	 *
1884	 * @param $file_path
1885	 *   The file path of the file to require.
1886	 */
1887	private function _requireOnce($file_path)
1888	{
1889		// TODO: use e107_require_once() instead?
1890		require_once $file_path;
1891	}
1892
1893	/**
1894	 * Gets the version information from an arbitrary library.
1895	 *
1896	 * @param $library
1897	 *   An associative array containing all information about the library.
1898	 * @param $options
1899	 *   An associative array containing with the following keys:
1900	 *   - file: The filename to parse for the version, relative to the library path. For example: 'docs/changelog.txt'.
1901	 *   - pattern: A string containing a regular expression (PCRE) to match the library version. For example:
1902	 *     '@version\s+([0-9a-zA-Z\.-]+)@'. Note that the returned version is not the match of the entire pattern (i.e.
1903	 *     '@version 1.2.3' in the above example) but the match of the first sub-pattern (i.e. '1.2.3' in the above example).
1904	 *   - lines: (optional) The maximum number of lines to search the pattern in. Defaults to 20.
1905	 *   - cols: (optional) The maximum number of characters per line to take into account. Defaults to 200. In case of
1906	 *     minified or compressed files, this prevents reading the entire file into memory.
1907	 *
1908	 * @return mixed
1909	 *   A string containing the version of the library. Or null.
1910	 */
1911	private function getVersion($library, $options)
1912	{
1913		// Provide defaults.
1914		$options += array(
1915			'file'    => '',
1916			'pattern' => '',
1917			'lines'   => 20,
1918			'cols'    => 200,
1919		);
1920
1921		$libraryPath = e107::getParser()->replaceConstants($library['library_path']);
1922		$libraryPath = ($library['path'] !== '' ? rtrim($libraryPath, '/') . '/' . $library['path'] : $libraryPath);
1923		$libraryPath = rtrim($libraryPath, '/');
1924
1925		$file = $libraryPath . '/' . $options['file'];
1926
1927		if(empty($options['file']))
1928		{
1929			return;
1930		}
1931
1932		// If remote file (e.g. CDN URL)... we download file to temp, and get version number.
1933		// The library will be cached with version number, so this only run once per library.
1934		if(substr($file, 0, 4) == 'http')
1935		{
1936			$content = e107::getFile()->getRemoteContent($file);
1937			$tmpFile = tempnam(sys_get_temp_dir(), 'lib_');
1938
1939			if($tmpFile)
1940			{
1941				file_put_contents($tmpFile, $content);
1942				$file = $tmpFile;
1943			}
1944		}
1945
1946		if(!file_exists($file))
1947		{
1948			return;
1949		}
1950
1951		$file = fopen($file, 'r');
1952		while($options['lines'] && $line = fgets($file, $options['cols']))
1953		{
1954			if(preg_match($options['pattern'], $line, $version))
1955			{
1956				fclose($file);
1957
1958				// If downloaded file, we need to unlink it from temp.
1959				if(isset($tmpFile) && file_exists($tmpFile))
1960				{
1961					unlink($tmpFile);
1962				}
1963
1964				return $version[1];
1965			}
1966			$options['lines']--;
1967		}
1968		fclose($file);
1969
1970		// If downloaded file, we need to unlink it from temp.
1971		if(isset($tmpFile) && file_exists($tmpFile))
1972		{
1973			unlink($tmpFile);
1974		}
1975
1976		return;
1977	}
1978
1979	/**
1980	 * Parses a dependency for comparison by checkIncompatibility().
1981	 *
1982	 * @param $dependency
1983	 *   A dependency string, which specifies a plugin dependency, and versions that are supported. Supported formats
1984	 *   include:
1985	 *   - 'plugin'
1986	 *   - 'plugin (>=version, version)'
1987	 *
1988	 * @return array
1989	 *   An associative array with three keys:
1990	 *   - 'name' includes the name of the thing to depend on (e.g. 'foo').
1991	 *   - 'original_version' contains the original version string (which can be used in the UI for reporting
1992	 *     incompatibilities).
1993	 *   - 'versions' is a list of associative arrays, each containing the keys 'op' and 'version'. 'op' can be one of:
1994	 *     '=', '==', '!=', '<>', '<', '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'.
1995	 *   Callers should pass this structure to checkIncompatibility().
1996	 */
1997	private function parseDependency($dependency)
1998	{
1999		$value = array();
2000
2001		// We use named subpatterns and support every op that version_compare supports. Also, op is optional and
2002		// defaults to equals.
2003		$p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?';
2004		$p_major = '(?P<major>\d+)';
2005		// By setting the minor version to x, branches can be matched.
2006		$p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
2007		$parts = explode('(', $dependency, 2);
2008		$value['name'] = trim($parts[0]);
2009
2010		if(isset($parts[1]))
2011		{
2012			$value['original_version'] = ' (' . $parts[1];
2013			foreach(explode(',', $parts[1]) as $version)
2014			{
2015				if(preg_match("/^\s*$p_op\s*$p_major\.$p_minor/", $version, $matches))
2016				{
2017					$op = !empty($matches['operation']) ? $matches['operation'] : '=';
2018					if($matches['minor'] == 'x')
2019					{
2020						// "2.x" to mean any version that begins with "2" (e.g. 2.0, 2.9 are all "2.x").
2021						// PHP's version_compare(), on the other hand, treats "x" as a string; so to version_compare(),
2022						// "2.x" is considered less than 2.0. This means that >=2.x and <2.x are handled by
2023						// version_compare() as we need, but > and <= are not.
2024						if($op == '>' || $op == '<=')
2025						{
2026							$matches['major']++;
2027						}
2028						// Equivalence can be checked by adding two restrictions.
2029						if($op == '=' || $op == '==')
2030						{
2031							$value['versions'][] = array('op' => '<', 'version' => ($matches['major'] + 1) . '.x');
2032							$op = '>=';
2033						}
2034					}
2035					$value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']);
2036				}
2037			}
2038		}
2039
2040		return $value;
2041	}
2042
2043	/**
2044	 * Checks whether a version is compatible with a given dependency.
2045	 *
2046	 * @param $v
2047	 *   The parsed dependency structure from parseDependency().
2048	 * @param $current_version
2049	 *   The version to check against (like 4.2).
2050	 *
2051	 * @return mixed
2052	 *   NULL if compatible, otherwise the original dependency version string that caused the incompatibility.
2053	 *
2054	 * @see parseDependency()
2055	 */
2056	private function checkIncompatibility($v, $current_version)
2057	{
2058		if(!empty($v['versions']))
2059		{
2060			foreach($v['versions'] as $required_version)
2061			{
2062				if((isset($required_version['op'])))
2063				{
2064					if(!version_compare($current_version, $required_version['version'], $required_version['op']))
2065					{
2066						return $v['original_version'];
2067					}
2068				}
2069			}
2070		}
2071	}
2072
2073	/**
2074	 * Alters library information before detecting.
2075	 */
2076	private function preDetect(&$library)
2077	{
2078		if(empty($library['machine_name']))
2079		{
2080			return;
2081		}
2082
2083		// Prevent plugins/themes from altering libraries on Admin UI.
2084		if(defset('e_ADMIN_AREA', false) == true)
2085		{
2086			$coreLibrary = new core_library();
2087			$coreLibs = $coreLibrary->config();
2088
2089			if (isset($coreLibs[$library['machine_name']])) {
2090				$coreLib = $coreLibs[$library['machine_name']];
2091				$library = array_replace_recursive($coreLib, array_replace_recursive($library, $coreLib));
2092			}
2093		}
2094	}
2095
2096	/**
2097	 * Alters library information before loading.
2098	 */
2099	private function preLoad(&$library)
2100	{
2101		if(empty($library['machine_name']))
2102		{
2103			return;
2104		}
2105
2106		$excluded = $this->getExcludedLibraries();
2107
2108		if(empty($excluded))
2109		{
2110			return;
2111		}
2112
2113		// Make sure we have the name without cdn prefix.
2114		$basename = str_replace('cdn.', '', $library['machine_name']);
2115
2116		// If this library (or the CDN version of this library) is excluded
2117		// by the theme is currently used.
2118		if (in_array($basename, $excluded) || in_array('cdn.' . $basename, $excluded))
2119		{
2120			unset($library['files']['css']);
2121
2122			if (!empty($library['variants']))
2123			{
2124				foreach($library['variants'] as &$variant)
2125				{
2126					if(!empty($variant['files']['css']))
2127					{
2128						unset($variant['files']['css']);
2129					}
2130				}
2131			}
2132		}
2133	}
2134
2135	/**
2136	 * Get excluded libraries.
2137	 *
2138	 * @return array
2139	 */
2140	public function getExcludedLibraries()
2141	{
2142		// This static cache is re-used by preLoad() to save memory.
2143		static $excludedLibraries;
2144
2145		if(!isset($excludedLibraries))
2146		{
2147			$excludedLibraries = array();
2148
2149			$exclude = e107::getTheme('current', false)->cssAttribute('auto', 'exclude');
2150
2151			if($exclude)
2152			{
2153				// Split string into array and remove whitespaces.
2154				$excludedLibraries = array_map('trim', explode(',', $exclude));
2155			}
2156		}
2157
2158		return $excludedLibraries;
2159	}
2160
2161}
2162