<?php

/**
 * e107 website system
 *
 * Copyright (C) 2008-2017 e107 Inc (e107.org)
 * Released under the terms and conditions of the
 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
 *
 * @file
 * External library handling for e107 core/plugins/themes.
 *
 * TODO:
 * - Provide the ability to use third-party callbacks (are defined in e_library.php files) for groups:
 *   'info', 'pre_detect', 'post_detect', 'pre_dependencies_load', 'pre_load', 'post_load'
 */

// [e_LANGUAGEDIR]/[e_LANGUAGE]/lan_library_manager.php
e107::lan('core', 'library_manager');



/**
 * Class core_library.
 */
class core_library
{

	/**
	 * Provides information about external libraries.
	 *
	 * Provides information about:
	 * - jQuery (CDN).
	 * - jQuery (local).
	 * - jQuery Once (CDN)
	 * - jQuery Once (local)
	 * - jQuery UI (CDN)
	 * - jQuery UI (local)
	 * - Bootstrap (CDN)
	 * - Bootstrap (local)
	 * - Bootstrap Editable (CDN)
	 * - Bootstrap Editable (local)
	 * - Font-Awesome (CDN)
	 * - Font-Awesome (local)
	 */
	public function config()
	{
		$libraries = array();




		// jQuery (CDN).
		$libraries['cdn.jquery'] = array(
			'name'              => 'jQuery 2 (CDN)',
			'vendor_url'        => 'https://jquery.com/',
			'version_arguments' => array(
				'file'    => 'jquery.min.js',
				'pattern' => '/jQuery\s+v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js' => array(
					'jquery.min.js' => array(
						'zone' => 1,
						'type' => 'url',
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js' => array(
							'jquery.js' => array(
								'zone' => 1,
								'type' => 'url',
							),
						),
					),
				),
			),
			// Override library path to CDN.
			'library_path'      => 'https://cdn.jsdelivr.net/jquery',
			'path'              => '2.2.4',
			'version'           => '2.2.4',
		);

