primo commit
This commit is contained in:
542
media/fef/fef.php
Normal file
542
media/fef/fef.php
Normal file
@ -0,0 +1,542 @@
|
||||
<?php
|
||||
/**
|
||||
* Akeeba Frontend Framework (FEF)
|
||||
*
|
||||
* @package fef
|
||||
* @copyright (c) 2017-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
// Protect from unauthorized access
|
||||
use Joomla\CMS\Document\PreloadManagerInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Utilities\ArrayHelper;
|
||||
|
||||
defined('_JEXEC') || die();
|
||||
|
||||
if (@file_exists(__DIR__ . '/version.php') && @is_file(__DIR__ . '/version.php') && @is_readable(__DIR__ . '/version.php'))
|
||||
{
|
||||
@include_once(__DIR__ . '/version.php');
|
||||
}
|
||||
|
||||
if (!defined('AKEEBAFEF_VERSION'))
|
||||
{
|
||||
define('AKEEBAFEF_VERSION', 'dev');
|
||||
define('AKEEBAFEF_DATE', gmdate('Y-m-d'));
|
||||
}
|
||||
|
||||
class AkeebaFEFHelper
|
||||
{
|
||||
/**
|
||||
* Media versioning tag
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.3
|
||||
*/
|
||||
public static $tag = null;
|
||||
|
||||
/**
|
||||
* The Akeeba FEF Loader object
|
||||
*
|
||||
* @var AkeebaFEFLoader
|
||||
* @since 2.0.0
|
||||
*/
|
||||
private static $loader;
|
||||
|
||||
/**
|
||||
* Is this Joomla 4?
|
||||
*
|
||||
* @var bool|null
|
||||
* @since 2.0.0
|
||||
*/
|
||||
private static $isJoomla4 = null;
|
||||
|
||||
/**
|
||||
* Loads the Akeeba Frontend Framework, both CSS and JS
|
||||
*
|
||||
* @param bool $withReset Should I also load the CSS reset for the FEF container?
|
||||
* @param bool $dark Include Dark Mode CSS?
|
||||
*
|
||||
* @return void
|
||||
* @since 1.0.3
|
||||
*/
|
||||
public static function load(bool $withReset = true, bool $dark = false)
|
||||
{
|
||||
self::loadCSSFramework();
|
||||
self::loadJSFramework();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the Akeeba FEF CSS Framework
|
||||
*
|
||||
* @param bool $withReset Should I also load the CSS reset for the FEF container?
|
||||
* @param bool $dark Include Dark Mode CSS?
|
||||
*
|
||||
* @return void
|
||||
* @since 1.0.3
|
||||
*/
|
||||
public static function loadCSSFramework(bool $withReset = true, bool $dark = false)
|
||||
{
|
||||
self::getLoader()->loadCSSFramework($withReset, $dark);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the Akeeba FEF JavaScript Framework
|
||||
*
|
||||
* @param bool $minimal Should I load the minimal framework (without optional features linked to FEF CSS?)
|
||||
*
|
||||
* @return void
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public static function loadJSFramework(bool $minimal = false)
|
||||
{
|
||||
self::getLoader()->loadJSFramework($minimal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy (FEF 1.1.x) alias to loadFEFScript.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @see self::loadFEFScript
|
||||
* @deprecated 3.0
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public static function loadScript(string $name): void
|
||||
{
|
||||
self::loadFEFScript($name, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an Akeeba FEF JavaScript file and its dependencies.
|
||||
*
|
||||
* @param string $name The basename of the file, e.g. "Tabs"
|
||||
* @param bool $defer Should I defer loading of the file?
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public static function loadFEFScript(string $name, bool $defer = true): void
|
||||
{
|
||||
self::getLoader()->loadFEFScript($name, $defer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this Joomla 4 or later?
|
||||
*
|
||||
* @return bool
|
||||
* @since 2.0.0
|
||||
*/
|
||||
private static function isJoomla4(): bool
|
||||
{
|
||||
if (!is_bool(self::$isJoomla4))
|
||||
{
|
||||
self::$isJoomla4 = version_compare(JVERSION, '3.999.999', 'gt');
|
||||
}
|
||||
|
||||
return self::$isJoomla4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a JavaScript file using the Joomla! API.
|
||||
*
|
||||
* Special considerations:
|
||||
*
|
||||
* We always load the minified version of the file. Joomla! will automatically use the non-minified one if Debug
|
||||
* Site is enabled.
|
||||
*
|
||||
* You can have browser-specific files, e.g. foo_firefox.min.js, foo_firefox_57.min.js etc. These are loaded
|
||||
* automatically instead of the foo.js file as needed.
|
||||
*
|
||||
* This method goes through Joomla's script loader, thus allowing template media overrides. The media overrides are
|
||||
* supposed to be in the templates/YOUR_TEMPLATE/js/fef folder for FEF.
|
||||
*
|
||||
* @param string $name The Joomla!-coded path of the file, e.g. 'foo/bar.min.js' for the JavaScript file
|
||||
* media/foo/js/bar.min.js
|
||||
*
|
||||
* @param bool $defer Should I load the script defered?
|
||||
*
|
||||
* @return void
|
||||
* @since 1.0.3
|
||||
*/
|
||||
private static function loadJS(string $name, bool $defer = true): void
|
||||
{
|
||||
$options = [
|
||||
'version' => self::getMediaVersion(),
|
||||
'relative' => true,
|
||||
'detectDebug' => false,
|
||||
'framework' => false,
|
||||
'pathOnly' => false,
|
||||
'detectBrowser' => true,
|
||||
];
|
||||
HTMLHelper::_('script', 'fef/' . $name . '.min.js', $options, [
|
||||
'defer' => $defer,
|
||||
'async' => false,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Preload the static resource we are definitely asking the browser to use for better performance.
|
||||
*
|
||||
* Yes, this is also useful for deferred scripts! On Joomla 4 this goes through Preload Manager which does an
|
||||
* HTTP/2 Push for the script files. This is **far** faster than the browser making a number of HEAD requests
|
||||
* to see if the scripts are cached then an equal (or smaller) number of GET requests to fetch the scripts.
|
||||
*/
|
||||
$options['pathOnly'] = 'true';
|
||||
$path = HTMLHelper::_('script', 'fef/' . $name . '.min.js', $options);
|
||||
|
||||
if (empty($path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self::preloadResource($path . '?' . self::getMediaVersion(), ['as' => 'script']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a CSS file using the Joomla! API.
|
||||
*
|
||||
* Special considerations:
|
||||
*
|
||||
* We always as Joomla to load the minified version of a file. Joomla! will automatically use the non-minified one
|
||||
* if Debug Site is enabled.
|
||||
*
|
||||
* You can have browser-specific files, e.g. foo_firefox.min.css, foo_firefox_57.min.css etc. These are loaded
|
||||
* automatically instead of the foo.css file as needed.
|
||||
*
|
||||
* This method goes through Joomla's script loader, thus allowing template media overrides. The media overrides are
|
||||
* supposed to be in the templates/YOUR_TEMPLATE/css/fef folder for FEF.
|
||||
*
|
||||
* We are instructing the browser to preload the CSS file we are inserting in the HTML. This can cause a small
|
||||
* performance increase. On Joomla 4 the increase is most noticeable because we go through the Preload Manager
|
||||
* which can leverage HTTP/2 Push.
|
||||
*
|
||||
* When loading the main CSS file (joomla-fef) we preload the WOFF font files which will be most likely used by the
|
||||
* browser consuming the CSS. This allows the browser to fetch the font files before fully parsing the stylesheet,
|
||||
* saving some time.
|
||||
*
|
||||
* @param string $name The Joomla!-coded path of the file, e.g. 'foo/bar.min.css' for the stylesheet file
|
||||
* media/foo/css/bar.min.css
|
||||
*
|
||||
* @return void
|
||||
* @since 1.0.3
|
||||
*/
|
||||
private static function loadCSS(string $name): void
|
||||
{
|
||||
/**
|
||||
* IMPORTANT! The $attribs (final parameter) MUST ALWAYS be non-empty. Otherwise Joomla! 3.x bugs out.
|
||||
*/
|
||||
$options = [
|
||||
'version' => self::getMediaVersion(),
|
||||
'relative' => true,
|
||||
'detectDebug' => false,
|
||||
'pathOnly' => false,
|
||||
'detectBrowser' => true,
|
||||
];
|
||||
HTMLHelper::_('stylesheet', 'fef/' . $name . '.min.css', $options, [
|
||||
'type' => 'text/css',
|
||||
]);
|
||||
|
||||
// Preload the static resource we are definitely asking the browser to use for better performance
|
||||
$options['pathOnly'] = 'true';
|
||||
$path = HTMLHelper::_('stylesheet', 'fef/' . $name . '.min.css', $options, [
|
||||
'type' => 'text/css',
|
||||
]);
|
||||
|
||||
if (empty($path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self::preloadResource($path . '?' . self::getMediaVersion(), ['as' => 'style']);
|
||||
|
||||
// Special case: loading the fef-joomla stylesheet. Preload the font files as well
|
||||
if ($name == 'fef-joomla')
|
||||
{
|
||||
$fontPath = dirname(dirname($path)) . '/fonts/akeeba/Akeeba-Products.woff';
|
||||
self::preloadResource($fontPath, ['as' => 'font', 'crossorigin' => 'anonymous']);
|
||||
|
||||
$fontPath = dirname(dirname($path)) . '/fonts/Ionicon/ionicons.woff';
|
||||
self::preloadResource($fontPath, ['as' => 'font', 'crossorigin' => 'anonymous']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload a resource.
|
||||
*
|
||||
* On Joomla 3 this adds a LINK tag to the HTML document, instructing the browser to preload the resource.
|
||||
*
|
||||
* On Joomla 4 we are using the document object's Preload Manager to leverage HTTP/2 Push if available.
|
||||
*
|
||||
* @param string $url The absolute or relative URL of the resource to preload.
|
||||
* @param array $options Preload options. You need to specify 'as' as the bare minimum.
|
||||
*
|
||||
* @return void
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
private static function preloadResource(string $url, array $options): void
|
||||
{
|
||||
if (!self::isJoomla4() || !self::preloadResourceJoomla4($url, $options))
|
||||
{
|
||||
self::preloadResourceJoomla3($url, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload a resource on Joomla 3.
|
||||
*
|
||||
* This adds a LINK tag to the HTML document, instructing the browser to preload the resource.
|
||||
*
|
||||
* @param string $url The absolute or relative URL of the resource to preload.
|
||||
* @param array $options Preload options. You need to specify 'as' as the bare minimum.
|
||||
*
|
||||
* @return bool True if successful; false if we can't get the document, or can't add a LINK tag e.g. this is not
|
||||
* an HTMLDocument
|
||||
* @since 2.0.0
|
||||
*/
|
||||
private static function preloadResourceJoomla3(string $url, array $options): bool
|
||||
{
|
||||
// Try to get Joomla's document object
|
||||
$document = self::getDocument();
|
||||
|
||||
// Make sure the document object implements addCustomTag
|
||||
if (!is_object($document) || !method_exists($document, 'addCustomTag'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$options['rel'] = 'preload';
|
||||
$options['href'] = self::relativeToAbsoluteURL($url);
|
||||
$document->addCustomTag('<link ' . ArrayHelper::toString($options) . '>');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload a resource on Joomla 4.
|
||||
*
|
||||
* We are using the document object's Preload Manager to leverage HTTP/2 Push if available.
|
||||
*
|
||||
* @param string $url The absolute or relative URL of the resource to preload.
|
||||
* @param array $options Preload options. You need to specify 'as' as the bare minimum.
|
||||
*
|
||||
* @return bool True if successful; false if we can't get the document, or can't add a LINK tag e.g. this is not
|
||||
* an HTMLDocument
|
||||
* @since 2.0.0
|
||||
*/
|
||||
private static function preloadResourceJoomla4(string $url, array $options): bool
|
||||
{
|
||||
// Make sure we're in a version of Joomla which has support for the Preload Manager
|
||||
if (!interface_exists('\\Joomla\CMS\Document\PreloadManagerInterface'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to get Joomla's document object
|
||||
$document = self::getDocument();
|
||||
|
||||
// Make sure the document object implements getPreloadManager
|
||||
if (!is_object($document) || !method_exists($document, 'getPreloadManager'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to get the preload manager
|
||||
try
|
||||
{
|
||||
$preloadManager = $document->getPreloadManager();
|
||||
}
|
||||
catch (Throwable $e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the preload manager is an object implementing the PreloadManagerInterface
|
||||
if (!is_object($preloadManager) || !($preloadManager instanceof PreloadManagerInterface))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$absoluteUrl = self::relativeToAbsoluteURL($url);
|
||||
$preloadManager->preload($absoluteUrl, $options);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Joomla document object
|
||||
*
|
||||
* @return JDocument|\Joomla\CMS\Document\Document|null NULL if we can't retrieve the document object.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
private static function getDocument()
|
||||
{
|
||||
// Get the CMS application
|
||||
try
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
}
|
||||
catch (Throwable $e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Make sure it's an object implementing getDocument
|
||||
if (!is_object($app) || !method_exists($app, 'getDocument'))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to get the document
|
||||
try
|
||||
{
|
||||
$document = $app->getDocument();
|
||||
}
|
||||
catch (Throwable $e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a relative URL to an absolute URL for the current site
|
||||
*
|
||||
* @param string $url The possibly relative URL.
|
||||
*
|
||||
* @return string The definitely absolute URL.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
private static function relativeToAbsoluteURL(string $url): string
|
||||
{
|
||||
static $baseUri;
|
||||
static $basePath;
|
||||
|
||||
// Get the base URI, e.g. 'https://localhost/test'
|
||||
if (empty($baseUri))
|
||||
{
|
||||
$baseUri = $baseUri ?? Uri::base();
|
||||
|
||||
if (substr($baseUri, -15) === '/administrator/')
|
||||
{
|
||||
$baseUri = substr($baseUri, 0, -15);
|
||||
}
|
||||
elseif (substr($baseUri, -14) === '/administrator')
|
||||
{
|
||||
$baseUri = substr($baseUri, 0, -14);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the base path, e.g. 'test'
|
||||
if (empty($basePath))
|
||||
{
|
||||
$basePath = $basePath ?? Uri::base(true);
|
||||
$basePath = empty($basePath) ? '' : trim($basePath, '/');
|
||||
|
||||
if ($basePath === 'administrator')
|
||||
{
|
||||
$basePath = '';
|
||||
}
|
||||
elseif (substr($basePath, -14) == '/administrator')
|
||||
{
|
||||
$basePath = trim(substr($basePath, 0, -14), '/');
|
||||
}
|
||||
}
|
||||
|
||||
if ((substr($url, 0, 2) == '//') ||
|
||||
(substr($url, 0, 7) == 'http://') ||
|
||||
(substr($url, 0, 8) == 'https://') ||
|
||||
(substr($url, 0, strlen($baseUri)) == $baseUri))
|
||||
{
|
||||
return $url;
|
||||
}
|
||||
|
||||
$url = ltrim($url, '/');
|
||||
|
||||
if ((strlen($basePath) != 0) && (substr($url, 0, strlen($basePath)) === $basePath))
|
||||
{
|
||||
$url = ltrim(substr($url, strlen($basePath)), '/');
|
||||
$url = ltrim($url, '/');
|
||||
}
|
||||
|
||||
return rtrim($baseUri, '/') . '/' . $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the media versioning tag. If it's not set, create one first.
|
||||
*
|
||||
* @return string
|
||||
* @since 1.1.0
|
||||
*/
|
||||
private static function getMediaVersion(): string
|
||||
{
|
||||
if (empty(self::$tag))
|
||||
{
|
||||
self::$tag = md5(AKEEBAFEF_VERSION . AKEEBAFEF_DATE . self::getApplicationSecret());
|
||||
}
|
||||
|
||||
return self::$tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the secret key for the Joomla! installation. Falls back to an MD5 of our file mod time.
|
||||
*
|
||||
* @return string
|
||||
* @since 1.1.0
|
||||
*/
|
||||
private static function getApplicationSecret(): string
|
||||
{
|
||||
$secret = md5(filemtime(__FILE__));
|
||||
|
||||
// Get the site's secret
|
||||
try
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
if (method_exists($app, 'get'))
|
||||
{
|
||||
return $app->get('secret', $secret);
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
}
|
||||
|
||||
return $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns or creates the Akeeba FEF Loader object.
|
||||
*
|
||||
* IMPORTANT: DO NOT SET A RETURN VALUE TYPE HINT. The AkeebaFEFLoader class is undefined until we load it in this
|
||||
* method. This causes a chicken and egg problem which results in a Fatal Error!
|
||||
*
|
||||
* @return AkeebaFEFLoader
|
||||
* @since 2.0.0
|
||||
*/
|
||||
private static function getLoader()
|
||||
{
|
||||
if (!is_null(self::$loader))
|
||||
{
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
if (!class_exists('AkeebaFEFLoader'))
|
||||
{
|
||||
require_once __DIR__ . '/php/AkeebaFEFLoader.php';
|
||||
}
|
||||
|
||||
self::$loader = new AkeebaFEFLoader(function (string $name) {
|
||||
self::loadCSS($name);
|
||||
}, function (string $name, bool $defer) {
|
||||
self::loadJS($name, $defer);
|
||||
}, 'joomla');
|
||||
|
||||
return self::$loader;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user