e107inc/e107

View on GitHub
e107_handlers/library_manager.php

Summary

Maintainability
A
30 mins
Test Coverage
B
84%
<?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;
    }

}