		// jQuery (local).
		$libraries['jquery'] = array(
			'name'              => 'jQuery 2 (local)',
			'vendor_url'        => 'https://jquery.com/',
			'version_arguments' => array(
				'file'    => 'dist/jquery.min.js',
				'pattern' => '/v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js' => array(
					'dist/jquery.min.js' => array(
						'zone' => 1,
						'type' => 'url',
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js' => array(
							'dist/jquery.js' => array(
								'zone' => 1,
								'type' => 'url',
							),
						),
					),
				),
			),
			'library_path'      => '{e_WEB}lib/jquery',
			'path'              => '2.2.4',
			'version'           => '2.2.4',
		);

		// jQuery Once (CDN).
		$libraries['cdn.jquery.once'] = array(
			'name'              => 'jQuery Once (CDN)',
			'vendor_url'        => 'https://plugins.jquery.com/once/',
			'version_arguments' => array(
				'file'    => 'jquery.once.min.js',
				'pattern' => '/jQuery\sOnce\s+v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js' => array(
					'jquery.once.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js' => array(
							// There is no non-minified version.
							'jquery.once.min.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
					),
				),
			),
			// Override library path to CDN.
			'library_path'      => 'https://cdn.jsdelivr.net/jquery.once',
			'path'              => '2.1.2',
			'version'           => '2.1.2',
		);

		// jQuery Once (local).
		$libraries['jquery.once'] = array(
			'name'              => 'jQuery Once (local)',
			'vendor_url'        => 'https://plugins.jquery.com/once/',
			'version_arguments' => array(
				'file'    => 'jquery.once.min.js',
				'pattern' => '/jQuery\sOnce\s+v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js' => array(
					'jquery.once.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js' => array(
							// There is no non-minified version.
							'jquery.once.min.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
					),
				),
			),
			// Override library path.
			'library_path'      => '{e_WEB}lib/jquery-once',
			'version'   => '2.2.3',
			'path' => '',
		);

		// jQuery UI (CDN).
		$libraries['cdn.jquery.ui'] = array(
			'name'              => 'jQuery UI (CDN)',
			'vendor_url'        => 'https://jqueryui.com/',
			'version_arguments' => array(
				'file'    => 'jquery-ui.min.js',
				'pattern' => '/v(\d\.\d+\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'jquery-ui.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
				'css' => array(
					'themes/base/jquery-ui.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js'  => array(
							// There is no non-minified version.
							'jquery-ui.min.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
						'css' => array(
							// There is no non-minified version.
							'jquery-ui.min.css' => array(
								'zone' => 2,
							),
						),
					),
				),
			),
			// Override library path to CDN.
			'library_path'      => 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui',
			'path'              => '1.13.2',
			'version'           => '1.13.2',
		);

		// jQuery UI (local).
		$libraries['jquery.ui'] = array(
			'name'              => 'jQuery UI (local)',
			'vendor_url'        => 'https://jqueryui.com/',
			'version_arguments' => array(
				'file'    => 'jquery-ui.js',
				'pattern' => '/v(\d\.\d+\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'jquery-ui.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
				'css' => array(
					'jquery-ui.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js'  => array(
							'jquery-ui.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
						'css' => array(
							'jquery-ui.css' => array(
								'zone' => 2,
							),
						),
					),
				),
			),
			// Override library path.
			'library_path'      => '{e_WEB}lib/jquery-ui',
			'version'           => '1.13.2'
		);



		// ----------------- jQuery 3 (frontend only) --------------//


		// jQuery (local).
		$libraries['jquery3'] = array(
			'name'              => 'jQuery 3 (local)',
			'vendor_url'        => 'https://jquery.com/',
			'version_arguments' => array(
				'file'    => 'jquery.min.js',
				'pattern' => '/v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js' => array(
					'jquery.min.js' => array(
						'zone' => 1,
						'type' => 'url',
					),
				),
			),
			'variants'          => array(
			),
			'library_path'      => '{e_WEB}lib/jquery',
			'path'              => '3',
			'version'           => '3.7.1',
		);




		// https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js
           https://cdn.jsdelivr.net/jquery/npm/jquery@3.6.0/dist/jquery.min.js
		$libraries['cdn.jquery3'] = array(
			'name'              => 'jQuery 3 (CDN)',
			'vendor_url'        => 'https://jquery.com/',
			'version_arguments' => array(
				'file'    => 'jquery.min.js',
				'pattern' => '/jQuery\s+v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js' => array(
					'jquery.min.js' => array(
						'zone' => 1,
						'type' => 'url',
					),
				),
			),
			'variants'          => array(
			),
			// Override library path to CDN.
			'library_path'      => 'https://cdnjs.cloudflare.com/ajax/libs/jquery',
			'path'              => '3.7.1',
			'version'           => '3.7.1',
		);






		// ----------------- Bootstrap 5 ---------------------------//

			// Bootstrap (CDN).
		$libraries['cdn.bootstrap5'] = array(
			'name'              => 'Bootstrap 5 (CDN)',
			'vendor_url'        => 'http://getbootstrap.com/',
			'version_arguments' => array(
				'file'    => 'dist/js/bootstrap.min.js',
				'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'dist/js/bootstrap.bundle.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
						'defer' => true,
					),
				),
				'css' => array(
					'dist/css/bootstrap.min.css' => array(
						'zone' => 1,
					),
				),
			),
			'variants'          => array(),
			// Override library path to CDN.
		//https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css
			'library_path'      => 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.3',
			'path'              => '',
			'version'           => '5.2.3',
		);


			// Bootstrap (local).
		$libraries['bootstrap5'] = array(
			'name'              => 'Bootstrap 5 (local)',
			'vendor_url'        => 'http://getbootstrap.com/',
			'version_arguments' => array(
				'file'    => 'js/bootstrap.bundle.min.js',
				'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'js/bootstrap.bundle.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
						'defer' => true,
					),
				),
				'css' => array(
					'css/bootstrap.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(),
			'library_path'      => '{e_WEB}lib/bootstrap',
			'path'              => '5',
			'version'           => '5.2.3',
		);







		// ----------------- Bootstrap 4 ---------------------------//

			// Bootstrap (CDN).
		$libraries['cdn.bootstrap4'] = array(
			'name'              => 'Bootstrap 4 (CDN)',
			'vendor_url'        => 'http://getbootstrap.com/',
			'version_arguments' => array(
				'file'    => 'dist/js/bootstrap.min.js',
				'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'dist/js/bootstrap.bundle.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
				'css' => array(
					'dist/css/bootstrap.min.css' => array(
						'zone' => 1,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				/*'dev' => array(
					'files' => array(
						'js'  => array(
							'js/bootstrap.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
						'css' => array(
							'css/bootstrap.css' => array(
								'zone' => 2,
							),
						),
					),
				),*/


			),
			// Override library path to CDN.
		//	https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.bundle.min.js
			'library_path'      => 'https://cdn.jsdelivr.net/npm/bootstrap@4.3.1',
			'path'              => '',
			'version'           => '4.3.1',
		);

		// Bootstrap (local).
		$libraries['bootstrap4'] = array(
			'name'              => 'Bootstrap 4 (local)',
			'vendor_url'        => 'http://getbootstrap.com/',
			'version_arguments' => array(
				'file'    => 'js/bootstrap.bundle.min.js',
				'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'js/bootstrap.bundle.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
				'css' => array(
					'css/bootstrap.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js'  => array(
							'js/bootstrap.bundle.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
						'css' => array(
							'css/bootstrap.css' => array(
								'zone' => 2,
							),
						),
					),
				),
			),
			'library_path'      => '{e_WEB}lib/bootstrap',
			'path'              => '4',
			'version'           => '4.3.1',
		);


		// ----------------------------------------------------- //



		// Bootstrap (CDN).
		$libraries['cdn.bootstrap'] = array(
			'name'              => 'Bootstrap (CDN)',
			'vendor_url'        => 'http://getbootstrap.com/',
			'version_arguments' => array(
				'file'    => 'js/bootstrap.min.js',
				'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'js/bootstrap.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
				'css' => array(
					'css/bootstrap.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js'  => array(
							'js/bootstrap.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
						'css' => array(
							'css/bootstrap.css' => array(
								'zone' => 2,
							),
						),
					),
				),


			),
			// Override library path to CDN.
		//	'library_path'      => 'https://cdn.jsdelivr.net/bootstrap',
			'library_path'      => 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap',
			'path'              => '3.4.1',
			'version'           => '3.4.1',
		);

		// Bootstrap (local).
		$libraries['bootstrap'] = array(
			'name'              => 'Bootstrap (local)',
			'vendor_url'        => 'http://getbootstrap.com/',
			'version_arguments' => array(
				'file'    => 'js/bootstrap.min.js',
				'pattern' => '/Bootstrap\s+v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'js/bootstrap.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
				'css' => array(
					'css/bootstrap.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js'  => array(
							'js/bootstrap.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
						'css' => array(
							'css/bootstrap.css' => array(
								'zone' => 2,
							),
						),
					),
				),
			),
			'library_path'      => '{e_WEB}lib/bootstrap',
			'path'              => '3',
			'version'           => '3.4.1',
			'preload'           => array(
				0   => array(
					'as'            => 'font',
					'type'          => 'font/woff2',
					'path'          => 'fonts/glyphicons-halflings-regular.woff2',
					'crossorigin'   => true,
					'browsercache'  => false,
				),

			),
		);

		// Bootstrap Editable (CDN).
		$libraries['cdn.bootstrap.editable'] = array(
			'name'              => 'Bootstrap Editable (CDN)',
			'vendor_url'        => 'https://vitalets.github.io/bootstrap-editable/',
			'version_arguments' => array(
				'file'    => 'js/bootstrap-editable.min.js',
				'pattern' => '/v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'js/bootstrap-editable.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
				'css' => array(
					'css/bootstrap-editable.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js'  => array(
							'js/bootstrap-editable.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
						'css' => array(
							'css/bootstrap-editable.css' => array(
								'zone' => 2,
							),
						),
					),
				),
			),
			// Override library path to CDN.
			'library_path'      => 'https://cdn.jsdelivr.net/bootstrap.editable',
			'path'              => '1.5.1',
			'version'           => '1.5.1',
		);

		// Bootstrap Editable (local).
		$libraries['bootstrap.editable'] = array(
			'name'              => 'Bootstrap Editable (local)',
			'vendor_url'        => 'https://vitalets.github.io/bootstrap-editable/',
			'version_arguments' => array(
				'file'    => 'js/bootstrap-editable.min.js',
				'pattern' => '/v(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'js/bootstrap-editable.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
				'css' => array(
					'css/bootstrap-editable.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js'  => array(
							'js/bootstrap-editable.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
						'css' => array(
							'css/bootstrap-editable.css' => array(
								'zone' => 2,
							),
						),
					),
				),
			),
			// Override library path.
			'library_path'      => '{e_WEB}js/bootstrap3-editable',
			'version'           => '1.5.1',
			'path'              => '',
		);

		// Bootstrap Switch (CDN).
		$libraries['cdn.bootstrap.switch'] = array(
			'name'              => 'Bootstrap Switch (CDN)',
			'vendor_url'        => 'http://www.bootstrap-switch.org',
			'version_arguments' => array(
				'file'    => 'js/bootstrap-switch.min.js',
				'pattern' => '/v(\d\.\d\.\d)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'js/bootstrap-switch.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
				'css' => array(
					'css/bootstrap3/bootstrap-switch.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js'  => array(
							'js/bootstrap-switch.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
						'css' => array(
							'css/bootstrap3/bootstrap-switch.css' => array(
								'zone' => 2,
							),
						),
					),
				),
			),
			// Override library path to CDN.
			'library_path'      => 'https://cdn.jsdelivr.net/bootstrap.switch',
			'path'              => '3.3.2',
			'version'           => '3.3.2',
		);

		// Bootstrap Switch (local).
		$libraries['bootstrap.switch'] = array(
			'name'              => 'Bootstrap Switch (local)',
			'vendor_url'        => 'http://www.bootstrap-switch.org',
			'version_arguments' => array(
				'file'    => 'dist/js/bootstrap-switch.min.js',
				'pattern' => '/v(\d\.\d\.\d)/',
				'lines'   => 5,
			),
			'files'             => array(
				'js'  => array(
					'dist/js/bootstrap-switch.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
					),
				),
				'css' => array(
					'dist/css/bootstrap3/bootstrap-switch.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'js'  => array(
							'dist/js/bootstrap-switch.js' => array(
								'zone' => 2,
								'type' => 'footer',
							),
						),
						'css' => array(
							'dist/css/bootstrap3/bootstrap-switch.css' => array(
								'zone' => 2,
							),
						),
					),
				),
			),
			// Override library path.
			'library_path'      => '{e_WEB}lib/bootstrap-switch',
			'version'           => '3.3.2',
		);

		// Font-Awesome 4 (CDN).
		$libraries['cdn.fontawesome'] = array(
			'name'              => 'Font-Awesome 4 (CDN)',
			'vendor_url'        => 'http://fontawesome.io/',
			'version_arguments' => array(
				'file'    => 'css/font-awesome.min.css',
				'pattern' => '/(\d\.\d\.\d+)/',
				'lines'   => 10,
			),
			'files'             => array(
				'css' => array(
					'css/font-awesome.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'css' => array(
							'css/font-awesome.css' => array(
								'zone' => 2,
							),
						),
					),
				),
			),
			// Override library path to CDN.
			'library_path'      => 'https://cdn.jsdelivr.net/fontawesome',
			'path'              => '4.7.0',
			'version'           => '4.7.0',
		);

		// Font-Awesome (local).
		$libraries['fontawesome'] = array(
			'name'              => 'Font-Awesome 4 (local)',
			'vendor_url'        => 'http://fontawesome.io/',
			'version_arguments' => array(
				'file'    => 'css/font-awesome.min.css',
				'pattern' => '/(\d\.\d\.\d+)/',
				'lines'   => 10,
			),
			'files'             => array(
				'css' => array(
					'css/font-awesome.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(
				// 'unminified' version for debugging.
				'dev' => array(
					'files' => array(
						'css' => array(
							'css/font-awesome.css' => array(
								'zone' => 2,
							),
						),
					),
				),
			),
			// Override library path.
			'library_path'      => '{e_WEB}lib/font-awesome',
			'path'              => '4.7.0',
			'version'           => '4.7.0',
			// preload in header using <link> tag. for speed optimization.
			'preload'           => array(
				0   => array(
					'as'            => 'style',
					'type'          => '',
					'path'          => 'css/font-awesome.min.css',
					'crossorigin'   => false,
					'browsercache'  => true, // add the generated ?xxxxxx to the path.
				),
				1   => array(
					'as'            => 'font',
					'type'          => 'font/woff2',
					'path'          => 'fonts/fontawesome-webfont.woff2?v=4.7.0',
					'crossorigin'   => true,
					'browsercache'  => false,
				),

			),
		);





		// Font-Awesome 5 (CDN).
		$libraries['cdn.fontawesome5'] = array(
			'name'              => 'Font-Awesome 5 (CDN)',
			'vendor_url'        => 'https://fontawesome.com/',
			'version_arguments' => array(
				'file'    => 'css/all.css',
				'pattern' => '/(\d\.\d*\.\d+)/',
				'lines'   => 2,
			),
			'files'             => array(
				'js'  => array(
					'js/all.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
						'defer' => true,
					),
					'js/v4-shims.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
						'defer' => true,
					),
				),
				'css' => array(
					'css/all.min.css' => array(
						'zone' => 2,
					),
					'css/v4-shims.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(),
			// Override library path to CDN.
			'library_path'      => 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4',
			'path'              => '',
			'version'           => '5.15.4',
		);

		// Font-Awesome 5 (local).
		$libraries['fontawesome5'] = array(
			'name'              => 'Font-Awesome 5 (local)',
			'vendor_url'        => 'https://fontawesome.com/',
			'version_arguments' => array(
				'file'    => 'css/all.css',
				'pattern' => '/(\d\.\d*\.\d+)/',
				'lines'   => 3,
			),
			'files'             => array(
				'js'  => array(
					'js/all.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
						'defer' => true,
					),
					'js/v4-shims.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
						'defer' => true,
					),
				),
				'css' => array(
					'css/all.min.css' => array(
						'zone' => 2,
					),
					'css/v4-shims.min.css' => array(
						'zone' => 2,
					),
				),
			),

			'variants'          => array(),
			'library_path'      => '{e_WEB}lib/font-awesome',
			'path'              => '5',
			'version'           => '5.15.4',
		/*	'preload'           => array(
				1   => array(
					'as'            => 'font',
					'type'          => 'font/woff',
					'path'          => 'webfonts/fa-solid-900.woff',
					'crossorigin'   => false,
					'browsercache'  => false,
				),
				2   => array(
					'as'            => 'font',
					'type'          => 'font/woff',
					'path'          => 'webfonts/fa-brands-400.woff',
					'crossorigin'   => false,
					'browsercache'  => false,
				),
				3   => array(
					'as'            => 'font',
					'type'          => 'font/woff2',
					'path'          => 'webfonts/fa-solid-900.woff2',
					'crossorigin'   => false,
					'browsercache'  => false,
				),
				4   => array(
					'as'            => 'font',
					'type'          => 'font/woff2',
					'path'          => 'webfonts/fa-brands-400.woff2',
					'crossorigin'   => false,
					'browsercache'  => false,
				),
			),*/
		);


		// Font-Awesome 6 (local).
		$libraries['fontawesome6'] = array(
			'name'              => 'Font-Awesome 6 (local)',
			'vendor_url'        => 'https://fontawesome.com/',
			'version_arguments' => array(
				'file'    => 'css/all.css',
				'pattern' => '/(\d\.\d*\.\d+)/',
				'lines'   => 3,
			),
			'files'             => array(
				'js'  => array(
					'js/all.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
						'defer' => true,
					),
					'js/v4-shims.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
						'defer' => true,
					),
				),
				'css' => array(
					'css/all.min.css' => array(
						'zone' => 2,
					),
					'css/v4-shims.min.css' => array(
						'zone' => 2,
					),
				),
			),

			'variants'          => array(),
			'library_path'      => '{e_WEB}lib/font-awesome',
			'path'              => '6',
			'version'           => '6.4.2',

		);


	// Font-Awesome 6 (CDN).
		$libraries['cdn.fontawesome6'] = array(
			'name'              => 'Font-Awesome 6 (CDN)',
			'vendor_url'        => 'https://fontawesome.com/',
			'version_arguments' => array(
				'file'    => 'css/all.css',
				'pattern' => '/(\d\.\d*\.\d+)/',
				'lines'   => 2,
			),
			'files'             => array(
				'js'  => array(
					'js/all.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
						'defer' => true,
					),
					'js/v4-shims.min.js' => array(
						'zone' => 2,
						'type' => 'footer',
						'defer' => true,
					),
				),
				'css' => array(
					'css/all.min.css' => array(
						'zone' => 2,
					),
					'css/v4-shims.min.css' => array(
						'zone' => 2,
					),
				),
			),
			'variants'          => array(),
			// Override library path to CDN.
			'library_path'      => 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2',
			'path'              => '',
			'version'           => '6.4.2',
		);



		// Animate (local).
		$libraries['animate.css'] = array(
			'name'              => 'Animate.css (local)',
			'vendor_url'        => 'https://daneden.github.io/animate.css/',
			'version_arguments' => array(
				'file'    => 'animate.min.css',
				'pattern' => '/(\d\.\d\.\d+)/',
				'lines'   => 5,
			),
			'files'             => array(
				'css' => array(
					'animate.min.css' => array(
						'zone' => 2,
					),
				),
			),

			'library_path'      => '{e_WEB}lib/animate.css',
		//	'path'              => '3.5.2',
			'version'           => '3.5.2',
		);

		// Animate (local).
		$libraries['bootstrap-suggest'] = array(
			'name'              => 'Bootstrap Suggest (local)',
			'vendor_url'        => 'https://github.com/lodev09/bootstrap-suggest',
			'version_arguments' => array(
				'file'    => 'bootstrap-suggest.js',
				'pattern' => '/(\d\.\d\.\d+)/',
				'lines'   => 3,
			),
			'files'             => array(
				'css' => array(
					'bootstrap-suggest.css' => array(
						'zone' => 2,
					),
				),
				'js' => array(
					'bootstrap-suggest.min.js' => array(
						'zone' => 2,
					),
				),
			),

			'library_path'      => '{e_WEB}lib/bootstrap-suggest',
			'path'              => 'dist',
			'version'           => '2.0.3',
		);


		return $libraries;
	}

	/**
	 * Alters library information before detection and caching takes place.
	 * @param array $libraries
	 */
	function config_alter(&$libraries)
	{
		$pref = e107::pref('core');
		$cdnProvider = varset($pref['e_jslib_cdn_provider'], 'jsdelivr');

		// If CDNJS is the selected provider, we alter core CDN libraries to use it
		// instead of jsDelivr.
		if($cdnProvider == 'cdnjs')
		{
			$libraries['cdn.jquery']['library_path'] = str_replace('https://cdn.jsdelivr.net/jquery', 'https://cdnjs.cloudflare.com/ajax/libs/jquery', $libraries['cdn.jquery']['library_path']);
			$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']);
			$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']);
			$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']);

			$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']);
			$libraries['cdn.bootstrap.editable']['path'] .= '/bootstrap-editable';

			$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']);
			$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']);
		}
	}

}


