primo commit
This commit is contained in:
295
libraries/fof30/Autoloader/Autoloader.php
Normal file
295
libraries/fof30/Autoloader/Autoloader.php
Normal file
@ -0,0 +1,295 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Autoloader;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A PSR-4 class autoloader. This is a modified version of Composer's ClassLoader class
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Autoloader
|
||||
{
|
||||
/** @var Autoloader The static instance of this autoloader */
|
||||
private static $instance;
|
||||
/** @var array Lengths of PSR-4 prefixes */
|
||||
private $prefixLengths = [];
|
||||
/** @var array Prefix to directory map */
|
||||
private $prefixDirs = [];
|
||||
/** @var array Fall-back directories */
|
||||
private $fallbackDirs = [];
|
||||
|
||||
/**
|
||||
* @return Autoloader
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (!is_object(self::$instance))
|
||||
{
|
||||
self::$instance = new Autoloader();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prefix to directory map
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
return $this->prefixDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of fall=back directories
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prefixing to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
* @param boolean $prepend Whether to prefix the directories
|
||||
*
|
||||
* @return $this for chaining
|
||||
*
|
||||
* @throws InvalidArgumentException When the prefix is invalid
|
||||
*/
|
||||
public function addMap($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if ($prefix)
|
||||
{
|
||||
$prefix = ltrim($prefix, '\\');
|
||||
}
|
||||
|
||||
if (!$prefix)
|
||||
{
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend)
|
||||
{
|
||||
$this->fallbackDirs = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirs
|
||||
);
|
||||
|
||||
$this->fallbackDirs = array_unique($this->fallbackDirs);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->fallbackDirs = array_merge(
|
||||
$this->fallbackDirs,
|
||||
(array) $paths
|
||||
);
|
||||
|
||||
$this->fallbackDirs = array_unique($this->fallbackDirs);
|
||||
}
|
||||
}
|
||||
elseif (!isset($this->prefixDirs[$prefix]))
|
||||
{
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1])
|
||||
{
|
||||
throw new InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengths[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirs[$prefix] = (array) $paths;
|
||||
}
|
||||
elseif ($prepend)
|
||||
{
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirs[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirs[$prefix]
|
||||
);
|
||||
|
||||
$this->prefixDirs[$prefix] = array_unique($this->prefixDirs[$prefix]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirs[$prefix] = array_merge(
|
||||
$this->prefixDirs[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
|
||||
$this->prefixDirs[$prefix] = array_unique($this->prefixDirs[$prefix]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the autoloader have a map for the specified prefix?
|
||||
*
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasMap($prefix)
|
||||
{
|
||||
return isset($this->prefixDirs[$prefix]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException When the prefix is invalid
|
||||
*
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public function setMap($prefix, $paths)
|
||||
{
|
||||
if ($prefix)
|
||||
{
|
||||
$prefix = ltrim($prefix, '\\');
|
||||
}
|
||||
|
||||
if (!$prefix)
|
||||
{
|
||||
$this->fallbackDirs = (array) $paths;
|
||||
}
|
||||
else
|
||||
{
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1])
|
||||
{
|
||||
throw new InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengths[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirs[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param boolean $prepend Whether to prepend the autoloader or not
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register([$this, 'loadClass'], true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister([$this, 'loadClass']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return bool True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class))
|
||||
{
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
include $file;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
|
||||
if ('\\' == $class[0])
|
||||
{
|
||||
$class = substr($class, 1);
|
||||
}
|
||||
|
||||
// FEFHelper lookup
|
||||
if (substr($class, 0, 9) == 'FEFHelper')
|
||||
{
|
||||
|
||||
if (file_exists($file = realpath(__DIR__ . '/..') . '/Utils/FEFHelper/' . strtolower(substr($class, 9)) . '.php'))
|
||||
{
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 lookup
|
||||
$logicalPath = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php';
|
||||
|
||||
$first = $class[0];
|
||||
|
||||
if (isset($this->prefixLengths[$first]))
|
||||
{
|
||||
foreach ($this->prefixLengths[$first] as $prefix => $length)
|
||||
{
|
||||
if (0 === strpos($class, $prefix))
|
||||
{
|
||||
foreach ($this->prefixDirs[$prefix] as $dir)
|
||||
{
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPath, $length)))
|
||||
{
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirs as $dir)
|
||||
{
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPath))
|
||||
{
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Register the current namespace with the autoloader
|
||||
Autoloader::getInstance()->addMap('FOF30\\', [realpath(__DIR__ . '/..')]);
|
||||
Autoloader::getInstance()->register();
|
||||
338
libraries/fof30/Cli/Application.php
Normal file
338
libraries/fof30/Cli/Application.php
Normal file
@ -0,0 +1,338 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
// Do not put the JEXEC or die check on this file
|
||||
|
||||
/**
|
||||
* FOF-powered Joomla! CLI application implementation.
|
||||
*
|
||||
* Get all the power of Joomla in CLI without all the awkward decisions which make CLI scripts fail on many common,
|
||||
* commercial hosting environments. We've been doing that in our software before Joomla got CLI support. We know of all
|
||||
* the pitfalls and this little gem here will work around most of them (or at least fail gracefully).
|
||||
*
|
||||
* Your CLI script must begin with the following boilerplate code:
|
||||
*
|
||||
* // Boilerplate -- START
|
||||
* define('_JEXEC', 1);
|
||||
*
|
||||
* foreach ([__DIR__, getcwd()] as $curdir)
|
||||
* {
|
||||
* if (file_exists($curdir . '/defines.php'))
|
||||
* {
|
||||
* define('JPATH_BASE', realpath($curdir . '/..'));
|
||||
* require_once $curdir . '/defines.php';
|
||||
*
|
||||
* break;
|
||||
* }
|
||||
*
|
||||
* if (file_exists($curdir . '/../includes/defines.php'))
|
||||
* {
|
||||
* define('JPATH_BASE', realpath($curdir . '/..'));
|
||||
* require_once $curdir . '/../includes/defines.php';
|
||||
*
|
||||
* break;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* defined('JPATH_LIBRARIES') || die ('This script must be placed in or run from the cli folder of your site.');
|
||||
*
|
||||
* require_once JPATH_LIBRARIES . '/fof30/Cli/Application.php';
|
||||
* // Boilerplate -- END
|
||||
*
|
||||
* Create a class which extends FOFCliApplication and implements doExecute, e.g.
|
||||
*
|
||||
* // Class definition -- START
|
||||
* class YourClassName extends FOFCliApplication
|
||||
* {
|
||||
* protected function doExecute()
|
||||
* {
|
||||
* // Do something useful
|
||||
* }
|
||||
* }
|
||||
* // Class definition -- END
|
||||
*
|
||||
* Finally, execute your script with:
|
||||
*
|
||||
* // Execute script -- START
|
||||
* FOFCliApplication::getInstance('YourClassName')->execute();
|
||||
* // Execute script -- END
|
||||
*
|
||||
* You can optionally define $minphp before the boilerplate code to enforce a different minimum PHP version.
|
||||
*/
|
||||
|
||||
// Abort immediately when this file is executed from a web SAPI
|
||||
if (array_key_exists('REQUEST_METHOD', $_SERVER))
|
||||
{
|
||||
die('This is a command line script. You are not allowed to access it over the web.');
|
||||
}
|
||||
|
||||
// Work around some misconfigured servers which print out notices
|
||||
if (function_exists('error_reporting'))
|
||||
{
|
||||
$oldLevel = error_reporting(E_ERROR | E_NOTICE | E_DEPRECATED);
|
||||
}
|
||||
|
||||
// Minimum PHP version check
|
||||
if (!isset($minphp))
|
||||
{
|
||||
$minphp = '5.6.0';
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, $minphp, 'lt'))
|
||||
{
|
||||
require_once __DIR__ . '/wrong_php.php';
|
||||
|
||||
die;
|
||||
}
|
||||
|
||||
// Required by scripts written for old Joomla! versions.
|
||||
defined('DS') || define('DS', DIRECTORY_SEPARATOR);
|
||||
|
||||
/**
|
||||
* Timezone fix
|
||||
*
|
||||
* This piece of code was originally put here because some PHP 5.3 servers forgot to declare a default timezone.
|
||||
* Unfortunately it's still required because some hosts STILL forget to provide a timezone in their php.ini files or,
|
||||
* worse, use invalid timezone names.
|
||||
*/
|
||||
if (function_exists('date_default_timezone_get') && function_exists('date_default_timezone_set'))
|
||||
{
|
||||
$serverTimezone = @date_default_timezone_get();
|
||||
|
||||
// Do I have no timezone set?
|
||||
if (empty($serverTimezone) || !is_string($serverTimezone))
|
||||
{
|
||||
$serverTimezone = 'UTC';
|
||||
}
|
||||
|
||||
// Do I have an invalid timezone?
|
||||
try
|
||||
{
|
||||
$testTimeZone = new DateTimeZone($serverTimezone);
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
$serverTimezone = 'UTC';
|
||||
}
|
||||
|
||||
// Set the default timezone to a correct thing
|
||||
@date_default_timezone_set($serverTimezone);
|
||||
}
|
||||
|
||||
// This is not necessary if you have used the boilerplate code.
|
||||
if (!isset($curdir) && !defined('JPATH_ROOT'))
|
||||
{
|
||||
foreach ([__DIR__ . '/../../../cli', getcwd()] as $curdir)
|
||||
{
|
||||
if (file_exists($curdir . '/defines.php'))
|
||||
{
|
||||
define('JPATH_BASE', realpath($curdir . '/..'));
|
||||
require_once $curdir . '/defines.php';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (file_exists($curdir . '/../includes/defines.php'))
|
||||
{
|
||||
define('JPATH_BASE', realpath($curdir . '/..'));
|
||||
require_once $curdir . '/../includes/defines.php';
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
defined('JPATH_LIBRARIES') || die ('This script must be placed in or run from the cli folder of your site.');
|
||||
}
|
||||
|
||||
// Restore the error reporting before importing Joomla core code
|
||||
if (function_exists('error_reporting'))
|
||||
{
|
||||
error_reporting($oldLevel);
|
||||
}
|
||||
|
||||
// Awkward Joomla version detection before we can actually load Joomla! itself
|
||||
$joomlaMajorVersion = 3;
|
||||
$joomlaMinorVersion = 0;
|
||||
$jVersionFile = JPATH_LIBRARIES . '/src/Version.php';
|
||||
|
||||
if ($versionFileContents = @file_get_contents($jVersionFile))
|
||||
{
|
||||
preg_match("/MAJOR_VERSION\s*=\s*(\d*)\s*;/", $versionFileContents, $versionMatches);
|
||||
$joomlaMajorVersion = (int) $versionMatches[1];
|
||||
preg_match("/MINOR_VERSION\s*=\s*(\d*)\s*;/", $versionFileContents, $versionMatches);
|
||||
$joomlaMinorVersion = (int) $versionMatches[1];
|
||||
}
|
||||
|
||||
// Load the Trait files
|
||||
include_once __DIR__ . '/Traits/CGIModeAware.php';
|
||||
include_once __DIR__ . '/Traits/CustomOptionsAware.php';
|
||||
include_once __DIR__ . '/Traits/JoomlaConfigAware.php';
|
||||
include_once __DIR__ . '/Traits/MemStatsAware.php';
|
||||
include_once __DIR__ . '/Traits/MessageAware.php';
|
||||
include_once __DIR__ . '/Traits/TimeAgoAware.php';
|
||||
|
||||
// The actual implementation of the CliApplication depends on the Joomla version we're running under
|
||||
switch ($joomlaMajorVersion)
|
||||
{
|
||||
case 3:
|
||||
default:
|
||||
require_once __DIR__ . '/Joomla3.php';
|
||||
|
||||
abstract class FOFApplicationCLI extends FOFCliApplicationJoomla3
|
||||
{
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
break;
|
||||
|
||||
case 4:
|
||||
require_once __DIR__ . '/Joomla4.php';
|
||||
|
||||
abstract class FOFApplicationCLI extends FOFCliApplicationJoomla4
|
||||
{
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* A default exception handler. Catches all unhandled exceptions, displays debug information about them and sets the
|
||||
* error level to 254.
|
||||
*
|
||||
* @param Throwable $ex The Exception / Error being handled
|
||||
*/
|
||||
function FOFCliExceptionHandler($ex)
|
||||
{
|
||||
echo "\n\n";
|
||||
echo "********** ERROR! **********\n\n";
|
||||
echo $ex->getMessage();
|
||||
echo "\n\nTechnical information:\n\n";
|
||||
echo "Code: " . $ex->getCode() . "\n";
|
||||
echo "File: " . $ex->getFile() . "\n";
|
||||
echo "Line: " . $ex->getLine() . "\n";
|
||||
echo "\nStack Trace:\n\n" . $ex->getTraceAsString();
|
||||
echo "\n\n";
|
||||
exit(254);
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeout handler
|
||||
*
|
||||
* This function is registered as a shutdown script. If a catchable timeout occurs it will detect it and print a helpful
|
||||
* error message instead of just dying cold. The error level is set to 253 in this case.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function FOFCliTimeoutHandler()
|
||||
{
|
||||
$connection_status = connection_status();
|
||||
|
||||
if ($connection_status == 0)
|
||||
{
|
||||
// Normal script termination, do not report an error.
|
||||
return;
|
||||
}
|
||||
|
||||
echo "\n\n";
|
||||
echo "********** ERROR! **********\n\n";
|
||||
|
||||
if ($connection_status == 1)
|
||||
{
|
||||
echo <<< END
|
||||
The process was aborted on user's request.
|
||||
|
||||
This usually means that you pressed CTRL-C to terminate the script (if you're
|
||||
running it from a terminal / SSH session), or that your host's CRON daemon
|
||||
aborted the execution of this script.
|
||||
|
||||
If you are running this script through a CRON job and saw this message, please
|
||||
contact your host and request an increase in the timeout limit for CRON jobs.
|
||||
Moreover you need to ask them to increase the max_execution_time in the
|
||||
php.ini file or, even better, set it to 0.
|
||||
END;
|
||||
}
|
||||
else
|
||||
{
|
||||
echo <<< END
|
||||
This script has timed out. As a result, the process has FAILED to complete.
|
||||
|
||||
Your host applies a maximum execution time for CRON jobs which is too low for
|
||||
this script to work properly. Please contact your host and request an increase
|
||||
in the timeout limit for CRON jobs. Moreover you need to ask them to increase
|
||||
the max_execution_time in the php.ini file or, even better, set it to 0.
|
||||
END;
|
||||
|
||||
|
||||
if (!function_exists('php_ini_loaded_file'))
|
||||
{
|
||||
echo "\n\n";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ini_location = php_ini_loaded_file();
|
||||
|
||||
echo <<<END
|
||||
The php.ini file your host will need to modify is located at:
|
||||
$ini_location
|
||||
Info for the host: the location above is reported by PHP's php_ini_loaded_file() method.
|
||||
|
||||
END;
|
||||
|
||||
echo "\n\n";
|
||||
exit(253);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handler. It tries to catch fatal errors and report them in a meaningful way. Obviously it only works for
|
||||
* catchable fatal errors. It sets the error level to 252.
|
||||
*
|
||||
* IMPORTANT! Under PHP 7 the default exception handler will be called instead, including when there is a non-catchable
|
||||
* fatal error.
|
||||
*
|
||||
* @param int $errno Error number
|
||||
* @param string $errstr Error string, tells us what went wrong
|
||||
* @param string $errfile Full path to file where the error occurred
|
||||
* @param int $errline Line number where the error occurred
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function FOFCliErrorHandler($errno, $errstr, $errfile, $errline)
|
||||
{
|
||||
switch ($errno)
|
||||
{
|
||||
case E_ERROR:
|
||||
case E_USER_ERROR:
|
||||
echo "\n\n";
|
||||
echo "********** ERROR! **********\n\n";
|
||||
echo "PHP Fatal Error: $errstr";
|
||||
echo "\n\nTechnical information:\n\n";
|
||||
echo "File: " . $errfile . "\n";
|
||||
echo "Line: " . $errline . "\n";
|
||||
echo "\nStack Trace:\n\n" . debug_backtrace();
|
||||
echo "\n\n";
|
||||
|
||||
exit(252);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom default handlers for otherwise unhandled exceptions and PHP catchable errors.
|
||||
*
|
||||
* Moreover, we register a shutdown function to catch timeouts and SIGTERM signals, because some hosts *are* monsters.
|
||||
*/
|
||||
set_exception_handler('FOFCliExceptionHandler');
|
||||
set_error_handler('FOFCliErrorHandler', E_ERROR | E_USER_ERROR);
|
||||
register_shutdown_function('FOFCliTimeoutHandler');
|
||||
134
libraries/fof30/Cli/Joomla3.php
Normal file
134
libraries/fof30/Cli/Joomla3.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
// Do not put the JEXEC or die check on this file
|
||||
|
||||
use FOF30\Cli\Traits\CGIModeAware;
|
||||
use FOF30\Cli\Traits\CustomOptionsAware;
|
||||
use FOF30\Cli\Traits\JoomlaConfigAware;
|
||||
use FOF30\Cli\Traits\MemStatsAware;
|
||||
use FOF30\Cli\Traits\MessageAware;
|
||||
use FOF30\Cli\Traits\TimeAgoAware;
|
||||
use FOF30\Utils\CliSessionHandler;
|
||||
use Joomla\CMS\Application\CliApplication;
|
||||
use Joomla\CMS\Input\Cli;
|
||||
|
||||
// Load the legacy Joomla! include files (Joomla! 3 only)
|
||||
include_once JPATH_LIBRARIES . '/import.legacy.php';
|
||||
|
||||
// Load the CMS import file if it exists (newer Joomla! 3 versions and Joomla! 4)
|
||||
$cmsImportFilePath = JPATH_LIBRARIES . '/cms.php';
|
||||
|
||||
if (@file_exists($cmsImportFilePath))
|
||||
{
|
||||
@include_once $cmsImportFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for a Joomla! command line application. Adapted from JCli / JApplicationCli
|
||||
*/
|
||||
abstract class FOFCliApplicationJoomla3 extends CliApplication
|
||||
{
|
||||
use CGIModeAware, CustomOptionsAware, JoomlaConfigAware, MemStatsAware, TimeAgoAware, MessageAware;
|
||||
|
||||
private $allowedToClose = false;
|
||||
|
||||
public static function getInstance($name = null)
|
||||
{
|
||||
// Load the Joomla global configuration in JFactory. This must happen BEFORE loading FOF.
|
||||
JFactory::getConfig(JPATH_CONFIGURATION . '/configuration.php');
|
||||
|
||||
// Load FOF
|
||||
if (!defined('FOF30_INCLUDED') && !@include_once(JPATH_LIBRARIES . '/fof30/include.php'))
|
||||
{
|
||||
throw new RuntimeException('Cannot load FOF', 500);
|
||||
}
|
||||
|
||||
// Create a CLI-specific session
|
||||
JFactory::$session = JSession::getInstance('none', [
|
||||
'expire' => 84400,
|
||||
], new CliSessionHandler());
|
||||
|
||||
$instance = parent::getInstance($name);
|
||||
|
||||
JFactory::$application = $instance;
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public function __construct(Cli $input = null, \Joomla\Registry\Registry $config = null, \JEventDispatcher $dispatcher = null)
|
||||
{
|
||||
// Some servers only provide a CGI executable. While not ideal for running CLI applications we can make do.
|
||||
$this->detectAndWorkAroundCGIMode();
|
||||
|
||||
// Initialize custom options handling which is a bit more straightforward than Input\Cli.
|
||||
$this->initialiseCustomOptions();
|
||||
|
||||
parent::__construct($input, $config, $dispatcher);
|
||||
|
||||
/**
|
||||
* Allow the application to close.
|
||||
*
|
||||
* This is required to allow CliApplication to execute under CGI mode. The checks performed in the parent
|
||||
* constructor will call close() if the application does not run pure CLI mode. However, some hosts only provide
|
||||
* the PHP CGI binary for executing CLI scripts. While wrong it will work in most cases. By default close() will
|
||||
* do nothing, thereby allowing the parent constructor to call it without a problem. Finally, we set this flag
|
||||
* to true to allow doExecute() to call close() and actually close the application properly. Yeehaw!
|
||||
*/
|
||||
$this->allowedToClose = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to close the application.
|
||||
*
|
||||
* See the constructor for details on why it works the way it works.
|
||||
*
|
||||
* @param integer $code The exit code (optional; default is 0).
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @since 1.0
|
||||
*/
|
||||
public function close($code = 0)
|
||||
{
|
||||
// See the constructor for details
|
||||
if (!$this->allowedToClose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
exit($code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the current running application.
|
||||
*
|
||||
* @return string The name of the application.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return get_class($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the menu object.
|
||||
*
|
||||
* @param string $name The application name for the menu
|
||||
* @param array $options An array of options to initialise the menu with
|
||||
*
|
||||
* @return \Joomla\CMS\Menu\AbstractMenu|null A AbstractMenu object or null if not set.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getMenu($name = null, $options = [])
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
198
libraries/fof30/Cli/Joomla4.php
Normal file
198
libraries/fof30/Cli/Joomla4.php
Normal file
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
// Do not put the JEXEC or die check on this file
|
||||
|
||||
use FOF30\Cli\Traits\CGIModeAware;
|
||||
use FOF30\Cli\Traits\CustomOptionsAware;
|
||||
use FOF30\Cli\Traits\JoomlaConfigAware;
|
||||
use FOF30\Cli\Traits\MemStatsAware;
|
||||
use FOF30\Cli\Traits\TimeAgoAware;
|
||||
use Joomla\CMS\Application\CliApplication;
|
||||
use Joomla\CMS\Application\ExtensionNamespaceMapper;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Event\Dispatcher;
|
||||
use Joomla\Registry\Registry;
|
||||
use Joomla\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* Load the legacy Joomla! include files
|
||||
*
|
||||
* Despite Joomla complaining about it with an E_DEPRECATED notice, if you use bootstrap.php instead of
|
||||
* import.legacy.php you get an HTML error page (yes, under CLI!) which is kinda daft.
|
||||
*/
|
||||
if (function_exists('error_reporting'))
|
||||
{
|
||||
$oldErrorReporting = @error_reporting(E_ERROR | E_NOTICE | E_DEPRECATED);
|
||||
}
|
||||
|
||||
include_once JPATH_LIBRARIES . '/import.legacy.php';
|
||||
|
||||
if (function_exists('error_reporting'))
|
||||
{
|
||||
@error_reporting($oldErrorReporting);
|
||||
}
|
||||
|
||||
// Load the Framework (J4 beta 1 and later) or CMS import file (J4 a12 and lower)
|
||||
$cmsImportFilePath = JPATH_BASE . '/includes/framework.php';
|
||||
$cmsImportFilePathOld = JPATH_LIBRARIES . '/cms.php';
|
||||
|
||||
if (@file_exists($cmsImportFilePath))
|
||||
{
|
||||
@include_once $cmsImportFilePath;
|
||||
|
||||
// Boot the DI container
|
||||
$container = \Joomla\CMS\Factory::getContainer();
|
||||
|
||||
/*
|
||||
* Alias the session service keys to the CLI session service as that is the primary session backend for this application
|
||||
*
|
||||
* In addition to aliasing "common" service keys, we also create aliases for the PHP classes to ensure autowiring objects
|
||||
* is supported. This includes aliases for aliased class names, and the keys for aliased class names should be considered
|
||||
* deprecated to be removed when the class name alias is removed as well.
|
||||
*/
|
||||
$container->alias('session', 'session.cli')
|
||||
->alias('JSession', 'session.cli')
|
||||
->alias(\Joomla\CMS\Session\Session::class, 'session.cli')
|
||||
->alias(\Joomla\Session\Session::class, 'session.cli')
|
||||
->alias(\Joomla\Session\SessionInterface::class, 'session.cli');
|
||||
}
|
||||
elseif (@file_exists($cmsImportFilePathOld))
|
||||
{
|
||||
@include_once $cmsImportFilePathOld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for a Joomla! command line application. Adapted from JCli / JApplicationCli
|
||||
*/
|
||||
abstract class FOFCliApplicationJoomla4 extends CliApplication
|
||||
{
|
||||
use ExtensionNamespaceMapper;
|
||||
|
||||
use CGIModeAware, CustomOptionsAware, JoomlaConfigAware, MemStatsAware, TimeAgoAware;
|
||||
|
||||
private $allowedToClose = false;
|
||||
|
||||
public static function getInstance($name = null)
|
||||
{
|
||||
$instance = parent::getInstance($name);
|
||||
|
||||
Factory::$application = $instance;
|
||||
|
||||
/**
|
||||
* Load FOF.
|
||||
*
|
||||
* In Joomla 4 this must happen after we have set up the application in the factory because Factory::getLanguage
|
||||
* goes through the application object to retrieve the configuration.
|
||||
*/
|
||||
if (!defined('FOF30_INCLUDED') && !@include_once(JPATH_LIBRARIES . '/fof30/include.php'))
|
||||
{
|
||||
throw new RuntimeException('Cannot load FOF', 500);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public function __construct(\Joomla\Input\Input $input = null, Registry $config = null, \Joomla\CMS\Application\CLI\CliOutput $output = null, \Joomla\CMS\Application\CLI\CliInput $cliInput = null, \Joomla\Event\DispatcherInterface $dispatcher = null, \Joomla\DI\Container $container = null)
|
||||
{
|
||||
// Some servers only provide a CGI executable. While not ideal for running CLI applications we can make do.
|
||||
$this->detectAndWorkAroundCGIMode();
|
||||
|
||||
// We need to tell Joomla to register its default namespace conventions
|
||||
$this->createExtensionNamespaceMap();
|
||||
|
||||
// Initialize custom options handling which is a bit more straightforward than Input\Cli.
|
||||
$this->initialiseCustomOptions();
|
||||
|
||||
// Default configuration: Joomla Global Configuration
|
||||
if (empty($config))
|
||||
{
|
||||
$config = new Registry($this->fetchConfigurationData());
|
||||
}
|
||||
|
||||
if (empty($dispatcher))
|
||||
{
|
||||
$dispatcher = new Dispatcher();
|
||||
}
|
||||
|
||||
parent::__construct($input, $config, $output, $cliInput, $dispatcher, $container);
|
||||
|
||||
/**
|
||||
* Allow the application to close.
|
||||
*
|
||||
* This is required to allow CliApplication to execute under CGI mode. The checks performed in the parent
|
||||
* constructor will call close() if the application does not run pure CLI mode. However, some hosts only provide
|
||||
* the PHP CGI binary for executing CLI scripts. While wrong it will work in most cases. By default close() will
|
||||
* do nothing, thereby allowing the parent constructor to call it without a problem. Finally, we set this flag
|
||||
* to true to allow doExecute() to call close() and actually close the application properly. Yeehaw!
|
||||
*/
|
||||
$this->allowedToClose = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to close the application.
|
||||
*
|
||||
* See the constructor for details on why it works the way it works.
|
||||
*
|
||||
* @param integer $code The exit code (optional; default is 0).
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @since 1.0
|
||||
*/
|
||||
public function close($code = 0)
|
||||
{
|
||||
// See the constructor for details
|
||||
if (!$this->allowedToClose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
exit($code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the current running application.
|
||||
*
|
||||
* @return string The name of the application.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return get_class($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the menu object.
|
||||
*
|
||||
* @param string $name The application name for the menu
|
||||
* @param array $options An array of options to initialise the menu with
|
||||
*
|
||||
* @return \Joomla\CMS\Menu\AbstractMenu|null A AbstractMenu object or null if not set.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getMenu($name = null, $options = [])
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the application session object.
|
||||
*
|
||||
* @return SessionInterface The session object
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getSession()
|
||||
{
|
||||
return $this->getContainer()->get('session.cli');
|
||||
}
|
||||
|
||||
}
|
||||
69
libraries/fof30/Cli/Traits/CGIModeAware.php
Normal file
69
libraries/fof30/Cli/Traits/CGIModeAware.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Cli\Traits;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* CGI Mode detection and workaround
|
||||
*
|
||||
* Some hosts only give access to the PHP CGI binary, even for running CLI scripts. While problematic, it mostly works.
|
||||
* This trait detects PHP-CGI and manipulates $_GET in such a way that we populate the $argv and $argc global variables
|
||||
* in the same way that PHP-CLI would set them. This allows the CLI input object to work. Moreover, we unset the PHP
|
||||
* execution time limit, if possible, to prevent accidental timeouts.
|
||||
*
|
||||
* @package FOF30\Cli\Traits
|
||||
*/
|
||||
trait CGIModeAware
|
||||
{
|
||||
/**
|
||||
* Detect if we are running under CGI mode. In this case it populates the global $argv and $argc parameters off the
|
||||
* CGI input ($_GET superglobal).
|
||||
*/
|
||||
private function detectAndWorkAroundCGIMode()
|
||||
{
|
||||
// This code only executes when running under CGI. So let's detect it first.
|
||||
$cgiMode = (!defined('STDOUT') || !defined('STDIN') || !isset($_SERVER['argv']));
|
||||
|
||||
if (!$cgiMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// CGI mode has a time limit. Unset it to prevent timeouts.
|
||||
if (function_exists('set_time_limit'))
|
||||
{
|
||||
set_time_limit(0);
|
||||
}
|
||||
|
||||
// Convert $_GET into the appropriate $argv representation. This allows Input\Cli to work under PHP-CGI.
|
||||
$query = "";
|
||||
|
||||
if (!empty($_GET))
|
||||
{
|
||||
foreach ($_GET as $k => $v)
|
||||
{
|
||||
$query .= " $k";
|
||||
if ($v != "")
|
||||
{
|
||||
$query .= "=$v";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$query = ltrim($query);
|
||||
|
||||
global $argv, $argc;
|
||||
|
||||
$argv = explode(' ', $query);
|
||||
$argc = count($argv);
|
||||
|
||||
$_SERVER['argv'] = $argv;
|
||||
}
|
||||
|
||||
}
|
||||
196
libraries/fof30/Cli/Traits/CustomOptionsAware.php
Normal file
196
libraries/fof30/Cli/Traits/CustomOptionsAware.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Cli\Traits;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
|
||||
/**
|
||||
* Implements a simpler, more straightforward options parser than the Joomla CLI input object. It supports short options
|
||||
* when the Joomla CLI input object doesn't. Eventually this will go away and we can use something like Symfony Console
|
||||
* instead.
|
||||
*
|
||||
* @package FOF30\Cli\Traits
|
||||
*/
|
||||
trait CustomOptionsAware
|
||||
{
|
||||
/**
|
||||
* POSIX-style CLI options. Access them with through the getOption method.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $cliOptions = [];
|
||||
|
||||
/**
|
||||
* Filter object to use for custom options parsing.
|
||||
*
|
||||
* @var InputFilter
|
||||
*/
|
||||
protected $filter = null;
|
||||
|
||||
/**
|
||||
* Initializes the custom CLI options parsing
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function initialiseCustomOptions()
|
||||
{
|
||||
// Create a new InputFilter
|
||||
$this->filter = InputFilter::getInstance();
|
||||
|
||||
// Parse the POSIX options
|
||||
$this->parseOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses POSIX command line options and sets the self::$cliOptions associative array. Each array item contains
|
||||
* a single dimensional array of values. Arguments without a dash are silently ignored.
|
||||
*
|
||||
* This works much better than JInputCli since it allows you to use all valid POSIX ways of defining CLI parameters.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseOptions()
|
||||
{
|
||||
global $argc, $argv;
|
||||
|
||||
// Workaround for PHP-CGI
|
||||
if (!isset($argc) && !isset($argv))
|
||||
{
|
||||
$query = "";
|
||||
|
||||
if (!empty($_GET))
|
||||
{
|
||||
foreach ($_GET as $k => $v)
|
||||
{
|
||||
$query .= " $k";
|
||||
|
||||
if ($v != "")
|
||||
{
|
||||
$query .= "=$v";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$query = ltrim($query);
|
||||
$argv = explode(' ', $query);
|
||||
$argc = count($argv);
|
||||
}
|
||||
|
||||
$currentName = "";
|
||||
$options = [];
|
||||
|
||||
for ($i = 1; $i < $argc; $i++)
|
||||
{
|
||||
$argument = $argv[$i];
|
||||
|
||||
$value = $argument;
|
||||
|
||||
if (strpos($argument, "-") === 0)
|
||||
{
|
||||
$argument = ltrim($argument, '-');
|
||||
|
||||
$name = $argument;
|
||||
$value = null;
|
||||
|
||||
if (strstr($argument, '='))
|
||||
{
|
||||
[$name, $value] = explode('=', $argument, 2);
|
||||
}
|
||||
|
||||
$currentName = $name;
|
||||
|
||||
if (!isset($options[$currentName]) || ($options[$currentName] == null))
|
||||
{
|
||||
$options[$currentName] = [];
|
||||
}
|
||||
}
|
||||
|
||||
if ((!is_null($value)) && (!is_null($currentName)))
|
||||
{
|
||||
$key = null;
|
||||
|
||||
if (strstr($value, '='))
|
||||
{
|
||||
$parts = explode('=', $value, 2);
|
||||
$key = $parts[0];
|
||||
$value = $parts[1];
|
||||
}
|
||||
|
||||
$values = $options[$currentName];
|
||||
|
||||
if (is_null($values))
|
||||
{
|
||||
$values = [];
|
||||
}
|
||||
|
||||
if (is_null($key))
|
||||
{
|
||||
array_push($values, $value);
|
||||
}
|
||||
else
|
||||
{
|
||||
$values[$key] = $value;
|
||||
}
|
||||
|
||||
$options[$currentName] = $values;
|
||||
}
|
||||
}
|
||||
|
||||
self::$cliOptions = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of a command line option. This does NOT use JInputCLI. You MUST run parseOptions before.
|
||||
*
|
||||
* @param string $key The full name of the option, e.g. "foobar"
|
||||
* @param mixed $default The default value to return
|
||||
* @param string $type Joomla! filter type, e.g. cmd, int, bool and so on.
|
||||
*
|
||||
* @return mixed The value of the option
|
||||
*/
|
||||
protected function getOption($key, $default = null, $type = 'raw')
|
||||
{
|
||||
// If the key doesn't exist set it to the default value
|
||||
if (!array_key_exists($key, self::$cliOptions))
|
||||
{
|
||||
self::$cliOptions[$key] = is_array($default) ? $default : [$default];
|
||||
}
|
||||
|
||||
$type = strtolower($type);
|
||||
|
||||
if ($type == 'array')
|
||||
{
|
||||
return self::$cliOptions[$key];
|
||||
}
|
||||
|
||||
$value = null;
|
||||
|
||||
if (!empty(self::$cliOptions[$key]))
|
||||
{
|
||||
$value = self::$cliOptions[$key][0];
|
||||
}
|
||||
|
||||
return $this->filterVariable($value, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a variable using JInputFilter
|
||||
*
|
||||
* @param mixed $var The variable to filter
|
||||
* @param string $type The filter type, default 'cmd'
|
||||
*
|
||||
* @return mixed The filtered value
|
||||
*/
|
||||
protected function filterVariable($var, $type = 'cmd')
|
||||
{
|
||||
return $this->filter->clean($var, $type);
|
||||
}
|
||||
|
||||
}
|
||||
55
libraries/fof30/Cli/Traits/JoomlaConfigAware.php
Normal file
55
libraries/fof30/Cli/Traits/JoomlaConfigAware.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Cli\Traits;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Allows the CLI application to use the Joomla Global Configuration parameters as its own configuration.
|
||||
*
|
||||
* @package FOF30\Cli\Traits
|
||||
*/
|
||||
trait JoomlaConfigAware
|
||||
{
|
||||
/**
|
||||
* Method to load the application configuration, returning it as an object or array
|
||||
*
|
||||
* This can be overridden in subclasses if you don't want to fetch config from a PHP class file.
|
||||
*
|
||||
* @param string|null $file The filepath to the file containing the configuration class. Default: Joomla's
|
||||
* configuration.php
|
||||
* @param string $className The name of the PHP class holding the configuration. Default: JConfig
|
||||
*
|
||||
* @return mixed Either an array or object to be loaded into the configuration object.
|
||||
*/
|
||||
protected function fetchConfigurationData($file = null, $className = 'JConfig')
|
||||
{
|
||||
// Set the configuration file name.
|
||||
if (empty($file))
|
||||
{
|
||||
$file = JPATH_BASE . '/configuration.php';
|
||||
}
|
||||
|
||||
// Import the configuration file.
|
||||
if (!is_file($file))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
include_once $file;
|
||||
|
||||
// Instantiate the configuration object.
|
||||
if (!class_exists('JConfig'))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return new $className();
|
||||
}
|
||||
|
||||
}
|
||||
73
libraries/fof30/Cli/Traits/MemStatsAware.php
Normal file
73
libraries/fof30/Cli/Traits/MemStatsAware.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Cli\Traits;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Memory statistics
|
||||
*
|
||||
* This is an optional trait which allows the developer to print memory usage statistics and format byte sizes into
|
||||
* human-readable strings.
|
||||
*
|
||||
* @package FOF30\Cli\Traits
|
||||
*/
|
||||
trait MemStatsAware
|
||||
{
|
||||
/**
|
||||
* Formats a number of bytes in human readable format
|
||||
*
|
||||
* @param int $size The size in bytes to format, e.g. 8254862
|
||||
*
|
||||
* @return string The human-readable representation of the byte size, e.g. "7.87 Mb"
|
||||
*/
|
||||
protected function formatByteSize($size)
|
||||
{
|
||||
$unit = ['b', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
|
||||
return @round($size / 1024 ** ($i = floor(log($size, 1024))), 2) . ' ' . $unit[$i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current memory usage, formatted
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function memUsage()
|
||||
{
|
||||
if (function_exists('memory_get_usage'))
|
||||
{
|
||||
$size = memory_get_usage();
|
||||
|
||||
return $this->formatByteSize($size);
|
||||
}
|
||||
else
|
||||
{
|
||||
return "(unknown)";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the peak memory usage, formatted
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function peakMemUsage()
|
||||
{
|
||||
if (function_exists('memory_get_peak_usage'))
|
||||
{
|
||||
$size = memory_get_peak_usage();
|
||||
|
||||
return $this->formatByteSize($size);
|
||||
}
|
||||
else
|
||||
{
|
||||
return "(unknown)";
|
||||
}
|
||||
}
|
||||
}
|
||||
64
libraries/fof30/Cli/Traits/MessageAware.php
Normal file
64
libraries/fof30/Cli/Traits/MessageAware.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Cli\Traits;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Sometimes other extensions will try to enqueue messages to the application. Methods for those tasks only exists in
|
||||
* web applications, so we have to replicate their behavior in CLI environment or fatal errors will occur
|
||||
*
|
||||
* @package FOF30\Cli\Traits
|
||||
*/
|
||||
trait MessageAware
|
||||
{
|
||||
/** @var array Queue holding all messages */
|
||||
protected $messageQueue = [];
|
||||
|
||||
/**
|
||||
* @param $msg
|
||||
* @param $type
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function enqueueMessage($msg, $type)
|
||||
{
|
||||
// Don't add empty messages.
|
||||
if (trim($msg) === '')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$message = ['message' => $msg, 'type' => strtolower($type)];
|
||||
|
||||
if (!in_array($message, $this->messageQueue))
|
||||
{
|
||||
// Enqueue the message.
|
||||
$this->messageQueue[] = $message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loosely based on Joomla getMessageQueue
|
||||
*
|
||||
* @param bool $clear
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMessageQueue($clear = false)
|
||||
{
|
||||
$messageQueue = $this->messageQueue;
|
||||
|
||||
if ($clear)
|
||||
{
|
||||
$this->messageQueue = [];
|
||||
}
|
||||
|
||||
return $messageQueue;
|
||||
}
|
||||
}
|
||||
100
libraries/fof30/Cli/Traits/TimeAgoAware.php
Normal file
100
libraries/fof30/Cli/Traits/TimeAgoAware.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Cli\Traits;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Allows the developer to show the relative time difference between two timestamps.
|
||||
*
|
||||
* @package FOF30\Cli\Traits
|
||||
*/
|
||||
trait TimeAgoAware
|
||||
{
|
||||
/**
|
||||
* Returns the relative time difference between two timestamps in a human readable format
|
||||
*
|
||||
* @param int $referenceTimestamp Timestamp of the reference date/time
|
||||
* @param int|null $currentTimestamp Timestamp of the current date/time. Null for time().
|
||||
* @param string $timeUnit Time unit. One of s, m, h, d, or y.
|
||||
* @param bool $autoSuffix Add "ago" / "from now" suffix?
|
||||
*
|
||||
* @return string For example, "10 seconds ago"
|
||||
*/
|
||||
protected function timeAgo($referenceTimestamp = 0, $currentTimestamp = null, $timeUnit = '', $autoSuffix = true)
|
||||
{
|
||||
if (is_null($currentTimestamp))
|
||||
{
|
||||
$currentTimestamp = time();
|
||||
}
|
||||
|
||||
// Raw time difference
|
||||
$raw = $currentTimestamp - $referenceTimestamp;
|
||||
$clean = abs($raw);
|
||||
|
||||
$calcNum = [
|
||||
['s', 60],
|
||||
['m', 60 * 60],
|
||||
['h', 60 * 60 * 60],
|
||||
['d', 60 * 60 * 60 * 24],
|
||||
['y', 60 * 60 * 60 * 24 * 365],
|
||||
];
|
||||
|
||||
$calc = [
|
||||
's' => [1, 'second'],
|
||||
'm' => [60, 'minute'],
|
||||
'h' => [60 * 60, 'hour'],
|
||||
'd' => [60 * 60 * 24, 'day'],
|
||||
'y' => [60 * 60 * 24 * 365, 'year'],
|
||||
];
|
||||
|
||||
$effectiveTimeUnit = $timeUnit;
|
||||
|
||||
if ($timeUnit == '')
|
||||
{
|
||||
$effectiveTimeUnit = 's';
|
||||
|
||||
for ($i = 0; $i < count($calcNum); $i++)
|
||||
{
|
||||
if ($clean <= $calcNum[$i][1])
|
||||
{
|
||||
$effectiveTimeUnit = $calcNum[$i][0];
|
||||
$i = count($calcNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$timeDifference = floor($clean / $calc[$effectiveTimeUnit][0]);
|
||||
$textSuffix = '';
|
||||
|
||||
if ($autoSuffix == true && ($currentTimestamp == time()))
|
||||
{
|
||||
if ($raw < 0)
|
||||
{
|
||||
$textSuffix = ' from now';
|
||||
}
|
||||
else
|
||||
{
|
||||
$textSuffix = ' ago';
|
||||
}
|
||||
}
|
||||
|
||||
if ($referenceTimestamp != 0)
|
||||
{
|
||||
if ($timeDifference == 1)
|
||||
{
|
||||
return $timeDifference . ' ' . $calc[$effectiveTimeUnit][1] . ' ' . $textSuffix;
|
||||
}
|
||||
|
||||
return $timeDifference . ' ' . $calc[$effectiveTimeUnit][1] . 's ' . $textSuffix;
|
||||
}
|
||||
|
||||
return '(no reference timestamp was provided).';
|
||||
}
|
||||
|
||||
}
|
||||
62
libraries/fof30/Cli/wrong_php.php
Normal file
62
libraries/fof30/Cli/wrong_php.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
/** @var string $minphp */
|
||||
?>
|
||||
|
||||
================================================================================
|
||||
WARNING! Incompatible PHP version <?php echo PHP_VERSION ?> (required: <?php echo $minphp ?> or later)
|
||||
================================================================================
|
||||
|
||||
This script must be run using PHP version <?php echo $minphp ?> or later. Your server is
|
||||
currently using a much older version which would cause this script to crash. As
|
||||
a result we have aborted execution of the script. Please contact your host and
|
||||
ask them for the correct path to the PHP CLI binary for PHP <?php echo $minphp ?> or later, then
|
||||
edit your CRON job and replace your current path to PHP with the one your host
|
||||
gave you.
|
||||
|
||||
For your information, the current PHP version information is as follows.
|
||||
|
||||
PATH: <?php echo PHP_BINDIR ?>
|
||||
VERSION: <?php echo PHP_VERSION ?>
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
IMPORTANT!
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
PHP version numbers are NOT decimals! Trailing zeros do matter. For example,
|
||||
PHP 5.3.28 is twenty four versions newer (greater than) than PHP 5.3.4.
|
||||
Please consult https://www.akeeba.com/how-do-version-numbers-work.html
|
||||
|
||||
|
||||
Further clarifications:
|
||||
|
||||
1. There is no possible way that you are receiving this message in error. We
|
||||
are using the PHP_VERSION constant to detect the PHP version you are
|
||||
currently using. This is what PHP itself reports as its own version. It
|
||||
simply cannot lie.
|
||||
|
||||
2. Even though your *site* may be running in a higher PHP version that the one
|
||||
reported above, your CRON scripts will most likely not be running under it.
|
||||
This has to do with the fact that your site DOES NOT run under the command
|
||||
line and there are different executable files (binaries) for the web and
|
||||
command line versions of PHP.
|
||||
|
||||
3. Please note that we cannot provide support about this error as the solution
|
||||
depends only on your server setup. The only people who know how your server
|
||||
is set up are your host's technicians. Therefore we can only advise you to
|
||||
contact your host and request them the correct path to the PHP CLI binary.
|
||||
Let us stress out that only your host knows and can give this information
|
||||
to you.
|
||||
|
||||
4. The latest published versions of PHP can be found at http://www.php.net/
|
||||
Any older version is considered insecure and must not be used on a
|
||||
production site. If your server uses a much older version of PHP than those
|
||||
published in the URL above please notify your host that their servers are
|
||||
insecure and in need of an update.
|
||||
|
||||
This script will now terminate. Goodbye.
|
||||
|
||||
233
libraries/fof30/Configuration/Configuration.php
Normal file
233
libraries/fof30/Configuration/Configuration.php
Normal file
@ -0,0 +1,233 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Configuration;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Container\Container;
|
||||
|
||||
/**
|
||||
* Reads and parses the fof.xml file in the back-end of a FOF-powered component,
|
||||
* provisioning the data to the rest of the FOF framework
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Configuration
|
||||
{
|
||||
/**
|
||||
* Cache of FOF components' configuration variables
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $configurations = [];
|
||||
/**
|
||||
* The component's container
|
||||
*
|
||||
* @var Container
|
||||
*/
|
||||
protected $container = null;
|
||||
|
||||
function __construct(Container $c)
|
||||
{
|
||||
$this->container = $c;
|
||||
|
||||
$this->parseComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of a variable. Variables use a dot notation, e.g.
|
||||
* view.config.whatever where the first part is the domain, the rest of the
|
||||
* parts specify the path to the variable.
|
||||
*
|
||||
* @param string $variable The variable name
|
||||
* @param mixed $default The default value, or null if not specified
|
||||
*
|
||||
* @return mixed The value of the variable
|
||||
*/
|
||||
public function get($variable, $default = null)
|
||||
{
|
||||
static $domains = null;
|
||||
|
||||
if (is_null($domains))
|
||||
{
|
||||
$domains = $this->getDomains();
|
||||
}
|
||||
|
||||
[$domain, $var] = explode('.', $variable, 2);
|
||||
|
||||
if (!in_array(ucfirst($domain), $domains))
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
|
||||
$class = '\\FOF30\\Configuration\\Domain\\' . ucfirst($domain);
|
||||
/** @var \FOF30\Configuration\Domain\DomainInterface $o */
|
||||
$o = new $class;
|
||||
|
||||
return $o->get(self::$configurations[$this->container->componentName], $var, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the configuration of the specified component
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseComponent()
|
||||
{
|
||||
if ($this->container->platform->isCli())
|
||||
{
|
||||
$order = ['cli', 'backend'];
|
||||
}
|
||||
elseif ($this->container->platform->isBackend())
|
||||
{
|
||||
$order = ['backend'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$order = ['frontend'];
|
||||
}
|
||||
|
||||
$order[] = 'common';
|
||||
|
||||
$order = array_reverse($order);
|
||||
self::$configurations[$this->container->componentName] = [];
|
||||
|
||||
foreach ([false, true] as $userConfig)
|
||||
{
|
||||
foreach ($order as $area)
|
||||
{
|
||||
$config = $this->parseComponentArea($area, $userConfig);
|
||||
self::$configurations[$this->container->componentName] = array_replace_recursive(self::$configurations[$this->container->componentName], $config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the configuration options of a specific component area
|
||||
*
|
||||
* @param string $area Which area to parse (frontend, backend, cli)
|
||||
* @param bool $userConfig When true the user configuration (fof.user.xml) file will be read
|
||||
*
|
||||
* @return array A hash array with the configuration data
|
||||
*/
|
||||
protected function parseComponentArea($area, $userConfig = false)
|
||||
{
|
||||
$component = $this->container->componentName;
|
||||
|
||||
// Initialise the return array
|
||||
$ret = [];
|
||||
|
||||
// Get the folders of the component
|
||||
$componentPaths = $this->container->platform->getComponentBaseDirs($component);
|
||||
$filesystem = $this->container->filesystem;
|
||||
$path = $componentPaths['admin'];
|
||||
|
||||
if (isset($this->container['backEndPath']))
|
||||
{
|
||||
$path = $this->container['backEndPath'];
|
||||
}
|
||||
|
||||
// This line unfortunately doesn't work with Unit Tests because JPath depends on the JPATH_SITE constant :(
|
||||
// $path = $filesystem->pathCheck($path);
|
||||
|
||||
// Check that the path exists
|
||||
if (!$filesystem->folderExists($path))
|
||||
{
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// Read the filename if it exists
|
||||
$filename = $path . '/fof.xml';
|
||||
|
||||
if ($userConfig)
|
||||
{
|
||||
$filename = $path . '/fof.user.xml';
|
||||
}
|
||||
|
||||
if (!$filesystem->fileExists($filename) && !file_exists($filename))
|
||||
{
|
||||
return $ret;
|
||||
}
|
||||
|
||||
$data = file_get_contents($filename);
|
||||
|
||||
// Load the XML data in a SimpleXMLElement object
|
||||
$xml = simplexml_load_string($data);
|
||||
|
||||
if (!($xml instanceof \SimpleXMLElement))
|
||||
{
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// Get this area's data
|
||||
$areaData = $xml->xpath('//' . $area);
|
||||
|
||||
if (empty($areaData))
|
||||
{
|
||||
return $ret;
|
||||
}
|
||||
|
||||
$xml = array_shift($areaData);
|
||||
|
||||
// Parse individual configuration domains
|
||||
$domains = $this->getDomains();
|
||||
|
||||
foreach ($domains as $dom)
|
||||
{
|
||||
$class = '\\FOF30\\Configuration\\Domain\\' . ucfirst($dom);
|
||||
|
||||
if (class_exists($class, true))
|
||||
{
|
||||
/** @var \FOF30\Configuration\Domain\DomainInterface $o */
|
||||
$o = new $class;
|
||||
$o->parseDomain($xml, $ret);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, return the result
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of the available configuration domain adapters
|
||||
*
|
||||
* @return array A list of the available domains
|
||||
*/
|
||||
protected function getDomains()
|
||||
{
|
||||
static $domains = [];
|
||||
|
||||
if (empty($domains))
|
||||
{
|
||||
$filesystem = $this->container->filesystem;
|
||||
|
||||
$files = $filesystem->folderFiles(__DIR__ . '/Domain', '.php');
|
||||
|
||||
if (!empty($files))
|
||||
{
|
||||
foreach ($files as $file)
|
||||
{
|
||||
$domain = basename($file, '.php');
|
||||
|
||||
if ($domain == 'DomainInterface')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$domain = preg_replace('/[^A-Za-z0-9]/', '', $domain);
|
||||
$domains[] = $domain;
|
||||
}
|
||||
|
||||
$domains = array_unique($domains);
|
||||
}
|
||||
}
|
||||
|
||||
return $domains;
|
||||
}
|
||||
|
||||
}
|
||||
81
libraries/fof30/Configuration/Domain/Authentication.php
Normal file
81
libraries/fof30/Configuration/Domain/Authentication.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Configuration\Domain;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* Configuration parser for the authentication-specific settings
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Authentication implements DomainInterface
|
||||
{
|
||||
/**
|
||||
* Parse the XML data, adding them to the $ret array
|
||||
*
|
||||
* @param SimpleXMLElement $xml The XML data of the component's configuration area
|
||||
* @param array &$ret The parsed data, in the form of a hash array
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parseDomain(SimpleXMLElement $xml, array &$ret)
|
||||
{
|
||||
// Initialise
|
||||
$ret['authentication'] = [];
|
||||
|
||||
// Parse the dispatcher configuration
|
||||
$authenticationData = $xml->authentication;
|
||||
|
||||
// Sanity check
|
||||
|
||||
if (empty($authenticationData))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$options = $xml->xpath('authentication/option');
|
||||
|
||||
if (!empty($options))
|
||||
{
|
||||
foreach ($options as $option)
|
||||
{
|
||||
$key = (string) $option['name'];
|
||||
$ret['authentication'][$key] = (string) $option;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a configuration variable
|
||||
*
|
||||
* @param string &$configuration Configuration variables (hashed array)
|
||||
* @param string $var The variable we want to fetch
|
||||
* @param mixed $default Default value
|
||||
*
|
||||
* @return mixed The variable's value
|
||||
*/
|
||||
public function get(&$configuration, $var, $default)
|
||||
{
|
||||
if ($var == '*')
|
||||
{
|
||||
return $configuration['authentication'];
|
||||
}
|
||||
|
||||
if (isset($configuration['authentication'][$var]))
|
||||
{
|
||||
return $configuration['authentication'][$var];
|
||||
}
|
||||
else
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
libraries/fof30/Configuration/Domain/Container.php
Normal file
81
libraries/fof30/Configuration/Domain/Container.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Configuration\Domain;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* Configuration parser for the Container-specific settings
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Container implements DomainInterface
|
||||
{
|
||||
/**
|
||||
* Parse the XML data, adding them to the $ret array
|
||||
*
|
||||
* @param SimpleXMLElement $xml The XML data of the component's configuration area
|
||||
* @param array &$ret The parsed data, in the form of a hash array
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parseDomain(SimpleXMLElement $xml, array &$ret)
|
||||
{
|
||||
// Initialise
|
||||
$ret['container'] = [];
|
||||
|
||||
// Parse the dispatcher configuration
|
||||
$containerData = $xml->container;
|
||||
|
||||
// Sanity check
|
||||
|
||||
if (empty($containerData))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$options = $xml->xpath('container/option');
|
||||
|
||||
if (!empty($options))
|
||||
{
|
||||
foreach ($options as $option)
|
||||
{
|
||||
$key = (string) $option['name'];
|
||||
$ret['container'][$key] = (string) $option;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a configuration variable
|
||||
*
|
||||
* @param string &$configuration Configuration variables (hashed array)
|
||||
* @param string $var The variable we want to fetch
|
||||
* @param mixed $default Default value
|
||||
*
|
||||
* @return mixed The variable's value
|
||||
*/
|
||||
public function get(&$configuration, $var, $default)
|
||||
{
|
||||
if ($var == '*')
|
||||
{
|
||||
return $configuration['container'];
|
||||
}
|
||||
|
||||
if (isset($configuration['container'][$var]))
|
||||
{
|
||||
return $configuration['container'][$var];
|
||||
}
|
||||
else
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
libraries/fof30/Configuration/Domain/Dispatcher.php
Normal file
81
libraries/fof30/Configuration/Domain/Dispatcher.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Configuration\Domain;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* Configuration parser for the dispatcher-specific settings
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Dispatcher implements DomainInterface
|
||||
{
|
||||
/**
|
||||
* Parse the XML data, adding them to the $ret array
|
||||
*
|
||||
* @param SimpleXMLElement $xml The XML data of the component's configuration area
|
||||
* @param array &$ret The parsed data, in the form of a hash array
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parseDomain(SimpleXMLElement $xml, array &$ret)
|
||||
{
|
||||
// Initialise
|
||||
$ret['dispatcher'] = [];
|
||||
|
||||
// Parse the dispatcher configuration
|
||||
$dispatcherData = $xml->dispatcher;
|
||||
|
||||
// Sanity check
|
||||
|
||||
if (empty($dispatcherData))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$options = $xml->xpath('dispatcher/option');
|
||||
|
||||
if (!empty($options))
|
||||
{
|
||||
foreach ($options as $option)
|
||||
{
|
||||
$key = (string) $option['name'];
|
||||
$ret['dispatcher'][$key] = (string) $option;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a configuration variable
|
||||
*
|
||||
* @param string &$configuration Configuration variables (hashed array)
|
||||
* @param string $var The variable we want to fetch
|
||||
* @param mixed $default Default value
|
||||
*
|
||||
* @return mixed The variable's value
|
||||
*/
|
||||
public function get(&$configuration, $var, $default)
|
||||
{
|
||||
if ($var == '*')
|
||||
{
|
||||
return $configuration['dispatcher'];
|
||||
}
|
||||
|
||||
if (isset($configuration['dispatcher'][$var]))
|
||||
{
|
||||
return $configuration['dispatcher'][$var];
|
||||
}
|
||||
else
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
libraries/fof30/Configuration/Domain/DomainInterface.php
Normal file
43
libraries/fof30/Configuration/Domain/DomainInterface.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Configuration\Domain;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* The Interface of a FOF configuration domain class. The methods are used to parse and
|
||||
* provision sensible information to consumers. The Configuration class acts as an
|
||||
* adapter to the domain classes.
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
interface DomainInterface
|
||||
{
|
||||
/**
|
||||
* Parse the XML data, adding them to the $ret array
|
||||
*
|
||||
* @param SimpleXMLElement $xml The XML data of the component's configuration area
|
||||
* @param array &$ret The parsed data, in the form of a hash array
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parseDomain(SimpleXMLElement $xml, array &$ret);
|
||||
|
||||
/**
|
||||
* Return a configuration variable
|
||||
*
|
||||
* @param string &$configuration Configuration variables (hashed array)
|
||||
* @param string $var The variable we want to fetch
|
||||
* @param mixed $default Default value
|
||||
*
|
||||
* @return mixed The variable's value
|
||||
*/
|
||||
public function get(&$configuration, $var, $default);
|
||||
}
|
||||
369
libraries/fof30/Configuration/Domain/Models.php
Normal file
369
libraries/fof30/Configuration/Domain/Models.php
Normal file
@ -0,0 +1,369 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Configuration\Domain;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* Configuration parser for the models-specific settings
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Models implements DomainInterface
|
||||
{
|
||||
/**
|
||||
* Parse the XML data, adding them to the $ret array
|
||||
*
|
||||
* @param SimpleXMLElement $xml The XML data of the component's configuration area
|
||||
* @param array &$ret The parsed data, in the form of a hash array
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parseDomain(SimpleXMLElement $xml, array &$ret)
|
||||
{
|
||||
// Initialise
|
||||
$ret['models'] = [];
|
||||
|
||||
// Parse model configuration
|
||||
$modelsData = $xml->xpath('model');
|
||||
|
||||
// Sanity check
|
||||
if (empty($modelsData))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($modelsData as $aModel)
|
||||
{
|
||||
$key = (string) $aModel['name'];
|
||||
|
||||
$ret['models'][$key]['behaviors'] = [];
|
||||
$ret['models'][$key]['behaviorsMerge'] = false;
|
||||
$ret['models'][$key]['tablealias'] = $aModel->xpath('tablealias');
|
||||
$ret['models'][$key]['fields'] = [];
|
||||
$ret['models'][$key]['relations'] = [];
|
||||
$ret['models'][$key]['config'] = [];
|
||||
|
||||
|
||||
// Parse configuration
|
||||
$optionData = $aModel->xpath('config/option');
|
||||
|
||||
if (!empty($optionData))
|
||||
{
|
||||
foreach ($optionData as $option)
|
||||
{
|
||||
$k = (string) $option['name'];
|
||||
$ret['models'][$key]['config'][$k] = (string) $option;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse field aliases
|
||||
$fieldData = $aModel->xpath('field');
|
||||
|
||||
if (!empty($fieldData))
|
||||
{
|
||||
foreach ($fieldData as $field)
|
||||
{
|
||||
$k = (string) $field['name'];
|
||||
$ret['models'][$key]['fields'][$k] = (string) $field;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse behaviours
|
||||
$behaviorsData = (string) $aModel->behaviors;
|
||||
$behaviorsMerge = (string) $aModel->behaviors['merge'];
|
||||
|
||||
if (!empty($behaviorsMerge))
|
||||
{
|
||||
$behaviorsMerge = trim($behaviorsMerge);
|
||||
$behaviorsMerge = strtoupper($behaviorsMerge);
|
||||
|
||||
if (in_array($behaviorsMerge, ['1', 'YES', 'ON', 'TRUE']))
|
||||
{
|
||||
$ret['models'][$key]['behaviorsMerge'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($behaviorsData))
|
||||
{
|
||||
$behaviorsData = explode(',', $behaviorsData);
|
||||
|
||||
foreach ($behaviorsData as $behavior)
|
||||
{
|
||||
$behavior = trim($behavior);
|
||||
|
||||
if (empty($behavior))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$ret['models'][$key]['behaviors'][] = $behavior;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse relations
|
||||
$relationsData = $aModel->xpath('relation');
|
||||
|
||||
if (!empty($relationsData))
|
||||
{
|
||||
foreach ($relationsData as $relationData)
|
||||
{
|
||||
$type = (string) $relationData['type'];
|
||||
$itemName = (string) $relationData['name'];
|
||||
|
||||
if (empty($type) || empty($itemName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$modelClass = (string) $relationData['foreignModelClass'];
|
||||
$localKey = (string) $relationData['localKey'];
|
||||
$foreignKey = (string) $relationData['foreignKey'];
|
||||
$pivotTable = (string) $relationData['pivotTable'];
|
||||
$ourPivotKey = (string) $relationData['pivotLocalKey'];
|
||||
$theirPivotKey = (string) $relationData['pivotForeignKey'];
|
||||
|
||||
$relation = [
|
||||
'type' => $type,
|
||||
'itemName' => $itemName,
|
||||
'foreignModelClass' => empty($modelClass) ? null : $modelClass,
|
||||
'localKey' => empty($localKey) ? null : $localKey,
|
||||
'foreignKey' => empty($foreignKey) ? null : $foreignKey,
|
||||
];
|
||||
|
||||
if (!empty($ourPivotKey) || !empty($theirPivotKey) || !empty($pivotTable))
|
||||
{
|
||||
$relation['pivotLocalKey'] = empty($ourPivotKey) ? null : $ourPivotKey;
|
||||
$relation['pivotForeignKey'] = empty($theirPivotKey) ? null : $theirPivotKey;
|
||||
$relation['pivotTable'] = empty($pivotTable) ? null : $pivotTable;
|
||||
}
|
||||
|
||||
$ret['models'][$key]['relations'][] = $relation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a configuration variable
|
||||
*
|
||||
* @param string &$configuration Configuration variables (hashed array)
|
||||
* @param string $var The variable we want to fetch
|
||||
* @param mixed $default Default value
|
||||
*
|
||||
* @return mixed The variable's value
|
||||
*/
|
||||
public function get(&$configuration, $var, $default)
|
||||
{
|
||||
$parts = explode('.', $var);
|
||||
|
||||
$view = $parts[0];
|
||||
$method = 'get' . ucfirst($parts[1]);
|
||||
|
||||
if (!method_exists($this, $method))
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
|
||||
array_shift($parts);
|
||||
array_shift($parts);
|
||||
|
||||
$ret = $this->$method($view, $configuration, $parts, $default);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to return the magic field mapping
|
||||
*
|
||||
* @param string $model The model for which we will be fetching a field map
|
||||
* @param array &$configuration The configuration parameters hash array
|
||||
* @param array $params Extra options
|
||||
* @param string $default Default magic field mapping; empty if not defined
|
||||
*
|
||||
* @return array Field map
|
||||
*/
|
||||
protected function getField($model, &$configuration, $params, $default = '')
|
||||
{
|
||||
$fieldmap = [];
|
||||
|
||||
if (isset($configuration['models']['*']) && isset($configuration['models']['*']['fields']))
|
||||
{
|
||||
$fieldmap = $configuration['models']['*']['fields'];
|
||||
}
|
||||
|
||||
if (isset($configuration['models'][$model]) && isset($configuration['models'][$model]['fields']))
|
||||
{
|
||||
$fieldmap = array_merge($fieldmap, $configuration['models'][$model]['fields']);
|
||||
}
|
||||
|
||||
$map = $default;
|
||||
|
||||
if (empty($params[0]) || ($params[0] == '*'))
|
||||
{
|
||||
$map = $fieldmap;
|
||||
}
|
||||
elseif (isset($fieldmap[$params[0]]))
|
||||
{
|
||||
$map = $fieldmap[$params[0]];
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to get model alias
|
||||
*
|
||||
* @param string $model The model for which we will be fetching table alias
|
||||
* @param array &$configuration The configuration parameters hash array
|
||||
* @param array $params Ignored
|
||||
* @param string $default Default table alias
|
||||
*
|
||||
* @return string Table alias
|
||||
*/
|
||||
protected function getTablealias($model, &$configuration, $params, $default = '')
|
||||
{
|
||||
$tableMap = [];
|
||||
|
||||
if (isset($configuration['models']['*']['tablealias']))
|
||||
{
|
||||
$tableMap = $configuration['models']['*']['tablealias'];
|
||||
}
|
||||
|
||||
if (isset($configuration['models'][$model]['tablealias']))
|
||||
{
|
||||
$tableMap = array_merge($tableMap, $configuration['models'][$model]['tablealias']);
|
||||
}
|
||||
|
||||
if (empty($tableMap))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $tableMap[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to get model behaviours
|
||||
*
|
||||
* @param string $model The model for which we will be fetching behaviours
|
||||
* @param array &$configuration The configuration parameters hash array
|
||||
* @param array $params Unused
|
||||
* @param string $default Default behaviour
|
||||
*
|
||||
* @return string Model behaviours
|
||||
*/
|
||||
protected function getBehaviors($model, &$configuration, $params, $default = '')
|
||||
{
|
||||
$behaviors = $default;
|
||||
|
||||
if (isset($configuration['models']['*'])
|
||||
&& isset($configuration['models']['*']['behaviors'])
|
||||
)
|
||||
{
|
||||
$behaviors = $configuration['models']['*']['behaviors'];
|
||||
}
|
||||
|
||||
if (isset($configuration['models'][$model])
|
||||
&& isset($configuration['models'][$model]['behaviors'])
|
||||
)
|
||||
{
|
||||
$merge = false;
|
||||
|
||||
if (isset($configuration['models'][$model])
|
||||
&& isset($configuration['models'][$model]['behaviorsMerge'])
|
||||
)
|
||||
{
|
||||
$merge = (bool) $configuration['models'][$model]['behaviorsMerge'];
|
||||
}
|
||||
|
||||
if ($merge)
|
||||
{
|
||||
$behaviors = array_merge($behaviors, $configuration['models'][$model]['behaviors']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$behaviors = $configuration['models'][$model]['behaviors'];
|
||||
}
|
||||
}
|
||||
|
||||
return $behaviors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to get model relations
|
||||
*
|
||||
* @param string $model The model for which we will be fetching relations
|
||||
* @param array &$configuration The configuration parameters hash array
|
||||
* @param array $params Unused
|
||||
* @param string $default Default relations
|
||||
*
|
||||
* @return array Model relations
|
||||
*/
|
||||
protected function getRelations($model, &$configuration, $params, $default = '')
|
||||
{
|
||||
$relations = $default;
|
||||
|
||||
if (isset($configuration['models']['*'])
|
||||
&& isset($configuration['models']['*']['relations'])
|
||||
)
|
||||
{
|
||||
$relations = $configuration['models']['*']['relations'];
|
||||
}
|
||||
|
||||
if (isset($configuration['models'][$model])
|
||||
&& isset($configuration['models'][$model]['relations'])
|
||||
)
|
||||
{
|
||||
$relations = $configuration['models'][$model]['relations'];
|
||||
}
|
||||
|
||||
return $relations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to return the a configuration option for the Model.
|
||||
*
|
||||
* @param string $model The view for which we will be fetching a task map
|
||||
* @param array &$configuration The configuration parameters hash array
|
||||
* @param array $params Extra options; key 0 defines the option variable we want to fetch
|
||||
* @param mixed $default Default option; null if not defined
|
||||
*
|
||||
* @return string The setting for the requested option
|
||||
*/
|
||||
protected function getConfig($model, &$configuration, $params, $default = null)
|
||||
{
|
||||
$ret = $default;
|
||||
|
||||
$config = [];
|
||||
|
||||
if (isset($configuration['models']['*']['config']))
|
||||
{
|
||||
$config = $configuration['models']['*']['config'];
|
||||
}
|
||||
|
||||
if (isset($configuration['models'][$model]['config']))
|
||||
{
|
||||
$config = array_merge($config, $configuration['models'][$model]['config']);
|
||||
}
|
||||
|
||||
if (empty($params) || empty($params[0]))
|
||||
{
|
||||
return $config;
|
||||
}
|
||||
|
||||
if (isset($config[$params[0]]))
|
||||
{
|
||||
$ret = $config[$params[0]];
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
317
libraries/fof30/Configuration/Domain/Views.php
Normal file
317
libraries/fof30/Configuration/Domain/Views.php
Normal file
@ -0,0 +1,317 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Configuration\Domain;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* Configuration parser for the view-specific settings
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Views implements DomainInterface
|
||||
{
|
||||
/**
|
||||
* Parse the XML data, adding them to the $ret array
|
||||
*
|
||||
* @param SimpleXMLElement $xml The XML data of the component's configuration area
|
||||
* @param array &$ret The parsed data, in the form of a hash array
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function parseDomain(SimpleXMLElement $xml, array &$ret)
|
||||
{
|
||||
// Initialise
|
||||
$ret['views'] = [];
|
||||
|
||||
// Parse view configuration
|
||||
$viewData = $xml->xpath('view');
|
||||
|
||||
// Sanity check
|
||||
|
||||
if (empty($viewData))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($viewData as $aView)
|
||||
{
|
||||
$key = (string) $aView['name'];
|
||||
|
||||
// Parse ACL options
|
||||
$ret['views'][$key]['acl'] = [];
|
||||
$aclData = $aView->xpath('acl/task');
|
||||
|
||||
if (!empty($aclData))
|
||||
{
|
||||
foreach ($aclData as $acl)
|
||||
{
|
||||
$k = (string) $acl['name'];
|
||||
$ret['views'][$key]['acl'][$k] = (string) $acl;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse taskmap
|
||||
$ret['views'][$key]['taskmap'] = [];
|
||||
$taskmapData = $aView->xpath('taskmap/task');
|
||||
|
||||
if (!empty($taskmapData))
|
||||
{
|
||||
foreach ($taskmapData as $map)
|
||||
{
|
||||
$k = (string) $map['name'];
|
||||
$ret['views'][$key]['taskmap'][$k] = (string) $map;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse controller configuration
|
||||
$ret['views'][$key]['config'] = [];
|
||||
$optionData = $aView->xpath('config/option');
|
||||
|
||||
if (!empty($optionData))
|
||||
{
|
||||
foreach ($optionData as $option)
|
||||
{
|
||||
$k = (string) $option['name'];
|
||||
$ret['views'][$key]['config'][$k] = (string) $option;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the toolbar
|
||||
$ret['views'][$key]['toolbar'] = [];
|
||||
$toolBars = $aView->xpath('toolbar');
|
||||
|
||||
if (!empty($toolBars))
|
||||
{
|
||||
foreach ($toolBars as $toolBar)
|
||||
{
|
||||
$taskName = isset($toolBar['task']) ? (string) $toolBar['task'] : '*';
|
||||
|
||||
// If a toolbar title is specified, create a title element.
|
||||
if (isset($toolBar['title']))
|
||||
{
|
||||
$ret['views'][$key]['toolbar'][$taskName]['title'] = [
|
||||
'value' => (string) $toolBar['title'],
|
||||
];
|
||||
}
|
||||
|
||||
// Parse the toolbar buttons data
|
||||
$toolbarData = $toolBar->xpath('button');
|
||||
|
||||
if (!empty($toolbarData))
|
||||
{
|
||||
foreach ($toolbarData as $button)
|
||||
{
|
||||
$k = (string) $button['type'];
|
||||
$ret['views'][$key]['toolbar'][$taskName][$k] = current($button->attributes());
|
||||
$ret['views'][$key]['toolbar'][$taskName][$k]['value'] = (string) $button;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a configuration variable
|
||||
*
|
||||
* @param string &$configuration Configuration variables (hashed array)
|
||||
* @param string $var The variable we want to fetch
|
||||
* @param mixed $default Default value
|
||||
*
|
||||
* @return mixed The variable's value
|
||||
*/
|
||||
public function get(&$configuration, $var, $default)
|
||||
{
|
||||
$parts = explode('.', $var);
|
||||
|
||||
$view = $parts[0];
|
||||
$method = 'get' . ucfirst($parts[1]);
|
||||
|
||||
if (!method_exists($this, $method))
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
|
||||
array_shift($parts);
|
||||
array_shift($parts);
|
||||
|
||||
$ret = $this->$method($view, $configuration, $parts, $default);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to return the task map for a view
|
||||
*
|
||||
* @param string $view The view for which we will be fetching a task map
|
||||
* @param array &$configuration The configuration parameters hash array
|
||||
* @param array $params Extra options (not used)
|
||||
* @param array $default ßDefault task map; empty array if not provided
|
||||
*
|
||||
* @return array The task map as a hash array in the format task => method
|
||||
*/
|
||||
protected function getTaskmap($view, &$configuration, $params, $default = [])
|
||||
{
|
||||
$taskmap = [];
|
||||
|
||||
if (isset($configuration['views']['*']) && isset($configuration['views']['*']['taskmap']))
|
||||
{
|
||||
$taskmap = $configuration['views']['*']['taskmap'];
|
||||
}
|
||||
|
||||
if (isset($configuration['views'][$view]) && isset($configuration['views'][$view]['taskmap']))
|
||||
{
|
||||
$taskmap = array_merge($taskmap, $configuration['views'][$view]['taskmap']);
|
||||
}
|
||||
|
||||
if (empty($taskmap))
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $taskmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to return the ACL mapping (privilege required to access
|
||||
* a specific task) for the given view's tasks
|
||||
*
|
||||
* @param string $view The view for which we will be fetching a task map
|
||||
* @param array &$configuration The configuration parameters hash array
|
||||
* @param array $params Extra options; key 0 defines the task we want to fetch
|
||||
* @param string $default Default ACL option; empty (no ACL check) if not defined
|
||||
*
|
||||
* @return string The privilege required to access this view
|
||||
*/
|
||||
protected function getAcl($view, &$configuration, $params, $default = '')
|
||||
{
|
||||
$aclmap = [];
|
||||
|
||||
if (isset($configuration['views']['*']) && isset($configuration['views']['*']['acl']))
|
||||
{
|
||||
$aclmap = $configuration['views']['*']['acl'];
|
||||
}
|
||||
|
||||
if (isset($configuration['views'][$view]) && isset($configuration['views'][$view]['acl']))
|
||||
{
|
||||
$aclmap = array_merge($aclmap, $configuration['views'][$view]['acl']);
|
||||
}
|
||||
|
||||
$acl = $default;
|
||||
|
||||
if (empty($params) || empty($params[0]))
|
||||
{
|
||||
return $aclmap;
|
||||
}
|
||||
|
||||
if (isset($aclmap['*']))
|
||||
{
|
||||
$acl = $aclmap['*'];
|
||||
}
|
||||
|
||||
if (isset($aclmap[$params[0]]))
|
||||
{
|
||||
$acl = $aclmap[$params[0]];
|
||||
}
|
||||
|
||||
return $acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to return the a configuration option for the view. These
|
||||
* are equivalent to $config array options passed to the Controller
|
||||
*
|
||||
* @param string $view The view for which we will be fetching a task map
|
||||
* @param array &$configuration The configuration parameters hash array
|
||||
* @param array $params Extra options; key 0 defines the option variable we want to fetch
|
||||
* @param mixed $default Default option; null if not defined
|
||||
*
|
||||
* @return string The setting for the requested option
|
||||
*/
|
||||
protected function getConfig($view, &$configuration, $params, $default = null)
|
||||
{
|
||||
$ret = $default;
|
||||
|
||||
$config = [];
|
||||
|
||||
if (isset($configuration['views']['*']['config']))
|
||||
{
|
||||
$config = $configuration['views']['*']['config'];
|
||||
}
|
||||
|
||||
if (isset($configuration['views'][$view]['config']))
|
||||
{
|
||||
$config = array_merge($config, $configuration['views'][$view]['config']);
|
||||
}
|
||||
|
||||
if (empty($params) || empty($params[0]))
|
||||
{
|
||||
return $config;
|
||||
}
|
||||
|
||||
if (isset($config[$params[0]]))
|
||||
{
|
||||
$ret = $config[$params[0]];
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to return the toolbar infos.
|
||||
*
|
||||
* @param string $view The view for which we will be fetching buttons
|
||||
* @param array &$configuration The configuration parameters hash array
|
||||
* @param array $params Extra options
|
||||
* @param string $default Default option
|
||||
*
|
||||
* @return string The toolbar data for this view
|
||||
*/
|
||||
protected function getToolbar($view, &$configuration, $params, $default = '')
|
||||
{
|
||||
$toolbar = [];
|
||||
|
||||
if (isset($configuration['views']['*'])
|
||||
&& isset($configuration['views']['*']['toolbar'])
|
||||
&& isset($configuration['views']['*']['toolbar']['*']))
|
||||
{
|
||||
$toolbar = $configuration['views']['*']['toolbar']['*'];
|
||||
}
|
||||
|
||||
if (isset($configuration['views']['*'])
|
||||
&& isset($configuration['views']['*']['toolbar'])
|
||||
&& isset($configuration['views']['*']['toolbar'][$params[0]]))
|
||||
{
|
||||
$toolbar = array_merge($toolbar, $configuration['views']['*']['toolbar'][$params[0]]);
|
||||
}
|
||||
|
||||
if (isset($configuration['views'][$view])
|
||||
&& isset($configuration['views'][$view]['toolbar'])
|
||||
&& isset($configuration['views'][$view]['toolbar']['*']))
|
||||
{
|
||||
$toolbar = array_merge($toolbar, $configuration['views'][$view]['toolbar']['*']);
|
||||
}
|
||||
|
||||
if (isset($configuration['views'][$view])
|
||||
&& isset($configuration['views'][$view]['toolbar'])
|
||||
&& isset($configuration['views'][$view]['toolbar'][$params[0]]))
|
||||
{
|
||||
$toolbar = array_merge($toolbar, $configuration['views'][$view]['toolbar'][$params[0]]);
|
||||
}
|
||||
|
||||
if (empty($toolbar))
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $toolbar;
|
||||
}
|
||||
}
|
||||
752
libraries/fof30/Container/Container.php
Normal file
752
libraries/fof30/Container/Container.php
Normal file
@ -0,0 +1,752 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Container;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Autoloader\Autoloader;
|
||||
use FOF30\Configuration\Configuration;
|
||||
use FOF30\Container\Exception\NoComponent;
|
||||
use FOF30\Dispatcher\Dispatcher;
|
||||
use FOF30\Encrypt\EncryptService;
|
||||
use FOF30\Factory\FactoryInterface;
|
||||
use FOF30\Inflector\Inflector;
|
||||
use FOF30\Input\Input as FOFInput;
|
||||
use FOF30\Params\Params;
|
||||
use FOF30\Platform\FilesystemInterface;
|
||||
use FOF30\Platform\Joomla\Filesystem as JoomlaFilesystem;
|
||||
use FOF30\Platform\PlatformInterface;
|
||||
use FOF30\Render\RenderInterface;
|
||||
use FOF30\Template\Template;
|
||||
use FOF30\Toolbar\Toolbar;
|
||||
use FOF30\TransparentAuthentication\TransparentAuthentication as TransparentAuth;
|
||||
use FOF30\Utils\MediaVersion;
|
||||
use FOF30\View\Compiler\Blade;
|
||||
use JDatabaseDriver;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Input\Input as CMSInput;
|
||||
|
||||
/**
|
||||
* Dependency injection container for FOF-powered components.
|
||||
*
|
||||
* The properties below (except componentName, bareComponentName and the ones marked with property-read) can be
|
||||
* configured in the fof.xml component configuration file.
|
||||
*
|
||||
* Sample fof.xml:
|
||||
*
|
||||
* <fof>
|
||||
* <common>
|
||||
* <container>
|
||||
* <option name="componentNamespace"><![CDATA[MyCompany\MyApplication]]></option>
|
||||
* <option name="frontEndPath"><![CDATA[%PUBLIC%\components\com_application]]></option>
|
||||
* <option name="factoryClass">magic</option>
|
||||
* </container>
|
||||
* </common>
|
||||
* </fof>
|
||||
*
|
||||
* The paths can use the variables %ROOT%, %PUBLIC%, %ADMIN%, %TMP%, %LOG% i.e. all the path keys returned by
|
||||
* Platform's
|
||||
* getPlatformBaseDirs() method in uppercase and surrounded by percent signs.
|
||||
*
|
||||
*
|
||||
* @property string $componentName The name of the component (com_something)
|
||||
* @property string $bareComponentName The name of the component without com_
|
||||
* (something)
|
||||
* @property string $componentNamespace The namespace of the component's classes
|
||||
* (\Foobar)
|
||||
* @property string $frontEndPath The absolute path to the front-end files
|
||||
* @property string $backEndPath The absolute path to the back-end files
|
||||
* @property string $thisPath The preferred path. Backend for Admin
|
||||
* application, frontend otherwise
|
||||
* @property string $rendererClass The fully qualified class name of the view
|
||||
* renderer we'll be using. Must implement FOF30\Render\RenderInterface.
|
||||
* @property string $factoryClass The fully qualified class name of the MVC
|
||||
* Factory object, default is FOF30\Factory\BasicFactory.
|
||||
* @property string $platformClass The fully qualified class name of the
|
||||
* Platform abstraction object, default is FOF30\Platform\Joomla\Platform.
|
||||
* @property MediaVersion $mediaVersion A version string for media files in forms.
|
||||
* Default: md5 of release version, release date and site secret (if found)
|
||||
*
|
||||
* @property-read Configuration $appConfig The application configuration registry
|
||||
* @property-read Blade $blade The Blade view template compiler engine
|
||||
* @property-read JDatabaseDriver $db The database connection object
|
||||
* @property-read Dispatcher $dispatcher The component's dispatcher
|
||||
* @property-read FactoryInterface $factory The MVC object factory
|
||||
* @property-read FilesystemInterface $filesystem The filesystem abstraction layer object
|
||||
* @property-read Inflector $inflector The English word inflector (pluralise /
|
||||
* singularize words etc)
|
||||
* @property-read Params $params The component's params
|
||||
* @property-read FOFInput $input The input object
|
||||
* @property-read PlatformInterface $platform The platform abstraction layer object
|
||||
* @property-read RenderInterface $renderer The view renderer
|
||||
* @property-read \JSession $session Joomla! session storage
|
||||
* @property-read Template $template The template helper
|
||||
* @property-read TransparentAuth $transparentAuth Transparent authentication handler
|
||||
* @property-read Toolbar $toolbar The component's toolbar
|
||||
* @property-read EncryptService $crypto The component's data encryption service
|
||||
*/
|
||||
class Container extends ContainerBase
|
||||
{
|
||||
|
||||
/**
|
||||
* Cache of created container instances
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $instances = [];
|
||||
|
||||
/**
|
||||
* Public constructor. This does NOT go through the fof.xml file. You are advised to use getInstance() instead.
|
||||
*
|
||||
* @param array $values Overrides for the container configuration and services
|
||||
*
|
||||
* @throws NoComponent If no component name is specified
|
||||
*/
|
||||
public function __construct(array $values = [])
|
||||
{
|
||||
// Initialise
|
||||
$this->bareComponentName = '';
|
||||
$this->componentName = '';
|
||||
$this->componentNamespace = '';
|
||||
$this->frontEndPath = '';
|
||||
$this->backEndPath = '';
|
||||
$this->thisPath = '';
|
||||
$this->factoryClass = 'FOF30\\Factory\\BasicFactory';
|
||||
$this->platformClass = 'FOF30\\Platform\\Joomla\\Platform';
|
||||
|
||||
$initMediaVersion = null;
|
||||
|
||||
if (isset($values['mediaVersion']) && !is_object($values['mediaVersion']))
|
||||
{
|
||||
$initMediaVersion = $values['mediaVersion'];
|
||||
|
||||
unset($values['mediaVersion']);
|
||||
}
|
||||
|
||||
// Try to construct this container object
|
||||
parent::__construct($values);
|
||||
|
||||
// Make sure we have a component name
|
||||
if (empty($this['componentName']))
|
||||
{
|
||||
throw new Exception\NoComponent;
|
||||
}
|
||||
|
||||
$bareComponent = substr($this->componentName, 4);
|
||||
|
||||
$this['bareComponentName'] = $bareComponent;
|
||||
|
||||
// Try to guess the component's namespace
|
||||
if (empty($this['componentNamespace']))
|
||||
{
|
||||
$this->componentNamespace = ucfirst($bareComponent);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->componentNamespace = trim($this->componentNamespace, '\\');
|
||||
}
|
||||
|
||||
// Make sure we have front-end and back-end paths
|
||||
if (empty($this['frontEndPath']))
|
||||
{
|
||||
$this->frontEndPath = JPATH_SITE . '/components/' . $this->componentName;
|
||||
}
|
||||
|
||||
if (empty($this['backEndPath']))
|
||||
{
|
||||
$this->backEndPath = JPATH_ADMINISTRATOR . '/components/' . $this->componentName;
|
||||
}
|
||||
|
||||
// Get the namespaces for the front-end and back-end parts of the component
|
||||
$frontEndNamespace = '\\' . $this->componentNamespace . '\\Site\\';
|
||||
$backEndNamespace = '\\' . $this->componentNamespace . '\\Admin\\';
|
||||
|
||||
// Special case: if the frontend and backend paths are identical, we don't use the Site and Admin namespace
|
||||
// suffixes after $this->componentNamespace (so you may use FOF with JApplicationWeb apps)
|
||||
if ($this->frontEndPath == $this->backEndPath)
|
||||
{
|
||||
$frontEndNamespace = '\\' . $this->componentNamespace . '\\';
|
||||
$backEndNamespace = '\\' . $this->componentNamespace . '\\';
|
||||
}
|
||||
|
||||
// Do we have to register the component's namespaces with the autoloader?
|
||||
$autoloader = Autoloader::getInstance();
|
||||
|
||||
if (!$autoloader->hasMap($frontEndNamespace))
|
||||
{
|
||||
$autoloader->addMap($frontEndNamespace, $this->frontEndPath);
|
||||
}
|
||||
|
||||
if (!$autoloader->hasMap($backEndNamespace))
|
||||
{
|
||||
$autoloader->addMap($backEndNamespace, $this->backEndPath);
|
||||
}
|
||||
|
||||
// Inflector service
|
||||
if (!isset($this['inflector']))
|
||||
{
|
||||
$this['inflector'] = function (Container $c) {
|
||||
return new Inflector();
|
||||
};
|
||||
}
|
||||
|
||||
// Filesystem abstraction service
|
||||
if (!isset($this['filesystem']))
|
||||
{
|
||||
$this['filesystem'] = function (Container $c) {
|
||||
return new JoomlaFilesystem($c);
|
||||
};
|
||||
}
|
||||
|
||||
// Platform abstraction service
|
||||
if (!isset($this['platform']))
|
||||
{
|
||||
if (empty($c['platformClass']))
|
||||
{
|
||||
$c['platformClass'] = 'FOF30\\Platform\\Joomla\\Platform';
|
||||
}
|
||||
|
||||
$this['platform'] = function (Container $c) {
|
||||
$className = $c['platformClass'];
|
||||
|
||||
return new $className($c);
|
||||
};
|
||||
}
|
||||
|
||||
if (empty($this['thisPath']))
|
||||
{
|
||||
$this['thisPath'] = $this['frontEndPath'];
|
||||
|
||||
if ($this->platform->isBackend())
|
||||
{
|
||||
$this['thisPath'] = $this['backEndPath'];
|
||||
}
|
||||
}
|
||||
|
||||
// MVC Factory service
|
||||
if (!isset($this['factory']))
|
||||
{
|
||||
$this['factory'] = function (Container $c) {
|
||||
if (empty($c['factoryClass']))
|
||||
{
|
||||
$c['factoryClass'] = 'FOF30\\Factory\\BasicFactory';
|
||||
}
|
||||
|
||||
if (strpos($c['factoryClass'], '\\') === false)
|
||||
{
|
||||
$class = $c->getNamespacePrefix() . 'Factory\\' . $c['factoryClass'];
|
||||
|
||||
if (class_exists($class))
|
||||
{
|
||||
$c['factoryClass'] = $class;
|
||||
}
|
||||
else
|
||||
{
|
||||
$c['factoryClass'] = '\\FOF30\\Factory\\' . ucfirst($c['factoryClass']) . 'Factory';
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists($c['factoryClass'], true))
|
||||
{
|
||||
$c['factoryClass'] = 'FOF30\\Factory\\BasicFactory';
|
||||
}
|
||||
|
||||
$factoryClass = $c['factoryClass'];
|
||||
|
||||
/** @var FactoryInterface $factory */
|
||||
$factory = new $factoryClass($c);
|
||||
|
||||
if (isset($c['section']))
|
||||
{
|
||||
$factory->setSection($c['section']);
|
||||
}
|
||||
|
||||
return $factory;
|
||||
};
|
||||
}
|
||||
|
||||
// Component Configuration service
|
||||
if (!isset($this['appConfig']))
|
||||
{
|
||||
$this['appConfig'] = function (Container $c) {
|
||||
$class = $c->getNamespacePrefix() . 'Configuration\\Configuration';
|
||||
|
||||
if (!class_exists($class, true))
|
||||
{
|
||||
$class = '\\FOF30\\Configuration\\Configuration';
|
||||
}
|
||||
|
||||
return new $class($c);
|
||||
};
|
||||
}
|
||||
|
||||
// Component Params service
|
||||
if (!isset($this['params']))
|
||||
{
|
||||
$this['params'] = function (Container $c) {
|
||||
return new Params($c);
|
||||
};
|
||||
}
|
||||
|
||||
// Blade view template compiler service
|
||||
if (!isset($this['blade']))
|
||||
{
|
||||
$this['blade'] = function (Container $c) {
|
||||
return new Blade($c);
|
||||
};
|
||||
}
|
||||
|
||||
// Database Driver service
|
||||
if (!isset($this['db']))
|
||||
{
|
||||
$this['db'] = function (Container $c) {
|
||||
return $c->platform->getDbo();
|
||||
};
|
||||
}
|
||||
|
||||
// Request Dispatcher service
|
||||
if (!isset($this['dispatcher']))
|
||||
{
|
||||
$this['dispatcher'] = function (Container $c) {
|
||||
return $c->factory->dispatcher();
|
||||
};
|
||||
}
|
||||
|
||||
// Component toolbar provider
|
||||
if (!isset($this['toolbar']))
|
||||
{
|
||||
$this['toolbar'] = function (Container $c) {
|
||||
return $c->factory->toolbar();
|
||||
};
|
||||
}
|
||||
|
||||
// Component toolbar provider
|
||||
if (!isset($this['transparentAuth']))
|
||||
{
|
||||
$this['transparentAuth'] = function (Container $c) {
|
||||
return $c->factory->transparentAuthentication();
|
||||
};
|
||||
}
|
||||
|
||||
// View renderer
|
||||
if (!isset($this['renderer']))
|
||||
{
|
||||
$this['renderer'] = function (Container $c) {
|
||||
if (isset($c['rendererClass']) && class_exists($c['rendererClass']))
|
||||
{
|
||||
$class = $c['rendererClass'];
|
||||
$renderer = new $class($c);
|
||||
|
||||
if ($renderer instanceof RenderInterface)
|
||||
{
|
||||
return $renderer;
|
||||
}
|
||||
}
|
||||
|
||||
$filesystem = $c->filesystem;
|
||||
|
||||
// Try loading the stock renderers shipped with F0F
|
||||
$path = dirname(__FILE__) . '/../Render/';
|
||||
$renderFiles = $filesystem->folderFiles($path, '.php');
|
||||
$renderer = null;
|
||||
$priority = 0;
|
||||
|
||||
if (!empty($renderFiles))
|
||||
{
|
||||
foreach ($renderFiles as $filename)
|
||||
{
|
||||
if ($filename == 'RenderBase.php')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($filename == 'RenderInterface.php')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$className = 'FOF30\\Render\\' . basename($filename, '.php');
|
||||
|
||||
if (!class_exists($className, true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var RenderInterface $o */
|
||||
$o = new $className($c);
|
||||
|
||||
$info = $o->getInformation();
|
||||
|
||||
if (!$info->enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($info->priority > $priority)
|
||||
{
|
||||
$priority = $info->priority;
|
||||
$renderer = $o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $renderer;
|
||||
};
|
||||
}
|
||||
|
||||
// Input Access service
|
||||
if (isset($this['input']) &&
|
||||
(!(is_object($this['input'])) ||
|
||||
!($this['input'] instanceof FOFInput) ||
|
||||
!($this['input'] instanceof CMSInput))
|
||||
)
|
||||
{
|
||||
if (empty($this['input']))
|
||||
{
|
||||
$this['input'] = [];
|
||||
}
|
||||
|
||||
// This swap is necessary to prevent infinite recursion
|
||||
$this['rawInputData'] = array_merge($this['input']);
|
||||
unset($this['input']);
|
||||
|
||||
$this['input'] = function (Container $c) {
|
||||
$input = new FOFInput($c['rawInputData']);
|
||||
unset($c['rawInputData']);
|
||||
|
||||
return $input;
|
||||
};
|
||||
}
|
||||
|
||||
if (!isset($this['input']))
|
||||
{
|
||||
$this['input'] = function () {
|
||||
return new FOFInput();
|
||||
};
|
||||
}
|
||||
|
||||
// Session service
|
||||
if (!isset($this['session']))
|
||||
{
|
||||
$this['session'] = function (Container $c) {
|
||||
if (version_compare(JVERSION, '3.999.999', 'le'))
|
||||
{
|
||||
return Factory::getSession();
|
||||
}
|
||||
|
||||
return Factory::getApplication()->getSession();
|
||||
};
|
||||
}
|
||||
|
||||
// Template service
|
||||
if (!isset($this['template']))
|
||||
{
|
||||
$this['template'] = function (Container $c) {
|
||||
return new Template($c);
|
||||
};
|
||||
}
|
||||
|
||||
// Media version string
|
||||
if (!isset($this['mediaVersion']))
|
||||
{
|
||||
$this['mediaVersion'] = function (Container $c) {
|
||||
return new MediaVersion($c);
|
||||
};
|
||||
|
||||
if (!is_null($initMediaVersion))
|
||||
{
|
||||
$this['mediaVersion']->setMediaVersion($initMediaVersion);
|
||||
}
|
||||
}
|
||||
|
||||
// Encryption / cryptography service
|
||||
if (!isset($this['crypto']))
|
||||
{
|
||||
$this['crypto'] = function (Container $c) {
|
||||
return new EncryptService($c);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a container instance for a specific component. This method goes through fof.xml to read the default
|
||||
* configuration values for the container. You are advised to use this unless you have a specific reason for
|
||||
* instantiating a Container without going through the fof.xml file.
|
||||
*
|
||||
* Pass the value 'tempInstance' => true in the $values array to get a temporary instance. Otherwise you will get
|
||||
* the cached instance of the previously created container.
|
||||
*
|
||||
* @param string $component The component you want to get a container for, e.g. com_foobar.
|
||||
* @param array $values Container configuration overrides you want to apply. Optional.
|
||||
* @param string $section The application section (site, admin) you want to fetch. Any other value results in
|
||||
* auto-detection.
|
||||
*
|
||||
* @return Container
|
||||
*/
|
||||
public static function &getInstance($component, array $values = [], $section = 'auto')
|
||||
{
|
||||
$tempInstance = false;
|
||||
|
||||
if (isset($values['tempInstance']))
|
||||
{
|
||||
$tempInstance = $values['tempInstance'];
|
||||
unset($values['tempInstance']);
|
||||
}
|
||||
|
||||
if ($tempInstance)
|
||||
{
|
||||
return self::makeInstance($component, $values, $section);
|
||||
}
|
||||
|
||||
$signature = md5($component . '@' . $section);
|
||||
|
||||
if (!isset(self::$instances[$signature]))
|
||||
{
|
||||
self::$instances[$signature] = self::makeInstance($component, $values, $section);
|
||||
}
|
||||
|
||||
return self::$instances[$signature];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a temporary container instance for a specific component.
|
||||
*
|
||||
* @param string $component The component you want to get a container for, e.g. com_foobar.
|
||||
* @param array $values Container configuration overrides you want to apply. Optional.
|
||||
* @param string $section The application section (site, admin) you want to fetch. Any other value results in
|
||||
* auto-detection.
|
||||
*
|
||||
* @return Container
|
||||
*/
|
||||
protected static function &makeInstance($component, array $values = [], $section = 'auto')
|
||||
{
|
||||
// Try to auto-detect some defaults
|
||||
$tmpConfig = array_merge($values, ['componentName' => $component]);
|
||||
$tmpContainer = new Container($tmpConfig);
|
||||
|
||||
if (!in_array($section, ['site', 'admin']))
|
||||
{
|
||||
$section = $tmpContainer->platform->isBackend() ? 'admin' : 'site';
|
||||
}
|
||||
|
||||
$appConfig = $tmpContainer->appConfig;
|
||||
|
||||
// Get the namespace from fof.xml
|
||||
$namespace = $appConfig->get('container.componentNamespace', null);
|
||||
|
||||
// $values always overrides $namespace and fof.xml
|
||||
if (isset($values['componentNamespace']))
|
||||
{
|
||||
$namespace = $values['componentNamespace'];
|
||||
}
|
||||
|
||||
// If there is no namespace set, try to guess it.
|
||||
if (empty($namespace))
|
||||
{
|
||||
$bareComponent = $component;
|
||||
|
||||
if (substr($component, 0, 4) == 'com_')
|
||||
{
|
||||
$bareComponent = substr($component, 4);
|
||||
}
|
||||
|
||||
$namespace = ucfirst($bareComponent);
|
||||
}
|
||||
|
||||
// Get the default front-end/back-end paths
|
||||
$frontEndPath = $appConfig->get('container.frontEndPath', JPATH_SITE . '/components/' . $component);
|
||||
$backEndPath = $appConfig->get('container.backEndPath', JPATH_ADMINISTRATOR . '/components/' . $component);
|
||||
|
||||
// Parse path variables if necessary
|
||||
$frontEndPath = $tmpContainer->parsePathVariables($frontEndPath);
|
||||
$backEndPath = $tmpContainer->parsePathVariables($backEndPath);
|
||||
|
||||
// Apply path overrides
|
||||
if (isset($values['frontEndPath']))
|
||||
{
|
||||
$frontEndPath = $values['frontEndPath'];
|
||||
}
|
||||
|
||||
if (isset($values['backEndPath']))
|
||||
{
|
||||
$backEndPath = $values['backEndPath'];
|
||||
}
|
||||
|
||||
$thisPath = ($section == 'admin') ? $backEndPath : $frontEndPath;
|
||||
|
||||
// Get the namespaces for the front-end and back-end parts of the component
|
||||
$frontEndNamespace = '\\' . $namespace . '\\Site\\';
|
||||
$backEndNamespace = '\\' . $namespace . '\\Admin\\';
|
||||
|
||||
// Special case: if the frontend and backend paths are identical, we don't use the Site and Admin namespace
|
||||
// suffixes after $this->componentNamespace (so you may use FOF with JApplicationWeb apps)
|
||||
if ($frontEndPath == $backEndPath)
|
||||
{
|
||||
$frontEndNamespace = '\\' . $namespace . '\\';
|
||||
$backEndNamespace = '\\' . $namespace . '\\';
|
||||
}
|
||||
|
||||
// Do we have to register the component's namespaces with the autoloader?
|
||||
$autoloader = Autoloader::getInstance();
|
||||
|
||||
if (!$autoloader->hasMap($frontEndNamespace))
|
||||
{
|
||||
$autoloader->addMap($frontEndNamespace, $frontEndPath);
|
||||
}
|
||||
|
||||
if (!$autoloader->hasMap($backEndNamespace))
|
||||
{
|
||||
$autoloader->addMap($backEndNamespace, $backEndPath);
|
||||
}
|
||||
|
||||
// Get the Container class name
|
||||
$classNamespace = ($section == 'admin') ? $backEndNamespace : $frontEndNamespace;
|
||||
$class = $classNamespace . 'Container';
|
||||
|
||||
// Get the values overrides from fof.xml
|
||||
$values = array_merge([
|
||||
'factoryClass' => '\\FOF30\\Factory\\BasicFactory',
|
||||
'platformClass' => '\\FOF30\\Platform\\Joomla\\Platform',
|
||||
'section' => $section,
|
||||
], $values);
|
||||
|
||||
$values = array_merge($values, [
|
||||
'componentName' => $component,
|
||||
'componentNamespace' => $namespace,
|
||||
'frontEndPath' => $frontEndPath,
|
||||
'backEndPath' => $backEndPath,
|
||||
'thisPath' => $thisPath,
|
||||
'rendererClass' => $appConfig->get('container.rendererClass', null),
|
||||
'factoryClass' => $appConfig->get('container.factoryClass', $values['factoryClass']),
|
||||
'platformClass' => $appConfig->get('container.platformClass', $values['platformClass']),
|
||||
]);
|
||||
|
||||
if (empty($values['rendererClass']))
|
||||
{
|
||||
unset ($values['rendererClass']);
|
||||
}
|
||||
|
||||
$mediaVersion = $appConfig->get('container.mediaVersion', null);
|
||||
|
||||
unset($appConfig);
|
||||
unset($tmpConfig);
|
||||
unset($tmpContainer);
|
||||
|
||||
if (class_exists($class, true))
|
||||
{
|
||||
$container = new $class($values);
|
||||
}
|
||||
else
|
||||
{
|
||||
$container = new Container($values);
|
||||
}
|
||||
|
||||
if (!is_null($mediaVersion))
|
||||
{
|
||||
$container->mediaVersion->setMediaVersion($mediaVersion);
|
||||
}
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* The container SHOULD NEVER be serialised. If this happens, it means that any of the installed version is doing
|
||||
* something REALLY BAD, so let's die and inform the user of what it's going on.
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
// If the site is in debug mode we die and let the user figure it out
|
||||
if (defined('JDEBUG') && JDEBUG)
|
||||
{
|
||||
$msg = <<< END
|
||||
Something on your site is broken and tries to save the plugin state in the cache. This is a major security issue and
|
||||
will cause your site to not work properly. Go to your site's backend, Global Configuration and set Caching to OFF as a
|
||||
temporary solution. Possible causes: older versions of JoomlaShine templates, JomSocial, BetterPreview and other third
|
||||
party Joomla! extensions.
|
||||
END;
|
||||
|
||||
die($msg);
|
||||
}
|
||||
|
||||
// Otherwise we serialise the Container
|
||||
return ['values', 'factories', 'protected', 'frozen', 'raw', 'keys'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the applicable namespace prefix for a component section. Possible sections:
|
||||
* auto Auto-detect which is the current component section
|
||||
* inverse The inverse area than auto
|
||||
* site Frontend
|
||||
* admin Backend
|
||||
*
|
||||
* @param string $section The section you want to get information for
|
||||
*
|
||||
* @return string The namespace prefix for the component's classes, e.g. \Foobar\Example\Site\
|
||||
*/
|
||||
public function getNamespacePrefix($section = 'auto')
|
||||
{
|
||||
// Get the namespaces for the front-end and back-end parts of the component
|
||||
$frontEndNamespace = '\\' . $this->componentNamespace . '\\Site\\';
|
||||
$backEndNamespace = '\\' . $this->componentNamespace . '\\Admin\\';
|
||||
|
||||
// Special case: if the frontend and backend paths are identical, we don't use the Site and Admin namespace
|
||||
// suffixes after $this->componentNamespace (so you may use FOF with JApplicationWeb apps)
|
||||
if ($this->frontEndPath == $this->backEndPath)
|
||||
{
|
||||
$frontEndNamespace = '\\' . $this->componentNamespace . '\\';
|
||||
$backEndNamespace = '\\' . $this->componentNamespace . '\\';
|
||||
}
|
||||
|
||||
switch ($section)
|
||||
{
|
||||
default:
|
||||
case 'auto':
|
||||
if ($this->platform->isBackend())
|
||||
{
|
||||
return $backEndNamespace;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $frontEndNamespace;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'inverse':
|
||||
if ($this->platform->isBackend())
|
||||
{
|
||||
return $frontEndNamespace;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $backEndNamespace;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'site':
|
||||
return $frontEndNamespace;
|
||||
break;
|
||||
|
||||
case 'admin':
|
||||
return $backEndNamespace;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function parsePathVariables($path)
|
||||
{
|
||||
$platformDirs = $this->platform->getPlatformBaseDirs();
|
||||
// root public admin tmp log
|
||||
|
||||
$search = array_map(function ($x) {
|
||||
return '%' . strtoupper($x) . '%';
|
||||
}, array_keys($platformDirs));
|
||||
$replace = array_values($platformDirs);
|
||||
|
||||
return str_replace($search, $replace, $path);
|
||||
}
|
||||
}
|
||||
50
libraries/fof30/Container/ContainerBase.php
Normal file
50
libraries/fof30/Container/ContainerBase.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Container;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Pimple\Container;
|
||||
|
||||
class ContainerBase extends Container
|
||||
{
|
||||
/**
|
||||
* Magic getter for alternative syntax, e.g. $container->foo instead of $container['foo']
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \InvalidArgumentException if the identifier is not defined
|
||||
*/
|
||||
function __get($name)
|
||||
{
|
||||
return $this->offsetGet($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic setter for alternative syntax, e.g. $container->foo instead of $container['foo']
|
||||
*
|
||||
* @param string $name The unique identifier for the parameter or object
|
||||
* @param mixed $value The value of the parameter or a closure for a service
|
||||
*
|
||||
* @throws \RuntimeException Prevent override of a frozen service
|
||||
*/
|
||||
function __set($name, $value)
|
||||
{
|
||||
// Special backwards compatible handling for the mediaVersion service
|
||||
if ($name == 'mediaVersion')
|
||||
{
|
||||
$this[$name]->setMediaVersion($value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->offsetSet($name, $value);
|
||||
}
|
||||
}
|
||||
30
libraries/fof30/Container/Exception/NoComponent.php
Normal file
30
libraries/fof30/Container/Exception/NoComponent.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Container\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
|
||||
class NoComponent extends \Exception
|
||||
{
|
||||
public function __construct($message = "", $code = 0, Exception $previous = null)
|
||||
{
|
||||
if (empty($message))
|
||||
{
|
||||
$message = 'No component specified building the Container object';
|
||||
}
|
||||
|
||||
if (empty($code))
|
||||
{
|
||||
$code = 500;
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
1277
libraries/fof30/Controller/Controller.php
Normal file
1277
libraries/fof30/Controller/Controller.php
Normal file
File diff suppressed because it is too large
Load Diff
1658
libraries/fof30/Controller/DataController.php
Normal file
1658
libraries/fof30/Controller/DataController.php
Normal file
File diff suppressed because it is too large
Load Diff
17
libraries/fof30/Controller/Exception/CannotGetName.php
Normal file
17
libraries/fof30/Controller/Exception/CannotGetName.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Controller\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Exception thrown when we can't get a Controller's name
|
||||
*/
|
||||
class CannotGetName extends \RuntimeException
|
||||
{
|
||||
}
|
||||
17
libraries/fof30/Controller/Exception/ItemNotFound.php
Normal file
17
libraries/fof30/Controller/Exception/ItemNotFound.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Controller\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Exception thrown when we can't find the requested item in a read task
|
||||
*/
|
||||
class ItemNotFound extends \RuntimeException
|
||||
{
|
||||
}
|
||||
29
libraries/fof30/Controller/Exception/LockedRecord.php
Normal file
29
libraries/fof30/Controller/Exception/LockedRecord.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Controller\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
/**
|
||||
* Exception thrown when the provided Model is locked for writing by another user
|
||||
*/
|
||||
class LockedRecord extends \RuntimeException
|
||||
{
|
||||
public function __construct($message = "", $code = 403, Exception $previous = null)
|
||||
{
|
||||
if (empty($message))
|
||||
{
|
||||
$message = Text::_('LIB_FOF_CONTROLLER_ERR_LOCKED');
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
17
libraries/fof30/Controller/Exception/NotADataModel.php
Normal file
17
libraries/fof30/Controller/Exception/NotADataModel.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Controller\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Exception thrown when the provided Model is not a DataModel
|
||||
*/
|
||||
class NotADataModel extends \InvalidArgumentException
|
||||
{
|
||||
}
|
||||
17
libraries/fof30/Controller/Exception/TaskNotFound.php
Normal file
17
libraries/fof30/Controller/Exception/TaskNotFound.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Controller\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Exception thrown when we can't find a suitable method to handle the requested task
|
||||
*/
|
||||
class TaskNotFound extends \InvalidArgumentException
|
||||
{
|
||||
}
|
||||
82
libraries/fof30/Controller/Mixin/PredefinedTaskList.php
Normal file
82
libraries/fof30/Controller/Mixin/PredefinedTaskList.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Controller\Mixin;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Controller\Controller;
|
||||
|
||||
/**
|
||||
* Force a Controller to allow access to specific tasks only, no matter which tasks are already defined in this
|
||||
* Controller.
|
||||
*
|
||||
* Include this Trait and then in your constructor do this:
|
||||
* $this->setPredefinedTaskList(['atask', 'anothertask', 'something']);
|
||||
*
|
||||
* WARNING: If you override execute() you will need to copy the logic from this trait's execute() method.
|
||||
*/
|
||||
trait PredefinedTaskList
|
||||
{
|
||||
|
||||
/**
|
||||
* A list of predefined tasks. Trying to access any other task will result in the first task of this list being
|
||||
* executed instead.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $predefinedTaskList = [];
|
||||
|
||||
/**
|
||||
* Overrides the execute method to implement the predefined task list feature
|
||||
*
|
||||
* @param string $task The task to execute
|
||||
*
|
||||
* @return mixed The controller task result
|
||||
*/
|
||||
public function execute($task)
|
||||
{
|
||||
if (!in_array($task, $this->predefinedTaskList))
|
||||
{
|
||||
$task = reset($this->predefinedTaskList);
|
||||
}
|
||||
|
||||
return parent::execute($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the predefined task list and registers the first task in the list as the Controller's default task
|
||||
*
|
||||
* @param array $taskList The task list to register
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPredefinedTaskList(array $taskList)
|
||||
{
|
||||
/** @var Controller $this */
|
||||
|
||||
// First, unregister all known tasks which are not in the taskList
|
||||
$allTasks = $this->getTasks();
|
||||
|
||||
foreach ($allTasks as $task)
|
||||
{
|
||||
if (in_array($task, $taskList))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->unregisterTask($task);
|
||||
}
|
||||
|
||||
// Set the predefined task list
|
||||
$this->predefinedTaskList = $taskList;
|
||||
|
||||
// Set the default task
|
||||
$this->registerDefaultTask(reset($this->predefinedTaskList));
|
||||
|
||||
}
|
||||
}
|
||||
1021
libraries/fof30/Database/Installer.php
Normal file
1021
libraries/fof30/Database/Installer.php
Normal file
File diff suppressed because it is too large
Load Diff
464
libraries/fof30/Date/Date.php
Normal file
464
libraries/fof30/Date/Date.php
Normal file
@ -0,0 +1,464 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Date;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use JDatabaseDriver;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
/**
|
||||
* The Date class is a fork of Joomla's JDate. We had to fork that code in April 2017 when Joomla! 7.0 was released
|
||||
* with
|
||||
* an untested change that completely broke date handling on PHP 7.0 and earlier versions. Since we can no longer trust
|
||||
* Joomla's core date and time handling we are providing our own, stable code.
|
||||
*
|
||||
* Date is a class that stores a date and provides logic to manipulate and render that date in a variety of formats.
|
||||
*
|
||||
* @method Date|bool add(DateInterval $interval) Adds an amount of days, months, years, hours, minutes and seconds
|
||||
* to a JDate object.
|
||||
* @method Date|bool sub(DateInterval $interval) Subtracts an amount of days, months, years, hours, minutes and
|
||||
* seconds from a JDate object.
|
||||
* @method Date|bool modify($modify) Alter the timestamp of this object by incre-/decrementing in a
|
||||
* format accepted by strtotime().
|
||||
*
|
||||
* @property-read string $daysinmonth t - Number of days in the given month.
|
||||
* @property-read string $dayofweek N - ISO-8601 numeric representation of the day of the week.
|
||||
* @property-read string $dayofyear z - The day of the year (starting from 0).
|
||||
* @property-read boolean $isleapyear L - Whether it's a leap year.
|
||||
* @property-read string $day d - Day of the month, 2 digits with leading zeros.
|
||||
* @property-read string $hour H - 24-hour format of an hour with leading zeros.
|
||||
* @property-read string $minute i - Minutes with leading zeros.
|
||||
* @property-read string $second s - Seconds with leading zeros.
|
||||
* @property-read string $microsecond u - Microseconds with leading zeros.
|
||||
* @property-read string $month m - Numeric representation of a month, with leading zeros.
|
||||
* @property-read string $ordinal S - English ordinal suffix for the day of the month, 2 characters.
|
||||
* @property-read string $week W - ISO-8601 week number of year, weeks starting on Monday.
|
||||
* @property-read string $year Y - A full numeric representation of a year, 4 digits.
|
||||
*/
|
||||
class Date extends DateTime
|
||||
{
|
||||
public const DAY_ABBR = "\x021\x03";
|
||||
public const DAY_NAME = "\x022\x03";
|
||||
public const MONTH_ABBR = "\x023\x03";
|
||||
public const MONTH_NAME = "\x024\x03";
|
||||
|
||||
/**
|
||||
* The format string to be applied when using the __toString() magic method.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $format = 'Y-m-d H:i:s';
|
||||
|
||||
/**
|
||||
* Placeholder for a DateTimeZone object with GMT as the time zone.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected static $gmt;
|
||||
|
||||
/**
|
||||
* Placeholder for a DateTimeZone object with the default server
|
||||
* time zone as the time zone.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected static $stz;
|
||||
|
||||
/**
|
||||
* The DateTimeZone object for usage in rending dates as strings.
|
||||
*
|
||||
* @var DateTimeZone
|
||||
*/
|
||||
protected $tz;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $date String in a format accepted by strtotime(), defaults to "now".
|
||||
* @param mixed $tz Time zone to be used for the date. Might be a string or a DateTimeZone object.
|
||||
*/
|
||||
public function __construct($date = 'now', $tz = null)
|
||||
{
|
||||
// Create the base GMT and server time zone objects.
|
||||
if (empty(self::$gmt) || empty(self::$stz))
|
||||
{
|
||||
self::$gmt = new DateTimeZone('GMT');
|
||||
self::$stz = new DateTimeZone(@date_default_timezone_get());
|
||||
}
|
||||
|
||||
// If the time zone object is not set, attempt to build it.
|
||||
if (!($tz instanceof DateTimeZone))
|
||||
{
|
||||
if ($tz === null)
|
||||
{
|
||||
$tz = self::$gmt;
|
||||
}
|
||||
elseif (is_string($tz))
|
||||
{
|
||||
$tz = new DateTimeZone($tz);
|
||||
}
|
||||
}
|
||||
|
||||
// On PHP 7.1 and later use an integer timestamp, without microseconds, to preserve backwards compatibility.
|
||||
// See http://php.net/manual/en/migration71.incompatible.php#migration71.incompatible.datetime-microseconds
|
||||
if ($date === 'now' && version_compare(PHP_VERSION, '7.1.0', '>='))
|
||||
{
|
||||
$date = time();
|
||||
}
|
||||
|
||||
// If the date is numeric assume a unix timestamp and convert it.
|
||||
date_default_timezone_set('UTC');
|
||||
$date = is_numeric($date) ? date('c', $date) : $date;
|
||||
|
||||
// Call the DateTime constructor.
|
||||
parent::__construct($date, $tz);
|
||||
|
||||
// Reset the timezone for 3rd party libraries/extension that does not use JDate
|
||||
date_default_timezone_set(self::$stz->getName());
|
||||
|
||||
// Set the timezone object for access later.
|
||||
$this->tz = $tz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy for new JDate().
|
||||
*
|
||||
* @param string $date String in a format accepted by strtotime(), defaults to "now".
|
||||
* @param mixed $tz Time zone to be used for the date.
|
||||
*
|
||||
* @return Date
|
||||
*/
|
||||
public static function getInstance($date = 'now', $tz = null)
|
||||
{
|
||||
return new Date($date, $tz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to access properties of the date given by class to the format method.
|
||||
*
|
||||
* @param string $name The name of the property.
|
||||
*
|
||||
* @return mixed A value if the property name is valid, null otherwise.
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
$value = null;
|
||||
|
||||
switch ($name)
|
||||
{
|
||||
case 'daysinmonth':
|
||||
$value = $this->format('t', true);
|
||||
break;
|
||||
|
||||
case 'dayofweek':
|
||||
$value = $this->format('N', true);
|
||||
break;
|
||||
|
||||
case 'dayofyear':
|
||||
$value = $this->format('z', true);
|
||||
break;
|
||||
|
||||
case 'isleapyear':
|
||||
$value = (boolean) $this->format('L', true);
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
$value = $this->format('d', true);
|
||||
break;
|
||||
|
||||
case 'hour':
|
||||
$value = $this->format('H', true);
|
||||
break;
|
||||
|
||||
case 'minute':
|
||||
$value = $this->format('i', true);
|
||||
break;
|
||||
|
||||
case 'second':
|
||||
$value = $this->format('s', true);
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
$value = $this->format('m', true);
|
||||
break;
|
||||
|
||||
case 'ordinal':
|
||||
$value = $this->format('S', true);
|
||||
break;
|
||||
|
||||
case 'week':
|
||||
$value = $this->format('W', true);
|
||||
break;
|
||||
|
||||
case 'year':
|
||||
$value = $this->format('Y', true);
|
||||
break;
|
||||
|
||||
default:
|
||||
$trace = debug_backtrace();
|
||||
trigger_error(
|
||||
'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'],
|
||||
E_USER_NOTICE
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to render the date object in the format specified in the public
|
||||
* static member JDate::$format.
|
||||
*
|
||||
* @return string The date as a formatted string.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return (string) parent::format(self::$format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates day of week number to a string.
|
||||
*
|
||||
* @param integer $day The numeric day of the week.
|
||||
* @param boolean $abbr Return the abbreviated day string?
|
||||
*
|
||||
* @return string The day of the week.
|
||||
*/
|
||||
public function dayToString($day, $abbr = false)
|
||||
{
|
||||
switch ($day)
|
||||
{
|
||||
case 0:
|
||||
default:
|
||||
return $abbr ? Text::_('SUN') : Text::_('SUNDAY');
|
||||
case 1:
|
||||
return $abbr ? Text::_('MON') : Text::_('MONDAY');
|
||||
case 2:
|
||||
return $abbr ? Text::_('TUE') : Text::_('TUESDAY');
|
||||
case 3:
|
||||
return $abbr ? Text::_('WED') : Text::_('WEDNESDAY');
|
||||
case 4:
|
||||
return $abbr ? Text::_('THU') : Text::_('THURSDAY');
|
||||
case 5:
|
||||
return $abbr ? Text::_('FRI') : Text::_('FRIDAY');
|
||||
case 6:
|
||||
return $abbr ? Text::_('SAT') : Text::_('SATURDAY');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date as a formatted string in a local calendar.
|
||||
*
|
||||
* @param string $format The date format specification string (see {@link PHP_MANUAL#date})
|
||||
* @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
|
||||
* @param boolean $translate True to translate localised strings
|
||||
*
|
||||
* @return string The date string in the specified format format.
|
||||
*/
|
||||
public function calendar($format, $local = false, $translate = true)
|
||||
{
|
||||
return $this->format($format, $local, $translate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date as a formatted string.
|
||||
*
|
||||
* @param string $format The date format specification string (see {@link PHP_MANUAL#date})
|
||||
* @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
|
||||
* @param boolean $translate True to translate localised strings
|
||||
*
|
||||
* @return string The date string in the specified format format.
|
||||
*/
|
||||
public function format($format, $local = false, $translate = true)
|
||||
{
|
||||
if ($translate)
|
||||
{
|
||||
// Do string replacements for date format options that can be translated.
|
||||
$format = preg_replace('/(^|[^\\\])D/', "\\1" . self::DAY_ABBR, $format);
|
||||
$format = preg_replace('/(^|[^\\\])l/', "\\1" . self::DAY_NAME, $format);
|
||||
$format = preg_replace('/(^|[^\\\])M/', "\\1" . self::MONTH_ABBR, $format);
|
||||
$format = preg_replace('/(^|[^\\\])F/', "\\1" . self::MONTH_NAME, $format);
|
||||
}
|
||||
|
||||
// If the returned time should not be local use GMT.
|
||||
if ($local == false && !empty(self::$gmt))
|
||||
{
|
||||
parent::setTimezone(self::$gmt);
|
||||
}
|
||||
|
||||
// Format the date.
|
||||
$return = parent::format($format);
|
||||
|
||||
if ($translate)
|
||||
{
|
||||
// Manually modify the month and day strings in the formatted time.
|
||||
if (strpos($return, self::DAY_ABBR) !== false)
|
||||
{
|
||||
$return = str_replace(self::DAY_ABBR, $this->dayToString(parent::format('w'), true), $return);
|
||||
}
|
||||
|
||||
if (strpos($return, self::DAY_NAME) !== false)
|
||||
{
|
||||
$return = str_replace(self::DAY_NAME, $this->dayToString(parent::format('w')), $return);
|
||||
}
|
||||
|
||||
if (strpos($return, self::MONTH_ABBR) !== false)
|
||||
{
|
||||
$return = str_replace(self::MONTH_ABBR, $this->monthToString(parent::format('n'), true), $return);
|
||||
}
|
||||
|
||||
if (strpos($return, self::MONTH_NAME) !== false)
|
||||
{
|
||||
$return = str_replace(self::MONTH_NAME, $this->monthToString(parent::format('n')), $return);
|
||||
}
|
||||
}
|
||||
|
||||
if ($local == false && !empty($this->tz))
|
||||
{
|
||||
parent::setTimezone($this->tz);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time offset from GMT in hours or seconds.
|
||||
*
|
||||
* @param boolean $hours True to return the value in hours.
|
||||
*
|
||||
* @return float The time offset from GMT either in hours or in seconds.
|
||||
*/
|
||||
public function getOffsetFromGmt($hours = false)
|
||||
{
|
||||
return (float) $hours ? ($this->tz->getOffset($this) / 3600) : $this->tz->getOffset($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates month number to a string.
|
||||
*
|
||||
* @param integer $month The numeric month of the year.
|
||||
* @param boolean $abbr If true, return the abbreviated month string
|
||||
*
|
||||
* @return string The month of the year.
|
||||
*/
|
||||
public function monthToString($month, $abbr = false)
|
||||
{
|
||||
switch ($month)
|
||||
{
|
||||
case 1:
|
||||
default:
|
||||
return $abbr ? Text::_('JANUARY_SHORT') : Text::_('JANUARY');
|
||||
case 2:
|
||||
return $abbr ? Text::_('FEBRUARY_SHORT') : Text::_('FEBRUARY');
|
||||
case 3:
|
||||
return $abbr ? Text::_('MARCH_SHORT') : Text::_('MARCH');
|
||||
case 4:
|
||||
return $abbr ? Text::_('APRIL_SHORT') : Text::_('APRIL');
|
||||
case 5:
|
||||
return $abbr ? Text::_('MAY_SHORT') : Text::_('MAY');
|
||||
case 6:
|
||||
return $abbr ? Text::_('JUNE_SHORT') : Text::_('JUNE');
|
||||
case 7:
|
||||
return $abbr ? Text::_('JULY_SHORT') : Text::_('JULY');
|
||||
case 8:
|
||||
return $abbr ? Text::_('AUGUST_SHORT') : Text::_('AUGUST');
|
||||
case 9:
|
||||
return $abbr ? Text::_('SEPTEMBER_SHORT') : Text::_('SEPTEMBER');
|
||||
case 10:
|
||||
return $abbr ? Text::_('OCTOBER_SHORT') : Text::_('OCTOBER');
|
||||
case 11:
|
||||
return $abbr ? Text::_('NOVEMBER_SHORT') : Text::_('NOVEMBER');
|
||||
case 12:
|
||||
return $abbr ? Text::_('DECEMBER_SHORT') : Text::_('DECEMBER');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to wrap the setTimezone() function and set the internal time zone object.
|
||||
*
|
||||
* @param DateTimeZone $tz The new DateTimeZone object.
|
||||
*
|
||||
* @return Date
|
||||
*
|
||||
* @note This method can't be type hinted due to a PHP bug: https://bugs.php.net/bug.php?id=61483
|
||||
*/
|
||||
public function setTimezone($tz)
|
||||
{
|
||||
$this->tz = $tz;
|
||||
|
||||
date_timezone_set($this, $tz);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date as an ISO 8601 string. IETF RFC 3339 defines the ISO 8601 format
|
||||
* and it can be found at the IETF Web site.
|
||||
*
|
||||
* @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
|
||||
*
|
||||
* @return string The date string in ISO 8601 format.
|
||||
*
|
||||
* @link http://www.ietf.org/rfc/rfc3339.txt
|
||||
*/
|
||||
public function toISO8601($local = false)
|
||||
{
|
||||
return $this->format(DateTime::RFC3339, $local, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date as an SQL datetime string.
|
||||
*
|
||||
* @param boolean $local True to return the date string in the local time zone, false to return it in
|
||||
* GMT.
|
||||
* @param JDatabaseDriver $db The database driver or null to use JFactory::getDbo()
|
||||
*
|
||||
* @return string The date string in SQL datetime format.
|
||||
*
|
||||
* @link http://dev.mysql.com/doc/refman/5.0/en/datetime.html
|
||||
*/
|
||||
public function toSql($local = false, JDatabaseDriver $db = null)
|
||||
{
|
||||
if ($db === null)
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
}
|
||||
|
||||
return $this->format($db->getDateFormat(), $local, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date as an RFC 822 string. IETF RFC 2822 supersedes RFC 822 and its definition
|
||||
* can be found at the IETF Web site.
|
||||
*
|
||||
* @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
|
||||
*
|
||||
* @return string The date string in RFC 822 format.
|
||||
*
|
||||
* @link http://www.ietf.org/rfc/rfc2822.txt
|
||||
*/
|
||||
public function toRFC822($local = false)
|
||||
{
|
||||
return $this->format(DateTime::RFC2822, $local, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date as UNIX time stamp.
|
||||
*
|
||||
* @return integer The date as a UNIX timestamp.
|
||||
*/
|
||||
public function toUnix()
|
||||
{
|
||||
return (int) parent::format('U');
|
||||
}
|
||||
}
|
||||
194
libraries/fof30/Date/DateDecorator.php
Normal file
194
libraries/fof30/Date/DateDecorator.php
Normal file
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Date;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use DateTime;
|
||||
use JDatabaseDriver;
|
||||
|
||||
/**
|
||||
* This decorator will get any DateTime descendant and turn it into a FOF30\Date\Date compatible class. If the methods
|
||||
* specific to Date/JDate are available they will be used. Otherwise a new Date object will be spun from the information
|
||||
* in the decorated DateTime object and the results of a call to its method will be returned.
|
||||
*/
|
||||
class DateDecorator extends Date
|
||||
{
|
||||
/**
|
||||
* The decorated object
|
||||
*
|
||||
* @var DateTime
|
||||
*/
|
||||
protected $decorated = null;
|
||||
|
||||
public function __construct($date = 'now', $tz = null)
|
||||
{
|
||||
if (is_object($date) && ($date instanceof DateTime))
|
||||
{
|
||||
$this->decorated = $date;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->decorated = new Date($date, $tz);
|
||||
}
|
||||
|
||||
$timestamp = $this->decorated->toISO8601(true);
|
||||
|
||||
parent::__construct($timestamp);
|
||||
|
||||
$this->setTimezone($this->decorated->getTimezone());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public static function getInstance($date = 'now', $tz = null)
|
||||
{
|
||||
$coreObject = new Date($date, $tz);
|
||||
|
||||
return new DateDecorator($coreObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to access properties of the date given by class to the format method.
|
||||
*
|
||||
* @param string $name The name of the property.
|
||||
*
|
||||
* @return mixed A value if the property name is valid, null otherwise.
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->decorated->$name;
|
||||
}
|
||||
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
if (method_exists($this->decorated, $name))
|
||||
{
|
||||
return call_user_func_array([$this->decorated, $name], $arguments);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("JDate object does not have a $name method");
|
||||
}
|
||||
|
||||
public function sub($interval)
|
||||
{
|
||||
// Note to self: ignore phpStorm; we must NOT use a typehint for $interval
|
||||
return $this->decorated->sub($interval);
|
||||
}
|
||||
|
||||
public function add($interval)
|
||||
{
|
||||
// Note to self: ignore phpStorm; we must NOT use a typehint for $interval
|
||||
return $this->decorated->add($interval);
|
||||
}
|
||||
|
||||
public function modify($modify)
|
||||
{
|
||||
return $this->decorated->modify($modify);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->decorated;
|
||||
}
|
||||
|
||||
public function dayToString($day, $abbr = false)
|
||||
{
|
||||
return $this->decorated->dayToString($day, $abbr);
|
||||
}
|
||||
|
||||
public function calendar($format, $local = false, $translate = true)
|
||||
{
|
||||
return $this->decorated->calendar($format, $local, $translate);
|
||||
}
|
||||
|
||||
public function format($format, $local = false, $translate = true)
|
||||
{
|
||||
if (($this->decorated instanceof Date) || ($this->decorated instanceof \Joomla\CMS\Date\Date))
|
||||
{
|
||||
return $this->decorated->format($format, $local, $translate);
|
||||
}
|
||||
|
||||
return $this->decorated->format($format);
|
||||
}
|
||||
|
||||
public function getOffsetFromGmt($hours = false)
|
||||
{
|
||||
return $this->decorated->getOffsetFromGMT($hours);
|
||||
}
|
||||
|
||||
public function monthToString($month, $abbr = false)
|
||||
{
|
||||
return $this->monthToString($month, $abbr);
|
||||
}
|
||||
|
||||
public function setTimezone($tz)
|
||||
{
|
||||
return $this->decorated->setTimezone($tz);
|
||||
}
|
||||
|
||||
public function getTimezone()
|
||||
{
|
||||
return $this->decorated->getTimezone();
|
||||
}
|
||||
|
||||
public function getOffset()
|
||||
{
|
||||
return $this->decorated->getOffset();
|
||||
}
|
||||
|
||||
public function toISO8601($local = false)
|
||||
{
|
||||
return $this->decorated->toISO8601($local);
|
||||
}
|
||||
|
||||
public function toSql($local = false, JDatabaseDriver $db = null)
|
||||
{
|
||||
return $this->decorated->toSql($local, $db);
|
||||
}
|
||||
|
||||
public function toRFC822($local = false)
|
||||
{
|
||||
return $this->decorated->toRFC822($local);
|
||||
}
|
||||
|
||||
public function toUnix()
|
||||
{
|
||||
return $this->decorated->toUnix();
|
||||
}
|
||||
|
||||
public function getTimestamp()
|
||||
{
|
||||
return $this->decorated->getTimestamp();
|
||||
}
|
||||
|
||||
public function setTime($hour, $minute, $second = 0, $microseconds = 0)
|
||||
{
|
||||
return $this->decorated->setTime($hour, $minute, $second, $microseconds);
|
||||
}
|
||||
|
||||
public function setDate($year, $month, $day)
|
||||
{
|
||||
return $this->decorated->setDate($year, $month, $day);
|
||||
}
|
||||
|
||||
public function setISODate($year, $week, $day = 1)
|
||||
{
|
||||
return $this->decorated->setISODate($year, $week, $day);
|
||||
}
|
||||
|
||||
public function setTimestamp($unixtimestamp)
|
||||
{
|
||||
return $this->decorated->setTimestamp($unixtimestamp);
|
||||
}
|
||||
|
||||
public function diff($datetime2, $absolute = false)
|
||||
{
|
||||
return $this->decorated->diff($datetime2, $absolute);
|
||||
}
|
||||
}
|
||||
381
libraries/fof30/Dispatcher/Dispatcher.php
Normal file
381
libraries/fof30/Dispatcher/Dispatcher.php
Normal file
@ -0,0 +1,381 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Dispatcher;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Controller\Controller;
|
||||
use FOF30\Dispatcher\Exception\AccessForbidden;
|
||||
use FOF30\TransparentAuthentication\TransparentAuthentication;
|
||||
|
||||
/**
|
||||
* A generic MVC dispatcher
|
||||
*
|
||||
* @property-read \FOF30\Input\Input $input The input object (magic __get returns the Input from the Container)
|
||||
*/
|
||||
class Dispatcher
|
||||
{
|
||||
/** @var string The name of the default view, in case none is specified */
|
||||
public $defaultView = null;
|
||||
|
||||
/** @var array Local cache of the dispatcher configuration */
|
||||
protected $config = [];
|
||||
|
||||
/** @var Container The container we belong to */
|
||||
protected $container = null;
|
||||
|
||||
/** @var string The view which will be rendered by the dispatcher */
|
||||
protected $view = null;
|
||||
|
||||
/** @var string The layout for rendering the view */
|
||||
protected $layout = null;
|
||||
|
||||
/** @var Controller The controller which will be used */
|
||||
protected $controller = null;
|
||||
|
||||
/** @var bool Is this user transparently logged in? */
|
||||
protected $isTransparentlyLoggedIn = false;
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* The $config array can contain the following optional values:
|
||||
* defaultView string The view to render if none is specified in $input
|
||||
*
|
||||
* Do note that $config is passed to the Controller and through it to the Model and View. Please see these classes
|
||||
* for more information on the configuration variables they accept.
|
||||
*
|
||||
* @param \FOF30\Container\Container $container
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(Container $container, array $config = [])
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
$this->config = $config;
|
||||
|
||||
$this->defaultView = $container->appConfig->get('dispatcher.defaultView', $this->defaultView);
|
||||
|
||||
if (isset($config['defaultView']))
|
||||
{
|
||||
$this->defaultView = $config['defaultView'];
|
||||
}
|
||||
|
||||
$this->supportCustomViewAndTaskParameters();
|
||||
|
||||
// Get the default values for the view and layout names
|
||||
$this->view = $this->input->getCmd('view', null);
|
||||
$this->layout = $this->input->getCmd('layout', null);
|
||||
|
||||
// Not redundant; you may pass an empty but non-null view which is invalid, so we need the fallback
|
||||
if (empty($this->view))
|
||||
{
|
||||
$this->view = $this->defaultView;
|
||||
$this->container->input->set('view', $this->view);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic get method. Handles magic properties:
|
||||
* $this->input mapped to $this->container->input
|
||||
*
|
||||
* @param string $name The property to fetch
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
// Handle $this->input
|
||||
if ($name == 'input')
|
||||
{
|
||||
return $this->container->input;
|
||||
}
|
||||
|
||||
// Property not found; raise error
|
||||
$trace = debug_backtrace();
|
||||
trigger_error(
|
||||
'Undefined property via __get(): ' . $name .
|
||||
' in ' . $trace[0]['file'] .
|
||||
' on line ' . $trace[0]['line'],
|
||||
E_USER_NOTICE);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main code of the Dispatcher. It spawns the necessary controller and
|
||||
* runs it.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws AccessForbidden When the access is forbidden
|
||||
*/
|
||||
public function dispatch()
|
||||
{
|
||||
// Load the translations for this component;
|
||||
$this->container->platform->loadTranslations($this->container->componentName);
|
||||
|
||||
// Perform transparent authentication
|
||||
if ($this->container->platform->getUser()->guest)
|
||||
{
|
||||
$this->transparentAuthenticationLogin();
|
||||
}
|
||||
|
||||
// Get the event names (different for CLI)
|
||||
$onBeforeEventName = 'onBeforeDispatch';
|
||||
$onAfterEventName = 'onAfterDispatch';
|
||||
|
||||
if ($this->container->platform->isCli())
|
||||
{
|
||||
$onBeforeEventName = 'onBeforeDispatchCLI';
|
||||
$onAfterEventName = 'onAfterDispatchCLI';
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$result = $this->triggerEvent($onBeforeEventName);
|
||||
$error = '';
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
$result = false;
|
||||
$error = $e->getMessage();
|
||||
}
|
||||
|
||||
if ($result === false)
|
||||
{
|
||||
if ($this->container->platform->isCli())
|
||||
{
|
||||
$this->container->platform->setHeader('Status', '403 Forbidden', true);
|
||||
}
|
||||
|
||||
$this->transparentAuthenticationLogout();
|
||||
|
||||
$this->container->platform->showErrorPage(new AccessForbidden);
|
||||
}
|
||||
|
||||
// Get and execute the controller
|
||||
$view = $this->input->getCmd('view', $this->defaultView);
|
||||
$task = $this->input->getCmd('task', 'default');
|
||||
|
||||
if (empty($task))
|
||||
{
|
||||
$task = 'default';
|
||||
$this->input->set('task', $task);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$this->controller = $this->container->factory->controller($view, $this->config);
|
||||
$status = $this->controller->execute($task);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$this->container->platform->showErrorPage($e);
|
||||
|
||||
// Redundant; just to make code sniffers happy
|
||||
return;
|
||||
}
|
||||
|
||||
if ($status !== false)
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->triggerEvent($onAfterEventName);
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
$status = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (($status === false))
|
||||
{
|
||||
if ($this->container->platform->isCli())
|
||||
{
|
||||
$this->container->platform->setHeader('Status', '403 Forbidden', true);
|
||||
}
|
||||
|
||||
$this->transparentAuthenticationLogout();
|
||||
|
||||
$this->container->platform->showErrorPage(new AccessForbidden);
|
||||
}
|
||||
|
||||
$this->transparentAuthenticationLogout();
|
||||
|
||||
$this->controller->redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the Controller object currently in use by the dispatcher
|
||||
*
|
||||
* @return Controller
|
||||
*/
|
||||
public function &getController()
|
||||
{
|
||||
return $this->controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an object-specific event. The event runs both locally –if a suitable method exists– and through the
|
||||
* Joomla! plugin system. A true/false return value is expected. The first false return cancels the event.
|
||||
*
|
||||
* EXAMPLE
|
||||
* Component: com_foobar, Object name: item, Event: onBeforeDispatch, Arguments: array(123, 456)
|
||||
* The event calls:
|
||||
* 1. $this->onBeforeDispatch(123, 456)
|
||||
* 2. Joomla! plugin event onComFoobarDispatcherBeforeDispatch($this, 123, 456)
|
||||
*
|
||||
* @param string $event The name of the event, typically named onPredicateVerb e.g. onBeforeKick
|
||||
* @param array $arguments The arguments to pass to the event handlers
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function triggerEvent($event, array $arguments = [])
|
||||
{
|
||||
$result = true;
|
||||
|
||||
// If there is an object method for this event, call it
|
||||
if (method_exists($this, $event))
|
||||
{
|
||||
switch (count($arguments))
|
||||
{
|
||||
case 0:
|
||||
$result = $this->{$event}();
|
||||
break;
|
||||
case 1:
|
||||
$result = $this->{$event}($arguments[0]);
|
||||
break;
|
||||
case 2:
|
||||
$result = $this->{$event}($arguments[0], $arguments[1]);
|
||||
break;
|
||||
case 3:
|
||||
$result = $this->{$event}($arguments[0], $arguments[1], $arguments[2]);
|
||||
break;
|
||||
case 4:
|
||||
$result = $this->{$event}($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
|
||||
break;
|
||||
case 5:
|
||||
$result = $this->{$event}($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
|
||||
break;
|
||||
default:
|
||||
$result = call_user_func_array([$this, $event], $arguments);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($result === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// All other event handlers live outside this object, therefore they need to be passed a reference to this
|
||||
// objects as the first argument.
|
||||
array_unshift($arguments, $this);
|
||||
|
||||
// If we have an "on" prefix for the event (e.g. onFooBar) remove it and stash it for later.
|
||||
$prefix = '';
|
||||
|
||||
if (substr($event, 0, 2) == 'on')
|
||||
{
|
||||
$prefix = 'on';
|
||||
$event = substr($event, 2);
|
||||
}
|
||||
|
||||
// Get the component/model prefix for the event
|
||||
$prefix .= 'Com' . ucfirst($this->container->bareComponentName) . 'Dispatcher';
|
||||
|
||||
// The event name will be something like onComFoobarItemsBeforeSomething
|
||||
$event = $prefix . $event;
|
||||
|
||||
// Call the Joomla! plugins
|
||||
$results = $this->container->platform->runPlugins($event, $arguments);
|
||||
|
||||
if (!empty($results))
|
||||
{
|
||||
foreach ($results as $result)
|
||||
{
|
||||
if ($result === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the transparent authentication log in
|
||||
*/
|
||||
protected function transparentAuthenticationLogin()
|
||||
{
|
||||
/** @var TransparentAuthentication $transparentAuth */
|
||||
$transparentAuth = $this->container->transparentAuth;
|
||||
$authInfo = $transparentAuth->getTransparentAuthenticationCredentials();
|
||||
|
||||
if (empty($authInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->isTransparentlyLoggedIn = $this->container->platform->loginUser($authInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the transparent authentication log out
|
||||
*/
|
||||
protected function transparentAuthenticationLogout()
|
||||
{
|
||||
if (!$this->isTransparentlyLoggedIn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var TransparentAuthentication $transparentAuth */
|
||||
$transparentAuth = $this->container->transparentAuth;
|
||||
|
||||
if (!$transparentAuth->getLogoutOnExit())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->container->platform->logoutUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds support for akview/aktask in lieu of view and task.
|
||||
*
|
||||
* This is for future-proofing FOF in case Joomla assigns special meaning to view and task, e.g. by trying to find a
|
||||
* specific controller / task class instead of letting the component's front-end router handle it. If that happens
|
||||
* FOF components can have a single Joomla-compatible view/task which launches the Dispatcher and perform internal
|
||||
* routing using akview/aktask.
|
||||
*
|
||||
* @return void
|
||||
* @since 3.6.3
|
||||
*/
|
||||
private function supportCustomViewAndTaskParameters()
|
||||
{
|
||||
$view = $this->input->getCmd('akview', null);
|
||||
$task = $this->input->getCmd('aktask', null);
|
||||
|
||||
if (!is_null($view))
|
||||
{
|
||||
$this->input->remove('akview');
|
||||
$this->input->set('view', $view);
|
||||
}
|
||||
|
||||
if (!is_null($task))
|
||||
{
|
||||
$this->input->remove('aktask');
|
||||
$this->input->set('task', $task);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
libraries/fof30/Dispatcher/Exception/AccessForbidden.php
Normal file
31
libraries/fof30/Dispatcher/Exception/AccessForbidden.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Dispatcher\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Exception thrown when the access to the requested resource is forbidden under the current execution context
|
||||
*/
|
||||
class AccessForbidden extends RuntimeException
|
||||
{
|
||||
public function __construct($message = "", $code = 403, Exception $previous = null)
|
||||
{
|
||||
if (empty($message))
|
||||
{
|
||||
$message = Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN');
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
81
libraries/fof30/Dispatcher/Mixin/ViewAliases.php
Normal file
81
libraries/fof30/Dispatcher/Mixin/ViewAliases.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Dispatcher\Mixin;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
/**
|
||||
* Lets you create view aliases. When you access a view alias the real view is loaded instead. You can optionally have
|
||||
* an HTTPS 301 redirection for GET requests to URLs that use the view name alias.
|
||||
*
|
||||
* IMPORTANT: This is a mixin (or, as we call it in PHP, a trait). Traits require PHP 5.4 or later. If you opt to use
|
||||
* this trait your component will no longer work under PHP 5.3.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* • Override $viewNameAliases with your view names map.
|
||||
* • If you want to issue HTTP 301 for GET requests set $permanentAliasRedirectionOnGET to true.
|
||||
* • If you have an onBeforeDispatch method remember to alias and call this traits' onBeforeDispatch method at the top.
|
||||
*
|
||||
* Regarding the last point, if you've never used traits before, the code looks like this. Top of the class:
|
||||
* use ViewAliases {
|
||||
* onBeforeDispatch as onBeforeDispatchViewAliases;
|
||||
* }
|
||||
* and inside your custom onBeforeDispatch method, the first statement should be:
|
||||
* $this->onBeforeDispatchViewAliases();
|
||||
* Simple!
|
||||
*/
|
||||
trait ViewAliases
|
||||
{
|
||||
/**
|
||||
* Maps view name aliases to actual views. The format is 'alias' => 'RealView'.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $viewNameAliases = [];
|
||||
|
||||
/**
|
||||
* If set to true, any GET request to the alias view will result in an HTTP 301 permanent redirection to the real
|
||||
* view name.
|
||||
*
|
||||
* This does NOT apply to POST, PUT, DELETE etc URLs. When you submit form data you cannot have a redirection. The
|
||||
* browser will _typically_ not resend the submitted data.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $permanentAliasRedirectionOnGET = false;
|
||||
|
||||
/**
|
||||
* Transparently replaces old view names with their counterparts.
|
||||
*
|
||||
* If you are overriding this method in your component remember to alias it and call it from your overridden method.
|
||||
*/
|
||||
protected function onBeforeDispatch()
|
||||
{
|
||||
if (!array_key_exists($this->view, $this->viewNameAliases))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->view = $this->viewNameAliases[$this->view];
|
||||
$this->container->input->set('view', $this->view);
|
||||
|
||||
// Perform HTTP 301 Moved permanently redirection on GET requests if requested to do so
|
||||
if ($this->permanentAliasRedirectionOnGET && isset($_SERVER['REQUEST_METHOD'])
|
||||
&& (strtoupper($_SERVER['REQUEST_METHOD']) == 'GET')
|
||||
)
|
||||
{
|
||||
$url = Uri::getInstance();
|
||||
$url->setVar('view', $this->view);
|
||||
|
||||
$this->container->platform->redirect($url, 301);
|
||||
}
|
||||
}
|
||||
}
|
||||
140
libraries/fof30/Download/Adapter/AbstractAdapter.php
Normal file
140
libraries/fof30/Download/Adapter/AbstractAdapter.php
Normal file
@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Download\Adapter;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Download\DownloadInterface;
|
||||
use FOF30\Download\Exception\DownloadError;
|
||||
|
||||
abstract class AbstractAdapter implements DownloadInterface
|
||||
{
|
||||
/**
|
||||
* Load order priority
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $priority = 100;
|
||||
|
||||
/**
|
||||
* Name of the adapter (identical to filename)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name = '';
|
||||
|
||||
/**
|
||||
* Is this adapter supported in the current execution environment?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $isSupported = false;
|
||||
|
||||
/**
|
||||
* Does this adapter support chunked downloads?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $supportsChunkDownload = false;
|
||||
|
||||
/**
|
||||
* Does this adapter support querying the remote file's size?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $supportsFileSize = false;
|
||||
|
||||
/**
|
||||
* Does this download adapter support downloading files in chunks?
|
||||
*
|
||||
* @return boolean True if chunk download is supported
|
||||
*/
|
||||
public function supportsChunkDownload()
|
||||
{
|
||||
return $this->supportsChunkDownload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this download adapter support reading the size of a remote file?
|
||||
*
|
||||
* @return boolean True if remote file size determination is supported
|
||||
*/
|
||||
public function supportsFileSize()
|
||||
{
|
||||
return $this->supportsFileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this download class supported in the current server environment?
|
||||
*
|
||||
* @return boolean True if this server environment supports this download class
|
||||
*/
|
||||
public function isSupported()
|
||||
{
|
||||
return $this->isSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the priority of this adapter. If multiple download adapters are
|
||||
* supported on a site, the one with the highest priority will be
|
||||
* used.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function getPriority()
|
||||
{
|
||||
return $this->priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this download adapter in use
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a part (or the whole) of a remote URL and return the downloaded
|
||||
* data. You are supposed to check the size of the returned data. If it's
|
||||
* smaller than what you expected you've reached end of file. If it's empty
|
||||
* you have tried reading past EOF. If it's larger than what you expected
|
||||
* the server doesn't support chunk downloads.
|
||||
*
|
||||
* If this class' supportsChunkDownload returns false you should assume
|
||||
* that the $from and $to parameters will be ignored.
|
||||
*
|
||||
* @param string $url The remote file's URL
|
||||
* @param integer $from Byte range to start downloading from. Use null for start of file.
|
||||
* @param integer $to Byte range to stop downloading. Use null to download the entire file ($from is
|
||||
* ignored)
|
||||
* @param array $params Additional params that will be added before performing the download
|
||||
*
|
||||
* @return string The raw file data retrieved from the remote URL.
|
||||
*
|
||||
* @throws DownloadError A generic exception is thrown on error
|
||||
*/
|
||||
public function downloadAndReturn($url, $from = null, $to = null, array $params = [])
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a remote file in bytes
|
||||
*
|
||||
* @param string $url The remote file's URL
|
||||
*
|
||||
* @return integer The file size, or -1 if the remote server doesn't support this feature
|
||||
*/
|
||||
public function getFileSize($url)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
273
libraries/fof30/Download/Adapter/Curl.php
Normal file
273
libraries/fof30/Download/Adapter/Curl.php
Normal file
@ -0,0 +1,273 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Download\Adapter;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Download\DownloadInterface;
|
||||
use FOF30\Download\Exception\DownloadError;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
/**
|
||||
* A download adapter using the cURL PHP integration
|
||||
*/
|
||||
class Curl extends AbstractAdapter implements DownloadInterface
|
||||
{
|
||||
protected $headers = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->priority = 110;
|
||||
$this->supportsFileSize = true;
|
||||
$this->supportsChunkDownload = true;
|
||||
$this->name = 'curl';
|
||||
$this->isSupported = function_exists('curl_init') && function_exists('curl_exec') && function_exists('curl_close');
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a part (or the whole) of a remote URL and return the downloaded
|
||||
* data. You are supposed to check the size of the returned data. If it's
|
||||
* smaller than what you expected you've reached end of file. If it's empty
|
||||
* you have tried reading past EOF. If it's larger than what you expected
|
||||
* the server doesn't support chunk downloads.
|
||||
*
|
||||
* If this class' supportsChunkDownload returns false you should assume
|
||||
* that the $from and $to parameters will be ignored.
|
||||
*
|
||||
* @param string $url The remote file's URL
|
||||
* @param integer $from Byte range to start downloading from. Use null for start of file.
|
||||
* @param integer $to Byte range to stop downloading. Use null to download the entire file ($from is
|
||||
* ignored)
|
||||
* @param array $params Additional params that will be added before performing the download
|
||||
*
|
||||
* @return string The raw file data retrieved from the remote URL.
|
||||
*
|
||||
* @throws DownloadError A generic exception is thrown on error
|
||||
*/
|
||||
public function downloadAndReturn($url, $from = null, $to = null, array $params = [])
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
if (empty($from))
|
||||
{
|
||||
$from = 0;
|
||||
}
|
||||
|
||||
if (empty($to))
|
||||
{
|
||||
$to = 0;
|
||||
}
|
||||
|
||||
if ($to < $from)
|
||||
{
|
||||
$temp = $to;
|
||||
$to = $from;
|
||||
$from = $temp;
|
||||
unset($temp);
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
|
||||
curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
curl_setopt($ch, CURLOPT_SSLVERSION, 0);
|
||||
curl_setopt($ch, CURLOPT_CAINFO, JPATH_LIBRARIES . '/src/Http/Transport/cacert.pem');
|
||||
curl_setopt($ch, CURLOPT_HEADERFUNCTION, [$this, 'reponseHeaderCallback']);
|
||||
|
||||
if (!(empty($from) && empty($to)))
|
||||
{
|
||||
curl_setopt($ch, CURLOPT_RANGE, "$from-$to");
|
||||
}
|
||||
|
||||
if (!is_array($params))
|
||||
{
|
||||
$params = [];
|
||||
}
|
||||
|
||||
$patched_accept_encoding = false;
|
||||
|
||||
// Work around LiteSpeed sending compressed output under HTTP/2 when no encoding was requested
|
||||
// See https://github.com/joomla/joomla-cms/issues/21423#issuecomment-410941000
|
||||
if (defined('CURLOPT_ACCEPT_ENCODING'))
|
||||
{
|
||||
if (!array_key_exists(CURLOPT_ACCEPT_ENCODING, $params))
|
||||
{
|
||||
$params[CURLOPT_ACCEPT_ENCODING] = 'identity';
|
||||
}
|
||||
|
||||
$patched_accept_encoding = true;
|
||||
}
|
||||
|
||||
if (!empty($params))
|
||||
{
|
||||
foreach ($params as $k => $v)
|
||||
{
|
||||
// I couldn't patch the accept encoding header (missing constant), so I'll check if we manually set it
|
||||
if (!$patched_accept_encoding && $k == CURLOPT_HTTPHEADER)
|
||||
{
|
||||
foreach ($v as $custom_header)
|
||||
{
|
||||
// Ok, we explicitly set the Accept-Encoding header, so we consider it patched
|
||||
if (stripos($custom_header, 'Accept-Encoding') !== false)
|
||||
{
|
||||
$patched_accept_encoding = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@curl_setopt($ch, $k, $v);
|
||||
}
|
||||
}
|
||||
|
||||
// Accept encoding wasn't patched, let's manually do that
|
||||
if (!$patched_accept_encoding)
|
||||
{
|
||||
@curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept-Encoding: identity']);
|
||||
|
||||
$patched_accept_encoding = true;
|
||||
}
|
||||
|
||||
$result = curl_exec($ch);
|
||||
|
||||
$errno = curl_errno($ch);
|
||||
$errmsg = curl_error($ch);
|
||||
$error = '';
|
||||
$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
if ($result === false)
|
||||
{
|
||||
$error = Text::sprintf('LIB_FOF_DOWNLOAD_ERR_CURL_ERROR', $errno, $errmsg);
|
||||
}
|
||||
elseif (($http_status >= 300) && ($http_status <= 399) && isset($this->headers['location']) && !empty($this->headers['location']))
|
||||
{
|
||||
return $this->downloadAndReturn($this->headers['location'], $from, $to, $params);
|
||||
}
|
||||
elseif ($http_status > 399)
|
||||
{
|
||||
$result = false;
|
||||
$errno = $http_status;
|
||||
$error = Text::sprintf('LIB_FOF_DOWNLOAD_ERR_HTTPERROR', $http_status);
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
if ($result === false)
|
||||
{
|
||||
throw new DownloadError($error, $errno);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a remote file in bytes
|
||||
*
|
||||
* @param string $url The remote file's URL
|
||||
*
|
||||
* @return integer The file size, or -1 if the remote server doesn't support this feature
|
||||
*/
|
||||
public function getFileSize($url)
|
||||
{
|
||||
$result = -1;
|
||||
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
curl_setopt($ch, CURLOPT_SSLVERSION, 0);
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_NOBODY, true);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_CAINFO, JPATH_LIBRARIES . '/src/Http/Transport/cacert.pem');
|
||||
|
||||
$data = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($data)
|
||||
{
|
||||
$content_length = "unknown";
|
||||
$status = "unknown";
|
||||
$redirection = null;
|
||||
|
||||
if (preg_match("/^HTTP\/1\.[01] (\d\d\d)/i", $data, $matches))
|
||||
{
|
||||
$status = (int) $matches[1];
|
||||
}
|
||||
|
||||
if (preg_match("/Content-Length: (\d+)/i", $data, $matches))
|
||||
{
|
||||
$content_length = (int) $matches[1];
|
||||
}
|
||||
|
||||
if (preg_match("/Location: (.*)/i", $data, $matches))
|
||||
{
|
||||
$redirection = (int) $matches[1];
|
||||
}
|
||||
|
||||
if ($status == 200 || ($status > 300 && $status <= 308))
|
||||
{
|
||||
$result = $content_length;
|
||||
}
|
||||
|
||||
if (($status > 300) && ($status <= 308))
|
||||
{
|
||||
if (!empty($redirection))
|
||||
{
|
||||
return $this->getFileSize($redirection);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the HTTP headers returned by cURL
|
||||
*
|
||||
* @param resource $ch cURL resource handle (unused)
|
||||
* @param string $data Each header line, as returned by the server
|
||||
*
|
||||
* @return int The length of the $data string
|
||||
*/
|
||||
protected function reponseHeaderCallback(&$ch, &$data)
|
||||
{
|
||||
$strlen = strlen($data);
|
||||
|
||||
if (($strlen) <= 2)
|
||||
{
|
||||
return $strlen;
|
||||
}
|
||||
|
||||
if (substr($data, 0, 4) == 'HTTP')
|
||||
{
|
||||
return $strlen;
|
||||
}
|
||||
|
||||
if (strpos($data, ':') === false)
|
||||
{
|
||||
return $strlen;
|
||||
}
|
||||
|
||||
[$header, $value] = explode(': ', trim($data), 2);
|
||||
|
||||
$this->headers[strtolower($header)] = $value;
|
||||
|
||||
return $strlen;
|
||||
}
|
||||
}
|
||||
164
libraries/fof30/Download/Adapter/Fopen.php
Normal file
164
libraries/fof30/Download/Adapter/Fopen.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Download\Adapter;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Download\DownloadInterface;
|
||||
use FOF30\Download\Exception\DownloadError;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
/**
|
||||
* A download adapter using URL fopen() wrappers
|
||||
*/
|
||||
class Fopen extends AbstractAdapter implements DownloadInterface
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->priority = 100;
|
||||
$this->supportsFileSize = false;
|
||||
$this->supportsChunkDownload = true;
|
||||
$this->name = 'fopen';
|
||||
|
||||
// If we are not allowed to use ini_get, we assume that URL fopen is disabled
|
||||
if (!function_exists('ini_get'))
|
||||
{
|
||||
$this->isSupported = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->isSupported = ini_get('allow_url_fopen');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a part (or the whole) of a remote URL and return the downloaded
|
||||
* data. You are supposed to check the size of the returned data. If it's
|
||||
* smaller than what you expected you've reached end of file. If it's empty
|
||||
* you have tried reading past EOF. If it's larger than what you expected
|
||||
* the server doesn't support chunk downloads.
|
||||
*
|
||||
* If this class' supportsChunkDownload returns false you should assume
|
||||
* that the $from and $to parameters will be ignored.
|
||||
*
|
||||
* @param string $url The remote file's URL
|
||||
* @param integer $from Byte range to start downloading from. Use null for start of file.
|
||||
* @param integer $to Byte range to stop downloading. Use null to download the entire file ($from is
|
||||
* ignored)
|
||||
* @param array $params Additional params that will be added before performing the download
|
||||
*
|
||||
* @return string The raw file data retrieved from the remote URL.
|
||||
*
|
||||
* @throws DownloadError A generic exception is thrown on error
|
||||
*/
|
||||
public function downloadAndReturn($url, $from = null, $to = null, array $params = [])
|
||||
{
|
||||
if (empty($from))
|
||||
{
|
||||
$from = 0;
|
||||
}
|
||||
|
||||
if (empty($to))
|
||||
{
|
||||
$to = 0;
|
||||
}
|
||||
|
||||
if ($to < $from)
|
||||
{
|
||||
$temp = $to;
|
||||
$to = $from;
|
||||
$from = $temp;
|
||||
unset($temp);
|
||||
}
|
||||
|
||||
|
||||
if (!(empty($from) && empty($to)))
|
||||
{
|
||||
$options = [
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'header' => "Range: bytes=$from-$to\r\n",
|
||||
],
|
||||
'ssl' => [
|
||||
'verify_peer' => true,
|
||||
'cafile' => JPATH_LIBRARIES . '/src/Http/Transport/cacert.pem',
|
||||
'verify_depth' => 5,
|
||||
],
|
||||
];
|
||||
|
||||
$options = array_merge($options, $params);
|
||||
|
||||
$context = stream_context_create($options);
|
||||
$result = @file_get_contents($url, false, $context, $from - $to + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
$options = [
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
],
|
||||
'ssl' => [
|
||||
'verify_peer' => true,
|
||||
'cafile' => JPATH_LIBRARIES . '/src/Http/Transport/cacert.pem',
|
||||
'verify_depth' => 5,
|
||||
],
|
||||
];
|
||||
|
||||
$options = array_merge($options, $params);
|
||||
|
||||
$context = stream_context_create($options);
|
||||
$result = @file_get_contents($url, false, $context);
|
||||
}
|
||||
|
||||
global $http_response_header_test;
|
||||
|
||||
if (!isset($http_response_header) && empty($http_response_header_test))
|
||||
{
|
||||
$error = Text::_('LIB_FOF_DOWNLOAD_ERR_FOPEN_ERROR');
|
||||
throw new DownloadError($error, 404);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Used for testing
|
||||
if (!isset($http_response_header) && !empty($http_response_header_test))
|
||||
{
|
||||
$http_response_header = $http_response_header_test;
|
||||
}
|
||||
|
||||
$http_code = 200;
|
||||
$nLines = count($http_response_header);
|
||||
|
||||
for ($i = $nLines - 1; $i >= 0; $i--)
|
||||
{
|
||||
$line = $http_response_header[$i];
|
||||
if (strncasecmp("HTTP", $line, 4) == 0)
|
||||
{
|
||||
$response = explode(' ', $line);
|
||||
$http_code = $response[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($http_code >= 299)
|
||||
{
|
||||
$error = Text::sprintf('LIB_FOF_DOWNLOAD_ERR_HTTPERROR', $http_code);
|
||||
throw new DownloadError($error, $http_code);
|
||||
}
|
||||
}
|
||||
|
||||
if ($result === false)
|
||||
{
|
||||
$error = Text::sprintf('LIB_FOF_DOWNLOAD_ERR_FOPEN_ERROR');
|
||||
throw new DownloadError($error, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
490
libraries/fof30/Download/Download.php
Normal file
490
libraries/fof30/Download/Download.php
Normal file
@ -0,0 +1,490 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Download;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Download\Exception\DownloadError;
|
||||
use FOF30\Timer\Timer;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
class Download
|
||||
{
|
||||
/**
|
||||
* The component container object
|
||||
*
|
||||
* @var Container
|
||||
*/
|
||||
protected $container = null;
|
||||
|
||||
/**
|
||||
* Parameters passed from the GUI when importing from URL
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $params = [];
|
||||
|
||||
/**
|
||||
* The download adapter which will be used by this class
|
||||
*
|
||||
* @var DownloadInterface
|
||||
*/
|
||||
private $adapter = null;
|
||||
|
||||
/**
|
||||
* Additional params that will be passed to the adapter while performing the download
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $adapterOptions = [];
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param Container $c The component container
|
||||
*/
|
||||
public function __construct(Container $c)
|
||||
{
|
||||
$this->container = $c;
|
||||
|
||||
// Find the best fitting adapter
|
||||
$allAdapters = self::getFiles(__DIR__ . '/Adapter', [], ['AbstractAdapter.php']);
|
||||
$priority = 0;
|
||||
|
||||
foreach ($allAdapters as $adapterInfo)
|
||||
{
|
||||
/** @var Adapter\AbstractAdapter $adapter */
|
||||
$adapter = new $adapterInfo['classname'];
|
||||
|
||||
if (!$adapter->isSupported())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($adapter->priority > $priority)
|
||||
{
|
||||
$this->adapter = $adapter;
|
||||
$priority = $adapter->priority;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the language strings
|
||||
$c->platform->loadTranslations('lib_fof30');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will crawl a starting directory and get all the valid files
|
||||
* that will be analyzed by __construct. Then it organizes them into an
|
||||
* associative array.
|
||||
*
|
||||
* @param string $path Folder where we should start looking
|
||||
* @param array $ignoreFolders Folder ignore list
|
||||
* @param array $ignoreFiles File ignore list
|
||||
*
|
||||
* @return array Associative array, where the `fullpath` key contains the path to the file,
|
||||
* and the `classname` key contains the name of the class
|
||||
*/
|
||||
protected static function getFiles($path, array $ignoreFolders = [], array $ignoreFiles = [])
|
||||
{
|
||||
$return = [];
|
||||
|
||||
$files = self::scanDirectory($path, $ignoreFolders, $ignoreFiles);
|
||||
|
||||
// Ok, I got the files, now I have to organize them
|
||||
foreach ($files as $file)
|
||||
{
|
||||
$clean = str_replace($path, '', $file);
|
||||
$clean = trim(str_replace('\\', '/', $clean), '/');
|
||||
|
||||
$parts = explode('/', $clean);
|
||||
|
||||
$return[] = [
|
||||
'fullpath' => $file,
|
||||
'classname' => '\\FOF30\\Download\\Adapter\\' . ucfirst(basename($parts[0], '.php')),
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function that will scan every directory unless it's in the
|
||||
* ignore list. Files that aren't in the ignore list are returned.
|
||||
*
|
||||
* @param string $path Folder where we should start looking
|
||||
* @param array $ignoreFolders Folder ignore list
|
||||
* @param array $ignoreFiles File ignore list
|
||||
*
|
||||
* @return array List of all the files
|
||||
*/
|
||||
protected static function scanDirectory($path, array $ignoreFolders = [], array $ignoreFiles = [])
|
||||
{
|
||||
$return = [];
|
||||
|
||||
$handle = @opendir($path);
|
||||
|
||||
if (!$handle)
|
||||
{
|
||||
return $return;
|
||||
}
|
||||
|
||||
while (($file = readdir($handle)) !== false)
|
||||
{
|
||||
if ($file == '.' || $file == '..')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$fullpath = $path . '/' . $file;
|
||||
|
||||
if ((is_dir($fullpath) && in_array($file, $ignoreFolders)) || (is_file($fullpath) && in_array($file, $ignoreFiles)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dir($fullpath))
|
||||
{
|
||||
$return = array_merge(self::scanDirectory($fullpath, $ignoreFolders, $ignoreFiles), $return);
|
||||
}
|
||||
else
|
||||
{
|
||||
$return[] = $path . '/' . $file;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the use of a specific adapter
|
||||
*
|
||||
* @param string $className The name of the class or the name of the adapter
|
||||
*/
|
||||
public function setAdapter($className)
|
||||
{
|
||||
$adapter = null;
|
||||
|
||||
if (class_exists($className, true))
|
||||
{
|
||||
$adapter = new $className;
|
||||
}
|
||||
elseif (class_exists('\\FOF30\\Download\\Adapter\\' . ucfirst($className)))
|
||||
{
|
||||
$className = '\\FOF30\\Download\\Adapter\\' . ucfirst($className);
|
||||
$adapter = new $className;
|
||||
}
|
||||
|
||||
if (is_object($adapter) && ($adapter instanceof DownloadInterface))
|
||||
{
|
||||
$this->adapter = $adapter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the current adapter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAdapterName()
|
||||
{
|
||||
if (is_object($this->adapter))
|
||||
{
|
||||
$class = get_class($this->adapter);
|
||||
|
||||
return strtolower(str_ireplace('FOF30\\Download\\Adapter\\', '', $class));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the additional options for the adapter
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getAdapterOptions()
|
||||
{
|
||||
return $this->adapterOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the additional options for the adapter
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function setAdapterOptions(array $options)
|
||||
{
|
||||
$this->adapterOptions = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download data from a URL and return it.
|
||||
*
|
||||
* Important note about ranges: byte ranges start at 0. This means that the first 500 bytes of a file are from 0
|
||||
* to 499, NOT from 1 to 500. If you ask more bytes than there are in the file or a range which is invalid or does
|
||||
* not exist this method will return false.
|
||||
*
|
||||
* @param string $url The URL to download from
|
||||
* @param int $from Byte range to start downloading from. Use null (default) for start of file.
|
||||
* @param int $to Byte range to stop downloading. Use null to download the entire file ($from will be
|
||||
* ignored!)
|
||||
*
|
||||
* @return bool|string The downloaded data or false on failure
|
||||
*/
|
||||
public function getFromURL($url, $from = null, $to = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return $this->adapter->downloadAndReturn($url, $from, $to, $this->adapterOptions);
|
||||
}
|
||||
catch (DownloadError $e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the staggered download of file.
|
||||
*
|
||||
* @param array $params A parameters array, as sent by the user interface
|
||||
*
|
||||
* @return array A return status array
|
||||
*/
|
||||
public function importFromURL($params)
|
||||
{
|
||||
$this->params = $params;
|
||||
|
||||
// Fetch data
|
||||
$url = $this->getParam('url');
|
||||
$localFilename = $this->getParam('localFilename');
|
||||
$frag = $this->getParam('frag', -1);
|
||||
$totalSize = $this->getParam('totalSize', -1);
|
||||
$doneSize = $this->getParam('doneSize', -1);
|
||||
$maxExecTime = $this->getParam('maxExecTime', 5);
|
||||
$runTimeBias = $this->getParam('runTimeBias', 75);
|
||||
$length = $this->getParam('length', 1048576);
|
||||
|
||||
if (empty($localFilename))
|
||||
{
|
||||
$localFilename = basename($url);
|
||||
|
||||
if (strpos($localFilename, '?') !== false)
|
||||
{
|
||||
$paramsPos = strpos($localFilename, '?');
|
||||
$localFilename = substr($localFilename, 0, $paramsPos - 1);
|
||||
|
||||
$platformBaseDirectories = $this->container->platform->getPlatformBaseDirs();
|
||||
$tmpDir = $platformBaseDirectories['tmp'];
|
||||
$tmpDir = rtrim($tmpDir, '/\\');
|
||||
|
||||
$localFilename = $tmpDir . '/' . $localFilename;
|
||||
}
|
||||
}
|
||||
|
||||
// Init retArray
|
||||
$retArray = [
|
||||
"status" => true,
|
||||
"error" => '',
|
||||
"frag" => $frag,
|
||||
"totalSize" => $totalSize,
|
||||
"doneSize" => $doneSize,
|
||||
"percent" => 0,
|
||||
"localfile" => $localFilename,
|
||||
];
|
||||
|
||||
try
|
||||
{
|
||||
$timer = new Timer($maxExecTime, $runTimeBias);
|
||||
$start = $timer->getRunningTime(); // Mark the start of this download
|
||||
$break = false; // Don't break the step
|
||||
|
||||
do
|
||||
{
|
||||
// Do we have to initialize the file?
|
||||
if ($frag == -1)
|
||||
{
|
||||
// Currently downloaded size
|
||||
$doneSize = 0;
|
||||
|
||||
if (@file_exists($localFilename))
|
||||
{
|
||||
@unlink($localFilename);
|
||||
}
|
||||
|
||||
// Delete and touch the output file
|
||||
$fp = @fopen($localFilename, 'wb');
|
||||
|
||||
if ($fp !== false)
|
||||
{
|
||||
@fclose($fp);
|
||||
}
|
||||
|
||||
// Init
|
||||
$frag = 0;
|
||||
|
||||
$retArray['totalSize'] = $this->adapter->getFileSize($url);
|
||||
|
||||
if ($retArray['totalSize'] <= 0)
|
||||
{
|
||||
$retArray['totalSize'] = 0;
|
||||
}
|
||||
|
||||
$totalSize = $retArray['totalSize'];
|
||||
}
|
||||
|
||||
// Calculate from and length
|
||||
$from = $frag * $length;
|
||||
$to = $length + $from - 1;
|
||||
|
||||
// Try to download the first frag
|
||||
$required_time = 1.0;
|
||||
|
||||
$error = '';
|
||||
|
||||
try
|
||||
{
|
||||
$result = $this->adapter->downloadAndReturn($url, $from, $to, $this->adapterOptions);
|
||||
}
|
||||
catch (DownloadError $e)
|
||||
{
|
||||
$result = false;
|
||||
$error = $e->getMessage();
|
||||
}
|
||||
|
||||
if ($result === false)
|
||||
{
|
||||
// Failed download
|
||||
if ($frag == 0)
|
||||
{
|
||||
// Failure to download first frag = failure to download. Period.
|
||||
$retArray['status'] = false;
|
||||
$retArray['error'] = $error;
|
||||
|
||||
return $retArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Since this is a staggered download, consider this normal and finish
|
||||
$frag = -1;
|
||||
$totalSize = $doneSize;
|
||||
$break = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the currently downloaded frag to the total size of downloaded files
|
||||
if ($result !== false)
|
||||
{
|
||||
$fileSize = strlen($result);
|
||||
$doneSize += $fileSize;
|
||||
|
||||
// Append the file
|
||||
$fp = @fopen($localFilename, 'ab');
|
||||
|
||||
if ($fp === false)
|
||||
{
|
||||
// Can't open the file for writing
|
||||
$retArray['status'] = false;
|
||||
$retArray['error'] = Text::sprintf('LIB_FOF_DOWNLOAD_ERR_COULDNOTWRITELOCALFILE', $localFilename);
|
||||
|
||||
return $retArray;
|
||||
}
|
||||
|
||||
fwrite($fp, $result);
|
||||
fclose($fp);
|
||||
|
||||
$frag++;
|
||||
|
||||
if (($fileSize < $length) || ($fileSize > $length)
|
||||
|| (($totalSize == $doneSize) && ($totalSize > 0))
|
||||
)
|
||||
{
|
||||
// A partial download or a download larger than the frag size means we are done
|
||||
$frag = -1;
|
||||
//debugMsg("-- Import complete (partial download of last frag)");
|
||||
$totalSize = $doneSize;
|
||||
$break = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Advance the frag pointer and mark the end
|
||||
$end = $timer->getRunningTime();
|
||||
|
||||
// Do we predict that we have enough time?
|
||||
$required_time = max(1.1 * ($end - $start), $required_time);
|
||||
|
||||
if ($required_time > (10 - $end + $start))
|
||||
{
|
||||
$break = true;
|
||||
}
|
||||
|
||||
$start = $end;
|
||||
|
||||
} while (($timer->getTimeLeft() > 0) && !$break);
|
||||
|
||||
if ($frag == -1)
|
||||
{
|
||||
$percent = 100;
|
||||
}
|
||||
elseif ($doneSize <= 0)
|
||||
{
|
||||
$percent = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($totalSize > 0)
|
||||
{
|
||||
$percent = 100 * ($doneSize / $totalSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
$percent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Update $retArray
|
||||
$retArray = [
|
||||
"status" => true,
|
||||
"error" => '',
|
||||
"frag" => $frag,
|
||||
"totalSize" => $totalSize,
|
||||
"doneSize" => $doneSize,
|
||||
"percent" => $percent,
|
||||
];
|
||||
}
|
||||
catch (DownloadError $e)
|
||||
{
|
||||
$retArray['status'] = false;
|
||||
$retArray['error'] = $e->getMessage();
|
||||
}
|
||||
|
||||
return $retArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to decode the $params array
|
||||
*
|
||||
* @param string $key The parameter key you want to retrieve the value for
|
||||
* @param mixed $default The default value, if none is specified
|
||||
*
|
||||
* @return mixed The value for this parameter key
|
||||
*/
|
||||
private function getParam($key, $default = null)
|
||||
{
|
||||
if (array_key_exists($key, $this->params))
|
||||
{
|
||||
return $this->params[$key];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
88
libraries/fof30/Download/DownloadInterface.php
Normal file
88
libraries/fof30/Download/DownloadInterface.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Download;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Download\Exception\DownloadError;
|
||||
|
||||
/**
|
||||
* Interface DownloadInterface
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
interface DownloadInterface
|
||||
{
|
||||
/**
|
||||
* Does this download adapter support downloading files in chunks?
|
||||
*
|
||||
* @return boolean True if chunk download is supported
|
||||
*/
|
||||
public function supportsChunkDownload();
|
||||
|
||||
/**
|
||||
* Does this download adapter support reading the size of a remote file?
|
||||
*
|
||||
* @return boolean True if remote file size determination is supported
|
||||
*/
|
||||
public function supportsFileSize();
|
||||
|
||||
/**
|
||||
* Is this download class supported in the current server environment?
|
||||
*
|
||||
* @return boolean True if this server environment supports this download class
|
||||
*/
|
||||
public function isSupported();
|
||||
|
||||
/**
|
||||
* Get the priority of this adapter. If multiple download adapters are
|
||||
* supported on a site, the one with the highest priority will be
|
||||
* used.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function getPriority();
|
||||
|
||||
/**
|
||||
* Returns the name of this download adapter in use
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Download a part (or the whole) of a remote URL and return the downloaded
|
||||
* data. You are supposed to check the size of the returned data. If it's
|
||||
* smaller than what you expected you've reached end of file. If it's empty
|
||||
* you have tried reading past EOF. If it's larger than what you expected
|
||||
* the server doesn't support chunk downloads.
|
||||
*
|
||||
* If this class' supportsChunkDownload returns false you should assume
|
||||
* that the $from and $to parameters will be ignored.
|
||||
*
|
||||
* @param string $url The remote file's URL
|
||||
* @param integer $from Byte range to start downloading from. Use null for start of file.
|
||||
* @param integer $to Byte range to stop downloading. Use null to download the entire file ($from is
|
||||
* ignored)
|
||||
* @param array $params Additional params that will be added before performing the download
|
||||
*
|
||||
* @return string The raw file data retrieved from the remote URL.
|
||||
*
|
||||
* @throws DownloadError A generic exception is thrown on error
|
||||
*/
|
||||
public function downloadAndReturn($url, $from = null, $to = null, array $params = []);
|
||||
|
||||
/**
|
||||
* Get the size of a remote file in bytes
|
||||
*
|
||||
* @param string $url The remote file's URL
|
||||
*
|
||||
* @return integer The file size, or -1 if the remote server doesn't support this feature
|
||||
*/
|
||||
public function getFileSize($url);
|
||||
}
|
||||
17
libraries/fof30/Download/Exception/DownloadError.php
Normal file
17
libraries/fof30/Download/Exception/DownloadError.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Download\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class DownloadError extends RuntimeException
|
||||
{
|
||||
|
||||
}
|
||||
277
libraries/fof30/Encrypt/Aes.php
Normal file
277
libraries/fof30/Encrypt/Aes.php
Normal file
@ -0,0 +1,277 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Encrypt\AesAdapter\AdapterInterface;
|
||||
use FOF30\Encrypt\AesAdapter\OpenSSL;
|
||||
use FOF30\Utils\Phpfunc;
|
||||
|
||||
/**
|
||||
* A simple abstraction to AES encryption
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* // Create a new instance. The key is ignored – only use it if you have legacy encrypted content you need to decrypt
|
||||
* $aes = new Aes('ignored');
|
||||
* // Set the password. Do not use uf you have legacy encrypted content you need to decrypt
|
||||
* $aes->setPassword('yourRealPassword');
|
||||
* // Encrypt something.
|
||||
* $cipherText = $aes->encryptString($sourcePlainText);
|
||||
* // Decrypt something
|
||||
* $plainText = $aes->decryptString($sourceCipherText);
|
||||
*/
|
||||
class Aes
|
||||
{
|
||||
/**
|
||||
* The cipher key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $key = '';
|
||||
|
||||
/**
|
||||
* The AES encryption adapter in use.
|
||||
*
|
||||
* @var AdapterInterface
|
||||
*/
|
||||
private $adapter;
|
||||
|
||||
/**
|
||||
* Initialise the AES encryption object.
|
||||
*
|
||||
* Note: If the key is not 16 bytes this class will do a stupid key expansion for legacy reasons (produce the
|
||||
* SHA-256 of the key string and throw away half of it).
|
||||
*
|
||||
* @param string $key The encryption key (password). It can be a raw key (16 bytes) or a passphrase.
|
||||
* @param int $strength Bit strength (128, 192 or 256) – ALWAYS USE 128 BITS. THIS PARAMETER IS DEPRECATED.
|
||||
* @param string $mode Encryption mode. Can be ebc or cbc. We recommend using cbc.
|
||||
* @param Phpfunc $phpfunc For testing
|
||||
*/
|
||||
public function __construct($key, $strength = 128, $mode = 'cbc', Phpfunc $phpfunc = null)
|
||||
{
|
||||
$this->adapter = new OpenSSL();
|
||||
|
||||
if (!$this->adapter->isSupported($phpfunc))
|
||||
{
|
||||
throw new \RuntimeException('Your server does not have the PHP OpenSSL extension enabled. This is required for encryption handling.');
|
||||
}
|
||||
|
||||
$this->adapter->setEncryptionMode($mode, $strength);
|
||||
$this->setPassword($key, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is AES encryption supported by this PHP installation?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isSupported(Phpfunc $phpfunc = null)
|
||||
{
|
||||
if (!is_object($phpfunc) || !($phpfunc instanceof $phpfunc))
|
||||
{
|
||||
$phpfunc = new Phpfunc();
|
||||
}
|
||||
|
||||
$adapter = new OpenSSL();
|
||||
|
||||
if (!$adapter->isSupported($phpfunc))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('base64_encode'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('base64_decode'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('hash_algos'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$algorightms = $phpfunc->hash_algos();
|
||||
|
||||
if (!in_array('sha256', $algorightms))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password for this instance.
|
||||
*
|
||||
* WARNING: Do not use the legacy mode, it's insecure
|
||||
*
|
||||
* @param string $password The password (either user-provided password or binary encryption key) to use
|
||||
* @param bool $legacyMode True to use the legacy key expansion. We recommend against using it.
|
||||
*/
|
||||
public function setPassword($password, $legacyMode = false)
|
||||
{
|
||||
$this->key = $password;
|
||||
|
||||
$passLength = strlen($password);
|
||||
|
||||
if (function_exists('mb_strlen'))
|
||||
{
|
||||
$passLength = mb_strlen($password, 'ASCII');
|
||||
}
|
||||
|
||||
// Legacy mode was doing something stupid, requiring a key of 32 bytes. DO NOT USE LEGACY MODE!
|
||||
if ($legacyMode && ($passLength != 32))
|
||||
{
|
||||
// Legacy mode: use the sha256 of the password
|
||||
$this->key = hash('sha256', $password, true);
|
||||
// We have to trim or zero pad the password (we end up throwing half of it away in Rijndael-128 / AES...)
|
||||
$this->key = $this->adapter->resizeKey($this->key, $this->adapter->getBlockSize());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a string using AES
|
||||
*
|
||||
* @param string $stringToEncrypt The plaintext to encrypt
|
||||
* @param bool $base64encoded Should I Base64-encode the result?
|
||||
*
|
||||
* @return string The cryptotext. Please note that the first 16 bytes of
|
||||
* the raw string is the IV (initialisation vector) which
|
||||
* is necessary for decoding the string.
|
||||
*/
|
||||
public function encryptString($stringToEncrypt, $base64encoded = true)
|
||||
{
|
||||
$blockSize = $this->adapter->getBlockSize();
|
||||
$randVal = new Randval();
|
||||
$iv = $randVal->generate($blockSize);
|
||||
|
||||
$key = $this->getExpandedKey($blockSize, $iv);
|
||||
$cipherText = $this->adapter->encrypt($stringToEncrypt, $key, $iv);
|
||||
|
||||
// Optionally pass the result through Base64 encoding
|
||||
if ($base64encoded)
|
||||
{
|
||||
$cipherText = base64_encode($cipherText);
|
||||
}
|
||||
|
||||
// Return the result
|
||||
return $cipherText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a ciphertext into a plaintext string using AES
|
||||
*
|
||||
* @param string $stringToDecrypt The ciphertext to decrypt. The first 16 bytes of the raw string must contain
|
||||
* the IV (initialisation vector).
|
||||
* @param bool $base64encoded Should I Base64-decode the data before decryption?
|
||||
*
|
||||
* @return string The plain text string
|
||||
*/
|
||||
public function decryptString($stringToDecrypt, $base64encoded = true)
|
||||
{
|
||||
if ($base64encoded)
|
||||
{
|
||||
$stringToDecrypt = base64_decode($stringToDecrypt);
|
||||
}
|
||||
|
||||
// Extract IV
|
||||
$iv_size = $this->adapter->getBlockSize();
|
||||
$iv = substr($stringToDecrypt, 0, $iv_size);
|
||||
$key = $this->getExpandedKey($iv_size, $iv);
|
||||
|
||||
// Decrypt the data
|
||||
$plainText = $this->adapter->decrypt($stringToDecrypt, $key);
|
||||
|
||||
return $plainText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $blockSize
|
||||
* @param $iv
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExpandedKey($blockSize, $iv)
|
||||
{
|
||||
$key = $this->key;
|
||||
$passLength = strlen($key);
|
||||
|
||||
if (function_exists('mb_strlen'))
|
||||
{
|
||||
$passLength = mb_strlen($key, 'ASCII');
|
||||
}
|
||||
|
||||
if ($passLength != $blockSize)
|
||||
{
|
||||
$iterations = 1000;
|
||||
$salt = $this->adapter->resizeKey($iv, 16);
|
||||
$key = hash_pbkdf2('sha256', $this->key, $salt, $iterations, $blockSize, true);
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('hash_pbkdf2'))
|
||||
{
|
||||
function hash_pbkdf2($algo, $password, $salt, $count, $length = 0, $raw_output = false)
|
||||
{
|
||||
if (!in_array(strtolower($algo), hash_algos()))
|
||||
{
|
||||
trigger_error(__FUNCTION__ . '(): Unknown hashing algorithm: ' . $algo, E_USER_WARNING);
|
||||
}
|
||||
|
||||
if (!is_numeric($count))
|
||||
{
|
||||
trigger_error(__FUNCTION__ . '(): expects parameter 4 to be long, ' . gettype($count) . ' given', E_USER_WARNING);
|
||||
}
|
||||
|
||||
if (!is_numeric($length))
|
||||
{
|
||||
trigger_error(__FUNCTION__ . '(): expects parameter 5 to be long, ' . gettype($length) . ' given', E_USER_WARNING);
|
||||
}
|
||||
|
||||
if ($count <= 0)
|
||||
{
|
||||
trigger_error(__FUNCTION__ . '(): Iterations must be a positive integer: ' . $count, E_USER_WARNING);
|
||||
}
|
||||
|
||||
if ($length < 0)
|
||||
{
|
||||
trigger_error(__FUNCTION__ . '(): Length must be greater than or equal to 0: ' . $length, E_USER_WARNING);
|
||||
}
|
||||
|
||||
$output = '';
|
||||
$block_count = $length ? ceil($length / strlen(hash($algo, '', $raw_output))) : 1;
|
||||
|
||||
for ($i = 1; $i <= $block_count; $i++)
|
||||
{
|
||||
$last = $xorsum = hash_hmac($algo, $salt . pack('N', $i), $password, true);
|
||||
|
||||
for ($j = 1; $j < $count; $j++)
|
||||
{
|
||||
$xorsum ^= ($last = hash_hmac($algo, $last, $password, true));
|
||||
}
|
||||
|
||||
$output .= $xorsum;
|
||||
}
|
||||
|
||||
if (!$raw_output)
|
||||
{
|
||||
$output = bin2hex($output);
|
||||
}
|
||||
|
||||
return $length ? substr($output, 0, $length) : $output;
|
||||
}
|
||||
}
|
||||
88
libraries/fof30/Encrypt/AesAdapter/AbstractAdapter.php
Normal file
88
libraries/fof30/Encrypt/AesAdapter/AbstractAdapter.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt\AesAdapter;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Abstract AES encryption class
|
||||
*/
|
||||
abstract class AbstractAdapter
|
||||
{
|
||||
/**
|
||||
* Trims or zero-pads a key / IV
|
||||
*
|
||||
* @param string $key The key or IV to treat
|
||||
* @param int $size The block size of the currently used algorithm
|
||||
*
|
||||
* @return null|string Null if $key is null, treated string of $size byte length otherwise
|
||||
*/
|
||||
public function resizeKey($key, $size)
|
||||
{
|
||||
if (empty($key))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$keyLength = strlen($key);
|
||||
|
||||
if (function_exists('mb_strlen'))
|
||||
{
|
||||
$keyLength = mb_strlen($key, 'ASCII');
|
||||
}
|
||||
|
||||
if ($keyLength == $size)
|
||||
{
|
||||
return $key;
|
||||
}
|
||||
|
||||
if ($keyLength > $size)
|
||||
{
|
||||
if (function_exists('mb_substr'))
|
||||
{
|
||||
return mb_substr($key, 0, $size, 'ASCII');
|
||||
}
|
||||
|
||||
return substr($key, 0, $size);
|
||||
}
|
||||
|
||||
return $key . str_repeat("\0", ($size - $keyLength));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null bytes to append to the string so that it's zero padded to the specified block size
|
||||
*
|
||||
* @param string $string The binary string which will be zero padded
|
||||
* @param int $blockSize The block size
|
||||
*
|
||||
* @return string The zero bytes to append to the string to zero pad it to $blockSize
|
||||
*/
|
||||
protected function getZeroPadding($string, $blockSize)
|
||||
{
|
||||
$stringSize = strlen($string);
|
||||
|
||||
if (function_exists('mb_strlen'))
|
||||
{
|
||||
$stringSize = mb_strlen($string, 'ASCII');
|
||||
}
|
||||
|
||||
if ($stringSize == $blockSize)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($stringSize < $blockSize)
|
||||
{
|
||||
return str_repeat("\0", $blockSize - $stringSize);
|
||||
}
|
||||
|
||||
$paddingBytes = $stringSize % $blockSize;
|
||||
|
||||
return str_repeat("\0", $blockSize - $paddingBytes);
|
||||
}
|
||||
}
|
||||
78
libraries/fof30/Encrypt/AesAdapter/AdapterInterface.php
Normal file
78
libraries/fof30/Encrypt/AesAdapter/AdapterInterface.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt\AesAdapter;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Utils\Phpfunc;
|
||||
|
||||
/**
|
||||
* Interface for AES encryption adapters
|
||||
*/
|
||||
interface AdapterInterface
|
||||
{
|
||||
/**
|
||||
* Sets the AES encryption mode.
|
||||
*
|
||||
* WARNING: The strength parameter is deprecated since FOF 3.1 and has no effect.
|
||||
*
|
||||
* @param string $mode Choose between CBC (recommended) or ECB
|
||||
* @param int $strength DEPRECATED AND UNUSED.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setEncryptionMode($mode = 'cbc', $strength = 128);
|
||||
|
||||
/**
|
||||
* Encrypts a string. Returns the raw binary ciphertext.
|
||||
*
|
||||
* WARNING: The plaintext is zero-padded to the algorithm's block size. You are advised to store the size of the
|
||||
* plaintext and trim the string to that length upon decryption.
|
||||
*
|
||||
* @param string $plainText The plaintext to encrypt
|
||||
* @param string $key The raw binary key (will be zero-padded or chopped if its size is different
|
||||
* than the block size)
|
||||
* @param null|string $iv The initialization vector (for CBC mode algorithms)
|
||||
*
|
||||
* @return string The raw encrypted binary string.
|
||||
*/
|
||||
public function encrypt($plainText, $key, $iv = null);
|
||||
|
||||
/**
|
||||
* Decrypts a string. Returns the raw binary plaintext.
|
||||
*
|
||||
* $ciphertext MUST start with the IV followed by the ciphertext, even for EBC data (the first block of data is
|
||||
* dropped in EBC mode since there is no concept of IV in EBC).
|
||||
*
|
||||
* WARNING: The returned plaintext is zero-padded to the algorithm's block size during encryption. You are advised
|
||||
* to trim the string to the original plaintext's length upon decryption. While rtrim($decrypted, "\0") sounds
|
||||
* appealing it's NOT the correct approach for binary data (zero bytes may actually be part of your plaintext, not
|
||||
* just padding!).
|
||||
*
|
||||
* @param string $cipherText The ciphertext to encrypt
|
||||
* @param string $key The raw binary key (will be zero-padded or chopped if its size is different than
|
||||
* the block size)
|
||||
*
|
||||
* @return string The raw unencrypted binary string.
|
||||
*/
|
||||
public function decrypt($cipherText, $key);
|
||||
|
||||
/**
|
||||
* Returns the encryption block size in bytes
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getBlockSize();
|
||||
|
||||
/**
|
||||
* Is this adapter supported?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSupported(Phpfunc $phpfunc = null);
|
||||
}
|
||||
192
libraries/fof30/Encrypt/AesAdapter/OpenSSL.php
Normal file
192
libraries/fof30/Encrypt/AesAdapter/OpenSSL.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt\AesAdapter;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Encrypt\Randval;
|
||||
use FOF30\Utils\Phpfunc;
|
||||
|
||||
class OpenSSL extends AbstractAdapter implements AdapterInterface
|
||||
{
|
||||
/**
|
||||
* The OpenSSL options for encryption / decryption
|
||||
*
|
||||
* PHP 5.3 does not have the constants OPENSSL_RAW_DATA and OPENSSL_ZERO_PADDING. In fact, the parameter
|
||||
* is called $raw_data and is a boolean. Since integer 1 is equivalent to boolean TRUE in PHP we can get
|
||||
* away with initializing this parameter with the integer 1.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $openSSLOptions = 1;
|
||||
|
||||
/**
|
||||
* The encryption method to use
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $method = 'aes-128-cbc';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
/**
|
||||
* PHP 5.4 and later replaced the $raw_data parameter with the $options parameter. Instead of a boolean we need
|
||||
* to pass some flags. Here you go.
|
||||
*
|
||||
* Since PHP 5.3 does NOT have the relevant constants we must NOT run this bit of code under PHP 5.3.
|
||||
*
|
||||
* See http://stackoverflow.com/questions/24707007/using-openssl-raw-data-param-in-openssl-decrypt-with-php-5-3#24707117
|
||||
*/
|
||||
if (version_compare(PHP_VERSION, '5.4.0', 'ge'))
|
||||
{
|
||||
$this->openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
|
||||
}
|
||||
}
|
||||
|
||||
public function setEncryptionMode($mode = 'cbc', $strength = 128)
|
||||
{
|
||||
static $availableAlgorithms = null;
|
||||
static $defaultAlgo = 'aes-128-cbc';
|
||||
|
||||
if (!is_array($availableAlgorithms))
|
||||
{
|
||||
$availableAlgorithms = openssl_get_cipher_methods();
|
||||
|
||||
foreach ([
|
||||
'aes-256-cbc', 'aes-256-ecb', 'aes-192-cbc',
|
||||
'aes-192-ecb', 'aes-128-cbc', 'aes-128-ecb',
|
||||
] as $algo)
|
||||
{
|
||||
if (in_array($algo, $availableAlgorithms))
|
||||
{
|
||||
$defaultAlgo = $algo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$strength = (int) $strength;
|
||||
$mode = strtolower($mode);
|
||||
|
||||
if (!in_array($strength, [128, 192, 256]))
|
||||
{
|
||||
$strength = 256;
|
||||
}
|
||||
|
||||
if (!in_array($mode, ['cbc', 'ebc']))
|
||||
{
|
||||
$mode = 'cbc';
|
||||
}
|
||||
|
||||
$algo = 'aes-' . $strength . '-' . $mode;
|
||||
|
||||
if (!in_array($algo, $availableAlgorithms))
|
||||
{
|
||||
$algo = $defaultAlgo;
|
||||
}
|
||||
|
||||
$this->method = $algo;
|
||||
}
|
||||
|
||||
public function encrypt($plainText, $key, $iv = null)
|
||||
{
|
||||
$iv_size = $this->getBlockSize();
|
||||
$key = $this->resizeKey($key, $iv_size);
|
||||
$iv = $this->resizeKey($iv, $iv_size);
|
||||
|
||||
if (empty($iv))
|
||||
{
|
||||
$randVal = new Randval();
|
||||
$iv = $randVal->generate($iv_size);
|
||||
}
|
||||
|
||||
$plainText .= $this->getZeroPadding($plainText, $iv_size);
|
||||
$cipherText = openssl_encrypt($plainText, $this->method, $key, $this->openSSLOptions, $iv);
|
||||
$cipherText = $iv . $cipherText;
|
||||
|
||||
return $cipherText;
|
||||
}
|
||||
|
||||
public function decrypt($cipherText, $key)
|
||||
{
|
||||
$iv_size = $this->getBlockSize();
|
||||
$key = $this->resizeKey($key, $iv_size);
|
||||
$iv = substr($cipherText, 0, $iv_size);
|
||||
$cipherText = substr($cipherText, $iv_size);
|
||||
$plainText = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv);
|
||||
|
||||
return $plainText;
|
||||
}
|
||||
|
||||
public function isSupported(Phpfunc $phpfunc = null)
|
||||
{
|
||||
if (!is_object($phpfunc) || !($phpfunc instanceof $phpfunc))
|
||||
{
|
||||
$phpfunc = new Phpfunc();
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('openssl_get_cipher_methods'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('openssl_random_pseudo_bytes'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('openssl_cipher_iv_length'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('openssl_encrypt'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('openssl_decrypt'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('hash'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$phpfunc->function_exists('hash_algos'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$algorightms = $phpfunc->openssl_get_cipher_methods();
|
||||
|
||||
if (!in_array('aes-128-cbc', $algorightms))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$algorightms = $phpfunc->hash_algos();
|
||||
|
||||
if (!in_array('sha256', $algorightms))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getBlockSize()
|
||||
{
|
||||
return openssl_cipher_iv_length($this->method);
|
||||
}
|
||||
}
|
||||
208
libraries/fof30/Encrypt/Base32.php
Normal file
208
libraries/fof30/Encrypt/Base32.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Base32 encoding class, used by the TOTP
|
||||
*/
|
||||
class Base32
|
||||
{
|
||||
/**
|
||||
* CSRFC3548
|
||||
*
|
||||
* The character set as defined by RFC3548
|
||||
* @link http://www.ietf.org/rfc/rfc3548.txt
|
||||
*/
|
||||
public const CSRFC3548 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
|
||||
/**
|
||||
* Convert any string to a base32 string
|
||||
* This should be binary safe...
|
||||
*
|
||||
* @param string $str The string to convert
|
||||
*
|
||||
* @return string The converted base32 string
|
||||
*/
|
||||
public function encode($str)
|
||||
{
|
||||
return $this->fromBin($this->str2bin($str));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert any base32 string to a normal sctring
|
||||
* This should be binary safe...
|
||||
*
|
||||
* @param string $str The base32 string to convert
|
||||
*
|
||||
* @return string The normal string
|
||||
*/
|
||||
public function decode($str)
|
||||
{
|
||||
$str = strtoupper($str);
|
||||
|
||||
return $this->bin2str($this->tobin($str));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts any ascii string to a binary string
|
||||
*
|
||||
* @param string $str The string you want to convert
|
||||
*
|
||||
* @return string String of 0's and 1's
|
||||
*/
|
||||
private function str2bin($str)
|
||||
{
|
||||
$chrs = unpack('C*', $str);
|
||||
|
||||
return vsprintf(str_repeat('%08b', count($chrs)), $chrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a binary string to an ascii string
|
||||
*
|
||||
* @param string $str The string of 0's and 1's you want to convert
|
||||
*
|
||||
* @return string The ascii output
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function bin2str($str)
|
||||
{
|
||||
if (strlen($str) % 8 > 0)
|
||||
{
|
||||
throw new InvalidArgumentException('Length must be divisible by 8');
|
||||
}
|
||||
|
||||
if (!preg_match('/^[01]+$/', $str))
|
||||
{
|
||||
throw new InvalidArgumentException('Only 0\'s and 1\'s are permitted');
|
||||
}
|
||||
|
||||
preg_match_all('/.{8}/', $str, $chrs);
|
||||
$chrs = array_map('bindec', $chrs[0]);
|
||||
|
||||
// I'm just being slack here
|
||||
array_unshift($chrs, 'C*');
|
||||
|
||||
return call_user_func_array('pack', $chrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a correct binary string to base32
|
||||
*
|
||||
* @param string $str The string of 0's and 1's you want to convert
|
||||
*
|
||||
* @return string String encoded as base32
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function fromBin($str)
|
||||
{
|
||||
if (strlen($str) % 8 > 0)
|
||||
{
|
||||
throw new InvalidArgumentException('Length must be divisible by 8');
|
||||
}
|
||||
|
||||
if (!preg_match('/^[01]+$/', $str))
|
||||
{
|
||||
throw new InvalidArgumentException('Only 0\'s and 1\'s are permitted');
|
||||
}
|
||||
|
||||
// Base32 works on the first 5 bits of a byte, so we insert blanks to pad it out
|
||||
$str = preg_replace('/(.{5})/', '000$1', $str);
|
||||
|
||||
// We need a string divisible by 5
|
||||
$length = strlen($str);
|
||||
$rbits = $length & 7;
|
||||
|
||||
if ($rbits > 0)
|
||||
{
|
||||
// Excessive bits need to be padded
|
||||
$ebits = substr($str, $length - $rbits);
|
||||
$str = substr($str, 0, $length - $rbits);
|
||||
$str .= "000$ebits" . str_repeat('0', 5 - strlen($ebits));
|
||||
}
|
||||
|
||||
preg_match_all('/.{8}/', $str, $chrs);
|
||||
$chrs = array_map([$this, 'mapCharset'], $chrs[0]);
|
||||
|
||||
return implode('', $chrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a base32 string and returns an ascii binary string
|
||||
*
|
||||
* @param string $str The base32 string to convert
|
||||
*
|
||||
* @return string Ascii binary string
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function toBin($str)
|
||||
{
|
||||
if (!preg_match('/^[' . self::CSRFC3548 . ']+$/', $str))
|
||||
{
|
||||
throw new InvalidArgumentException('Base64 string must match character set');
|
||||
}
|
||||
|
||||
// Convert the base32 string back to a binary string
|
||||
$str = implode('', array_map([$this, 'mapBin'], str_split($str)));
|
||||
|
||||
// Remove the extra 0's we added
|
||||
$str = preg_replace('/000(.{5})/', '$1', $str);
|
||||
|
||||
// Remove padding if necessary
|
||||
$length = strlen($str);
|
||||
$rbits = $length & 7;
|
||||
|
||||
if ($rbits > 0)
|
||||
{
|
||||
$str = substr($str, 0, $length - $rbits);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used with array_map to map the bits from a binary string
|
||||
* directly into a base32 character set
|
||||
*
|
||||
* @param string $str The string of 0's and 1's you want to convert
|
||||
*
|
||||
* @return string Resulting base32 character
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function mapCharset($str)
|
||||
{
|
||||
// Huh!
|
||||
$x = self::CSRFC3548;
|
||||
|
||||
return $x[bindec($str)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Used with array_map to map the characters from a base32
|
||||
* character set directly into a binary string
|
||||
*
|
||||
* @param string $chr The character to map
|
||||
*
|
||||
* @return string String of 0's and 1's
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function mapBin($chr)
|
||||
{
|
||||
return sprintf('%08b', strpos(self::CSRFC3548, $chr));
|
||||
}
|
||||
|
||||
}
|
||||
277
libraries/fof30/Encrypt/EncryptService.php
Normal file
277
libraries/fof30/Encrypt/EncryptService.php
Normal file
@ -0,0 +1,277 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Utils\Phpfunc;
|
||||
|
||||
/**
|
||||
* Data encryption service for FOF-based components.
|
||||
*
|
||||
* This service allows you to transparently encrypt and decrypt *text* plaintext data. Use it to provide encryption for
|
||||
* sensitive or personal data stored in your database. Please remember:
|
||||
*
|
||||
* - The default behavior is to create a file with a random key on your component's root. If the file cannot be created
|
||||
* the encryption is turned off.
|
||||
* - The key file is only created when you access the service. If you never use this service nothing happens (for
|
||||
* backwards compatibility).
|
||||
* - You have to manually encrypt and decrypt data. It won't happen magically.
|
||||
* - Encrypted data cannot be searched unless you implement your own, slow, search algorithm.
|
||||
* - Data encryption is meant to be used on top of, not instead of, any other security measures for your site.
|
||||
* - Data encryption only protects against exploits targeting the database. If the attacker *also* gains read access to
|
||||
* your filesystem OR if the attacker gains read / write access to the filesystem the encryption won't protect you.
|
||||
* This is a full compromise of your site. At this point you're pwned and nothing can protect you. If you don't
|
||||
* understand this simple truth do NOT use encryption.
|
||||
* - This is meant as a simple and basic encryption layer. It has not been independently verified. Use at your own risk.
|
||||
*
|
||||
* This service has the following FOF application configuration parameters which can be declared under the "container"
|
||||
* key (e.g. the "name" attribute of the fof.xml elements under fof > common > container > option):
|
||||
*
|
||||
* - encrypt_key_file The path to the key file, relative to the component's backend root and WITHOUT the .php extension
|
||||
* - encrypt_key_const The constant for the key. By default it is COMPONENTNAME_FOF_ENCRYPT_SERVICE_SECRETKEY where
|
||||
* COMPONENTNAME corresponds to the uppercase com_componentname without the com_ prefix.
|
||||
*
|
||||
* @package FOF30\Encrypt
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
class EncryptService
|
||||
{
|
||||
/**
|
||||
* The component's container
|
||||
*
|
||||
* @var Container
|
||||
* @since 3.3.2
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* The encryption engine used by this service
|
||||
*
|
||||
* @var Aes
|
||||
* @since 3.3.2
|
||||
*/
|
||||
private $aes;
|
||||
|
||||
/**
|
||||
* EncryptService constructor.
|
||||
*
|
||||
* @param Container $c The FOF component container
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
public function __construct(Container $c)
|
||||
{
|
||||
$this->container = $c;
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the plaintext $data and return the ciphertext prefixed by ###AES128###
|
||||
*
|
||||
* @param string $data The plaintext data
|
||||
*
|
||||
* @return string The ciphertext, prefixed by ###AES128###
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
public function encrypt($data)
|
||||
{
|
||||
if (!is_object($this->aes))
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
$encrypted = $this->aes->encryptString($data, true);
|
||||
|
||||
return '###AES128###' . $encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the ciphertext, prefixed by ###AES128###, and return the plaintext.
|
||||
*
|
||||
* @param string $data The ciphertext, prefixed by ###AES128###
|
||||
*
|
||||
* @return string The plaintext data
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
public function decrypt($data)
|
||||
{
|
||||
if (substr($data, 0, 12) != '###AES128###')
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data = substr($data, 12);
|
||||
|
||||
if (!is_object($this->aes))
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
$decrypted = $this->aes->decryptString($data, true);
|
||||
|
||||
// Decrypted data is null byte padded. We have to remove the padding before proceeding.
|
||||
return rtrim($decrypted, "\0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the AES cryptography object
|
||||
*
|
||||
* @return void
|
||||
* @since 3.3.2
|
||||
*
|
||||
*/
|
||||
private function initialize()
|
||||
{
|
||||
if (is_object($this->aes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$password = $this->getPassword();
|
||||
|
||||
if (empty($password))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$phpFunc = new Phpfunc();
|
||||
$this->aes = new Aes($password, 128, 'cbc', $phpFunc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the secret key file
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
private function getPasswordFilePath()
|
||||
{
|
||||
$default = 'encrypt_service_key';
|
||||
$baseName = $this->container->appConfig->get('container.encrypt_key_file', $default);
|
||||
$baseName = trim($baseName, '/\\');
|
||||
|
||||
return $this->container->backEndPath . '/' . $baseName . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the constant where the secret key is stored. Remember that this is searched first, before a new
|
||||
* key file is created. You can define this constant anywhere in your code loaded before the encryption service is
|
||||
* first used to prevent a key file being created.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
private function getConstantName()
|
||||
{
|
||||
$default = strtoupper($this->container->bareComponentName) . '_FOF_ENCRYPT_SERVICE_SECRETKEY';
|
||||
|
||||
return $this->container->appConfig->get('container.encrypt_key_const', $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the password used to encrypt information
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
private function getPassword()
|
||||
{
|
||||
$constantName = $this->getConstantName();
|
||||
|
||||
// If we have already read the file just return the key
|
||||
if (defined($constantName))
|
||||
{
|
||||
return constant($constantName);
|
||||
}
|
||||
|
||||
// Do I have a secret key file?
|
||||
$filePath = $this->getPasswordFilePath();
|
||||
|
||||
// I can't get the path to the file. Cut our losses and assume we can get no key.
|
||||
if (empty($filePath))
|
||||
{
|
||||
define($constantName, '');
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// If not, try to create one.
|
||||
if (!file_exists($filePath))
|
||||
{
|
||||
$this->makePasswordFile();
|
||||
}
|
||||
|
||||
// We failed to create a new file? Cut our losses and assume we can get no key.
|
||||
if (!file_exists($filePath) || !is_readable($filePath))
|
||||
{
|
||||
define($constantName, '');
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Try to include the key file
|
||||
include_once $filePath;
|
||||
|
||||
// The key file contains garbage. Treason! Cut our losses and assume we can get no key.
|
||||
if (!defined($constantName))
|
||||
{
|
||||
define($constantName, '');
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Finally, return the key which was defined in the file (happy path).
|
||||
return constant($constantName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new secret key file using a long, randomly generated password. The password generator uses a crypto-safe
|
||||
* pseudorandom number generator (PRNG) to ensure suitability of the password for encrypting data at rest.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
private function makePasswordFile()
|
||||
{
|
||||
// Get the path to the new secret key file.
|
||||
$filePath = $this->getPasswordFilePath();
|
||||
|
||||
// I can't get the path to the file. Sorry.
|
||||
if (empty($filePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$phpFunc = new Phpfunc();
|
||||
$randval = new Randval($phpFunc);
|
||||
$secretKey = $randval->getRandomPassword(64);
|
||||
$constantName = $this->getConstantName();
|
||||
|
||||
$fileContent = "<?" . 'ph' . "p\n\n";
|
||||
$fileContent .= <<< END
|
||||
/**
|
||||
* This file is automatically generated. It contains a secret key used for encrypting data by the component. Please do
|
||||
* not remove, edit or manually replace this file. It will render your existing encrypted data unreadable forever.
|
||||
*/
|
||||
|
||||
define('$constantName', '$secretKey');
|
||||
|
||||
END;
|
||||
|
||||
$this->container->filesystem->fileWrite($filePath, $fileContent);
|
||||
}
|
||||
}
|
||||
229
libraries/fof30/Encrypt/Randval.php
Normal file
229
libraries/fof30/Encrypt/Randval.php
Normal file
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Utils\Phpfunc;
|
||||
|
||||
/**
|
||||
* Generates cryptographically-secure random values.
|
||||
*/
|
||||
class Randval implements RandvalInterface
|
||||
{
|
||||
/**
|
||||
* @var Phpfunc
|
||||
*/
|
||||
protected $phpfunc;
|
||||
|
||||
/**
|
||||
*
|
||||
* Constructor.
|
||||
*
|
||||
* @param Phpfunc $phpfunc An object to intercept PHP function calls;
|
||||
* this makes testing easier.
|
||||
*
|
||||
*/
|
||||
public function __construct(Phpfunc $phpfunc = null)
|
||||
{
|
||||
if (!is_object($phpfunc) || !($phpfunc instanceof Phpfunc))
|
||||
{
|
||||
$phpfunc = new Phpfunc();
|
||||
}
|
||||
|
||||
$this->phpfunc = $phpfunc;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns a cryptographically secure random value.
|
||||
*
|
||||
* @param integer $bytes How many bytes to return
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate($bytes = 32)
|
||||
{
|
||||
if ($this->phpfunc->extension_loaded('openssl') && (version_compare(PHP_VERSION, '5.3.4') >= 0 || IS_WIN))
|
||||
{
|
||||
$strong = false;
|
||||
$randBytes = openssl_random_pseudo_bytes($bytes, $strong);
|
||||
|
||||
if ($strong)
|
||||
{
|
||||
return $randBytes;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->genRandomBytes($bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random bytes. Adapted from Joomla! 3.2.
|
||||
*
|
||||
* @param integer $length Length of the random data to generate
|
||||
*
|
||||
* @return string Random binary data
|
||||
*/
|
||||
public function genRandomBytes($length = 32)
|
||||
{
|
||||
$length = (int) $length;
|
||||
$sslStr = '';
|
||||
|
||||
/*
|
||||
* Collect any entropy available in the system along with a number
|
||||
* of time measurements of operating system randomness.
|
||||
*/
|
||||
$bitsPerRound = 2;
|
||||
$maxTimeMicro = 400;
|
||||
$shaHashLength = 20;
|
||||
$randomStr = '';
|
||||
$total = $length;
|
||||
|
||||
// Check if we can use /dev/urandom.
|
||||
$urandom = false;
|
||||
$handle = null;
|
||||
|
||||
// This is PHP 5.3.3 and up
|
||||
if ($this->phpfunc->function_exists('stream_set_read_buffer') && @is_readable('/dev/urandom'))
|
||||
{
|
||||
$handle = @fopen('/dev/urandom', 'rb');
|
||||
|
||||
if ($handle)
|
||||
{
|
||||
$urandom = true;
|
||||
}
|
||||
}
|
||||
|
||||
while ($length > strlen($randomStr))
|
||||
{
|
||||
$bytes = ($total > $shaHashLength) ? $shaHashLength : $total;
|
||||
$total -= $bytes;
|
||||
|
||||
/*
|
||||
* Collect any entropy available from the PHP system and filesystem.
|
||||
* If we have ssl data that isn't strong, we use it once.
|
||||
*/
|
||||
$entropy = random_int(0, mt_getrandmax()) . uniqid(random_int(0, mt_getrandmax()), true) . $sslStr;
|
||||
|
||||
$entropy .= implode('', @fstat(fopen(__FILE__, 'r')));
|
||||
$entropy .= memory_get_usage();
|
||||
$sslStr = '';
|
||||
|
||||
if ($urandom)
|
||||
{
|
||||
stream_set_read_buffer($handle, 0);
|
||||
$entropy .= @fread($handle, $bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* There is no external source of entropy so we repeat calls
|
||||
* to mt_rand until we are assured there's real randomness in
|
||||
* the result.
|
||||
*
|
||||
* Measure the time that the operations will take on average.
|
||||
*/
|
||||
$samples = 3;
|
||||
$duration = 0;
|
||||
|
||||
for ($pass = 0; $pass < $samples; ++$pass)
|
||||
{
|
||||
$microStart = microtime(true) * 1000000;
|
||||
$hash = sha1(random_int(0, mt_getrandmax()), true);
|
||||
|
||||
for ($count = 0; $count < 50; ++$count)
|
||||
{
|
||||
$hash = sha1($hash, true);
|
||||
}
|
||||
|
||||
$microEnd = microtime(true) * 1000000;
|
||||
$entropy .= $microStart . $microEnd;
|
||||
|
||||
if ($microStart >= $microEnd)
|
||||
{
|
||||
$microEnd += 1000000;
|
||||
}
|
||||
|
||||
$duration += $microEnd - $microStart;
|
||||
}
|
||||
|
||||
$duration = $duration / $samples;
|
||||
|
||||
/*
|
||||
* Based on the average time, determine the total rounds so that
|
||||
* the total running time is bounded to a reasonable number.
|
||||
*/
|
||||
$rounds = (int) (($maxTimeMicro / $duration) * 50);
|
||||
|
||||
/*
|
||||
* Take additional measurements. On average we can expect
|
||||
* at least $bitsPerRound bits of entropy from each measurement.
|
||||
*/
|
||||
$iter = $bytes * (int) ceil(8 / $bitsPerRound);
|
||||
|
||||
for ($pass = 0; $pass < $iter; ++$pass)
|
||||
{
|
||||
$microStart = microtime(true);
|
||||
$hash = sha1(random_int(0, mt_getrandmax()), true);
|
||||
|
||||
for ($count = 0; $count < $rounds; ++$count)
|
||||
{
|
||||
$hash = sha1($hash, true);
|
||||
}
|
||||
|
||||
$entropy .= $microStart . microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
$randomStr .= sha1($entropy, true);
|
||||
}
|
||||
|
||||
if ($urandom)
|
||||
{
|
||||
@fclose($handle);
|
||||
}
|
||||
|
||||
return substr($randomStr, 0, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a randomly generated password using safe characters (a-z, A-Z, 0-9).
|
||||
*
|
||||
* @param int $length How many characters long should the password be. Default is 64.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.2
|
||||
*/
|
||||
public function getRandomPassword($length = 64)
|
||||
{
|
||||
$salt = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
$base = strlen($salt);
|
||||
$makepass = '';
|
||||
|
||||
/*
|
||||
* Start with a cryptographic strength random string, then convert it to
|
||||
* a string with the numeric base of the salt.
|
||||
* Shift the base conversion on each character so the character
|
||||
* distribution is even, and randomize the start shift so it's not
|
||||
* predictable.
|
||||
*/
|
||||
$random = $this->generate($length + 1);
|
||||
$shift = ord($random[0]);
|
||||
|
||||
for ($i = 1; $i <= $length; ++$i)
|
||||
{
|
||||
$makepass .= $salt[($shift + ord($random[$i])) % $base];
|
||||
$shift += ord($random[$i]);
|
||||
}
|
||||
|
||||
return $makepass;
|
||||
}
|
||||
|
||||
}
|
||||
22
libraries/fof30/Encrypt/RandvalInterface.php
Normal file
22
libraries/fof30/Encrypt/RandvalInterface.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
interface RandvalInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* Returns a cryptographically secure random value.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function generate();
|
||||
}
|
||||
197
libraries/fof30/Encrypt/Totp.php
Normal file
197
libraries/fof30/Encrypt/Totp.php
Normal file
@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Encrypt;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
class Totp
|
||||
{
|
||||
/**
|
||||
* @var int The length of the resulting passcode (default: 6 digits)
|
||||
*/
|
||||
private $passCodeLength = 6;
|
||||
|
||||
/**
|
||||
* @var number The PIN modulo. It is set automatically to log10(passCodeLength)
|
||||
*/
|
||||
private $pinModulo;
|
||||
|
||||
/**
|
||||
* The length of the secret key, in characters (default: 10)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $secretLength = 10;
|
||||
|
||||
/**
|
||||
* The time step between successive TOTPs in seconds (default: 30 seconds)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $timeStep = 30;
|
||||
|
||||
/**
|
||||
* The Base32 encoder class
|
||||
*
|
||||
* @var Base32|null
|
||||
*/
|
||||
private $base32 = null;
|
||||
|
||||
/**
|
||||
* Initialises an RFC6238-compatible TOTP generator. Please note that this
|
||||
* class does not implement the constraint in the last paragraph of §5.2
|
||||
* of RFC6238. It's up to you to ensure that the same user/device does not
|
||||
* retry validation within the same Time Step.
|
||||
*
|
||||
* @param int $timeStep The Time Step (in seconds). Use 30 to be compatible with Google Authenticator.
|
||||
* @param int $passCodeLength The generated passcode length. Default: 6 digits.
|
||||
* @param int $secretLength The length of the secret key. Default: 10 bytes (80 bits).
|
||||
* @param Base32 $base32 The base32 en/decrypter
|
||||
*/
|
||||
public function __construct($timeStep = 30, $passCodeLength = 6, $secretLength = 10, Base32 $base32 = null)
|
||||
{
|
||||
$this->timeStep = $timeStep;
|
||||
$this->passCodeLength = $passCodeLength;
|
||||
$this->secretLength = $secretLength;
|
||||
$this->pinModulo = 10 ** $this->passCodeLength;
|
||||
|
||||
if (is_null($base32))
|
||||
{
|
||||
$this->base32 = new Base32();
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->base32 = $base32;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time period based on the $time timestamp and the Time Step
|
||||
* defined. If $time is skipped or set to null the current timestamp will
|
||||
* be used.
|
||||
*
|
||||
* @param int|null $time Timestamp
|
||||
*
|
||||
* @return int The time period since the UNIX Epoch
|
||||
*/
|
||||
public function getPeriod($time = null)
|
||||
{
|
||||
if (is_null($time))
|
||||
{
|
||||
$time = time();
|
||||
}
|
||||
|
||||
$period = floor($time / $this->timeStep);
|
||||
|
||||
return $period;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is the given passcode $code is a valid TOTP generated using secret
|
||||
* key $secret
|
||||
*
|
||||
* @param string $secret The Base32-encoded secret key
|
||||
* @param string $code The passcode to check
|
||||
* @param int $time The time to check it against. Leave null to check for the current server time.
|
||||
*
|
||||
* @return boolean True if the code is valid
|
||||
*/
|
||||
public function checkCode($secret, $code, $time = null)
|
||||
{
|
||||
$time = $this->getPeriod($time);
|
||||
|
||||
for ($i = -1; $i <= 1; $i++)
|
||||
{
|
||||
if ($this->getCode($secret, ($time + $i) * $this->timeStep) == $code)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TOTP passcode for a given secret key $secret and a given UNIX
|
||||
* timestamp $time
|
||||
*
|
||||
* @param string $secret The Base32-encoded secret key
|
||||
* @param int $time UNIX timestamp
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCode($secret, $time = null)
|
||||
{
|
||||
$period = $this->getPeriod($time);
|
||||
$secret = $this->base32->decode($secret);
|
||||
|
||||
$time = pack("N", $period);
|
||||
$time = str_pad($time, 8, chr(0), STR_PAD_LEFT);
|
||||
|
||||
$hash = hash_hmac('sha1', $time, $secret, true);
|
||||
$offset = ord(substr($hash, -1));
|
||||
$offset = $offset & 0xF;
|
||||
|
||||
$truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF;
|
||||
$pinValue = str_pad($truncatedHash % $this->pinModulo, $this->passCodeLength, "0", STR_PAD_LEFT);
|
||||
|
||||
return $pinValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a QR code URL for easy setup of TOTP apps like Google Authenticator
|
||||
*
|
||||
* @param string $user User
|
||||
* @param string $hostname Hostname
|
||||
* @param string $secret Secret string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl($user, $hostname, $secret)
|
||||
{
|
||||
$url = sprintf("otpauth://totp/%s@%s?secret=%s", $user, $hostname, $secret);
|
||||
$encoder = "https://chart.googleapis.com/chart?chs=200x200&chld=Q|2&cht=qr&chl=";
|
||||
$encoderURL = $encoder . urlencode($url);
|
||||
|
||||
return $encoderURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a (semi-)random Secret Key for TOTP generation
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateSecret()
|
||||
{
|
||||
$secret = "";
|
||||
|
||||
for ($i = 1; $i <= $this->secretLength; $i++)
|
||||
{
|
||||
$c = random_int(0, 255);
|
||||
$secret .= pack("c", $c);
|
||||
}
|
||||
|
||||
return $this->base32->encode($secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a part of a hash as an integer
|
||||
*
|
||||
* @param string $bytes The hash
|
||||
* @param string $start The char to start from (0 = first char)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function hashToInt($bytes, $start)
|
||||
{
|
||||
$input = substr($bytes, $start, strlen($bytes) - $start);
|
||||
$val2 = unpack("N", substr($input, 0, 4));
|
||||
|
||||
return $val2[1];
|
||||
}
|
||||
}
|
||||
317
libraries/fof30/Event/Dispatcher.php
Normal file
317
libraries/fof30/Event/Dispatcher.php
Normal file
@ -0,0 +1,317 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Event;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Container\Container;
|
||||
|
||||
class Dispatcher implements Observable
|
||||
{
|
||||
/** @var Container The container this event dispatcher is attached to */
|
||||
protected $container = null;
|
||||
|
||||
/** @var array The observers attached to the dispatcher */
|
||||
protected $observers = [];
|
||||
|
||||
/** @var array Maps events to observers */
|
||||
protected $events = [];
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param Container $container The container this event dispatcher is attached to
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the container this event dispatcher is attached to
|
||||
*
|
||||
* @return Container
|
||||
*/
|
||||
public function getContainer()
|
||||
{
|
||||
return $this->container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches an observer to the object
|
||||
*
|
||||
* @param Observer $observer The observer to attach
|
||||
*
|
||||
* @return Dispatcher Ourselves, for chaining
|
||||
*/
|
||||
public function attach(Observer $observer)
|
||||
{
|
||||
$className = get_class($observer);
|
||||
|
||||
// Make sure this observer is not already registered
|
||||
if (isset($this->observers[$className]))
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Attach observer
|
||||
$this->observers[$className] = $observer;
|
||||
|
||||
// Register the observable events
|
||||
$events = $observer->getObservableEvents();
|
||||
|
||||
foreach ($events as $event)
|
||||
{
|
||||
$event = strtolower($event);
|
||||
|
||||
if (!isset($this->events[$event]))
|
||||
{
|
||||
$this->events[$event] = [$className];
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->events[$event][] = $className;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches an observer from the object
|
||||
*
|
||||
* @param Observer $observer The observer to detach
|
||||
*
|
||||
* @return Dispatcher Ourselves, for chaining
|
||||
*/
|
||||
public function detach(Observer $observer)
|
||||
{
|
||||
$className = get_class($observer);
|
||||
|
||||
// Make sure this observer is already registered
|
||||
if (!isset($this->observers[$className]))
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Unregister the observable events
|
||||
$events = $observer->getObservableEvents();
|
||||
|
||||
foreach ($events as $event)
|
||||
{
|
||||
$event = strtolower($event);
|
||||
|
||||
if (isset($this->events[$event]))
|
||||
{
|
||||
$key = array_search($className, $this->events[$event]);
|
||||
|
||||
if ($key !== false)
|
||||
{
|
||||
unset($this->events[$event][$key]);
|
||||
|
||||
if (empty($this->events[$event]))
|
||||
{
|
||||
unset ($this->events[$event]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detach observer
|
||||
unset($this->observers[$className]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is an observer object already registered with this dispatcher?
|
||||
*
|
||||
* @param Observer $observer The observer to check if it's attached
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasObserver(Observer $observer)
|
||||
{
|
||||
$className = get_class($observer);
|
||||
|
||||
return $this->hasObserverClass($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is there an observer of the specified class already registered with this dispatcher?
|
||||
*
|
||||
* @param string $className The observer class name to check if it's attached
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasObserverClass($className)
|
||||
{
|
||||
return isset($this->observers[$className]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observer attached to this behaviours dispatcher by its class name
|
||||
*
|
||||
* @param string $className The class name of the observer object to return
|
||||
*
|
||||
* @return null|Observer
|
||||
*/
|
||||
public function getObserverByClass($className)
|
||||
{
|
||||
if (!$this->hasObserverClass($className))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->observers[$className];
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an event in the attached observers
|
||||
*
|
||||
* @param string $event The event to attach
|
||||
* @param array $args Arguments to the event handler
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function trigger($event, array $args = [])
|
||||
{
|
||||
$event = strtolower($event);
|
||||
|
||||
$result = [];
|
||||
|
||||
// Make sure the event is known to us, otherwise return an empty array
|
||||
if (!isset($this->events[$event]) || empty($this->events[$event]))
|
||||
{
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($this->events[$event] as $className)
|
||||
{
|
||||
// Make sure the observer exists.
|
||||
if (!isset($this->observers[$className]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the observer
|
||||
$observer = $this->observers[$className];
|
||||
|
||||
// Make sure the method exists
|
||||
if (!method_exists($observer, $event))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Call the event handler and add its output to the return value. The switch allows for execution up to 2x
|
||||
// faster than using call_user_func_array
|
||||
switch (count($args))
|
||||
{
|
||||
case 0:
|
||||
$result[] = $observer->{$event}();
|
||||
break;
|
||||
case 1:
|
||||
$result[] = $observer->{$event}($args[0]);
|
||||
break;
|
||||
case 2:
|
||||
$result[] = $observer->{$event}($args[0], $args[1]);
|
||||
break;
|
||||
case 3:
|
||||
$result[] = $observer->{$event}($args[0], $args[1], $args[2]);
|
||||
break;
|
||||
case 4:
|
||||
$result[] = $observer->{$event}($args[0], $args[1], $args[2], $args[3]);
|
||||
break;
|
||||
case 5:
|
||||
$result[] = $observer->{$event}($args[0], $args[1], $args[2], $args[3], $args[4]);
|
||||
break;
|
||||
default:
|
||||
$result[] = call_user_func_array([$observer, $event], $args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the observers' result in an array
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks each observer to handle an event based on the provided arguments. The first observer to return a non-null
|
||||
* result wins. This is a *very* simplistic implementation of the Chain of Command pattern.
|
||||
*
|
||||
* @param string $event The event name to handle
|
||||
* @param array $args The arguments to the event
|
||||
*
|
||||
* @return mixed Null if the event can't be handled by any observer
|
||||
*/
|
||||
public function chainHandle($event, $args = [])
|
||||
{
|
||||
$event = strtolower($event);
|
||||
|
||||
$result = null;
|
||||
|
||||
// Make sure the event is known to us, otherwise return an empty array
|
||||
if (!isset($this->events[$event]) || empty($this->events[$event]))
|
||||
{
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($this->events[$event] as $className)
|
||||
{
|
||||
// Make sure the observer exists.
|
||||
if (!isset($this->observers[$className]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the observer
|
||||
$observer = $this->observers[$className];
|
||||
|
||||
// Make sure the method exists
|
||||
if (!method_exists($observer, $event))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Call the event handler and add its output to the return value. The switch allows for execution up to 2x
|
||||
// faster than using call_user_func_array
|
||||
switch (count($args))
|
||||
{
|
||||
case 0:
|
||||
$result = $observer->{$event}();
|
||||
break;
|
||||
case 1:
|
||||
$result = $observer->{$event}($args[0]);
|
||||
break;
|
||||
case 2:
|
||||
$result = $observer->{$event}($args[0], $args[1]);
|
||||
break;
|
||||
case 3:
|
||||
$result = $observer->{$event}($args[0], $args[1], $args[2]);
|
||||
break;
|
||||
case 4:
|
||||
$result = $observer->{$event}($args[0], $args[1], $args[2], $args[3]);
|
||||
break;
|
||||
case 5:
|
||||
$result = $observer->{$event}($args[0], $args[1], $args[2], $args[3], $args[4]);
|
||||
break;
|
||||
default:
|
||||
$result = call_user_func_array([$observer, $event], $args);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!is_null($result))
|
||||
{
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the observers' result in an array
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
46
libraries/fof30/Event/Observable.php
Normal file
46
libraries/fof30/Event/Observable.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Event;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Interface Observable
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
interface Observable
|
||||
{
|
||||
/**
|
||||
* Attaches an observer to the object
|
||||
*
|
||||
* @param Observer $observer The observer to attach
|
||||
*
|
||||
* @return Observable Ourselves, for chaining
|
||||
*/
|
||||
public function attach(Observer $observer);
|
||||
|
||||
/**
|
||||
* Detaches an observer from the object
|
||||
*
|
||||
* @param Observer $observer The observer to detach
|
||||
*
|
||||
* @return Observable Ourselves, for chaining
|
||||
*/
|
||||
public function detach(Observer $observer);
|
||||
|
||||
/**
|
||||
* Triggers an event in the attached observers
|
||||
*
|
||||
* @param string $event The event to attach
|
||||
* @param array $args Arguments to the event handler
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function trigger($event, array $args = []);
|
||||
}
|
||||
70
libraries/fof30/Event/Observer.php
Normal file
70
libraries/fof30/Event/Observer.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Event;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use ReflectionMethod;
|
||||
use ReflectionObject;
|
||||
|
||||
class Observer
|
||||
{
|
||||
/** @var Observable The object to observe */
|
||||
protected $subject = null;
|
||||
|
||||
protected $events = null;
|
||||
|
||||
/**
|
||||
* Creates the observer and attaches it to the observable subject object
|
||||
*
|
||||
* @param Observable $subject The observable object to attach the observer to
|
||||
*/
|
||||
function __construct(Observable &$subject)
|
||||
{
|
||||
// Attach this observer to the subject
|
||||
$subject->attach($this);
|
||||
|
||||
// Store a reference to the subject object
|
||||
$this->subject = $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of events observable by this observer. Set the $this->events array manually for faster
|
||||
* processing, or let this method use reflection to return a list of all public methods.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getObservableEvents()
|
||||
{
|
||||
if (is_null($this->events))
|
||||
{
|
||||
// Assign an empty array to protect us from behaviours without any valid method
|
||||
$this->events = [];
|
||||
|
||||
$reflection = new ReflectionObject($this);
|
||||
$methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
|
||||
|
||||
foreach ($methods as $m)
|
||||
{
|
||||
if ($m->name == 'getObservableEvents')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($m->name == '__construct')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->events[] = $m->name;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->events;
|
||||
}
|
||||
}
|
||||
510
libraries/fof30/Factory/BasicFactory.php
Normal file
510
libraries/fof30/Factory/BasicFactory.php
Normal file
@ -0,0 +1,510 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Controller\Controller;
|
||||
use FOF30\Dispatcher\Dispatcher;
|
||||
use FOF30\Factory\Exception\ControllerNotFound;
|
||||
use FOF30\Factory\Exception\DispatcherNotFound;
|
||||
use FOF30\Factory\Exception\ModelNotFound;
|
||||
use FOF30\Factory\Exception\ToolbarNotFound;
|
||||
use FOF30\Factory\Exception\TransparentAuthenticationNotFound;
|
||||
use FOF30\Factory\Exception\ViewNotFound;
|
||||
use FOF30\Model\Model;
|
||||
use FOF30\Toolbar\Toolbar;
|
||||
use FOF30\TransparentAuthentication\TransparentAuthentication;
|
||||
use FOF30\View\View;
|
||||
use FOF30\View\ViewTemplateFinder;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* MVC object factory. This implements the basic functionality, i.e. creating MVC objects only if the classes exist in
|
||||
* the same component section (front-end, back-end) you are currently running in. The Dispatcher and Toolbar will be
|
||||
* created from default objects if specialised classes are not found in your application.
|
||||
*/
|
||||
class BasicFactory implements FactoryInterface
|
||||
{
|
||||
/** @var Container The container we belong to */
|
||||
protected $container = null;
|
||||
|
||||
/**
|
||||
* Section used to build the namespace prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $section = 'auto';
|
||||
|
||||
/**
|
||||
* Public constructor for the factory object
|
||||
*
|
||||
* @param Container $container The container we belong to
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Controller object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a Controller for.
|
||||
* @param array $config Optional MVC configuration values for the Controller object.
|
||||
*
|
||||
* @return Controller
|
||||
*/
|
||||
public function controller($viewName, array $config = [])
|
||||
{
|
||||
$controllerClass = $this->container->getNamespacePrefix($this->getSection()) . 'Controller\\' . ucfirst($viewName);
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createController($controllerClass, $config);
|
||||
}
|
||||
catch (ControllerNotFound $e)
|
||||
{
|
||||
}
|
||||
|
||||
$controllerClass = $this->container->getNamespacePrefix($this->getSection()) . 'Controller\\' . ucfirst($this->container->inflector->singularize($viewName));
|
||||
|
||||
$controller = $this->createController($controllerClass, $config);
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Model object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a Model for.
|
||||
* @param array $config Optional MVC configuration values for the Model object.
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function model($viewName, array $config = [])
|
||||
{
|
||||
$modelClass = $this->container->getNamespacePrefix($this->getSection()) . 'Model\\' . ucfirst($viewName);
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createModel($modelClass, $config);
|
||||
}
|
||||
catch (ModelNotFound $e)
|
||||
{
|
||||
}
|
||||
|
||||
$modelClass = $this->container->getNamespacePrefix($this->getSection()) . 'Model\\' . ucfirst($this->container->inflector->singularize($viewName));
|
||||
|
||||
$model = $this->createModel($modelClass, $config);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new View object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a View object for.
|
||||
* @param string $viewType The type of the View object. By default it's "html".
|
||||
* @param array $config Optional MVC configuration values for the View object.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function view($viewName, $viewType = 'html', array $config = [])
|
||||
{
|
||||
$container = $this->container;
|
||||
$prefix = $this->container->getNamespacePrefix($this->getSection());
|
||||
|
||||
$viewClass = $prefix . 'View\\' . ucfirst($viewName) . '\\' . ucfirst($viewType);
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createView($viewClass, $config);
|
||||
}
|
||||
catch (ViewNotFound $e)
|
||||
{
|
||||
}
|
||||
|
||||
$viewClass = $prefix . 'View\\' . ucfirst($container->inflector->singularize($viewName)) . '\\' . ucfirst($viewType);
|
||||
|
||||
$view = $this->createView($viewClass, $config);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Dispatcher
|
||||
*
|
||||
* @param array $config The configuration values for the Dispatcher object
|
||||
*
|
||||
* @return Dispatcher
|
||||
*/
|
||||
public function dispatcher(array $config = [])
|
||||
{
|
||||
$dispatcherClass = $this->container->getNamespacePrefix($this->getSection()) . 'Dispatcher\\Dispatcher';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createDispatcher($dispatcherClass, $config);
|
||||
}
|
||||
catch (DispatcherNotFound $e)
|
||||
{
|
||||
// Not found. Return the default Dispatcher
|
||||
return new Dispatcher($this->container, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Toolbar
|
||||
*
|
||||
* @param array $config The configuration values for the Toolbar object
|
||||
*
|
||||
* @return Toolbar
|
||||
*/
|
||||
public function toolbar(array $config = [])
|
||||
{
|
||||
$toolbarClass = $this->container->getNamespacePrefix($this->getSection()) . 'Toolbar\\Toolbar';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createToolbar($toolbarClass, $config);
|
||||
}
|
||||
catch (ToolbarNotFound $e)
|
||||
{
|
||||
// Not found. Return the default Toolbar
|
||||
return new Toolbar($this->container, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new TransparentAuthentication handler
|
||||
*
|
||||
* @param array $config The configuration values for the TransparentAuthentication object
|
||||
*
|
||||
* @return TransparentAuthentication
|
||||
*/
|
||||
public function transparentAuthentication(array $config = [])
|
||||
{
|
||||
$authClass = $this->container->getNamespacePrefix($this->getSection()) . 'TransparentAuthentication\\TransparentAuthentication';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createTransparentAuthentication($authClass, $config);
|
||||
}
|
||||
catch (TransparentAuthenticationNotFound $e)
|
||||
{
|
||||
// Not found. Return the default TA
|
||||
return new TransparentAuthentication($this->container, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a view template finder object for a specific View
|
||||
*
|
||||
* The default configuration is:
|
||||
* Look for .php, .blade.php files; default layout "default"; no default sub-template;
|
||||
* look only for the specified view; do NOT fall back to the default layout or sub-template;
|
||||
* look for templates ONLY in site or admin, depending on where we're running from
|
||||
*
|
||||
* @param View $view The view this view template finder will be attached to
|
||||
* @param array $config Configuration variables for the object
|
||||
*
|
||||
* @return ViewTemplateFinder
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function viewFinder(View $view, array $config = [])
|
||||
{
|
||||
// Initialise the configuration with the default values
|
||||
$defaultConfig = [
|
||||
'extensions' => ['.php', '.blade.php'],
|
||||
'defaultLayout' => 'default',
|
||||
'defaultTpl' => '',
|
||||
'strictView' => true,
|
||||
'strictTpl' => true,
|
||||
'strictLayout' => true,
|
||||
'sidePrefix' => 'auto',
|
||||
];
|
||||
|
||||
$config = array_merge($defaultConfig, $config);
|
||||
|
||||
// Apply fof.xml overrides
|
||||
$appConfig = $this->container->appConfig;
|
||||
$key = "views." . ucfirst($view->getName()) . ".config";
|
||||
|
||||
$fofXmlConfig = [
|
||||
'extensions' => $appConfig->get("$key.templateExtensions", $config['extensions']),
|
||||
'strictView' => $appConfig->get("$key.templateStrictView", $config['strictView']),
|
||||
'strictTpl' => $appConfig->get("$key.templateStrictTpl", $config['strictTpl']),
|
||||
'strictLayout' => $appConfig->get("$key.templateStrictLayout", $config['strictLayout']),
|
||||
'sidePrefix' => $appConfig->get("$key.templateLocation", $config['sidePrefix']),
|
||||
];
|
||||
|
||||
$config = array_merge($config, $fofXmlConfig);
|
||||
|
||||
// Create the new view template finder object
|
||||
return new ViewTemplateFinder($view, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSection()
|
||||
{
|
||||
return $this->section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $section
|
||||
*/
|
||||
public function setSection($section)
|
||||
{
|
||||
$this->section = $section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is scaffolding enabled?
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @deprecated 3.6.0 Always returns false
|
||||
*/
|
||||
public function isScaffolding()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scaffolding status
|
||||
*
|
||||
* @param bool $scaffolding
|
||||
*
|
||||
* @deprecated Removed since 3.6.0, does nothing
|
||||
*/
|
||||
public function setScaffolding($scaffolding): void
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
/**
|
||||
* Is saving the scaffolding result to disk enabled?
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @deprecated 3.6.0 Always returns false
|
||||
*/
|
||||
public function isSaveScaffolding()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status of saving the scaffolding result to disk.
|
||||
*
|
||||
* @param bool $saveScaffolding
|
||||
*
|
||||
* @deprecated 3.6.0 Does nothing
|
||||
*/
|
||||
public function setSaveScaffolding($saveScaffolding)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we save controller to disk?
|
||||
*
|
||||
* @param bool $state
|
||||
*
|
||||
* @deprecated 3.6.0 Does nothing
|
||||
*/
|
||||
public function setSaveControllerScaffolding($state)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we save controller scaffolding to disk?
|
||||
*
|
||||
* @return bool $state
|
||||
*
|
||||
* @deprecated 3.6.0 Always returns false
|
||||
*/
|
||||
public function isSaveControllerScaffolding()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we save model to disk?
|
||||
*
|
||||
* @param bool $state
|
||||
*
|
||||
* @deprecated 3.6.0 Does nothing
|
||||
*/
|
||||
public function setSaveModelScaffolding($state)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we save model scaffolding to disk?
|
||||
*
|
||||
* @return bool $state
|
||||
*
|
||||
* @deprecated 3.6.0 Always returns false
|
||||
*/
|
||||
public function isSaveModelScaffolding()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we save view to disk?
|
||||
*
|
||||
* @param bool $state
|
||||
*
|
||||
* @deprecated 3.6.0 Does nothing
|
||||
*/
|
||||
public function setSaveViewScaffolding($state)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we save view scaffolding to disk?
|
||||
*
|
||||
* @return bool $state
|
||||
*
|
||||
* @deprecated 3.6.0 Always returns false
|
||||
*/
|
||||
public function isSaveViewScaffolding()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Controller object
|
||||
*
|
||||
* @param string $controllerClass The fully qualified class name for the Controller
|
||||
* @param array $config Optional MVC configuration values for the Controller object.
|
||||
*
|
||||
* @return Controller
|
||||
*
|
||||
* @throws RuntimeException If the $controllerClass does not exist
|
||||
*/
|
||||
protected function createController($controllerClass, array $config = [])
|
||||
{
|
||||
if (!class_exists($controllerClass))
|
||||
{
|
||||
throw new ControllerNotFound($controllerClass);
|
||||
}
|
||||
|
||||
return new $controllerClass($this->container, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Model object
|
||||
*
|
||||
* @param string $modelClass The fully qualified class name for the Model
|
||||
* @param array $config Optional MVC configuration values for the Model object.
|
||||
*
|
||||
* @return Model
|
||||
*
|
||||
* @throws RuntimeException If the $modelClass does not exist
|
||||
*/
|
||||
protected function createModel($modelClass, array $config = [])
|
||||
{
|
||||
if (!class_exists($modelClass))
|
||||
{
|
||||
throw new ModelNotFound($modelClass);
|
||||
}
|
||||
|
||||
return new $modelClass($this->container, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a View object
|
||||
*
|
||||
* @param string $viewClass The fully qualified class name for the View
|
||||
* @param array $config Optional MVC configuration values for the View object.
|
||||
*
|
||||
* @return View
|
||||
*
|
||||
* @throws RuntimeException If the $viewClass does not exist
|
||||
*/
|
||||
protected function createView($viewClass, array $config = [])
|
||||
{
|
||||
if (!class_exists($viewClass))
|
||||
{
|
||||
throw new ViewNotFound($viewClass);
|
||||
}
|
||||
|
||||
return new $viewClass($this->container, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Toolbar object
|
||||
*
|
||||
* @param string $toolbarClass The fully qualified class name for the Toolbar
|
||||
* @param array $config The configuration values for the Toolbar object
|
||||
*
|
||||
* @return Toolbar
|
||||
*
|
||||
* @throws RuntimeException If the $toolbarClass does not exist
|
||||
*/
|
||||
protected function createToolbar($toolbarClass, array $config = [])
|
||||
{
|
||||
if (!class_exists($toolbarClass))
|
||||
{
|
||||
throw new ToolbarNotFound($toolbarClass);
|
||||
}
|
||||
|
||||
return new $toolbarClass($this->container, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Dispatcher object
|
||||
*
|
||||
* @param string $dispatcherClass The fully qualified class name for the Dispatcher
|
||||
* @param array $config The configuration values for the Dispatcher object
|
||||
*
|
||||
* @return Dispatcher
|
||||
*
|
||||
* @throws RuntimeException If the $dispatcherClass does not exist
|
||||
*/
|
||||
protected function createDispatcher($dispatcherClass, array $config = [])
|
||||
{
|
||||
if (!class_exists($dispatcherClass))
|
||||
{
|
||||
throw new DispatcherNotFound($dispatcherClass);
|
||||
}
|
||||
|
||||
return new $dispatcherClass($this->container, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TransparentAuthentication object
|
||||
*
|
||||
* @param string $authClass The fully qualified class name for the TransparentAuthentication
|
||||
* @param array $config The configuration values for the TransparentAuthentication object
|
||||
*
|
||||
* @return TransparentAuthentication
|
||||
*
|
||||
* @throws RuntimeException If the $authClass does not exist
|
||||
*/
|
||||
protected function createTransparentAuthentication($authClass, $config)
|
||||
{
|
||||
if (!class_exists($authClass))
|
||||
{
|
||||
throw new TransparentAuthenticationNotFound($authClass);
|
||||
}
|
||||
|
||||
return new $authClass($this->container, $config);
|
||||
}
|
||||
}
|
||||
25
libraries/fof30/Factory/Exception/ControllerNotFound.php
Normal file
25
libraries/fof30/Factory/Exception/ControllerNotFound.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use RuntimeException;
|
||||
|
||||
class ControllerNotFound extends RuntimeException
|
||||
{
|
||||
public function __construct($controller, $code = 500, Exception $previous = null)
|
||||
{
|
||||
$message = Text::sprintf('LIB_FOF_CONTROLLER_ERR_NOT_FOUND', $controller);
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
25
libraries/fof30/Factory/Exception/DispatcherNotFound.php
Normal file
25
libraries/fof30/Factory/Exception/DispatcherNotFound.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use RuntimeException;
|
||||
|
||||
class DispatcherNotFound extends RuntimeException
|
||||
{
|
||||
public function __construct($dispatcherClass, $code = 500, Exception $previous = null)
|
||||
{
|
||||
$message = Text::sprintf('LIB_FOF_DISPATCHER_ERR_NOT_FOUND', $dispatcherClass);
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
25
libraries/fof30/Factory/Exception/ModelNotFound.php
Normal file
25
libraries/fof30/Factory/Exception/ModelNotFound.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use RuntimeException;
|
||||
|
||||
class ModelNotFound extends RuntimeException
|
||||
{
|
||||
public function __construct($modelClass, $code = 500, Exception $previous = null)
|
||||
{
|
||||
$message = Text::sprintf('LIB_FOF_MODEL_ERR_NOT_FOUND', $modelClass);
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
25
libraries/fof30/Factory/Exception/ToolbarNotFound.php
Normal file
25
libraries/fof30/Factory/Exception/ToolbarNotFound.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use RuntimeException;
|
||||
|
||||
class ToolbarNotFound extends RuntimeException
|
||||
{
|
||||
public function __construct($toolbarClass, $code = 500, Exception $previous = null)
|
||||
{
|
||||
$message = Text::sprintf('LIB_FOF_TOOLBAR_ERR_NOT_FOUND', $toolbarClass);
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use RuntimeException;
|
||||
|
||||
class TransparentAuthenticationNotFound extends RuntimeException
|
||||
{
|
||||
public function __construct($taClass, $code = 500, Exception $previous = null)
|
||||
{
|
||||
$message = Text::sprintf('LIB_FOF_TRANSPARENTAUTH_ERR_NOT_FOUND', $taClass);
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
25
libraries/fof30/Factory/Exception/ViewNotFound.php
Normal file
25
libraries/fof30/Factory/Exception/ViewNotFound.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use RuntimeException;
|
||||
|
||||
class ViewNotFound extends RuntimeException
|
||||
{
|
||||
public function __construct($viewClass, $code = 500, Exception $previous = null)
|
||||
{
|
||||
$message = Text::sprintf('LIB_FOF_VIEW_ERR_NOT_FOUND', $viewClass);
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
109
libraries/fof30/Factory/FactoryInterface.php
Normal file
109
libraries/fof30/Factory/FactoryInterface.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Controller\Controller;
|
||||
use FOF30\Dispatcher\Dispatcher;
|
||||
use FOF30\Model\Model;
|
||||
use FOF30\Toolbar\Toolbar;
|
||||
use FOF30\TransparentAuthentication\TransparentAuthentication;
|
||||
use FOF30\View\View;
|
||||
|
||||
/**
|
||||
* Interface for the MVC object factory
|
||||
*/
|
||||
interface FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Public constructor for the factory object
|
||||
*
|
||||
* @param Container $container The container we belong to
|
||||
*/
|
||||
function __construct(Container $container);
|
||||
|
||||
/**
|
||||
* Create a new Controller object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a Controller for.
|
||||
* @param array $config Optional MVC configuration values for the Controller object.
|
||||
*
|
||||
* @return Controller
|
||||
*/
|
||||
function controller($viewName, array $config = []);
|
||||
|
||||
/**
|
||||
* Create a new Model object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a Model for.
|
||||
* @param array $config Optional MVC configuration values for the Model object.
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
function model($viewName, array $config = []);
|
||||
|
||||
/**
|
||||
* Create a new View object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a View object for.
|
||||
* @param string $viewType The type of the View object. By default it's "html".
|
||||
* @param array $config Optional MVC configuration values for the View object.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
function view($viewName, $viewType = 'html', array $config = []);
|
||||
|
||||
/**
|
||||
* Creates a new Toolbar
|
||||
*
|
||||
* @param array $config The configuration values for the Toolbar object
|
||||
*
|
||||
* @return Toolbar
|
||||
*/
|
||||
function toolbar(array $config = []);
|
||||
|
||||
/**
|
||||
* Creates a new Dispatcher
|
||||
*
|
||||
* @param array $config The configuration values for the Dispatcher object
|
||||
*
|
||||
* @return Dispatcher
|
||||
*/
|
||||
function dispatcher(array $config = []);
|
||||
|
||||
/**
|
||||
* Creates a new TransparentAuthentication handler
|
||||
*
|
||||
* @param array $config The configuration values for the TransparentAuthentication object
|
||||
*
|
||||
* @return TransparentAuthentication
|
||||
*/
|
||||
function transparentAuthentication(array $config = []);
|
||||
|
||||
/**
|
||||
* Creates a view template finder object for a specific View
|
||||
*
|
||||
* @param View $view The view this view template finder will be attached to
|
||||
* @param array $config Configuration variables for the object
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function viewFinder(View $view, array $config = []);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSection();
|
||||
|
||||
/**
|
||||
* @param string $section
|
||||
*/
|
||||
public function setSection($section);
|
||||
}
|
||||
53
libraries/fof30/Factory/Magic/BaseFactory.php
Normal file
53
libraries/fof30/Factory/Magic/BaseFactory.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Magic;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Container\Container;
|
||||
|
||||
abstract class BaseFactory
|
||||
{
|
||||
/**
|
||||
* @var Container|null The container where this factory belongs to
|
||||
*/
|
||||
protected $container = null;
|
||||
|
||||
/**
|
||||
* Section used to build the namespace prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $section = 'auto';
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param Container $container The container we belong to
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSection()
|
||||
{
|
||||
return $this->section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $section
|
||||
*/
|
||||
public function setSection($section)
|
||||
{
|
||||
$this->section = $section;
|
||||
}
|
||||
}
|
||||
76
libraries/fof30/Factory/Magic/ControllerFactory.php
Normal file
76
libraries/fof30/Factory/Magic/ControllerFactory.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Magic;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Controller\DataController;
|
||||
use FOF30\Factory\Exception\ControllerNotFound;
|
||||
|
||||
/**
|
||||
* Creates a DataController object instance based on the information provided by the fof.xml configuration file
|
||||
*/
|
||||
class ControllerFactory extends BaseFactory
|
||||
{
|
||||
/**
|
||||
* Create a new object instance
|
||||
*
|
||||
* @param string $name The name of the class we're making
|
||||
* @param array $config The config parameters which override the fof.xml information
|
||||
*
|
||||
* @return DataController A new DataController object
|
||||
*/
|
||||
public function make($name = null, array $config = [])
|
||||
{
|
||||
if (empty($name))
|
||||
{
|
||||
throw new ControllerNotFound($name);
|
||||
}
|
||||
|
||||
$appConfig = $this->container->appConfig;
|
||||
$name = ucfirst($name);
|
||||
|
||||
$defaultConfig = [
|
||||
'name' => $name,
|
||||
'default_task' => $appConfig->get("views.$name.config.default_task", 'main'),
|
||||
'autoRouting' => $appConfig->get("views.$name.config.autoRouting", 1),
|
||||
'csrfProtection' => $appConfig->get("views.$name.config.csrfProtection", 2),
|
||||
'viewName' => $appConfig->get("views.$name.config.viewName", null),
|
||||
'modelName' => $appConfig->get("views.$name.config.modelName", null),
|
||||
'taskPrivileges' => $appConfig->get("views.$name.acl"),
|
||||
'cacheableTasks' => $appConfig->get("views.$name.config.cacheableTasks", [
|
||||
'browse',
|
||||
'read',
|
||||
]),
|
||||
'taskMap' => $appConfig->get("views.$name.taskmap"),
|
||||
];
|
||||
|
||||
$config = array_merge($defaultConfig, $config);
|
||||
|
||||
$className = $this->container->getNamespacePrefix($this->getSection()) . 'Controller\\DefaultDataController';
|
||||
|
||||
if (!class_exists($className, true))
|
||||
{
|
||||
$className = 'FOF30\\Controller\\DataController';
|
||||
}
|
||||
|
||||
$controller = new $className($this->container, $config);
|
||||
|
||||
$taskMap = $config['taskMap'];
|
||||
|
||||
if (is_array($taskMap) && !empty($taskMap))
|
||||
{
|
||||
foreach ($taskMap as $virtualTask => $method)
|
||||
{
|
||||
$controller->registerTask($virtualTask, $method);
|
||||
}
|
||||
}
|
||||
|
||||
return $controller;
|
||||
}
|
||||
}
|
||||
43
libraries/fof30/Factory/Magic/DispatcherFactory.php
Normal file
43
libraries/fof30/Factory/Magic/DispatcherFactory.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Magic;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Dispatcher\Dispatcher;
|
||||
|
||||
/**
|
||||
* Creates a Dispatcher object instance based on the information provided by the fof.xml configuration file
|
||||
*/
|
||||
class DispatcherFactory extends BaseFactory
|
||||
{
|
||||
/**
|
||||
* Create a new object instance
|
||||
*
|
||||
* @param array $config The config parameters which override the fof.xml information
|
||||
*
|
||||
* @return Dispatcher A new Dispatcher object
|
||||
*/
|
||||
public function make(array $config = [])
|
||||
{
|
||||
$appConfig = $this->container->appConfig;
|
||||
$defaultConfig = $appConfig->get('dispatcher.*');
|
||||
$config = array_merge($defaultConfig, $config);
|
||||
|
||||
$className = $this->container->getNamespacePrefix($this->getSection()) . 'Dispatcher\\DefaultDispatcher';
|
||||
|
||||
if (!class_exists($className, true))
|
||||
{
|
||||
$className = '\\FOF30\\Dispatcher\\Dispatcher';
|
||||
}
|
||||
|
||||
$dispatcher = new $className($this->container, $config);
|
||||
|
||||
return $dispatcher;
|
||||
}
|
||||
}
|
||||
86
libraries/fof30/Factory/Magic/ModelFactory.php
Normal file
86
libraries/fof30/Factory/Magic/ModelFactory.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Magic;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Factory\Exception\ModelNotFound;
|
||||
use FOF30\Model\DataModel;
|
||||
use FOF30\Model\TreeModel;
|
||||
|
||||
/**
|
||||
* Creates a DataModel/TreeModel object instance based on the information provided by the fof.xml configuration file
|
||||
*/
|
||||
class ModelFactory extends BaseFactory
|
||||
{
|
||||
/**
|
||||
* Create a new object instance
|
||||
*
|
||||
* @param string $name The name of the class we're making
|
||||
* @param array $config The config parameters which override the fof.xml information
|
||||
*
|
||||
* @return TreeModel|DataModel A new TreeModel or DataModel object
|
||||
*/
|
||||
public function make($name = null, array $config = [])
|
||||
{
|
||||
if (empty($name))
|
||||
{
|
||||
throw new ModelNotFound($name);
|
||||
}
|
||||
|
||||
$appConfig = $this->container->appConfig;
|
||||
$name = ucfirst($name);
|
||||
|
||||
$defaultConfig = [
|
||||
'name' => $name,
|
||||
'use_populate' => $appConfig->get("models.$name.config.use_populate"),
|
||||
'ignore_request' => $appConfig->get("models.$name.config.ignore_request"),
|
||||
'tableName' => $appConfig->get("models.$name.config.tbl"),
|
||||
'idFieldName' => $appConfig->get("models.$name.config.tbl_key"),
|
||||
'knownFields' => $appConfig->get("models.$name.config.knownFields", null),
|
||||
'autoChecks' => $appConfig->get("models.$name.config.autoChecks"),
|
||||
'contentType' => $appConfig->get("models.$name.config.contentType"),
|
||||
'fieldsSkipChecks' => $appConfig->get("models.$name.config.fieldsSkipChecks", []),
|
||||
'aliasFields' => $appConfig->get("models.$name.field", []),
|
||||
'behaviours' => $appConfig->get("models.$name.behaviors", []),
|
||||
'fillable_fields' => $appConfig->get("models.$name.config.fillable_fields", []),
|
||||
'guarded_fields' => $appConfig->get("models.$name.config.guarded_fields", []),
|
||||
'relations' => $appConfig->get("models.$name.relations", []),
|
||||
];
|
||||
|
||||
$config = array_merge($defaultConfig, $config);
|
||||
|
||||
// Get the default class names
|
||||
$dataModelClassName = $this->container->getNamespacePrefix($this->getSection()) . 'Model\\DefaultDataModel';
|
||||
|
||||
if (!class_exists($dataModelClassName, true))
|
||||
{
|
||||
$dataModelClassName = '\\FOF30\\Model\\DataModel';
|
||||
}
|
||||
|
||||
$treeModelClassName = $this->container->getNamespacePrefix($this->getSection()) . 'Model\\DefaultTreeModel';
|
||||
|
||||
if (!class_exists($treeModelClassName, true))
|
||||
{
|
||||
$treeModelClassName = '\\FOF30\\Model\\TreeModel';
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// First try creating a TreeModel
|
||||
$model = new $treeModelClassName($this->container, $config);
|
||||
}
|
||||
catch (DataModel\Exception\TreeIncompatibleTable $e)
|
||||
{
|
||||
// If the table isn't a nested set, create a regular DataModel
|
||||
$model = new $dataModelClassName($this->container, $config);
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Magic;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Dispatcher\Dispatcher;
|
||||
|
||||
/**
|
||||
* Creates a TransparentAuthentication object instance based on the information provided by the fof.xml configuration
|
||||
* file
|
||||
*/
|
||||
class TransparentAuthenticationFactory extends BaseFactory
|
||||
{
|
||||
/**
|
||||
* Create a new object instance
|
||||
*
|
||||
* @param array $config The config parameters which override the fof.xml information
|
||||
*
|
||||
* @return Dispatcher A new Dispatcher object
|
||||
*/
|
||||
public function make(array $config = [])
|
||||
{
|
||||
$appConfig = $this->container->appConfig;
|
||||
$defaultConfig = $appConfig->get('authentication.*');
|
||||
$config = array_merge($defaultConfig, $config);
|
||||
|
||||
$className = $this->container->getNamespacePrefix($this->getSection()) . 'TransparentAuthentication\\DefaultTransparentAuthentication';
|
||||
|
||||
if (!class_exists($className, true))
|
||||
{
|
||||
$className = '\\FOF30\\TransparentAuthentication\\TransparentAuthentication';
|
||||
}
|
||||
|
||||
$dispatcher = new $className($this->container, $config);
|
||||
|
||||
return $dispatcher;
|
||||
}
|
||||
}
|
||||
70
libraries/fof30/Factory/Magic/ViewFactory.php
Normal file
70
libraries/fof30/Factory/Magic/ViewFactory.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory\Magic;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Factory\Exception\ViewNotFound;
|
||||
use FOF30\View\DataView\DataViewInterface;
|
||||
|
||||
/**
|
||||
* Creates a DataModel/TreeModel object instance based on the information provided by the fof.xml configuration file
|
||||
*/
|
||||
class ViewFactory extends BaseFactory
|
||||
{
|
||||
/**
|
||||
* Create a new object instance
|
||||
*
|
||||
* @param string $name The name of the class we're making
|
||||
* @param string $viewType The view type, default html, possible values html, form, raw, json, csv
|
||||
* @param array $config The config parameters which override the fof.xml information
|
||||
*
|
||||
* @return DataViewInterface A new TreeModel or DataModel object
|
||||
*/
|
||||
public function make($name = null, $viewType = 'html', array $config = [])
|
||||
{
|
||||
if (empty($name))
|
||||
{
|
||||
throw new ViewNotFound("[name : type] = [$name : $viewType]");
|
||||
}
|
||||
|
||||
$appConfig = $this->container->appConfig;
|
||||
$name = ucfirst($name);
|
||||
|
||||
$defaultConfig = [
|
||||
'name' => $name,
|
||||
'template_path' => $appConfig->get("views.$name.config.template_path"),
|
||||
'layout' => $appConfig->get("views.$name.config.layout"),
|
||||
// You can pass something like .php => Class1, .foo.bar => Class 2
|
||||
'viewEngineMap' => $appConfig->get("views.$name.config.viewEngineMap"),
|
||||
];
|
||||
|
||||
$config = array_merge($defaultConfig, $config);
|
||||
|
||||
$className = $this->container->getNamespacePrefix($this->getSection()) . 'View\\DataView\\Default' . ucfirst($viewType);
|
||||
|
||||
if (!class_exists($className, true))
|
||||
{
|
||||
$className = '\\FOF30\\View\\DataView\\' . ucfirst($viewType);
|
||||
}
|
||||
|
||||
if (!class_exists($className, true))
|
||||
{
|
||||
$className = $this->container->getNamespacePrefix($this->getSection()) . 'View\\DataView\\DefaultHtml';
|
||||
}
|
||||
|
||||
if (!class_exists($className))
|
||||
{
|
||||
$className = '\\FOF30\\View\\DataView\\Html';
|
||||
}
|
||||
|
||||
$view = new $className($this->container, $config);
|
||||
|
||||
return $view;
|
||||
}
|
||||
}
|
||||
167
libraries/fof30/Factory/MagicFactory.php
Normal file
167
libraries/fof30/Factory/MagicFactory.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Controller\Controller;
|
||||
use FOF30\Factory\Exception\ControllerNotFound;
|
||||
use FOF30\Factory\Exception\DispatcherNotFound;
|
||||
use FOF30\Factory\Exception\ModelNotFound;
|
||||
use FOF30\Factory\Exception\TransparentAuthenticationNotFound;
|
||||
use FOF30\Factory\Exception\ViewNotFound;
|
||||
use FOF30\Factory\Magic\DispatcherFactory;
|
||||
use FOF30\Factory\Magic\TransparentAuthenticationFactory;
|
||||
use FOF30\Model\Model;
|
||||
use FOF30\Toolbar\Toolbar;
|
||||
use FOF30\TransparentAuthentication\TransparentAuthentication;
|
||||
use FOF30\View\View;
|
||||
|
||||
/**
|
||||
* Magic MVC object factory. This factory will "magically" create MVC objects even if the respective classes do not
|
||||
* exist, based on information in your fof.xml file.
|
||||
*
|
||||
* Note: This factory class will ONLY look for MVC objects in the same component section (front-end, back-end) you are
|
||||
* currently running in. If they are not found a new one will be created magically.
|
||||
*/
|
||||
class MagicFactory extends BasicFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Create a new Controller object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a Controller for.
|
||||
* @param array $config Optional MVC configuration values for the Controller object.
|
||||
*
|
||||
* @return Controller
|
||||
*/
|
||||
public function controller($viewName, array $config = [])
|
||||
{
|
||||
try
|
||||
{
|
||||
return parent::controller($viewName, $config);
|
||||
}
|
||||
catch (ControllerNotFound $e)
|
||||
{
|
||||
$magic = new Magic\ControllerFactory($this->container);
|
||||
|
||||
return $magic->make($viewName, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Model object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a Model for.
|
||||
* @param array $config Optional MVC configuration values for the Model object.
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function model($viewName, array $config = [])
|
||||
{
|
||||
try
|
||||
{
|
||||
return parent::model($viewName, $config);
|
||||
}
|
||||
catch (ModelNotFound $e)
|
||||
{
|
||||
$magic = new Magic\ModelFactory($this->container);
|
||||
|
||||
return $magic->make($viewName, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new View object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a View object for.
|
||||
* @param string $viewType The type of the View object. By default it's "html".
|
||||
* @param array $config Optional MVC configuration values for the View object.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function view($viewName, $viewType = 'html', array $config = [])
|
||||
{
|
||||
try
|
||||
{
|
||||
return parent::view($viewName, $viewType, $config);
|
||||
}
|
||||
catch (ViewNotFound $e)
|
||||
{
|
||||
$magic = new Magic\ViewFactory($this->container);
|
||||
|
||||
return $magic->make($viewName, $viewType, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Toolbar
|
||||
*
|
||||
* @param array $config The configuration values for the Toolbar object
|
||||
*
|
||||
* @return Toolbar
|
||||
*/
|
||||
public function toolbar(array $config = [])
|
||||
{
|
||||
$appConfig = $this->container->appConfig;
|
||||
|
||||
$defaultConfig = [
|
||||
'useConfigurationFile' => true,
|
||||
'renderFrontendButtons' => in_array($appConfig->get("views.*.config.renderFrontendButtons"), [
|
||||
true, 'true', 'yes', 'on', 1,
|
||||
]),
|
||||
'renderFrontendSubmenu' => in_array($appConfig->get("views.*.config.renderFrontendSubmenu"), [
|
||||
true, 'true', 'yes', 'on', 1,
|
||||
]),
|
||||
];
|
||||
|
||||
$config = array_merge($defaultConfig, $config);
|
||||
|
||||
return parent::toolbar($config);
|
||||
}
|
||||
|
||||
public function dispatcher(array $config = [])
|
||||
{
|
||||
$dispatcherClass = $this->container->getNamespacePrefix() . 'Dispatcher\\Dispatcher';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createDispatcher($dispatcherClass, $config);
|
||||
}
|
||||
catch (DispatcherNotFound $e)
|
||||
{
|
||||
// Not found. Return the magically created Dispatcher
|
||||
$magic = new DispatcherFactory($this->container);
|
||||
|
||||
return $magic->make($config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new TransparentAuthentication handler
|
||||
*
|
||||
* @param array $config The configuration values for the TransparentAuthentication object
|
||||
*
|
||||
* @return TransparentAuthentication
|
||||
*/
|
||||
public function transparentAuthentication(array $config = [])
|
||||
{
|
||||
$authClass = $this->container->getNamespacePrefix() . 'TransparentAuthentication\\TransparentAuthentication';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createTransparentAuthentication($authClass, $config);
|
||||
}
|
||||
catch (TransparentAuthenticationNotFound $e)
|
||||
{
|
||||
// Not found. Return the magically created TA
|
||||
$magic = new TransparentAuthenticationFactory($this->container);
|
||||
|
||||
return $magic->make($config);
|
||||
}
|
||||
}
|
||||
}
|
||||
208
libraries/fof30/Factory/MagicSwitchFactory.php
Normal file
208
libraries/fof30/Factory/MagicSwitchFactory.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Controller\Controller;
|
||||
use FOF30\Dispatcher\Dispatcher;
|
||||
use FOF30\Factory\Exception\ControllerNotFound;
|
||||
use FOF30\Factory\Exception\DispatcherNotFound;
|
||||
use FOF30\Factory\Exception\ModelNotFound;
|
||||
use FOF30\Factory\Exception\TransparentAuthenticationNotFound;
|
||||
use FOF30\Factory\Exception\ViewNotFound;
|
||||
use FOF30\Factory\Magic\DispatcherFactory;
|
||||
use FOF30\Factory\Magic\TransparentAuthenticationFactory;
|
||||
use FOF30\Model\Model;
|
||||
use FOF30\Toolbar\Toolbar;
|
||||
use FOF30\TransparentAuthentication\TransparentAuthentication;
|
||||
use FOF30\View\View;
|
||||
|
||||
/**
|
||||
* Magic MVC object factory. This factory will "magically" create MVC objects even if the respective classes do not
|
||||
* exist, based on information in your fof.xml file.
|
||||
*
|
||||
* Note: This factory class will look for MVC objects in BOTH component sections (front-end, back-end), not just the one
|
||||
* you are currently running in. If no class is found a new object will be created magically. This is the same behaviour
|
||||
* as FOF 2.x.
|
||||
*/
|
||||
class MagicSwitchFactory extends SwitchFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Create a new Controller object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a Controller for.
|
||||
* @param array $config Optional MVC configuration values for the Controller object.
|
||||
*
|
||||
* @return Controller
|
||||
*/
|
||||
public function controller($viewName, array $config = [])
|
||||
{
|
||||
try
|
||||
{
|
||||
return parent::controller($viewName, $config);
|
||||
}
|
||||
catch (ControllerNotFound $e)
|
||||
{
|
||||
$magic = new Magic\ControllerFactory($this->container);
|
||||
// Let's pass the section override (if any)
|
||||
$magic->setSection($this->getSection());
|
||||
|
||||
return $magic->make($viewName, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Model object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a Model for.
|
||||
* @param array $config Optional MVC configuration values for the Model object.
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function model($viewName, array $config = [])
|
||||
{
|
||||
try
|
||||
{
|
||||
return parent::model($viewName, $config);
|
||||
}
|
||||
catch (ModelNotFound $e)
|
||||
{
|
||||
$magic = new Magic\ModelFactory($this->container);
|
||||
// Let's pass the section override (if any)
|
||||
$magic->setSection($this->getSection());
|
||||
|
||||
return $magic->make($viewName, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new View object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a View object for.
|
||||
* @param string $viewType The type of the View object. By default it's "html".
|
||||
* @param array $config Optional MVC configuration values for the View object.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function view($viewName, $viewType = 'html', array $config = [])
|
||||
{
|
||||
try
|
||||
{
|
||||
return parent::view($viewName, $viewType, $config);
|
||||
}
|
||||
catch (ViewNotFound $e)
|
||||
{
|
||||
$magic = new Magic\ViewFactory($this->container);
|
||||
// Let's pass the section override (if any)
|
||||
$magic->setSection($this->getSection());
|
||||
|
||||
return $magic->make($viewName, $viewType, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Toolbar
|
||||
*
|
||||
* @param array $config The configuration values for the Toolbar object
|
||||
*
|
||||
* @return Toolbar
|
||||
*/
|
||||
public function toolbar(array $config = [])
|
||||
{
|
||||
$appConfig = $this->container->appConfig;
|
||||
|
||||
$defaultConfig = [
|
||||
'useConfigurationFile' => true,
|
||||
'renderFrontendButtons' => in_array($appConfig->get("views.*.config.renderFrontendButtons"), [
|
||||
true, 'true', 'yes', 'on', 1,
|
||||
]),
|
||||
'renderFrontendSubmenu' => in_array($appConfig->get("views.*.config.renderFrontendSubmenu"), [
|
||||
true, 'true', 'yes', 'on', 1,
|
||||
]),
|
||||
];
|
||||
|
||||
$config = array_merge($defaultConfig, $config);
|
||||
|
||||
return parent::toolbar($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Dispatcher
|
||||
*
|
||||
* @param array $config The configuration values for the Dispatcher object
|
||||
*
|
||||
* @return Dispatcher
|
||||
*/
|
||||
public function dispatcher(array $config = [])
|
||||
{
|
||||
$dispatcherClass = $this->container->getNamespacePrefix($this->getSection()) . 'Dispatcher\\Dispatcher';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createDispatcher($dispatcherClass, $config);
|
||||
}
|
||||
catch (DispatcherNotFound $e)
|
||||
{
|
||||
// Not found. Let's go on.
|
||||
}
|
||||
|
||||
$dispatcherClass = $this->container->getNamespacePrefix('inverse') . 'Dispatcher\\Dispatcher';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createDispatcher($dispatcherClass, $config);
|
||||
}
|
||||
catch (DispatcherNotFound $e)
|
||||
{
|
||||
// Not found. Return the magically created Dispatcher
|
||||
$magic = new DispatcherFactory($this->container);
|
||||
// Let's pass the section override (if any)
|
||||
$magic->setSection($this->getSection());
|
||||
|
||||
return $magic->make($config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new TransparentAuthentication
|
||||
*
|
||||
* @param array $config The configuration values for the TransparentAuthentication object
|
||||
*
|
||||
* @return TransparentAuthentication
|
||||
*/
|
||||
public function transparentAuthentication(array $config = [])
|
||||
{
|
||||
$toolbarClass = $this->container->getNamespacePrefix($this->getSection()) . 'TransparentAuthentication\\TransparentAuthentication';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createTransparentAuthentication($toolbarClass, $config);
|
||||
}
|
||||
catch (TransparentAuthenticationNotFound $e)
|
||||
{
|
||||
// Not found. Let's go on.
|
||||
}
|
||||
|
||||
$toolbarClass = $this->container->getNamespacePrefix('inverse') . 'TransparentAuthentication\\TransparentAuthentication';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createTransparentAuthentication($toolbarClass, $config);
|
||||
}
|
||||
catch (TransparentAuthenticationNotFound $e)
|
||||
{
|
||||
// Not found. Return the magically created TransparentAuthentication
|
||||
$magic = new TransparentAuthenticationFactory($this->container);
|
||||
// Let's pass the section override (if any)
|
||||
$magic->setSection($this->getSection());
|
||||
|
||||
return $magic->make($config);
|
||||
}
|
||||
}
|
||||
}
|
||||
264
libraries/fof30/Factory/SwitchFactory.php
Normal file
264
libraries/fof30/Factory/SwitchFactory.php
Normal file
@ -0,0 +1,264 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Factory;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Controller\Controller;
|
||||
use FOF30\Dispatcher\Dispatcher;
|
||||
use FOF30\Factory\Exception\ControllerNotFound;
|
||||
use FOF30\Factory\Exception\DispatcherNotFound;
|
||||
use FOF30\Factory\Exception\ModelNotFound;
|
||||
use FOF30\Factory\Exception\ToolbarNotFound;
|
||||
use FOF30\Factory\Exception\TransparentAuthenticationNotFound;
|
||||
use FOF30\Factory\Exception\ViewNotFound;
|
||||
use FOF30\Model\Model;
|
||||
use FOF30\Toolbar\Toolbar;
|
||||
use FOF30\TransparentAuthentication\TransparentAuthentication;
|
||||
use FOF30\View\View;
|
||||
|
||||
/**
|
||||
* MVC object factory. This implements the advanced functionality, i.e. creating MVC objects only if the classes exist
|
||||
* in any component section (front-end, back-end). For example, if you're in the front-end and a Model class doesn't
|
||||
* exist there but does exist in the back-end then the back-end class will be returned.
|
||||
*
|
||||
* The Dispatcher and Toolbar will be created from default objects if specialised classes are not found in your
|
||||
* application.
|
||||
*/
|
||||
class SwitchFactory extends BasicFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Create a new Controller object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a Controller for.
|
||||
* @param array $config Optional MVC configuration values for the Controller object.
|
||||
*
|
||||
* @return Controller
|
||||
*/
|
||||
public function controller($viewName, array $config = [])
|
||||
{
|
||||
try
|
||||
{
|
||||
return parent::controller($viewName, $config);
|
||||
}
|
||||
catch (ControllerNotFound $e)
|
||||
{
|
||||
}
|
||||
|
||||
$controllerClass = $this->container->getNamespacePrefix('inverse') . 'Controller\\' . ucfirst($viewName);
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createController($controllerClass, $config);
|
||||
}
|
||||
catch (ControllerNotFound $e)
|
||||
{
|
||||
}
|
||||
|
||||
$controllerClass = $this->container->getNamespacePrefix('inverse') . 'Controller\\' . ucfirst($this->container->inflector->singularize($viewName));
|
||||
|
||||
return $this->createController($controllerClass, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Model object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a Model for.
|
||||
* @param array $config Optional MVC configuration values for the Model object.
|
||||
*
|
||||
* @return Model
|
||||
*/
|
||||
public function model($viewName, array $config = [])
|
||||
{
|
||||
try
|
||||
{
|
||||
return parent::model($viewName, $config);
|
||||
}
|
||||
catch (ModelNotFound $e)
|
||||
{
|
||||
}
|
||||
|
||||
$modelClass = $this->container->getNamespacePrefix('inverse') . 'Model\\' . ucfirst($viewName);
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createModel($modelClass, $config);
|
||||
}
|
||||
catch (ModelNotFound $e)
|
||||
{
|
||||
$modelClass = $this->container->getNamespacePrefix('inverse') . 'Model\\' . ucfirst($this->container->inflector->singularize($viewName));
|
||||
|
||||
return $this->createModel($modelClass, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new View object
|
||||
*
|
||||
* @param string $viewName The name of the view we're getting a View object for.
|
||||
* @param string $viewType The type of the View object. By default it's "html".
|
||||
* @param array $config Optional MVC configuration values for the View object.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function view($viewName, $viewType = 'html', array $config = [])
|
||||
{
|
||||
try
|
||||
{
|
||||
return parent::view($viewName, $viewType, $config);
|
||||
}
|
||||
catch (ViewNotFound $e)
|
||||
{
|
||||
}
|
||||
|
||||
$viewClass = $this->container->getNamespacePrefix('inverse') . 'View\\' . ucfirst($viewName) . '\\' . ucfirst($viewType);
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createView($viewClass, $config);
|
||||
}
|
||||
catch (ViewNotFound $e)
|
||||
{
|
||||
$viewClass = $this->container->getNamespacePrefix('inverse') . 'View\\' . ucfirst($this->container->inflector->singularize($viewName)) . '\\' . ucfirst($viewType);
|
||||
|
||||
return $this->createView($viewClass, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Dispatcher
|
||||
*
|
||||
* @param array $config The configuration values for the Dispatcher object
|
||||
*
|
||||
* @return Dispatcher
|
||||
*/
|
||||
public function dispatcher(array $config = [])
|
||||
{
|
||||
$dispatcherClass = $this->container->getNamespacePrefix($this->getSection()) . 'Dispatcher\\Dispatcher';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createDispatcher($dispatcherClass, $config);
|
||||
}
|
||||
catch (DispatcherNotFound $e)
|
||||
{
|
||||
// Not found. Let's go on.
|
||||
}
|
||||
|
||||
$dispatcherClass = $this->container->getNamespacePrefix('inverse') . 'Dispatcher\\Dispatcher';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createDispatcher($dispatcherClass, $config);
|
||||
}
|
||||
catch (DispatcherNotFound $e)
|
||||
{
|
||||
// Not found. Return the default Dispatcher
|
||||
return new Dispatcher($this->container, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Toolbar
|
||||
*
|
||||
* @param array $config The configuration values for the Toolbar object
|
||||
*
|
||||
* @return Toolbar
|
||||
*/
|
||||
public function toolbar(array $config = [])
|
||||
{
|
||||
$toolbarClass = $this->container->getNamespacePrefix($this->getSection()) . 'Toolbar\\Toolbar';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createToolbar($toolbarClass, $config);
|
||||
}
|
||||
catch (ToolbarNotFound $e)
|
||||
{
|
||||
// Not found. Let's go on.
|
||||
}
|
||||
|
||||
$toolbarClass = $this->container->getNamespacePrefix('inverse') . 'Toolbar\\Toolbar';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createToolbar($toolbarClass, $config);
|
||||
}
|
||||
catch (ToolbarNotFound $e)
|
||||
{
|
||||
// Not found. Return the default Toolbar
|
||||
return new Toolbar($this->container, $config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new TransparentAuthentication
|
||||
*
|
||||
* @param array $config The configuration values for the TransparentAuthentication object
|
||||
*
|
||||
* @return TransparentAuthentication
|
||||
*/
|
||||
public function transparentAuthentication(array $config = [])
|
||||
{
|
||||
$toolbarClass = $this->container->getNamespacePrefix($this->getSection()) . 'TransparentAuthentication\\TransparentAuthentication';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createTransparentAuthentication($toolbarClass, $config);
|
||||
}
|
||||
catch (TransparentAuthenticationNotFound $e)
|
||||
{
|
||||
// Not found. Let's go on.
|
||||
}
|
||||
|
||||
$toolbarClass = $this->container->getNamespacePrefix('inverse') . 'TransparentAuthentication\\TransparentAuthentication';
|
||||
|
||||
try
|
||||
{
|
||||
return $this->createTransparentAuthentication($toolbarClass, $config);
|
||||
}
|
||||
catch (TransparentAuthenticationNotFound $e)
|
||||
{
|
||||
// Not found. Return the default TransparentAuthentication
|
||||
return new TransparentAuthentication($this->container, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a view template finder object for a specific View.
|
||||
*
|
||||
* The default configuration is:
|
||||
* Look for .php, .blade.php files; default layout "default"; no default sub-template;
|
||||
* look for both pluralised and singular views; fall back to the default layout without sub-template;
|
||||
* look for templates in both site and admin
|
||||
*
|
||||
* @param View $view The view this view template finder will be attached to
|
||||
* @param array $config Configuration variables for the object
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function viewFinder(View $view, array $config = [])
|
||||
{
|
||||
// Initialise the configuration with the default values
|
||||
$defaultConfig = [
|
||||
'extensions' => ['.php', '.blade.php'],
|
||||
'defaultLayout' => 'default',
|
||||
'defaultTpl' => '',
|
||||
'strictView' => false,
|
||||
'strictTpl' => false,
|
||||
'strictLayout' => false,
|
||||
'sidePrefix' => 'any',
|
||||
];
|
||||
|
||||
$config = array_merge($defaultConfig, $config);
|
||||
|
||||
return parent::viewFinder($view, $config);
|
||||
}
|
||||
}
|
||||
424
libraries/fof30/Inflector/Inflector.php
Normal file
424
libraries/fof30/Inflector/Inflector.php
Normal file
@ -0,0 +1,424 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Inflector;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* An Inflector to pluralize and singularize English nouns.
|
||||
*/
|
||||
class Inflector
|
||||
{
|
||||
/**
|
||||
* Rules for pluralizing and singularizing of nouns.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $rules = [
|
||||
// Pluralization rules. The regex on the left transforms to the regex on the right.
|
||||
'pluralization' => [
|
||||
'/move$/i' => 'moves',
|
||||
'/sex$/i' => 'sexes',
|
||||
'/child$/i' => 'children',
|
||||
'/children$/i' => 'children',
|
||||
'/man$/i' => 'men',
|
||||
'/men$/i' => 'men',
|
||||
'/foot$/i' => 'feet',
|
||||
'/feet$/i' => 'feet',
|
||||
'/person$/i' => 'people',
|
||||
'/people$/i' => 'people',
|
||||
'/taxon$/i' => 'taxa',
|
||||
'/taxa$/i' => 'taxa',
|
||||
'/(quiz)$/i' => '$1zes',
|
||||
'/^(ox)$/i' => '$1en',
|
||||
'/oxen$/i' => 'oxen',
|
||||
'/(m|l)ouse$/i' => '$1ice',
|
||||
'/(m|l)ice$/i' => '$1ice',
|
||||
'/(matr|vert|ind|suff)ix|ex$/i' => '$1ices',
|
||||
'/(x|ch|ss|sh)$/i' => '$1es',
|
||||
'/([^aeiouy]|qu)y$/i' => '$1ies',
|
||||
'/(?:([^f])fe|([lr])f)$/i' => '$1$2ves',
|
||||
'/sis$/i' => 'ses',
|
||||
'/([ti]|addend)um$/i' => '$1a',
|
||||
'/([ti]|addend)a$/i' => '$1a',
|
||||
'/(alumn|formul)a$/i' => '$1ae',
|
||||
'/(alumn|formul)ae$/i' => '$1ae',
|
||||
'/(buffal|tomat|her)o$/i' => '$1oes',
|
||||
'/(bu)s$/i' => '$1ses',
|
||||
'/(campu)s$/i' => '$1ses',
|
||||
'/(alias|status)$/i' => '$1es',
|
||||
'/(octop|vir)us$/i' => '$1i',
|
||||
'/(octop|vir)i$/i' => '$1i',
|
||||
'/(gen)us$/i' => '$1era',
|
||||
'/(gen)era$/i' => '$1era',
|
||||
'/(ax|test)is$/i' => '$1es',
|
||||
'/s$/i' => 's',
|
||||
'/$/' => 's',
|
||||
],
|
||||
// Singularization rules. The regex on the left transforms to the regex on the right.
|
||||
'singularization' => [
|
||||
'/cookies$/i' => 'cookie',
|
||||
'/moves$/i' => 'move',
|
||||
'/sexes$/i' => 'sex',
|
||||
'/children$/i' => 'child',
|
||||
'/men$/i' => 'man',
|
||||
'/feet$/i' => 'foot',
|
||||
'/people$/i' => 'person',
|
||||
'/taxa$/i' => 'taxon',
|
||||
'/databases$/i' => 'database',
|
||||
'/menus$/i' => 'menu',
|
||||
'/(quiz)zes$/i' => '\1',
|
||||
'/(matr|suff)ices$/i' => '\1ix',
|
||||
'/(vert|ind|cod)ices$/i' => '\1ex',
|
||||
'/^(ox)en/i' => '\1',
|
||||
'/(alias|status)es$/i' => '\1',
|
||||
'/(tomato|hero|buffalo)es$/i' => '\1',
|
||||
'/([octop|vir])i$/i' => '\1us',
|
||||
'/(gen)era$/i' => '\1us',
|
||||
'/(cris|^ax|test)es$/i' => '\1is',
|
||||
'/is$/i' => 'is',
|
||||
'/us$/i' => 'us',
|
||||
'/ias$/i' => 'ias',
|
||||
'/(shoe)s$/i' => '\1',
|
||||
'/(o)es$/i' => '\1e',
|
||||
'/(bus)es$/i' => '\1',
|
||||
'/(campus)es$/i' => '\1',
|
||||
'/([m|l])ice$/i' => '\1ouse',
|
||||
'/(x|ch|ss|sh)es$/i' => '\1',
|
||||
'/(m)ovies$/i' => '\1ovie',
|
||||
'/(s)eries$/i' => '\1eries',
|
||||
'/(v)ies$/i' => '\1ie',
|
||||
'/([^aeiouy]|qu)ies$/i' => '\1y',
|
||||
'/([lr])ves$/i' => '\1f',
|
||||
'/(tive)s$/i' => '\1',
|
||||
'/(hive)s$/i' => '\1',
|
||||
'/([^f])ves$/i' => '\1fe',
|
||||
'/(^analy)ses$/i' => '\1sis',
|
||||
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
|
||||
'/([ti]|addend)a$/i' => '\1um',
|
||||
'/(alumn|formul)ae$/i' => '$1a',
|
||||
'/(n)ews$/i' => '\1ews',
|
||||
'/(.*)ss$/i' => '\1ss',
|
||||
'/(.*)s$/i' => '\1',
|
||||
],
|
||||
// Uncountable objects are always singular
|
||||
'uncountable' => [
|
||||
'aircraft',
|
||||
'cannon',
|
||||
'deer',
|
||||
'equipment',
|
||||
'fish',
|
||||
'information',
|
||||
'money',
|
||||
'moose',
|
||||
'news',
|
||||
'rice',
|
||||
'series',
|
||||
'sheep',
|
||||
'species',
|
||||
'swine',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Cache of pluralized and singularized nouns.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cache = [
|
||||
'singularized' => [],
|
||||
'pluralized' => [],
|
||||
];
|
||||
|
||||
public function deleteCache()
|
||||
{
|
||||
$this->cache['pluralized'] = [];
|
||||
$this->cache['singularized'] = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a word to the cache, useful to make exceptions or to add words in other languages.
|
||||
*
|
||||
* @param string $singular word.
|
||||
* @param string $plural word.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addWord($singular, $plural)
|
||||
{
|
||||
$this->cache['pluralized'][$singular] = $plural;
|
||||
$this->cache['singularized'][$plural] = $singular;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singular English word to plural.
|
||||
*
|
||||
* @param string $word word to pluralize.
|
||||
*
|
||||
* @return string Plural noun.
|
||||
*/
|
||||
public function pluralize($word)
|
||||
{
|
||||
// Get the cached noun of it exists
|
||||
if (isset($this->cache['pluralized'][$word]))
|
||||
{
|
||||
return $this->cache['pluralized'][$word];
|
||||
}
|
||||
|
||||
// Check if the noun is already in plural form, i.e. in the singularized cache
|
||||
if (isset($this->cache['singularized'][$word]))
|
||||
{
|
||||
return $word;
|
||||
}
|
||||
|
||||
// Create the plural noun
|
||||
if (in_array($word, $this->rules['uncountable']))
|
||||
{
|
||||
$_cache['pluralized'][$word] = $word;
|
||||
|
||||
return $word;
|
||||
}
|
||||
|
||||
foreach ($this->rules['pluralization'] as $regexp => $replacement)
|
||||
{
|
||||
$matches = null;
|
||||
$plural = preg_replace($regexp, $replacement, $word, -1, $matches);
|
||||
|
||||
if ($matches > 0)
|
||||
{
|
||||
$_cache['pluralized'][$word] = $plural;
|
||||
|
||||
return $plural;
|
||||
}
|
||||
}
|
||||
|
||||
return $word;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural English word to singular.
|
||||
*
|
||||
* @param string $word Word to singularize.
|
||||
*
|
||||
* @return string Singular noun.
|
||||
*/
|
||||
public function singularize($word)
|
||||
{
|
||||
// Get the cached noun of it exists
|
||||
if (isset($this->cache['singularized'][$word]))
|
||||
{
|
||||
return $this->cache['singularized'][$word];
|
||||
}
|
||||
|
||||
// Check if the noun is already in singular form, i.e. in the pluralized cache
|
||||
if (isset($this->cache['pluralized'][$word]))
|
||||
{
|
||||
return $word;
|
||||
}
|
||||
|
||||
// Create the singular noun
|
||||
if (in_array($word, $this->rules['uncountable']))
|
||||
{
|
||||
$_cache['singularized'][$word] = $word;
|
||||
|
||||
return $word;
|
||||
}
|
||||
|
||||
foreach ($this->rules['singularization'] as $regexp => $replacement)
|
||||
{
|
||||
$matches = null;
|
||||
$singular = preg_replace($regexp, $replacement, $word, -1, $matches);
|
||||
|
||||
if ($matches > 0)
|
||||
{
|
||||
$_cache['singularized'][$word] = $singular;
|
||||
|
||||
return $singular;
|
||||
}
|
||||
}
|
||||
|
||||
return $word;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns given word as CamelCased.
|
||||
*
|
||||
* Converts a word like "foo_bar" or "foo bar" to "FooBar". It
|
||||
* will remove non alphanumeric characters from the word, so
|
||||
* "who's online" will be converted to "WhoSOnline"
|
||||
*
|
||||
* @param string $word Word to convert to camel case.
|
||||
*
|
||||
* @return string UpperCamelCasedWord
|
||||
*/
|
||||
public function camelize($word)
|
||||
{
|
||||
$word = preg_replace('/[^a-zA-Z0-9\s]/', ' ', $word);
|
||||
$word = str_replace(' ', '', ucwords(strtolower(str_replace('_', ' ', $word))));
|
||||
|
||||
return $word;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a word "into_it_s_underscored_version"
|
||||
*
|
||||
* Convert any "CamelCased" or "ordinary Word" into an "underscored_word".
|
||||
*
|
||||
* @param string $word Word to underscore
|
||||
*
|
||||
* @return string Underscored word
|
||||
*/
|
||||
public function underscore($word)
|
||||
{
|
||||
$word = preg_replace('/(\s)+/', '_', $word);
|
||||
$word = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $word));
|
||||
|
||||
return $word;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert any "CamelCased" word into an array of strings
|
||||
*
|
||||
* Returns an array of strings each of which is a substring of string formed
|
||||
* by splitting it at the camelcased letters.
|
||||
*
|
||||
* @param string $word Word to explode
|
||||
*
|
||||
* @return array Array of strings
|
||||
*/
|
||||
public function explode($word)
|
||||
{
|
||||
$result = explode('_', self::underscore($word));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of strings into a "CamelCased" word.
|
||||
*
|
||||
* @param array $words Array to implode
|
||||
*
|
||||
* @return string UpperCamelCasedWord
|
||||
*/
|
||||
public function implode($words)
|
||||
{
|
||||
$result = self::camelize(implode('_', $words));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable string from $word.
|
||||
*
|
||||
* Returns a human-readable string from $word, by replacing
|
||||
* underscores with a space, and by upper-casing the initial
|
||||
* character by default.
|
||||
*
|
||||
* @param string $word String to "humanize"
|
||||
*
|
||||
* @return string Human-readable word
|
||||
*/
|
||||
public function humanize($word)
|
||||
{
|
||||
$result = ucwords(strtolower(str_replace("_", " ", $word)));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns camelBacked version of a string. Same as camelize but first char is lowercased.
|
||||
*
|
||||
* @param string $string String to be camelBacked.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @see camelize
|
||||
*/
|
||||
public function variablize($string)
|
||||
{
|
||||
$string = self::camelize(self::underscore($string));
|
||||
$result = strtolower(substr($string, 0, 1));
|
||||
$variable = preg_replace('/\\w/', $result, $string, 1);
|
||||
|
||||
return $variable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if an English word is singular
|
||||
*
|
||||
* @param string $string The word to check
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isSingular($string)
|
||||
{
|
||||
// Check cache assuming the string is plural.
|
||||
$singular = $this->cache['singularized'][$string] ?? null;
|
||||
$plural = $singular && isset($this->cache['pluralized'][$singular]) ? $this->cache['pluralized'][$singular] : null;
|
||||
|
||||
if ($singular && $plural)
|
||||
{
|
||||
return $plural != $string;
|
||||
}
|
||||
|
||||
// If string is not in the cache, try to pluralize and singularize it.
|
||||
return self::singularize(self::pluralize($string)) == $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if an English word is plural.
|
||||
*
|
||||
* @param string $string String to be checked.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isPlural($string)
|
||||
{
|
||||
// Uncountable objects are always singular (e.g. information)
|
||||
if (in_array($string, $this->rules['uncountable']))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check cache assuming the string is singular.
|
||||
$plural = $this->cache['pluralized'][$string] ?? null;
|
||||
$singular = $plural && isset($this->cache['singularized'][$plural]) ? $this->cache['singularized'][$plural] : null;
|
||||
|
||||
if ($plural && $singular)
|
||||
{
|
||||
return $singular != $string;
|
||||
}
|
||||
|
||||
// If string is not in the cache, try to singularize and pluralize it.
|
||||
return self::pluralize(self::singularize($string)) == $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a part of a CamelCased word by index.
|
||||
*
|
||||
* Use a negative index to start at the last part of the word (-1 is the
|
||||
* last part)
|
||||
*
|
||||
* @param string $string Word
|
||||
* @param integer $index Index of the part
|
||||
* @param string $default Default value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPart($string, $index, $default = null)
|
||||
{
|
||||
$parts = self::explode($string);
|
||||
|
||||
if ($index < 0)
|
||||
{
|
||||
$index = count($parts) + $index;
|
||||
}
|
||||
|
||||
return $parts[$index] ?? $default;
|
||||
}
|
||||
}
|
||||
258
libraries/fof30/Input/Input.php
Normal file
258
libraries/fof30/Input/Input.php
Normal file
@ -0,0 +1,258 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Input;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filter\InputFilter;
|
||||
use Joomla\CMS\Input\Input as JInput;
|
||||
use ReflectionObject;
|
||||
|
||||
class Input extends JInput
|
||||
{
|
||||
/**
|
||||
* Public constructor. Overridden to allow specifying the global input array
|
||||
* to use as a string and instantiate from an object holding variables.
|
||||
*
|
||||
* @param array|string|object|null $source Source data; set null to use the default Joomla input source
|
||||
* @param array $options Filter options
|
||||
*/
|
||||
public function __construct($source = null, array $options = [])
|
||||
{
|
||||
$hash = null;
|
||||
|
||||
if (is_string($source))
|
||||
{
|
||||
$hash = strtoupper($source);
|
||||
|
||||
if (!in_array($hash, ['GET', 'POST', 'FILES', 'COOKIE', 'ENV', 'SERVER', 'REQUEST']))
|
||||
{
|
||||
$hash = 'REQUEST';
|
||||
}
|
||||
|
||||
$source = $this->extractJoomlaSource($hash);
|
||||
}
|
||||
elseif (is_object($source) && ($source instanceof Input))
|
||||
{
|
||||
$source = $source->getData();
|
||||
}
|
||||
elseif (is_object($source) && ($source instanceof JInput))
|
||||
{
|
||||
$serialised = $source->serialize();
|
||||
[$xOptions, $xData, $xInput] = unserialize($serialised);
|
||||
unset ($xOptions);
|
||||
unset ($xInput);
|
||||
unset ($source);
|
||||
$source = $xData;
|
||||
unset ($xData);
|
||||
}
|
||||
elseif (is_object($source))
|
||||
{
|
||||
try
|
||||
{
|
||||
$source = (array) $source;
|
||||
}
|
||||
catch (Exception $exc)
|
||||
{
|
||||
$source = null;
|
||||
}
|
||||
}
|
||||
elseif (is_array($source))
|
||||
{
|
||||
// Nothing, it's already an array
|
||||
}
|
||||
else
|
||||
{
|
||||
// Any other case
|
||||
$source = null;
|
||||
}
|
||||
|
||||
// TODO Joomla 4 -- get the data from the application input
|
||||
|
||||
// If we are not sure use the REQUEST array
|
||||
if (empty($source))
|
||||
{
|
||||
$source = $this->extractJoomlaSource('REQUEST');
|
||||
}
|
||||
|
||||
parent::__construct($source, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value from the input data. Overridden to allow specifying a filter
|
||||
* mask.
|
||||
*
|
||||
* @param string $name Name of the value to get.
|
||||
* @param mixed $default Default value to return if variable does not exist.
|
||||
* @param string $filter Filter to apply to the value.
|
||||
* @param int $mask The filter mask
|
||||
*
|
||||
* @return mixed The filtered input value.
|
||||
*/
|
||||
public function get($name, $default = null, $filter = 'cmd', $mask = 0)
|
||||
{
|
||||
if (isset($this->data[$name]))
|
||||
{
|
||||
return $this->_cleanVar($this->data[$name], $mask, $filter);
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a key from the input data.
|
||||
*
|
||||
* @param string $name The key name to remove from the input data.
|
||||
*
|
||||
* @return void
|
||||
* @since 3.6.3
|
||||
*/
|
||||
public function remove($name)
|
||||
{
|
||||
if (!isset($this->data[$name]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
unset($this->data[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the raw data stored in the class
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function setData(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to get filtered input data.
|
||||
*
|
||||
* @param mixed $name Name of the value to get.
|
||||
* @param string $arguments [0] The name of the variable [1] The default value [2] Mask
|
||||
*
|
||||
* @return boolean The filtered boolean input value.
|
||||
*/
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
if (substr($name, 0, 3) == 'get')
|
||||
{
|
||||
$filter = substr($name, 3);
|
||||
|
||||
$default = null;
|
||||
$mask = 0;
|
||||
|
||||
if (isset($arguments[1]))
|
||||
{
|
||||
$default = $arguments[1];
|
||||
}
|
||||
|
||||
if (isset($arguments[2]))
|
||||
{
|
||||
$mask = $arguments[2];
|
||||
}
|
||||
|
||||
return $this->get($arguments[0], $default, $filter, $mask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom filter implementation. Works better with arrays and allows the use
|
||||
* of a filter mask.
|
||||
*
|
||||
* @param mixed $var The variable (value) to clean
|
||||
* @param integer $mask The clean mask
|
||||
* @param string $type The variable type
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _cleanVar($var, $mask = 0, $type = null)
|
||||
{
|
||||
if (is_array($var))
|
||||
{
|
||||
$temp = [];
|
||||
|
||||
foreach ($var as $k => $v)
|
||||
{
|
||||
$temp[$k] = self::_cleanVar($v, $mask);
|
||||
}
|
||||
|
||||
return $temp;
|
||||
}
|
||||
|
||||
// If the no trim flag is not set, trim the variable
|
||||
if (!($mask & 1) && is_string($var))
|
||||
{
|
||||
$var = trim($var);
|
||||
}
|
||||
|
||||
// Now we handle input filtering
|
||||
if ($mask & 2)
|
||||
{
|
||||
// If the allow raw flag is set, do not modify the variable
|
||||
}
|
||||
elseif ($mask & 4)
|
||||
{
|
||||
// If the allow HTML flag is set, apply a safe HTML filter to the variable
|
||||
if (version_compare(JVERSION, '3.999.999', 'le'))
|
||||
{
|
||||
$safeHtmlFilter = InputFilter::getInstance(null, null, 1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
$safeHtmlFilter = InputFilter::getInstance([], [], 1, 1);
|
||||
}
|
||||
$var = $safeHtmlFilter->clean($var, $type);
|
||||
}
|
||||
else
|
||||
{
|
||||
$var = $this->filter->clean($var, $type);
|
||||
}
|
||||
|
||||
return $var;
|
||||
}
|
||||
|
||||
protected function extractJoomlaSource($hash = 'REQUEST')
|
||||
{
|
||||
if (!in_array(strtoupper($hash), ['GET', 'POST', 'FILES', 'COOKIE', 'ENV', 'SERVER', 'REQUEST']))
|
||||
{
|
||||
$hash = 'REQUEST';
|
||||
}
|
||||
|
||||
$hash = strtolower($hash);
|
||||
|
||||
try
|
||||
{
|
||||
$input = Factory::getApplication()->input;
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$input = new \Joomla\Input\Input();
|
||||
}
|
||||
|
||||
if ($hash !== 'request')
|
||||
{
|
||||
$input = $input->{$hash};
|
||||
}
|
||||
|
||||
$refObject = new ReflectionObject($input);
|
||||
$refProp = $refObject->getProperty('data');
|
||||
$refProp->setAccessible(true);
|
||||
|
||||
return $refProp->getValue($input);
|
||||
}
|
||||
}
|
||||
87
libraries/fof30/Layout/LayoutFile.php
Normal file
87
libraries/fof30/Layout/LayoutFile.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Layout;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Container\Container;
|
||||
use Joomla\CMS\Layout\FileLayout;
|
||||
|
||||
/**
|
||||
* Base class for rendering a display layout
|
||||
* loaded from from a layout file
|
||||
*
|
||||
* This class searches for Joomla! version override Layouts. For example,
|
||||
* if you have run this under Joomla! 3.0 and you try to load
|
||||
* mylayout.default it will automatically search for the
|
||||
* layout files default.j30.php, default.j3.php and default.php, in this
|
||||
* order.
|
||||
*
|
||||
* @package FrameworkOnFramework
|
||||
*/
|
||||
class LayoutFile extends FileLayout
|
||||
{
|
||||
/** @var Container The component container */
|
||||
public $container = null;
|
||||
|
||||
/**
|
||||
* Method to finds the full real file path, checking possible overrides
|
||||
*
|
||||
* @return string The full path to the layout file
|
||||
*/
|
||||
protected function getPath()
|
||||
{
|
||||
if (is_null($this->container))
|
||||
{
|
||||
$component = $this->options->get('component');
|
||||
$this->container = Container::getInstance($component);
|
||||
}
|
||||
|
||||
$filesystem = $this->container->filesystem;
|
||||
|
||||
if (is_null($this->fullPath) && !empty($this->layoutId))
|
||||
{
|
||||
$parts = explode('.', $this->layoutId);
|
||||
$file = array_pop($parts);
|
||||
|
||||
$filePath = implode('/', $parts);
|
||||
$suffixes = $this->container->platform->getTemplateSuffixes();
|
||||
|
||||
foreach ($suffixes as $suffix)
|
||||
{
|
||||
$files[] = $file . $suffix . '.php';
|
||||
}
|
||||
|
||||
$files[] = $file . '.php';
|
||||
|
||||
$platformDirs = $this->container->platform->getPlatformBaseDirs();
|
||||
$prefix = $this->container->platform->isBackend() ? $platformDirs['admin'] : $platformDirs['root'];
|
||||
|
||||
$possiblePaths = [
|
||||
$prefix . '/templates/' . $this->container->platform->getTemplate() . '/html/layouts/' . $filePath,
|
||||
$this->basePath . '/' . $filePath,
|
||||
$platformDirs['root'] . '/layouts/' . $filePath,
|
||||
];
|
||||
|
||||
reset($files);
|
||||
|
||||
foreach ($files as $fileName)
|
||||
{
|
||||
if (!is_null($this->fullPath))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
$r = $filesystem->pathFind($possiblePaths, $fileName);
|
||||
$this->fullPath = $r === false ? null : $r;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fullPath;
|
||||
}
|
||||
}
|
||||
48
libraries/fof30/Layout/LayoutHelper.php
Normal file
48
libraries/fof30/Layout/LayoutHelper.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Layout;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Container\Container;
|
||||
|
||||
class LayoutHelper
|
||||
{
|
||||
/**
|
||||
* A default base path that will be used if none is provided when calling the render method.
|
||||
* Note that JLayoutFile itself will defaults to JPATH_ROOT . '/layouts' if no basePath is supplied at all
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $defaultBasePath = '';
|
||||
|
||||
/**
|
||||
* Method to render the layout.
|
||||
*
|
||||
* @param Container $container The container of your component
|
||||
* @param string $layoutFile Dot separated path to the layout file, relative to base path
|
||||
* @param object $displayData Object which properties are used inside the layout file to build displayed
|
||||
* output
|
||||
* @param string $basePath Base path to use when loading layout files
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function render(Container $container, $layoutFile, $displayData = null, $basePath = '')
|
||||
{
|
||||
$basePath = empty($basePath) ? self::$defaultBasePath : $basePath;
|
||||
|
||||
// Make sure we send null to LayoutFile if no path set
|
||||
$basePath = empty($basePath) ? null : $basePath;
|
||||
$layout = new LayoutFile($layoutFile, $basePath);
|
||||
$layout->container = $container;
|
||||
$renderedLayout = $layout->render($displayData);
|
||||
|
||||
return $renderedLayout;
|
||||
}
|
||||
|
||||
}
|
||||
4222
libraries/fof30/Model/DataModel.php
Normal file
4222
libraries/fof30/Model/DataModel.php
Normal file
File diff suppressed because it is too large
Load Diff
76
libraries/fof30/Model/DataModel/Behaviour/Access.php
Normal file
76
libraries/fof30/Model/DataModel/Behaviour/Access.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use JDatabaseQuery;
|
||||
|
||||
/**
|
||||
* FOF model behavior class to filter access to items based on the viewing access levels.
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Access extends Observer
|
||||
{
|
||||
/**
|
||||
* This event runs after we have built the query used to fetch a record
|
||||
* list in a model. It is used to apply automatic query filters.
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
* @param JDatabaseQuery &$query The query we are manipulating
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterBuildQuery(&$model, &$query)
|
||||
{
|
||||
// Make sure the field actually exists
|
||||
if (!$model->hasField('access'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$model->applyAccessFiltering(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* The event runs after DataModel has retrieved a single item from the database. It is used to apply automatic
|
||||
* filters.
|
||||
*
|
||||
* @param DataModel &$model The model which was called
|
||||
* @param Array &$keys The keys used to locate the record which was loaded
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterLoad(&$model, &$keys)
|
||||
{
|
||||
// Make sure we have a DataModel
|
||||
if (!($model instanceof DataModel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the field actually exists
|
||||
if (!$model->hasField('access'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the user
|
||||
$user = $model->getContainer()->platform->getUser();
|
||||
$recordAccessLevel = $model->getFieldValue('access', null);
|
||||
|
||||
// Filter by authorised access levels
|
||||
if (!in_array($recordAccessLevel, $user->getAuthorisedViewLevels()))
|
||||
{
|
||||
$model->reset(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
198
libraries/fof30/Model/DataModel/Behaviour/Assets.php
Normal file
198
libraries/fof30/Model/DataModel/Behaviour/Assets.php
Normal file
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use Joomla\CMS\Access\Rules;
|
||||
use Joomla\CMS\Table\Table;
|
||||
|
||||
/**
|
||||
* FOF model behavior class to add Joomla! ACL assets support
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Assets extends Observer
|
||||
{
|
||||
public function onAfterSave(DataModel &$model)
|
||||
{
|
||||
if (!$model->hasField('asset_id') || !$model->isAssetsTracked())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
$assetFieldAlias = $model->getFieldAlias('asset_id');
|
||||
$currentAssetId = $model->getFieldValue('asset_id');
|
||||
|
||||
unset($model->$assetFieldAlias);
|
||||
|
||||
// Create the object used for inserting/updating data to the database
|
||||
$fields = $model->getTableFields();
|
||||
|
||||
// Let's remove the asset_id field, since we unset the property above and we would get a PHP notice
|
||||
if (isset($fields[$assetFieldAlias]))
|
||||
{
|
||||
unset($fields[$assetFieldAlias]);
|
||||
}
|
||||
|
||||
// Asset Tracking
|
||||
$parentId = $model->getAssetParentId();
|
||||
$name = $model->getAssetName();
|
||||
$title = $model->getAssetTitle();
|
||||
|
||||
$asset = Table::getInstance('Asset');
|
||||
$asset->loadByName($name);
|
||||
|
||||
// Re-inject the asset id.
|
||||
$this->$assetFieldAlias = $asset->id;
|
||||
|
||||
// Check for an error.
|
||||
$error = $asset->getError();
|
||||
|
||||
// Since we are using JTable, there is no way to mock it and test for failures :(
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($error)
|
||||
{
|
||||
throw new Exception($error);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
// Specify how a new or moved node asset is inserted into the tree.
|
||||
// Since we're unsetting the table field before, this statement is always true...
|
||||
if (empty($model->$assetFieldAlias) || $asset->parent_id != $parentId)
|
||||
{
|
||||
$asset->setLocation($parentId, 'last-child');
|
||||
}
|
||||
|
||||
// Prepare the asset to be stored.
|
||||
$asset->parent_id = $parentId;
|
||||
$asset->name = $name;
|
||||
$asset->title = $title;
|
||||
|
||||
if ($model->getRules() instanceof Rules)
|
||||
{
|
||||
$asset->rules = (string) $model->getRules();
|
||||
}
|
||||
|
||||
// Since we are using JTable, there is no way to mock it and test for failures :(
|
||||
// @codeCoverageIgnoreStart
|
||||
if (!$asset->check() || !$asset->store())
|
||||
{
|
||||
throw new Exception($asset->getError());
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
// Create an asset_id or heal one that is corrupted.
|
||||
if (empty($model->$assetFieldAlias) || (($currentAssetId != $model->$assetFieldAlias) && !empty($model->$assetFieldAlias)))
|
||||
{
|
||||
// Update the asset_id field in this table.
|
||||
$model->$assetFieldAlias = (int) $asset->id;
|
||||
|
||||
$k = $model->getKeyName();
|
||||
|
||||
$db = $model->getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->qn($model->getTableName()))
|
||||
->set($db->qn($assetFieldAlias) . ' = ' . (int) $model->$assetFieldAlias)
|
||||
->where($db->qn($k) . ' = ' . (int) $model->$k);
|
||||
|
||||
$db->setQuery($query)->execute();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onAfterBind(DataModel &$model, &$src)
|
||||
{
|
||||
if (!$model->isAssetsTracked())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
$rawRules = [];
|
||||
|
||||
if (is_array($src) && array_key_exists('rules', $src) && is_array($src['rules']))
|
||||
{
|
||||
$rawRules = $src['rules'];
|
||||
}
|
||||
elseif (is_object($src) && isset($src->rules) && is_array($src->rules))
|
||||
{
|
||||
$rawRules = $src->rules;
|
||||
}
|
||||
|
||||
if (empty($rawRules))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bind the rules.
|
||||
if (isset($rawRules) && is_array($rawRules))
|
||||
{
|
||||
// We have to manually remove any empty value, since they will be converted to int,
|
||||
// and "Inherited" values will become "Denied". Joomla is doing this manually, too.
|
||||
$rules = [];
|
||||
|
||||
foreach ($rawRules as $action => $ids)
|
||||
{
|
||||
// Build the rules array.
|
||||
$rules[$action] = [];
|
||||
|
||||
foreach ($ids as $id => $p)
|
||||
{
|
||||
if ($p !== '')
|
||||
{
|
||||
$rules[$action][$id] = ($p == '1' || $p == 'true') ? true : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$model->setRules($rules);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onBeforeDelete(DataModel &$model, $oid)
|
||||
{
|
||||
if (!$model->isAssetsTracked())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
$k = $model->getKeyName();
|
||||
|
||||
// If the table is not loaded, let's try to load it with the id
|
||||
if (!$model->$k)
|
||||
{
|
||||
$model->load($oid);
|
||||
}
|
||||
|
||||
// If I have an invalid assetName I have to stop
|
||||
$name = $model->getAssetName();
|
||||
|
||||
// Do NOT touch JTable here -- we are loading the core asset table which is a JTable, not a FOF Table
|
||||
$asset = Table::getInstance('Asset');
|
||||
|
||||
if ($asset->loadByName($name))
|
||||
{
|
||||
// Since we are using JTable, there is no way to mock it and test for failures :(
|
||||
// @codeCoverageIgnoreStart
|
||||
if (!$asset->delete())
|
||||
{
|
||||
throw new Exception($asset->getError());
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
100
libraries/fof30/Model/DataModel/Behaviour/ContentHistory.php
Normal file
100
libraries/fof30/Model/DataModel/Behaviour/ContentHistory.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use Joomla\CMS\Component\ComponentHelper;
|
||||
use Joomla\CMS\Helper\ContentHistoryHelper;
|
||||
|
||||
/**
|
||||
* FOF model behavior class to add Joomla! content history support
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class ContentHistory extends Observer
|
||||
{
|
||||
/** @var ContentHistoryHelper */
|
||||
protected $historyHelper;
|
||||
|
||||
/**
|
||||
* The event which runs after storing (saving) data to the database
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
*
|
||||
* @return boolean True to allow saving without an error
|
||||
*/
|
||||
public function onAfterSave(&$model)
|
||||
{
|
||||
$aliasParts = explode('.', $model->getContentType());
|
||||
$model->checkContentType();
|
||||
|
||||
if (ComponentHelper::getParams($aliasParts[0])->get('save_history', 0))
|
||||
{
|
||||
if (!$this->historyHelper)
|
||||
{
|
||||
$this->historyHelper = new ContentHistoryHelper($model->getContentType());
|
||||
}
|
||||
|
||||
$this->historyHelper->store($model);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The event which runs before deleting a record
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
* @param integer $oid The PK value of the record to delete
|
||||
*
|
||||
* @return boolean True to allow the deletion
|
||||
*/
|
||||
public function onBeforeDelete(&$model, $oid)
|
||||
{
|
||||
$aliasParts = explode('.', $model->getContentType());
|
||||
|
||||
if (ComponentHelper::getParams($aliasParts[0])->get('save_history', 0))
|
||||
{
|
||||
if (!$this->historyHelper)
|
||||
{
|
||||
$this->historyHelper = new ContentHistoryHelper($model->getContentType());
|
||||
}
|
||||
|
||||
$this->historyHelper->deleteHistory($model);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This event runs after publishing a record in a model
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterPublish(&$model)
|
||||
{
|
||||
$model->updateUcmContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* This event runs after unpublishing a record in a model
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterUnpublish(&$model)
|
||||
{
|
||||
$model->updateUcmContent();
|
||||
}
|
||||
}
|
||||
73
libraries/fof30/Model/DataModel/Behaviour/Created.php
Normal file
73
libraries/fof30/Model/DataModel/Behaviour/Created.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* FOF model behavior class to updated the created_by and created_on fields on newly created records.
|
||||
*
|
||||
* This behaviour is added to DataModel by default. If you want to remove it you need to do
|
||||
* $this->behavioursDispatcher->removeBehaviour('Created');
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
class Created extends Observer
|
||||
{
|
||||
/**
|
||||
* Add the created_on and created_by fields in the fieldsSkipChecks list of the model. We expect them to be empty
|
||||
* so that we can fill them in through this behaviour.
|
||||
*
|
||||
* @param DataModel $model
|
||||
*/
|
||||
public function onBeforeCheck(&$model)
|
||||
{
|
||||
$model->addSkipCheckField('created_on');
|
||||
$model->addSkipCheckField('created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataModel $model
|
||||
* @param stdClass $dataObject
|
||||
*/
|
||||
public function onBeforeCreate(&$model, &$dataObject)
|
||||
{
|
||||
// Handle the created_on field
|
||||
if ($model->hasField('created_on'))
|
||||
{
|
||||
$nullDate = $model->isNullableField('created_on') ? null : $model->getDbo()->getNullDate();
|
||||
$created_on = $model->getFieldValue('created_on');
|
||||
|
||||
if (empty($created_on) || ($created_on == $nullDate))
|
||||
{
|
||||
$model->setFieldValue('created_on', $model->getContainer()->platform->getDate()->toSql(false, $model->getDbo()));
|
||||
|
||||
$createdOnField = $model->getFieldAlias('created_on');
|
||||
$dataObject->$createdOnField = $model->getFieldValue('created_on');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the created_by field
|
||||
if ($model->hasField('created_by'))
|
||||
{
|
||||
$created_by = $model->getFieldValue('created_by');
|
||||
|
||||
if (empty($created_by))
|
||||
{
|
||||
$model->setFieldValue('created_by', $model->getContainer()->platform->getUser()->id);
|
||||
|
||||
$createdByField = $model->getFieldAlias('created_by');
|
||||
$dataObject->$createdByField = $model->getFieldValue('created_by');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
libraries/fof30/Model/DataModel/Behaviour/EmptyNonZero.php
Normal file
36
libraries/fof30/Model/DataModel/Behaviour/EmptyNonZero.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use JDatabaseQuery;
|
||||
|
||||
/**
|
||||
* FOF model behavior class to let the Filters behaviour know that zero value is a valid filter value
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class EmptyNonZero extends Observer
|
||||
{
|
||||
/**
|
||||
* This event runs after we have built the query used to fetch a record
|
||||
* list in a model. It is used to apply automatic query filters.
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
* @param JDatabaseQuery &$query The query we are manipulating
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterBuildQuery(&$model, &$query)
|
||||
{
|
||||
$model->setBehaviorParam('filterZero', 1);
|
||||
}
|
||||
}
|
||||
75
libraries/fof30/Model/DataModel/Behaviour/Enabled.php
Normal file
75
libraries/fof30/Model/DataModel/Behaviour/Enabled.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use JDatabaseQuery;
|
||||
|
||||
/**
|
||||
* FOF model behavior class to filter access to items based on the enabled status
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Enabled extends Observer
|
||||
{
|
||||
/**
|
||||
* This event runs before we have built the query used to fetch a record
|
||||
* list in a model. It is used to apply automatic query filters.
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
* @param JDatabaseQuery &$query The query we are manipulating
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onBeforeBuildQuery(&$model, &$query)
|
||||
{
|
||||
// Make sure the field actually exists
|
||||
if (!$model->hasField('enabled'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$fieldName = $model->getFieldAlias('enabled');
|
||||
$db = $model->getDbo();
|
||||
|
||||
$model->whereRaw($db->qn($fieldName) . ' = ' . $db->q(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* The event runs after DataModel has retrieved a single item from the database. It is used to apply automatic
|
||||
* filters.
|
||||
*
|
||||
* @param DataModel &$model The model which was called
|
||||
* @param Array &$keys The keys used to locate the record which was loaded
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterLoad(&$model, &$keys)
|
||||
{
|
||||
// Make sure we have a DataModel
|
||||
if (!($model instanceof DataModel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the field actually exists
|
||||
if (!$model->hasField('enabled'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter by enabled status
|
||||
if (!$model->getFieldValue('enabled', 0))
|
||||
{
|
||||
$model->reset(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
133
libraries/fof30/Model/DataModel/Behaviour/Filters.php
Normal file
133
libraries/fof30/Model/DataModel/Behaviour/Filters.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use JDatabaseQuery;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
class Filters extends Observer
|
||||
{
|
||||
/**
|
||||
* This event runs after we have built the query used to fetch a record
|
||||
* list in a model. It is used to apply automatic query filters.
|
||||
*
|
||||
* @param DataModel & $model The model which calls this event
|
||||
* @param JDatabaseQuery &$query The query we are manipulating
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterBuildQuery(&$model, &$query)
|
||||
{
|
||||
$tableKey = $model->getIdFieldName();
|
||||
$db = $model->getDbo();
|
||||
|
||||
$fields = $model->getTableFields();
|
||||
$blacklist = $model->getBlacklistFilters();
|
||||
$filterZero = $model->getBehaviorParam('filterZero', null);
|
||||
$tableAlias = $model->getBehaviorParam('tableAlias', null);
|
||||
|
||||
foreach ($fields as $fieldname => $fieldmeta)
|
||||
{
|
||||
if (in_array($fieldname, $blacklist))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldInfo = (object) [
|
||||
'name' => $fieldname,
|
||||
'type' => $fieldmeta->Type,
|
||||
'filterZero' => $filterZero,
|
||||
'tableAlias' => $tableAlias,
|
||||
];
|
||||
|
||||
$filterName = $fieldInfo->name;
|
||||
$filterState = $model->getState($filterName, null);
|
||||
|
||||
// Special primary key handling: if ignore request is set we'll also look for an 'id' state variable if a
|
||||
// state variable by the same name as the key doesn't exist. If ignore request is not set in the model we
|
||||
// do not filter by 'id' since this interferes with going from an edit page to a browse page (the list is
|
||||
// filtered by id without user controls to unset it).
|
||||
if ($fieldInfo->name == $tableKey)
|
||||
{
|
||||
$filterState = $model->getState($filterName, null);
|
||||
|
||||
if (!$model->getIgnoreRequest())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($filterState))
|
||||
{
|
||||
$filterState = $model->getState('id', null);
|
||||
}
|
||||
}
|
||||
|
||||
$field = DataModel\Filter\AbstractFilter::getField($fieldInfo, ['dbo' => $db]);
|
||||
|
||||
if (!is_object($field) || !($field instanceof DataModel\Filter\AbstractFilter))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((is_array($filterState) && (
|
||||
array_key_exists('value', $filterState) ||
|
||||
array_key_exists('from', $filterState) ||
|
||||
array_key_exists('to', $filterState)
|
||||
)) || is_object($filterState))
|
||||
{
|
||||
$options = class_exists('JRegistry') ? new Registry($filterState) : new Registry($filterState);
|
||||
}
|
||||
else
|
||||
{
|
||||
$options = class_exists('JRegistry') ? new Registry() : new Registry();
|
||||
$options->set('value', $filterState);
|
||||
}
|
||||
|
||||
$methods = $field->getSearchMethods();
|
||||
$method = $options->get('method', $field->getDefaultSearchMethod());
|
||||
|
||||
if (!in_array($method, $methods))
|
||||
{
|
||||
$method = 'exact';
|
||||
}
|
||||
|
||||
switch ($method)
|
||||
{
|
||||
case 'between':
|
||||
case 'outside':
|
||||
case 'range' :
|
||||
$sql = $field->$method($options->get('from', null), $options->get('to', null), $options->get('include', false));
|
||||
break;
|
||||
|
||||
case 'interval':
|
||||
case 'modulo':
|
||||
$sql = $field->$method($options->get('value', null), $options->get('interval'));
|
||||
break;
|
||||
|
||||
case 'search':
|
||||
$sql = $field->$method($options->get('value', null), $options->get('operator', '='));
|
||||
break;
|
||||
|
||||
case 'exact':
|
||||
case 'partial':
|
||||
default:
|
||||
$sql = $field->$method($options->get('value', null));
|
||||
break;
|
||||
}
|
||||
|
||||
if ($sql)
|
||||
{
|
||||
$query->where($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
171
libraries/fof30/Model/DataModel/Behaviour/Language.php
Normal file
171
libraries/fof30/Model/DataModel/Behaviour/Language.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use JDatabaseQuery;
|
||||
use Joomla\CMS\Application\SiteApplication;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\Registry\Registry;
|
||||
use PlgSystemLanguageFilter;
|
||||
|
||||
/**
|
||||
* FOF model behavior class to filter front-end access to items
|
||||
* based on the language.
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Language extends Observer
|
||||
{
|
||||
/** @var PlgSystemLanguageFilter */
|
||||
protected $lang_filter_plugin;
|
||||
|
||||
/**
|
||||
* This event runs before we have built the query used to fetch a record
|
||||
* list in a model. It is used to blacklist the language filter
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
* @param JDatabaseQuery &$query The model which calls this event
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onBeforeBuildQuery(&$model, &$query)
|
||||
{
|
||||
if ($model->getContainer()->platform->isFrontend())
|
||||
{
|
||||
$model->blacklistFilters('language');
|
||||
}
|
||||
|
||||
// Make sure the field actually exists AND we're not in CLI
|
||||
if (!$model->hasField('language') || $model->getContainer()->platform->isCli())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var SiteApplication $app */
|
||||
$app = Factory::getApplication();
|
||||
$hasLanguageFilter = method_exists($app, 'getLanguageFilter');
|
||||
|
||||
if ($hasLanguageFilter)
|
||||
{
|
||||
$hasLanguageFilter = $app->getLanguageFilter();
|
||||
}
|
||||
|
||||
if (!$hasLanguageFilter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask Joomla for the plugin only if we don't already have it. Useful for tests
|
||||
if (!$this->lang_filter_plugin)
|
||||
{
|
||||
$this->lang_filter_plugin = PluginHelper::getPlugin('system', 'languagefilter');
|
||||
}
|
||||
|
||||
$lang_filter_params = class_exists('JRegistry') ? new Registry($this->lang_filter_plugin->params) : new Registry($this->lang_filter_plugin->params);
|
||||
|
||||
$languages = ['*'];
|
||||
|
||||
if ($lang_filter_params->get('remove_default_prefix'))
|
||||
{
|
||||
// Get default site language
|
||||
$platform = $model->getContainer()->platform;
|
||||
$lg = $platform->getLanguage();
|
||||
$languages[] = $lg->getTag();
|
||||
}
|
||||
else
|
||||
{
|
||||
// We have to use JInput since the language fragment is not set in the $_REQUEST, thus we won't have it in our model
|
||||
// TODO Double check the previous assumption
|
||||
$languages[] = Factory::getApplication()->input->getCmd('language', '*');
|
||||
}
|
||||
|
||||
// Filter out double languages
|
||||
$languages = array_unique($languages);
|
||||
|
||||
// And filter the query output by these languages
|
||||
$db = $model->getDbo();
|
||||
$languages = array_map([$db, 'quote'], $languages);
|
||||
$fieldName = $model->getFieldAlias('language');
|
||||
|
||||
$model->whereRaw($db->qn($fieldName) . ' IN(' . implode(', ', $languages) . ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* The event runs after DataModel has retrieved a single item from the database. It is used to apply automatic
|
||||
* filters.
|
||||
*
|
||||
* @param DataModel &$model The model which was called
|
||||
* @param Array &$keys The keys used to locate the record which was loaded
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterLoad(&$model, &$keys)
|
||||
{
|
||||
// Make sure we have a DataModel
|
||||
if (!($model instanceof DataModel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the field actually exists AND we're not in CLI
|
||||
if (!$model->hasField('language') || $model->getContainer()->platform->isCli())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure it is a multilingual site and get a list of languages
|
||||
/** @var SiteApplication $app */
|
||||
$app = Factory::getApplication();
|
||||
$hasLanguageFilter = method_exists($app, 'getLanguageFilter');
|
||||
|
||||
if ($hasLanguageFilter)
|
||||
{
|
||||
$hasLanguageFilter = $app->getLanguageFilter();
|
||||
}
|
||||
|
||||
if (!$hasLanguageFilter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask Joomla for the plugin only if we don't already have it. Useful for tests
|
||||
if (!$this->lang_filter_plugin)
|
||||
{
|
||||
$this->lang_filter_plugin = PluginHelper::getPlugin('system', 'languagefilter');
|
||||
}
|
||||
|
||||
$lang_filter_params = class_exists('JRegistry') ? new Registry($this->lang_filter_plugin->params) : new Registry($this->lang_filter_plugin->params);
|
||||
|
||||
$languages = ['*'];
|
||||
|
||||
if ($lang_filter_params->get('remove_default_prefix'))
|
||||
{
|
||||
// Get default site language
|
||||
$lg = $model->getContainer()->platform->getLanguage();
|
||||
$languages[] = $lg->getTag();
|
||||
}
|
||||
else
|
||||
{
|
||||
$languages[] = Factory::getApplication()->input->getCmd('language', '*');
|
||||
}
|
||||
|
||||
// Filter out double languages
|
||||
$languages = array_unique($languages);
|
||||
|
||||
// Filter by language
|
||||
if (!in_array($model->getFieldValue('language'), $languages))
|
||||
{
|
||||
$model->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
71
libraries/fof30/Model/DataModel/Behaviour/Modified.php
Normal file
71
libraries/fof30/Model/DataModel/Behaviour/Modified.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* FOF model behavior class to updated the modified_by and modified_on fields on newly created records.
|
||||
*
|
||||
* This behaviour is added to DataModel by default. If you want to remove it you need to do
|
||||
* $this->behavioursDispatcher->removeBehaviour('Modified');
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
class Modified extends Observer
|
||||
{
|
||||
/**
|
||||
* Add the modified_on and modified_by fields in the fieldsSkipChecks list of the model. We expect them to be empty
|
||||
* so that we can fill them in through this behaviour.
|
||||
*
|
||||
* @param DataModel $model
|
||||
*/
|
||||
public function onBeforeCheck(&$model)
|
||||
{
|
||||
$model->addSkipCheckField('modified_on');
|
||||
$model->addSkipCheckField('modified_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataModel $model
|
||||
* @param stdClass $dataObject
|
||||
*/
|
||||
public function onBeforeUpdate(&$model, &$dataObject)
|
||||
{
|
||||
// Make sure we're not modifying a locked record
|
||||
$userId = $model->getContainer()->platform->getUser()->id;
|
||||
$isLocked = $model->isLocked($userId);
|
||||
|
||||
if ($isLocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the modified_on field
|
||||
if ($model->hasField('modified_on'))
|
||||
{
|
||||
$model->setFieldValue('modified_on', $model->getContainer()->platform->getDate()->toSql(false, $model->getDbo()));
|
||||
|
||||
$modifiedOnField = $model->getFieldAlias('modified_on');
|
||||
$dataObject->$modifiedOnField = $model->getFieldValue('modified_on');
|
||||
}
|
||||
|
||||
// Handle the modified_by field
|
||||
if ($model->hasField('modified_by'))
|
||||
{
|
||||
$model->setFieldValue('modified_by', $userId);
|
||||
|
||||
$modifiedByField = $model->getFieldAlias('modified_by');
|
||||
$dataObject->$modifiedByField = $model->getFieldValue('modified_by');
|
||||
}
|
||||
}
|
||||
}
|
||||
82
libraries/fof30/Model/DataModel/Behaviour/Own.php
Normal file
82
libraries/fof30/Model/DataModel/Behaviour/Own.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use JDatabaseQuery;
|
||||
|
||||
/**
|
||||
* FOF model behavior class to filter access to items owned by the currently logged in user only
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Own extends Observer
|
||||
{
|
||||
/**
|
||||
* This event runs after we have built the query used to fetch a record
|
||||
* list in a model. It is used to apply automatic query filters.
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
* @param JDatabaseQuery &$query The query we are manipulating
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterBuildQuery(&$model, &$query)
|
||||
{
|
||||
// Make sure the field actually exists
|
||||
if (!$model->hasField('created_by'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current user's id
|
||||
$user_id = $model->getContainer()->platform->getUser()->id;
|
||||
|
||||
// And filter the query output by the user id
|
||||
$db = $model->getContainer()->platform->getDbo();
|
||||
|
||||
$query->where($db->qn($model->getFieldAlias('created_by')) . ' = ' . $db->q($user_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* The event runs after DataModel has retrieved a single item from the database. It is used to apply automatic
|
||||
* filters.
|
||||
*
|
||||
* @param DataModel &$model The model which was called
|
||||
* @param Array &$keys The keys used to locate the record which was loaded
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterLoad(&$model, &$keys)
|
||||
{
|
||||
// Make sure we have a DataModel
|
||||
if (!($model instanceof DataModel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the field actually exists
|
||||
if (!$model->hasField('created_by'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the user
|
||||
$user_id = $model->getContainer()->platform->getUser()->id;
|
||||
$recordUser = $model->getFieldValue('created_by', null);
|
||||
|
||||
// Filter by authorised access levels
|
||||
if ($recordUser != $user_id)
|
||||
{
|
||||
$model->reset(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use Joomla\CMS\Application\SiteApplication;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
/**
|
||||
* FOF model behavior class to populate the state with the front-end page parameters
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class PageParametersToState extends Observer
|
||||
{
|
||||
public function onAfterConstruct(DataModel &$model)
|
||||
{
|
||||
// This only applies to the front-end
|
||||
if (!$model->getContainer()->platform->isFrontend())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the page parameters
|
||||
/** @var SiteApplication $app */
|
||||
$app = Factory::getApplication();
|
||||
/** @var Registry|Registry $params */
|
||||
$params = $app->getParams();
|
||||
|
||||
// Extract the page parameter keys
|
||||
$asArray = $params->toArray();
|
||||
|
||||
if (empty($asArray))
|
||||
{
|
||||
// There are no keys; no point in going on.
|
||||
return;
|
||||
}
|
||||
|
||||
$keys = array_keys($asArray);
|
||||
unset($asArray);
|
||||
|
||||
// Loop all page parameter keys
|
||||
foreach ($keys as $key)
|
||||
{
|
||||
// This is the current model state
|
||||
$currentState = $model->getState($key);
|
||||
// This is the explicitly requested state in the input
|
||||
$explicitInput = $model->input->get($key, null, 'raw');
|
||||
|
||||
// If the current state is empty and there's no explicit input we'll use the page parameters instead
|
||||
if (is_null($currentState) && is_null($explicitInput))
|
||||
{
|
||||
$model->setState($key, $params->get($key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use JDatabaseQuery;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
class RelationFilters extends Observer
|
||||
{
|
||||
/**
|
||||
* This event runs after we have built the query used to fetch a record list in a model. It is used to apply
|
||||
* automatic query filters based on model relations.
|
||||
*
|
||||
* @param DataModel & $model The model which calls this event
|
||||
* @param JDatabaseQuery & $query The query we are manipulating
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterBuildQuery(&$model, &$query)
|
||||
{
|
||||
$relationFilters = $model->getRelationFilters();
|
||||
|
||||
foreach ($relationFilters as $filterState)
|
||||
{
|
||||
$relationName = $filterState['relation'];
|
||||
|
||||
$tableAlias = $model->getBehaviorParam('tableAlias', null);
|
||||
$subQuery = $model->getRelations()->getCountSubquery($relationName, $tableAlias);
|
||||
|
||||
// Callback method needs different handling
|
||||
if (isset($filterState['method']) && ($filterState['method'] == 'callback'))
|
||||
{
|
||||
call_user_func_array($filterState['value'], [&$subQuery]);
|
||||
$filterState['method'] = 'search';
|
||||
$filterState['operator'] = '>=';
|
||||
$filterState['value'] = '1';
|
||||
}
|
||||
|
||||
$options = class_exists('JRegistry') ? new Registry($filterState) : new Registry($filterState);
|
||||
|
||||
$filter = new DataModel\Filter\Relation($model->getDbo(), $relationName, $subQuery);
|
||||
$methods = $filter->getSearchMethods();
|
||||
$method = $options->get('method', $filter->getDefaultSearchMethod());
|
||||
|
||||
if (!in_array($method, $methods))
|
||||
{
|
||||
$method = 'exact';
|
||||
}
|
||||
|
||||
switch ($method)
|
||||
{
|
||||
case 'between':
|
||||
case 'outside':
|
||||
$sql = $filter->$method($options->get('from', null), $options->get('to'));
|
||||
break;
|
||||
|
||||
case 'interval':
|
||||
$sql = $filter->$method($options->get('value', null), $options->get('interval'));
|
||||
break;
|
||||
|
||||
case 'search':
|
||||
$sql = $filter->$method($options->get('value', null), $options->get('operator', '='));
|
||||
break;
|
||||
|
||||
default:
|
||||
$sql = $filter->$method($options->get('value', null));
|
||||
break;
|
||||
}
|
||||
|
||||
if ($sql)
|
||||
{
|
||||
$query->where($sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
171
libraries/fof30/Model/DataModel/Behaviour/Tags.php
Normal file
171
libraries/fof30/Model/DataModel/Behaviour/Tags.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Behaviour;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF30\Event\Observable;
|
||||
use FOF30\Event\Observer;
|
||||
use FOF30\Model\DataModel;
|
||||
use Joomla\CMS\Helper\TagsHelper;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* FOF model behavior class to add Joomla! Tags support
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Tags extends Observer
|
||||
{
|
||||
/** @var TagsHelper */
|
||||
protected $tagsHelper;
|
||||
|
||||
public function __construct(Observable &$subject)
|
||||
{
|
||||
parent::__construct($subject);
|
||||
|
||||
$this->tagsHelper = new TagsHelper();
|
||||
}
|
||||
|
||||
/**
|
||||
* This event runs after unpublishing a record in a model
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
* @param stdClass & $dataObject The data to bind to the form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onBeforeCreate(&$model, &$dataObject)
|
||||
{
|
||||
$tagField = $model->getBehaviorParam('tagFieldName', 'tags');
|
||||
|
||||
unset($dataObject->$tagField);
|
||||
}
|
||||
|
||||
/**
|
||||
* This event runs after unpublishing a record in a model
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
* @param stdClass & $dataObject The data to bind to the form
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onBeforeUpdate(&$model, &$dataObject)
|
||||
{
|
||||
$tagField = $model->getBehaviorParam('tagFieldName', 'tags');
|
||||
|
||||
unset($dataObject->$tagField);
|
||||
}
|
||||
|
||||
/**
|
||||
* The event which runs after binding data to the table
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception Error message if failed to store tags
|
||||
*/
|
||||
public function onAfterSave(&$model)
|
||||
{
|
||||
$tagField = $model->getBehaviorParam('tagFieldName', 'tags');
|
||||
|
||||
// Avoid to update on other method (e.g. publish, ...)
|
||||
if (!in_array($model->getContainer()->input->getCmd('task'), ['apply', 'save', 'savenew']))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$oldTags = $this->tagsHelper->getTagIds($model->getId(), $model->getContentType());
|
||||
$newTags = $model->$tagField ? implode(',', $model->$tagField) : null;
|
||||
|
||||
// If no changes, we stop here
|
||||
if ($oldTags == $newTags)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the content type exists, and create it if it does not
|
||||
$model->checkContentType();
|
||||
|
||||
$this->tagsHelper->typeAlias = $model->getContentType();
|
||||
|
||||
if (!$this->tagsHelper->postStoreProcess($model, $model->$tagField))
|
||||
{
|
||||
throw new Exception('Error storing tags');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The event which runs after deleting a record
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
* @param integer $oid The PK value of the record which was deleted
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception Error message if failed to delete tags
|
||||
*/
|
||||
public function onAfterDelete(&$model, $oid)
|
||||
{
|
||||
$this->tagsHelper->typeAlias = $model->getContentType();
|
||||
|
||||
if (!$this->tagsHelper->deleteTagData($model, $oid))
|
||||
{
|
||||
throw new Exception('Error deleting Tags');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This event runs after unpublishing a record in a model
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
* @param mixed $data An associative array or object to bind to the DataModel instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterBind(&$model, &$data)
|
||||
{
|
||||
$tagField = $model->getBehaviorParam('tagFieldName', 'tags');
|
||||
|
||||
if ($model->$tagField)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$type = $model->getContentType();
|
||||
|
||||
$model->addKnownField($tagField);
|
||||
$model->$tagField = $this->tagsHelper->getTagIds($model->getId(), $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* This event runs after publishing a record in a model
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterPublish(&$model)
|
||||
{
|
||||
$model->updateUcmContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* This event runs after unpublishing a record in a model
|
||||
*
|
||||
* @param DataModel &$model The model which calls this event
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onAfterUnpublish(&$model)
|
||||
{
|
||||
$model->updateUcmContent();
|
||||
}
|
||||
}
|
||||
336
libraries/fof30/Model/DataModel/Collection.php
Normal file
336
libraries/fof30/Model/DataModel/Collection.php
Normal file
@ -0,0 +1,336 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Model\DataModel;
|
||||
use FOF30\Utils\Collection as BaseCollection;
|
||||
|
||||
/**
|
||||
* A collection of data models. You can enumerate it like an array, use it everywhere a collection is expected (e.g. a
|
||||
* foreach loop) and even implements a countable interface. You can also batch-apply DataModel methods on it thanks to
|
||||
* its magic __call() method, hence the type-hinting below.
|
||||
*
|
||||
* @method void setFieldValue(string $name, mixed $value = '')
|
||||
* @method void archive()
|
||||
* @method void save(mixed $data, string $orderingFilter = '', bool $ignore = null)
|
||||
* @method void push(mixed $data, string $orderingFilter = '', bool $ignore = null, array $relations = null)
|
||||
* @method void bind(mixed $data, array $ignore = [])
|
||||
* @method void check()
|
||||
* @method void reorder(string $where = '')
|
||||
* @method void delete(mixed $id = null)
|
||||
* @method void trash(mixed $id)
|
||||
* @method void forceDelete(mixed $id = null)
|
||||
* @method void lock(int $userId = null)
|
||||
* @method void move(int $delta, string $where = '')
|
||||
* @method void publish()
|
||||
* @method void restore(mixed $id)
|
||||
* @method void touch(int $userId = null)
|
||||
* @method void unlock()
|
||||
* @method void unpublish()
|
||||
*/
|
||||
class Collection extends BaseCollection
|
||||
{
|
||||
/**
|
||||
* Find a model in the collection by key.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return DataModel
|
||||
*/
|
||||
public function find($key, $default = null)
|
||||
{
|
||||
if ($key instanceof DataModel)
|
||||
{
|
||||
$key = $key->getId();
|
||||
}
|
||||
|
||||
return array_first($this->items, function ($itemKey, $model) use ($key) {
|
||||
/** @var DataModel $model */
|
||||
return $model->getId() == $key;
|
||||
|
||||
}, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item in the collection by key
|
||||
*
|
||||
* @param mixed $key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeById($key)
|
||||
{
|
||||
if ($key instanceof DataModel)
|
||||
{
|
||||
$key = $key->getId();
|
||||
}
|
||||
|
||||
$index = array_search($key, $this->modelKeys());
|
||||
|
||||
if ($index !== false)
|
||||
{
|
||||
unset($this->items[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the collection.
|
||||
*
|
||||
* @param mixed $item
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function add($item)
|
||||
{
|
||||
$this->items[] = $item;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a key exists in the collection.
|
||||
*
|
||||
* @param mixed $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function contains($key)
|
||||
{
|
||||
return !is_null($this->find($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a nested element of the collection.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function fetch($key)
|
||||
{
|
||||
return new static(array_fetch($this->toArray(), $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the max value of a given key.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function max($key)
|
||||
{
|
||||
return $this->reduce(function ($result, $item) use ($key) {
|
||||
return (is_null($result) || $item->{$key} > $result) ? $item->{$key} : $result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the min value of a given key.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function min($key)
|
||||
{
|
||||
return $this->reduce(function ($result, $item) use ($key) {
|
||||
return (is_null($result) || $item->{$key} < $result) ? $item->{$key} : $result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array of primary keys
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function modelKeys()
|
||||
{
|
||||
return array_map(
|
||||
function ($m) {
|
||||
/** @var DataModel $m */
|
||||
return $m->getId();
|
||||
},
|
||||
$this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the collection with the given items.
|
||||
*
|
||||
* @param BaseCollection|array $collection
|
||||
*
|
||||
* @return BaseCollection
|
||||
*/
|
||||
public function merge($collection)
|
||||
{
|
||||
$dictionary = $this->getDictionary($this);
|
||||
|
||||
foreach ($collection as $item)
|
||||
{
|
||||
$dictionary[$item->getId()] = $item;
|
||||
}
|
||||
|
||||
return new static(array_values($dictionary));
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff the collection with the given items.
|
||||
*
|
||||
* @param BaseCollection|array $collection
|
||||
*
|
||||
* @return BaseCollection
|
||||
*/
|
||||
public function diff($collection)
|
||||
{
|
||||
$diff = new static;
|
||||
|
||||
$dictionary = $this->getDictionary($collection);
|
||||
|
||||
foreach ($this->items as $item)
|
||||
{
|
||||
/** @var DataModel $item */
|
||||
if (!isset($dictionary[$item->getId()]))
|
||||
{
|
||||
$diff->add($item);
|
||||
}
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersect the collection with the given items.
|
||||
*
|
||||
* @param BaseCollection|array $collection
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function intersect($collection)
|
||||
{
|
||||
$intersect = new static;
|
||||
|
||||
$dictionary = $this->getDictionary($collection);
|
||||
|
||||
foreach ($this->items as $item)
|
||||
{
|
||||
/** @var DataModel $item */
|
||||
if (isset($dictionary[$item->getId()]))
|
||||
{
|
||||
$intersect->add($item);
|
||||
}
|
||||
}
|
||||
|
||||
return $intersect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only unique items from the collection.
|
||||
*
|
||||
* @return BaseCollection
|
||||
*/
|
||||
public function unique()
|
||||
{
|
||||
$dictionary = $this->getDictionary($this);
|
||||
|
||||
return new static(array_values($dictionary));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a base Support collection instance from this collection.
|
||||
*
|
||||
* @return BaseCollection
|
||||
*/
|
||||
public function toBase()
|
||||
{
|
||||
return new BaseCollection($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method which allows you to run a DataModel method to all items in the collection.
|
||||
*
|
||||
* For example, you can do $collection->save('foobar' => 1) to update the 'foobar' column to 1 across all items in
|
||||
* the collection.
|
||||
*
|
||||
* IMPORTANT: The return value of the method call is not returned back to you!
|
||||
*
|
||||
* @param string $name The method to call
|
||||
* @param array $arguments The arguments to the method
|
||||
*/
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
if (!count($this))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$class = get_class($this->first());
|
||||
|
||||
if (method_exists($class, $name))
|
||||
{
|
||||
foreach ($this as $item)
|
||||
{
|
||||
switch (count($arguments))
|
||||
{
|
||||
case 0:
|
||||
$item->$name();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$item->$name($arguments[0]);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$item->$name($arguments[0], $arguments[1]);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$item->$name($arguments[0], $arguments[1], $arguments[2]);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
$item->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
$item->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
$item->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]);
|
||||
break;
|
||||
|
||||
default:
|
||||
call_user_func_array([$item, $name], $arguments);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a dictionary keyed by primary keys.
|
||||
*
|
||||
* @param BaseCollection $collection
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDictionary($collection)
|
||||
{
|
||||
$dictionary = [];
|
||||
|
||||
foreach ($collection as $value)
|
||||
{
|
||||
$dictionary[$value->getId()] = $value;
|
||||
}
|
||||
|
||||
return $dictionary;
|
||||
}
|
||||
}
|
||||
17
libraries/fof30/Model/DataModel/Exception/BaseException.php
Normal file
17
libraries/fof30/Model/DataModel/Exception/BaseException.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class BaseException extends RuntimeException
|
||||
{
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
class CannotLockNotLoadedRecord extends BaseException
|
||||
{
|
||||
public function __construct($message = '', $code = 500, Exception $previous = null)
|
||||
{
|
||||
if (empty($message))
|
||||
{
|
||||
$message = Text::_('LIB_FOF_MODEL_ERR_CANNOTLOCKNOTLOADEDRECORD');
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
class InvalidSearchMethod extends BaseException
|
||||
{
|
||||
|
||||
}
|
||||
28
libraries/fof30/Model/DataModel/Exception/NoAssetKey.php
Normal file
28
libraries/fof30/Model/DataModel/Exception/NoAssetKey.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class NoAssetKey extends UnexpectedValueException
|
||||
{
|
||||
public function __construct($message = '', $code = 500, Exception $previous = null)
|
||||
{
|
||||
if (empty($message))
|
||||
{
|
||||
$message = Text::_('LIB_FOF_MODEL_ERR_NOASSETKEY');
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
25
libraries/fof30/Model/DataModel/Exception/NoContentType.php
Normal file
25
libraries/fof30/Model/DataModel/Exception/NoContentType.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class NoContentType extends UnexpectedValueException
|
||||
{
|
||||
public function __construct($className, $code = 500, Exception $previous = null)
|
||||
{
|
||||
$message = Text::sprintf('LIB_FOF_MODEL_ERR_NOCONTENTTYPE', $className);
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
24
libraries/fof30/Model/DataModel/Exception/NoItemsFound.php
Normal file
24
libraries/fof30/Model/DataModel/Exception/NoItemsFound.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
class NoItemsFound extends BaseException
|
||||
{
|
||||
public function __construct($className, $code = 404, Exception $previous = null)
|
||||
{
|
||||
$message = Text::sprintf('LIB_FOF_MODEL_ERR_NOITEMSFOUND', $className);
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
15
libraries/fof30/Model/DataModel/Exception/NoTableColumns.php
Normal file
15
libraries/fof30/Model/DataModel/Exception/NoTableColumns.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
class NoTableColumns extends BaseException
|
||||
{
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
class RecordNotLoaded extends BaseException
|
||||
{
|
||||
public function __construct($message = "", $code = 404, Exception $previous = null)
|
||||
{
|
||||
if (empty($message))
|
||||
{
|
||||
$message = Text::_('LIB_FOF_MODEL_ERR_COULDNOTLOAD');
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
class SpecialColumnMissing extends BaseException
|
||||
{
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class TreeIncompatibleTable extends UnexpectedValueException
|
||||
{
|
||||
public function __construct($tableName, $code = 500, Exception $previous = null)
|
||||
{
|
||||
$message = Text::sprintf('LIB_FOF_MODEL_ERR_TREE_INCOMPATIBLETABLE', $tableName);
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Model\DataModel\Exception;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class TreeInvalidLftRgt extends RuntimeException
|
||||
{
|
||||
public function __construct($message = '', $code = 500, Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user