/**
 * Class e_library_manager.
 */
class e_library_manager
{
	/** @var array list of callbacks to track performance  */
	private $callbacks = array();

	/**
	 * Constructor
	 * Use {@link getInstance()}, direct instantiating is not possible for signleton objects.
	 */
	public function __construct()
	{
	}

	/**
	 * @return void
	 */
	protected function _init()
	{
	}

	/**
	 * Cloning is not allowed.
	 */
	private function __clone()
	{
	}

	/**
	 * @return array
	 */
	public function getCallbackLog()
	{
		return $this->callbacks;
	}

	/**
	 * Tries to detect a library and its installed version.
	 *
	 * @param $name
	 *   The machine name of a library to return registered information for.
	 * @param bool $force - removes the 'version' and has the callback look it up from the library file.
	 * @return array|false
	 *   An associative array containing registered information for the library specified by $name, or FALSE if the
	 *   library $name is not registered. In addition to the keys returned by info(), the following keys are
	 *   contained:
	 *   - installed: A boolean indicating whether the library is installed. Note that not only the top-level library,
	 *     but also each variant contains this key.
	 *   - version: If the version could be detected, the full version string.
	 *   - error: If an error occurred during library detection, one of the following error statuses:
	 *     "not found", "not detected", "not supported".
	 *   - error_message: If an error occurred during library detection, a detailed error_message.
	 */
	public function detect($name, $force = false)
	{

		// Re-use the statically cached value of info() to save memory.
		$library = &$this->info($name, $force);

		// Exit early if the library was not found.
		if($library === false)
		{
			return $library;
		}

		// If 'installed' is set, library detection ran already.
		if(isset($library['installed']))
		{
			return $library;
		}

		$library['installed'] = false;

		// Check whether the library exists.
		if(!isset($library['library_path']) && !empty($library['machine_name']))
		{
			$library['library_path'] = $this->detectPath($library['machine_name']);
		}

		$libraryPath = !empty($library['library_path']) ? e107::getParser()->replaceConstants($library['library_path']) : '';

		if(empty($library['library_path']) || (!empty($libraryPath) && !file_exists($libraryPath) && strpos($libraryPath, 'http') !== 0))
		{
			$library['error'] = LAN_NOT_FOUND;

			$replace_with = array($name);
			$library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_03, $replace_with, true);

			return $library;
		}

		// TODO:
		// Invoke callbacks in the 'pre_detect' group.
		$this->invoke('pre_detect', $library);

		if($force === true)
		{
			unset($library['version']);
		}

		// Detect library version, if not hardcoded.
		if(!isset($library['version']))
		{

			// If version_callback is a method in $this class.
			if(method_exists($this, $library['version_callback'])) // runs getVersion() which will be slow.
			{
				$this->callbacks[] = $name;
				e107::getDebug()->logTime("[Looking up version of ".$name."]");
				// We support both a single parameter, which is an associative array, and an indexed array of multiple
				// parameters.
				if(isset($library['version_arguments'][0]))
				{
					// Add the library as the first argument.
					$classMethod = array($this, $library['version_callback']);
					$params = array_merge(array($library), $library['version_arguments']);
					$library['version'] = call_user_func_array($classMethod, $params);
				}
				else
				{
					$method = $library['version_callback'];
					$library['version'] = $this->$method($library, $library['version_arguments']);
				}
			}
			// If version_callback is a method in e_library.php file.
			else
			{
				$library['version'] = '';
				$class = false;

				if(!empty($library['plugin']))
				{
					$class = e107::getAddon($library['plugin'], 'e_library');
				}
				elseif(!empty($library['theme']))
				{
					// e107::getAddon() does not support theme folders.
					if(is_readable(e_THEME . $library['theme'] . '/theme_library.php'))
					{
						e107_require_once(e_THEME . $library['theme'] . '/theme_library.php');
						$addonClass = 'theme_library';

						if(class_exists($addonClass))
						{
							$class = new $addonClass();
						}
					}

					// e107::getAddon() does not support theme folders.
					if(is_readable(e_THEME . $library['theme'] . '/admin_theme_library.php'))
					{
						e107_require_once(e_THEME . $library['theme'] . '/admin_theme_library.php');
						$addonClass = 'admin_theme_library';

						if(class_exists($addonClass))
						{
							$class = new $addonClass();
						}
					}
				}

				// We support both a single parameter, which is an associative array, and an
				// indexed array of multiple parameters.
				if(isset($library['version_arguments'][0]))
				{
					if($class)
					{
						$params = array_merge(array($library), $library['version_arguments']);
						$library['version'] = e107::callMethod($class, $library['version_callback'], $params);
					}
				}
				else
				{
					if($class)
					{
						$library['version'] = e107::callMethod($class, $library['version_callback'], $library, $library['version_arguments']);
					}
				}
			}

			if(empty($library['version']))
			{
				$library['error'] = LAN_LIBRARY_MANAGER_10;

				$replace_with = array($library['name']);
				$library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_04, $replace_with, true);

				return $library;
			}
		}

		// Determine to which supported version the installed version maps.
		if(!empty($library['versions']))
		{
			ksort($library['versions']);
			$version = 0;
			foreach($library['versions'] as $supported_version => $version_properties)
			{
				if(version_compare($library['version'], $supported_version, '>='))
				{
					$version = $supported_version;
				}
			}
			if(!$version)
			{
				$library['error'] = LAN_LIBRARY_MANAGER_11;

				$replace_with = array($library['version'], $library['name']);
				$library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_05, $replace_with, true);

				return $library;
			}

			// Apply version specific definitions and overrides.
			$library = array_merge($library, $library['versions'][$version]);
			unset($library['versions']);
		}

		// Check each variant if it is installed.
		if(!empty($library['variants']))
		{
			foreach($library['variants'] as $variant_name => &$variant)
			{
				// If no variant callback has been set, assume the variant to be installed.
				if(!isset($variant['variant_callback']))
				{
					$variant['installed'] = true;
				}
				else
				{
					$variant['installed'] = false;
					$class = false;

					if(varset($library['plugin'], false))
					{
						$class = e107::getAddon($library['plugin'], 'e_library');
					}
					elseif(varset($library['theme'], false))
					{
						// e107::getAddon() does not support theme folders.
						if(is_readable(e_THEME . $library['theme'] . '/theme_library.php'))
						{
							e107_require_once(e_THEME . $library['theme'] . '/theme_library.php');
							$addonClass = 'theme_library';

							if(class_exists($addonClass))
							{
								$class = new $addonClass();
							}
						}

						// e107::getAddon() does not support theme folders.
						if(is_readable(e_THEME . $library['theme'] . '/admin_theme_library.php'))
						{
							e107_require_once(e_THEME . $library['theme'] . '/admin_theme_library.php');
							$addonClass = 'admin_theme_library';

							if(class_exists($addonClass))
							{
								$class = new $addonClass();
							}
						}
					}

					// We support both a single parameter, which is an associative array, and an indexed array of
					// multiple parameters.
					if(isset($variant['variant_arguments'][0]))
					{
						if($class)
						{
							$params = array_merge(array($library, $variant_name), $variant['variant_arguments']);
							$variant['installed'] = e107::callMethod($class, $library['variant_callback'], $params);
						}
					}
					else
					{
						if($class)
						{
							// Can't use e107::callMethod(), because it only supports 2 params.
							if(method_exists($class, $variant['variant_callback']))
							{
								// Call PLUGIN/THEME_library::VARIANT_CALLBACK().
								$method = $variant['variant_callback'];
								$variant['installed'] = $class->$method($library, $variant_name, $variant['variant_arguments']);
							}
						}
					}

					if(!$variant['installed'])
					{
						$variant['error'] = LAN_NOT_FOUND;

						$replace_with = array($variant_name, $library['name']);
						$variant['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_06, $replace_with, true);
					}
				}
			}
		}

		// If we end up here, the library should be usable.
		$library['installed'] = true;

		// Invoke callbacks in the 'post_detect' group.
		$this->invoke('post_detect', $library);

		return $library;
	}

	/**
	 * Loads a library.
	 *
	 * @param $name
	 *   The name of the library to load.
	 * @param $variant
	 *   The name of the variant to load. Note that only one variant of a library can be loaded within a single
	 *   request. The variant that has been passed first is used; different variant names in subsequent calls are
	 *   ignored.
	 *
	 * @return mixed
	 *   An associative array of the library information as returned from config(). The top-level properties
	 *   contain the effective definition of the library (variant) that has been loaded. Additionally:
	 *   - installed: Whether the library is installed, as determined by detect().
	 *   - loaded: Either the amount of library files that have been loaded, or FALSE if the library could not be
	 *   loaded. See MYPLUGIN_library::config() for more information.
	 */
	public function load($name, $variant = null, $types = ['js', 'css'])
	{
		// Re-use the statically cached value to save memory.

		static $loaded;

		if(!isset($loaded[$name]))
		{
			$cache = e107::getCache();
			$cache_context = (defset('e_ADMIN_AREA', false) == true) ? 'AdminArea' : 'UserArea';
			$cacheID = 'Library_' . $cache_context . '_' . e107::getParser()->filter($name, 'file');
			$cached = $cache->retrieve($cacheID, false, true, true);

			if($cached)
			{
				$library = e107::unserialize($cached);
			}

			if(empty($library))
			{
				$library = $this->detect($name);
				$cacheData = e107::serialize($library, 'json');
				$cache->set($cacheID, $cacheData, true, true, true);
			}

			// Exit early if the library was not found.
			if($library === false)
			{
				$loaded[$name] = $library;
				return $loaded[$name];
			}

			// If a variant was specified, override the top-level properties with the variant properties.
			if(isset($variant))
			{
				// Ensure that the $variant key exists, and if it does not, set its 'installed' property to FALSE by
				// default. This will prevent the loading of the library files below.
				$library['variants'] += array($variant => array('installed' => false));
				$library = array_merge($library, $library['variants'][$variant]);
			}
			// Regardless of whether a specific variant was requested or not, there can only be one variant of a
			// library within a single request.
			unset($library['variants']);

			// TODO:
			// Invoke callbacks in the 'pre_dependencies_load' group.
			$this->invoke('pre_dependencies_load', $library);

			// If the library (variant) is installed, load it.
			$library['loaded'] = false;
			if($library['installed'])
			{
				// Load library dependencies.
				if(isset($library['dependencies']))
				{
					foreach($library['dependencies'] as $dependency)
					{
						$this->load($dependency);
					}
				}

				// TODO:
				// Invoke callbacks in the 'pre_load' group.
				$this->invoke('pre_load', $library);

				// Load all the files associated with the library.
				$library['loaded'] = $this->loadFiles($library, $types);

				// TODO:
				// Invoke callbacks in the 'post_load' group.
				$this->invoke('post_load', $library);
			}
			$loaded[$name] = $library;
		}

		return $loaded[$name];
	}

	/**
	 * Gets the path of a library.
	 *
	 * @param $name
	 *   The machine name of a library to return the path for.
	 *
	 * @return string
	 *   The path to the specified library or FALSE if the library wasn't found.
	 */
	private function detectPath($name)
	{
		static $libraries;

		if(!isset($libraries))
		{
			$libraries = $this->getLibraries();
		}

		$path = '';
		if(!isset($libraries[$name]))
		{
			return false;
		}
		else
		{
			$path .= $libraries[$name];
		}

		return $path;
	}

	/**
	 * Returns an array of library directories.
	 *
	 * @return array
	 *   A list of library directories.
	 */
	private function getLibraries()
	{
		$dir = e_WEB . 'lib';
		$directories = array();

		// Retrieve list of directories.
		$file = e107::getFile();
		$dirs = $file->get_dirs($dir);

		foreach($dirs as $dirName)
		{
			$directories[$dirName] = "{e_WEB}lib/$dirName";
		}

		return $directories;
	}

	/**
	 * Returns with the selected property of a library.
	 *
	 * @param string $library
	 *  Library machine name. For example: bootstrap
	 *
	 * @param string $property
	 *  The property name. For example: library_path
	 *
	 * @return mixed
	 */
	public function getProperty($library, $property)
	{
		$lib = self::info($library);
		return varset($lib[$property], false);
	}


	/**
	 * Return full path to a library in different formats.
	 * @param string $library
	 * The library name eg. bootstrap
	 *
	 * @param null $mode
	 * The mode: null | 'full' | 'abs'
	 *
	 * @return string
	 */
	public function getPath($library, $mode=null)
	{
		$path = self::getProperty($library, 'library_path').'/'. self::getProperty($library, 'path');
		return trim(e107::getParser()->replaceConstants($path,$mode),'/')  .'/';
	}


	/**
	 * Returns information about registered libraries.
	 *
	 * The returned information is unprocessed; i.e., as registered by plugins.
	 *
	 * @param string $library
	 *   (optional) The machine name of a library to return registered information for. If omitted, information
	 *   about all registered libraries is returned.
	 *
	 * @param bool $force - ignore any cached values and reload.
	 *
	 * @return array|false
	 *   An associative array containing registered information for all libraries, the registered information for the
	 *   library specified by $name, or FALSE if the library $name is not registered.
	 */
	public function &info($library = null, $force = false)
	{
		// This static cache is re-used by detect() to save memory.
		if($force === false)
		{
			static $libraries;
		}

		if(!isset($libraries))
		{
			$libraries = array();

			$coreLibrary = new core_library();
			$info = $coreLibrary->config();
			if(is_array($info))
			{
				foreach($info as $machine_name => $properties)
				{
					$properties['info_type'] = 'core';
					$libraries[$machine_name] = $properties;
				}
			}

			$plugins = array();
			$themes = array();

			// Gather information from PLUGIN_library::config().
			$pluginInfo = e107::getAddonConfig('e_library', 'library'); // 'config' is the default.
			foreach($pluginInfo as $plugin => $info)
			{
				foreach($info as $machine_name => $properties)
				{
					$properties['info_type'] = 'plugin';
					$properties['plugin'] = $plugin;
					$libraries[$machine_name] = $properties;
					$plugins[] = $plugin; // This plugin has a valid e_library implementation.
				}
			}

			$themes[] = array(
				'name'  => e107::getPref('sitetheme'),
				'file'  => 'theme_library',
				'class' => 'theme_library',
			);

			$themes[] = array(
				'name'  => e107::getPref('admintheme'),
				'file'  => 'admin_theme_library',
				'class' => 'admin_theme_library',
			);

			foreach($themes as $theme)
			{
				if(is_readable(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php'))
				{
					e107_require_once(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php');

					$info = e107::callMethod($theme['class'], 'config');
					if(is_array($info))
					{
						foreach($info as $machine_name => $properties)
						{
							$properties['info_type'] = 'theme';
							$properties['theme'] = $theme['name'];
							$libraries[$machine_name] = $properties;
						}
					}
				}
			}

			// Provide defaults.
			foreach($libraries as $machine_name => &$properties)
			{
				$this->infoDefaults($properties, $machine_name);
			}

			// Alter config array. For example, change CDN provider.
			$coreLibrary->config_alter($libraries);

			// Allow enabled plugins (with e_library.php file) to alter the registered libraries.
			foreach($plugins as $plugin)
			{
				$class = e107::getAddon($plugin, 'e_library');
				if($class && method_exists($class, 'config_alter'))
				{
					// The library definitions are passed by reference.
					$class->config_alter($libraries);
				}
			}

			// Allow enabled themes to alter the registered libraries.
			foreach($themes as $theme)
			{
				if(is_readable(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php'))
				{
					e107_require_once(e_THEME . $theme['name'] . '/' . $theme['file'] . '.php');

					if(class_exists($theme['class']))
					{
						$class = new $theme['class']();
						if(method_exists($class, 'config_alter'))
						{
							// We cannot use e107::callMethod() because need to pass variable by reference.
							$class->config_alter($libraries);
						}
					}
				}
			}

			// TODO:
			// Invoke callbacks in the 'info' group.
			foreach($libraries as &$properties)
			{
				$this->invoke('info', $properties);
			}
		}

		if(isset($library))
		{
			if(!empty($libraries[$library]))
			{
				return $libraries[$library];
			}
			else
			{
				$false = false;
				return $false;
			}
		}

		return $libraries;
	}

	/**
	 * Applies default properties to a library definition.
	 *
	 * @param array $library
	 *   An array of library information, passed by reference.
	 * @param string $name
	 *   The machine name of the passed-in library.
	 *
	 * @return array
	 */
	private function infoDefaults(&$library, $name)
	{
		$library += array(
			'machine_name'      => $name,
			'name'              => $name,
			'vendor_url'        => '',
			'download_url'      => '',
			'path'              => '',
			'library_path'      => null,
			'version_callback'  => 'getVersion',
			'version_arguments' => array(),
			'files'             => array(),
			'dependencies'      => array(),
			'variants'          => array(),
			'versions'          => array(),
			'integration_files' => array(),
			'callbacks'         => array(),
		);

		$library['callbacks'] += array(
			'info'                  => array(),
			'pre_detect'            => array('preDetect'),
			'post_detect'           => array(),
			'pre_dependencies_load' => array(),
			'pre_load'              => array('preLoad'),
			'post_load'             => array(),
		);

		// Add our own callbacks before any others.
		array_unshift($library['callbacks']['info'], 'prepareFiles');
		array_unshift($library['callbacks']['post_detect'], 'detectDependencies');

		return $library;
	}

	/**
	 * Library info callback to make all 'files' properties consistent.
	 *
	 * This turns libraries' file information declared as e.g.
	 * @code
	 * $library['files']['js'] = array('example_1.js', 'example_2.js');
	 * @endcode
	 * into
	 * @code
	 * $library['files']['js'] = array(
	 *   'example_1.js' => array(),
	 *   'example_2.js' => array(),
	 * );
	 * @endcode
	 * It does the same for the 'integration_files' property.
	 *
	 * @param $library
	 *   An associative array of library information or a part of it, passed by reference.
	 * @param $version
	 *   If the library information belongs to a specific version, the version string. NULL otherwise.
	 * @param $variant
	 *   If the library information belongs to a specific variant, the variant name. NULL otherwise.
	 */
	private function prepareFiles(&$library, $version = null, $variant = null)
	{
		// Both the 'files' property and the 'integration_files' property contain file declarations, and we want to make
		// both consistent.
		$file_types = array();
		if(isset($library['files']))
		{
			$file_types[] = &$library['files'];
		}
		if(isset($library['integration_files']))
		{
			// Integration files are additionally keyed by plugin.
			foreach($library['integration_files'] as &$integration_files)
			{
				$file_types[] = &$integration_files;
			}
		}
		foreach($file_types as &$files)
		{
			// Go through all supported types of files.
			foreach(array('js', 'css', 'php') as $type)
			{
				if(isset($files[$type]))
				{
					foreach($files[$type] as $key => $value)
					{
						// Unset numeric keys and turn the respective values into keys.
						if(is_numeric($key))
						{
							$files[$type][$value] = array();
							unset($files[$type][$key]);
						}
					}
				}
			}
		}
	}

	/**
	 * Library post detect callback to process and detect dependencies.
	 *
	 * It checks whether each of the dependencies of a library are installed and available in a compatible version.
	 *
	 * @param $library
	 *   An associative array of library information or a part of it, passed by reference.
	 * @param $version
	 *   If the library information belongs to a specific version, the version string. NULL otherwise.
	 * @param $variant
	 *   If the library information belongs to a specific variant, the variant name. NULL otherwise.
	 */
	private function detectDependencies(&$library, $version = null, $variant = null)
	{
		if(isset($library['dependencies']))
		{
			foreach($library['dependencies'] as &$dependency_string)
			{
				$dependency_info = $this->parseDependency($dependency_string);
				$dependency = $this->detect($dependency_info['name']);
				if(!$dependency['installed'])
				{
					$library['installed'] = false;
					$library['error'] = LAN_LIBRARY_MANAGER_07;

					$replace_with = array($dependency['name'], $library['name']);
					$library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_01, $replace_with, true);
				}
				elseif($this->checkIncompatibility($dependency_info, $dependency['version']))
				{
					$library['installed'] = false;
					$library['error'] = LAN_LIBRARY_MANAGER_08;

					$replace_with = array($dependency['version'], $library['name'], $library['name']);
					$library['error_message'] = e107::getParser()->lanVars(LAN_LIBRARY_MANAGER_02, $replace_with, true);
				}

				// Remove the version string from the dependency, so load() can load the libraries directly.
				$dependency_string = $dependency_info['name'];
			}
		}
	}

	/**
	 * Invokes library callbacks.
	 *
	 * @param $group
	 *   A string containing the group of callbacks that is to be applied. Should be either 'info', 'post_detect'.
	 * @param $library
	 *   An array of library information, passed by reference.
	 */
	private function invoke($group, &$library)
	{
		// When introducing new callback groups in newer versions, stale cached library information somehow reaches
		// this point during the database update before clearing the library cache.
		if(empty($library['callbacks'][$group]))
		{
			return;
		}

		foreach($library['callbacks'][$group] as $callback)
		{
			$this->traverseLibrary($library, $callback);
		}
	}

	/**
	 * Helper function to apply a callback to all parts of a library.
	 *
	 * Because library declarations can include variants and versions, and those version declarations can in turn
	 * include variants, modifying e.g. the 'files' property everywhere it is declared can be quite cumbersome, in
	 * which case this helper function is useful.
	 *
	 * @param $library
	 *   An array of library information, passed by reference.
	 * @param $callback
	 *   A string containing the callback to apply to all parts of a library.
	 */
	private function traverseLibrary(&$library, $callback)
	{
		// If callback belongs to $this class.
		if(method_exists($this, $callback))
		{
			// Always apply the callback to the top-level library.
			// Params: $library, $version, $variant
			$this->{$callback}($library, null, null);

			// Apply the callback to versions.
			if(isset($library['versions']))
			{
				foreach($library['versions'] as $version_string => &$version)
				{
					$this->{$callback}($version, $version_string, null);

					// Versions can include variants as well.
					if(isset($version['variants']))
					{
						foreach($version['variants'] as $version_variant_name => &$version_variant)
						{
							$this->{$callback}($version_variant, $version_string, $version_variant_name);
						}
					}
				}
			}

			// Apply the callback to variants.
			if(isset($library['variants']))
			{
				foreach($library['variants'] as $variant_name => &$variant)
				{
					$this->$callback($variant, null, $variant_name);
				}
			}
		}
		//else
	//	{
			// TODO: Provide the ability to use third-party callbacks (are defined in e_library.php files) for groups:
			// 'info', 'pre_detect', 'post_detect', 'pre_dependencies_load', 'pre_load', 'post_load'
	//	}
	}

	/**
	 * Loads a library's files.
	 *
	 * @param $library
	 *   An array of library information as returned by info().
	 *
	 * @return int
	 *   The number of loaded files.
	 */
	private function loadFiles($library, $types = array('js', 'css'))
	{
		$siteTheme = e107::getPref('sitetheme');
		$adminTheme = e107::getPref('admintheme');

		// Load integration_files.
		if(!empty($library['post_load_integration_files']) && !$library['post_load_integration_files'] && !empty($library['integration_files']))
		{
			foreach($library['integration_files'] as $provider => $files)
			{
				// If provider is an installed plugin.
				if(e107::isInstalled($provider))
				{
					$this->loadFiles(array(
						'files'                       => $files,
						'path'                        => '',
						'library_path'                => e_PLUGIN . $provider,
						'post_load_integration_files' => false,
					));
				}
				// If provider is the admin theme, we only allow it for admin pages.
				elseif(e_ADMIN_AREA && $provider == $adminTheme)
				{
					$this->loadFiles(array(
						'files'                       => $files,
						'path'                        => '',
						'library_path'                => e_THEME . $provider,
						'post_load_integration_files' => false,
					));
				}
				// If provider is the site theme, we only allow it on user areas.
				elseif(!deftrue(e_ADMIN_AREA, false) && $provider == $siteTheme)
				{
					$this->loadFiles(array(
						'files'                       => $files,
						'path'                        => '',
						'library_path'                => e_THEME . $provider,
						'post_load_integration_files' => false,
					));
				}
			}
		}

		// Construct the full path to the library for later use.
		$path = e107::getParser()->replaceConstants($library['library_path']);
		$path = ($library['path'] !== '' ? rtrim($path, '/') . '/' . $library['path'] : $path);
		$path = rtrim($path, '/');

		// Count the number of loaded files for the return value.
		$count = 0;

		// Load both the JavaScript and the CSS files.
		foreach($types as $type)
		{
			if(!empty($library['files'][$type]))
			{
				foreach($library['files'][$type] as $data => $options)
				{
					// If the value is not an array, it's a filename and passed as first (and only) argument.
					if(!is_array($options))
					{
						$data = $options;
						$options = array();
					}
					// In some cases, the first parameter ($data) is an array. Arrays can't be passed as keys in PHP,
					// so we have to get $data from the value array.
					if(is_numeric($data))
					{
						$data = $options['data'];
						unset($options['data']);
					}
					// Prepend the library_path to the file name.
					$data = "$path/$data";
					// Apply the default zone if the zone isn't explicitly given.
					if(!isset($options['zone']))
					{
						$options['zone'] = ($type == 'js') ? 2 : 2; // TODO: default zones.
					}
					// Apply the default type if the type isn't explicitly given.
					if(!isset($options['type']))
					{
						$options['type'] = 'url';
					}
					if($type == 'js')
					{
						e107::js($options['type'], $data, $options, $options['zone']);
					}
					elseif($type == 'css')
					{
						e107::getJs()->libraryCSS($data); // load before others.
					//	e107::css($options['type'], $data, null);
					}
					$count++;
				}
			}
		}

		// Load PHP files.
		if(!empty($library['files']['php']))
		{
			foreach($library['files']['php'] as $file => $array)
			{
				$file_path1 = $path . '/' . $file;
				$file_path2 = e_ROOT . $path . '/' . $file;

				if(file_exists($file_path1))
				{
					$this->_requireOnce($file_path1);
					$count++;
				}
				elseif(file_exists($file_path2))
				{
					$this->_requireOnce($file_path2);
					$count++;
				}
			}
		}

		// Load integration_files.
		if(!empty($library['post_load_integration_files']) && $library['post_load_integration_files'] && !empty($library['integration_files']))
		{
			foreach($library['integration_files'] as $provider => $files)
			{
				// If provider is an installed plugin.
				if(e107::isInstalled($provider))
				{
					$this->loadFiles(array(
						'files'                       => $files,
						'path'                        => '',
						'library_path'                => e_PLUGIN . $provider,
						'post_load_integration_files' => false,
					));
				}
				// If provider is the admin theme, we only allow it for admin pages.
				elseif(e_ADMIN_AREA && $provider == $adminTheme)
				{
					$this->loadFiles(array(
						'files'                       => $files,
						'path'                        => '',
						'library_path'                => e_THEME . $provider,
						'post_load_integration_files' => false,
					));
				}
				// If provider is the site theme, we only allow it on user areas.
				elseif(!deftrue(e_ADMIN_AREA, false) && $provider == $siteTheme)
				{
					$this->loadFiles(array(
						'files'                       => $files,
						'path'                        => '',
						'library_path'                => e_THEME . $provider,
						'post_load_integration_files' => false,
					));
				}
			}
		}

		return $count;
	}

	/**
	 * Wrapper function for require_once.
	 *
	 * A library file could set a $path variable in file scope. Requiring such a file directly in loadFiles()
	 * would lead to the local $path variable being overridden after the require_once statement. This would break
	 * loading further files. Therefore we use this trivial wrapper which has no local state that can be tampered with.
	 *
	 * @param $file_path
	 *   The file path of the file to require.
	 */
	private function _requireOnce($file_path)
	{
		// TODO: use e107_require_once() instead?
		require_once $file_path;
	}

	/**
	 * Gets the version information from an arbitrary library.
	 *
	 * @param $library
	 *   An associative array containing all information about the library.
	 * @param $options
	 *   An associative array containing with the following keys:
	 *   - file: The filename to parse for the version, relative to the library path. For example: 'docs/changelog.txt'.
	 *   - pattern: A string containing a regular expression (PCRE) to match the library version. For example:
	 *     '@version\s+([0-9a-zA-Z\.-]+)@'. Note that the returned version is not the match of the entire pattern (i.e.
	 *     '@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).
	 *   - lines: (optional) The maximum number of lines to search the pattern in. Defaults to 20.
	 *   - cols: (optional) The maximum number of characters per line to take into account. Defaults to 200. In case of
	 *     minified or compressed files, this prevents reading the entire file into memory.
	 *
	 * @return mixed
	 *   A string containing the version of the library. Or null.
	 */
	private function getVersion($library, $options)
	{
		// Provide defaults.
		$options += array(
			'file'    => '',
			'pattern' => '',
			'lines'   => 20,
			'cols'    => 200,
		);

		$libraryPath = e107::getParser()->replaceConstants($library['library_path']);
		$libraryPath = ($library['path'] !== '' ? rtrim($libraryPath, '/') . '/' . $library['path'] : $libraryPath);
		$libraryPath = rtrim($libraryPath, '/');

		$file = $libraryPath . '/' . $options['file'];

		if(empty($options['file']))
		{
			return;
		}

		// If remote file (e.g. CDN URL)... we download file to temp, and get version number.
		// The library will be cached with version number, so this only run once per library.
		if(strpos($file, 'http') === 0)
		{
			$content = e107::getFile()->getRemoteContent($file);
			$tmpFile = tempnam(sys_get_temp_dir(), 'lib_');

			if($tmpFile)
			{
				file_put_contents($tmpFile, $content);
				$file = $tmpFile;
			}
		}

		if(!file_exists($file))
		{
			return;
		}

		$file = fopen($file, 'r');
		while($options['lines'] && $line = fgets($file, $options['cols']))
		{
			if(preg_match($options['pattern'], $line, $version))
			{
				fclose($file);

				// If downloaded file, we need to unlink it from temp.
				if(isset($tmpFile) && file_exists($tmpFile))
				{
					unlink($tmpFile);
				}

				return $version[1];
			}
			$options['lines']--;
		}
		fclose($file);

		// If downloaded file, we need to unlink it from temp.
		if(isset($tmpFile) && file_exists($tmpFile))
		{
			unlink($tmpFile);
		}

		return;
	}

	/**
	 * Parses a dependency for comparison by checkIncompatibility().
	 *
	 * @param $dependency
	 *   A dependency string, which specifies a plugin dependency, and versions that are supported. Supported formats
	 *   include:
	 *   - 'plugin'
	 *   - 'plugin (>=version, version)'
	 *
	 * @return array
	 *   An associative array with three keys:
	 *   - 'name' includes the name of the thing to depend on (e.g. 'foo').
	 *   - 'original_version' contains the original version string (which can be used in the UI for reporting
	 *     incompatibilities).
	 *   - 'versions' is a list of associative arrays, each containing the keys 'op' and 'version'. 'op' can be one of:
	 *     '=', '==', '!=', '<>', '<', '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'.
	 *   Callers should pass this structure to checkIncompatibility().
	 */
	private function parseDependency($dependency)
	{
		$value = array();

		// We use named subpatterns and support every op that version_compare supports. Also, op is optional and
		// defaults to equals.
		$p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?';
		$p_major = '(?P<major>\d+)';
		// By setting the minor version to x, branches can be matched.
		$p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
		$parts = explode('(', $dependency, 2);
		$value['name'] = trim($parts[0]);

		if(isset($parts[1]))
		{
			$value['original_version'] = ' (' . $parts[1];
			foreach(explode(',', $parts[1]) as $version)
			{
				if(preg_match("/^\s*$p_op\s*$p_major\.$p_minor/", $version, $matches))
				{
					$op = !empty($matches['operation']) ? $matches['operation'] : '=';
					if($matches['minor'] == 'x')
					{
						// "2.x" to mean any version that begins with "2" (e.g. 2.0, 2.9 are all "2.x").
						// PHP's version_compare(), on the other hand, treats "x" as a string; so to version_compare(),
						// "2.x" is considered less than 2.0. This means that >=2.x and <2.x are handled by
						// version_compare() as we need, but > and <= are not.
						if($op == '>' || $op == '<=')
						{
							$matches['major']++;
						}
						// Equivalence can be checked by adding two restrictions.
						if($op == '=' || $op == '==')
						{
							$value['versions'][] = array('op' => '<', 'version' => ($matches['major'] + 1) . '.x');
							$op = '>=';
						}
					}
					$value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']);
				}
			}
		}

		return $value;
	}

	/**
	 * Checks whether a version is compatible with a given dependency.
	 *
	 * @param $v
	 *   The parsed dependency structure from parseDependency().
	 * @param $current_version
	 *   The version to check against (like 4.2).
	 *
	 * @return mixed
	 *   NULL if compatible, otherwise the original dependency version string that caused the incompatibility.
	 *
	 * @see parseDependency()
	 */
	private function checkIncompatibility($v, $current_version)
	{
		if(!empty($v['versions']))
		{
			foreach($v['versions'] as $required_version)
			{
				if((isset($required_version['op'])))
				{
					if(!version_compare($current_version, $required_version['version'], $required_version['op']))
					{
						return $v['original_version'];
					}
				}
			}
		}
	}

	/**
	 * Alters library information before detecting.
	 */
	private function preDetect(&$library)
	{
		if(empty($library['machine_name']))
		{
			return;
		}

		// Prevent plugins/themes from altering libraries on Admin UI.
		if(defset('e_ADMIN_AREA', false) == true)
		{
			$coreLibrary = new core_library();
			$coreLibs = $coreLibrary->config();

			if (isset($coreLibs[$library['machine_name']])) {
				$coreLib = $coreLibs[$library['machine_name']];
				$library = array_replace_recursive($coreLib, array_replace_recursive($library, $coreLib));
			}
		}
	}

	/**
	 * Alters library information before loading.
	 */
	private function preLoad(&$library)
	{
		if(empty($library['machine_name']))
		{
			return;
		}

		$excluded = $this->getExcludedLibraries();

		if(empty($excluded))
		{
			return;
		}

		// Make sure we have the name without cdn prefix.
		$basename = str_replace('cdn.', '', $library['machine_name']);

		// If this library (or the CDN version of this library) is excluded
		// by the theme is currently used.
		if (in_array($basename, $excluded) || in_array('cdn.' . $basename, $excluded))
		{
			unset($library['files']['css']);

			if (!empty($library['variants']))
			{
				foreach($library['variants'] as &$variant)
				{
					if(!empty($variant['files']['css']))
					{
						unset($variant['files']['css']);
					}
				}
			}
		}
	}

	/**
	 * Get excluded libraries.
	 *
	 * @return array
	 */
	public function getExcludedLibraries()
	{
		// This static cache is re-used by preLoad() to save memory.
		static $excludedLibraries;

		if(!isset($excludedLibraries))
		{
			$excludedLibraries = array();

			$exclude = e107::getTheme('current', false)->cssAttribute('auto', 'exclude');

			if($exclude)
			{
				// Split string into array and remove whitespaces.
				$excludedLibraries = array_map('trim', explode(',', $exclude));
			}
		}

		return $excludedLibraries;
	}

}
