primo commit
This commit is contained in:
565
libraries/fof30/Utils/ArrayHelper.php
Normal file
565
libraries/fof30/Utils/ArrayHelper.php
Normal file
@ -0,0 +1,565 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use ArrayAccess;
|
||||
use InvalidArgumentException;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* ArrayHelper is an array utility class for doing all sorts of odds and ends with arrays.
|
||||
*
|
||||
* Copied from Joomla Framework to avoid class name issues between Joomla! versions 3 and 4. sortObjects is not included
|
||||
* because it needs the UTF-8 package. If you need to use that then you should be using the Joomla! Framework's helper
|
||||
* anyway.
|
||||
*/
|
||||
final class ArrayHelper
|
||||
{
|
||||
/**
|
||||
* Private constructor to prevent instantiation of this class
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to convert array to integer values
|
||||
*
|
||||
* @param array $array The source array to convert
|
||||
* @param mixed $default A default value (int|array) to assign if $array is not an array
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function toInteger($array, $default = null)
|
||||
{
|
||||
if (is_array($array))
|
||||
{
|
||||
return array_map('intval', $array);
|
||||
}
|
||||
|
||||
if ($default === null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_array($default))
|
||||
{
|
||||
return static::toInteger($default, null);
|
||||
}
|
||||
|
||||
return [(int) $default];
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to map an array to a stdClass object.
|
||||
*
|
||||
* @param array $array The array to map.
|
||||
* @param string $class Name of the class to create
|
||||
* @param boolean $recursive Convert also any array inside the main array
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function toObject(array $array, $class = 'stdClass', $recursive = true)
|
||||
{
|
||||
$obj = new $class;
|
||||
|
||||
foreach ($array as $k => $v)
|
||||
{
|
||||
if ($recursive && is_array($v))
|
||||
{
|
||||
$obj->$k = static::toObject($v, $class);
|
||||
}
|
||||
else
|
||||
{
|
||||
$obj->$k = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to map an array to a string.
|
||||
*
|
||||
* @param array $array The array to map.
|
||||
* @param string $inner_glue The glue (optional, defaults to '=') between the key and the value.
|
||||
* @param string $outer_glue The glue (optional, defaults to ' ') between array elements.
|
||||
* @param boolean $keepOuterKey True if final key should be kept.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function toString(array $array, $inner_glue = '=', $outer_glue = ' ', $keepOuterKey = false)
|
||||
{
|
||||
$output = [];
|
||||
|
||||
foreach ($array as $key => $item)
|
||||
{
|
||||
if (is_array($item))
|
||||
{
|
||||
if ($keepOuterKey)
|
||||
{
|
||||
$output[] = $key;
|
||||
}
|
||||
|
||||
// This is value is an array, go and do it again!
|
||||
$output[] = static::toString($item, $inner_glue, $outer_glue, $keepOuterKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
$output[] = $key . $inner_glue . '"' . $item . '"';
|
||||
}
|
||||
}
|
||||
|
||||
return implode($outer_glue, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to map an object to an array
|
||||
*
|
||||
* @param object $p_obj The source object
|
||||
* @param boolean $recurse True to recurse through multi-level objects
|
||||
* @param string $regex An optional regular expression to match on field names
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function fromObject($p_obj, $recurse = true, $regex = null)
|
||||
{
|
||||
if (is_object($p_obj) || is_array($p_obj))
|
||||
{
|
||||
return self::arrayFromObject($p_obj, $recurse, $regex);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a column from an array of arrays or objects
|
||||
*
|
||||
* @param array $array The source array
|
||||
* @param string $valueCol The index of the column or name of object property to be used as value
|
||||
* It may also be NULL to return complete arrays or objects (this is
|
||||
* useful together with <var>$keyCol</var> to reindex the array).
|
||||
* @param string $keyCol The index of the column or name of object property to be used as key
|
||||
*
|
||||
* @return array Column of values from the source array
|
||||
*
|
||||
* @since 1.0
|
||||
* @see http://php.net/manual/en/language.types.array.php
|
||||
* @see http://php.net/manual/en/function.array-column.php
|
||||
*/
|
||||
public static function getColumn(array $array, $valueCol, $keyCol = null)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($array as $item)
|
||||
{
|
||||
// Convert object to array
|
||||
$subject = is_object($item) ? static::fromObject($item) : $item;
|
||||
|
||||
/*
|
||||
* We process arrays (and objects already converted to array)
|
||||
* Only if the value column (if required) exists in this item
|
||||
*/
|
||||
if (is_array($subject) && (!isset($valueCol) || isset($subject[$valueCol])))
|
||||
{
|
||||
// Use whole $item if valueCol is null, else use the value column.
|
||||
$value = isset($valueCol) ? $subject[$valueCol] : $item;
|
||||
|
||||
// Array keys can only be integer or string. Casting will occur as per the PHP Manual.
|
||||
if (isset($keyCol) && isset($subject[$keyCol]) && is_scalar($subject[$keyCol]))
|
||||
{
|
||||
$key = $subject[$keyCol];
|
||||
$result[$key] = $value;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result[] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to return a value from a named array or a specified default
|
||||
*
|
||||
* @param array|ArrayAccess $array A named array or object that implements ArrayAccess
|
||||
* @param string $name The key to search for
|
||||
* @param mixed $default The default value to give if no key found
|
||||
* @param string $type Return type for the variable (INT, FLOAT, STRING, WORD, BOOLEAN, ARRAY)
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function getValue($array, $name, $default = null, $type = '')
|
||||
{
|
||||
if (!is_array($array) && !($array instanceof ArrayAccess))
|
||||
{
|
||||
throw new InvalidArgumentException('The object must be an array or an object that implements ArrayAccess');
|
||||
}
|
||||
|
||||
$result = null;
|
||||
|
||||
if (isset($array[$name]))
|
||||
{
|
||||
$result = $array[$name];
|
||||
}
|
||||
|
||||
// Handle the default case
|
||||
if (is_null($result))
|
||||
{
|
||||
$result = $default;
|
||||
}
|
||||
|
||||
// Handle the type constraint
|
||||
switch (strtoupper($type))
|
||||
{
|
||||
case 'INT':
|
||||
case 'INTEGER':
|
||||
// Only use the first integer value
|
||||
@preg_match('/-?[0-9]+/', $result, $matches);
|
||||
$result = @(int) $matches[0];
|
||||
break;
|
||||
|
||||
case 'FLOAT':
|
||||
case 'DOUBLE':
|
||||
// Only use the first floating point value
|
||||
@preg_match('/-?[0-9]+(\.[0-9]+)?/', $result, $matches);
|
||||
$result = @(float) $matches[0];
|
||||
break;
|
||||
|
||||
case 'BOOL':
|
||||
case 'BOOLEAN':
|
||||
$result = (bool) $result;
|
||||
break;
|
||||
|
||||
case 'ARRAY':
|
||||
if (!is_array($result))
|
||||
{
|
||||
$result = [$result];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'STRING':
|
||||
$result = (string) $result;
|
||||
break;
|
||||
|
||||
case 'WORD':
|
||||
$result = (string) preg_replace('#\W#', '', $result);
|
||||
break;
|
||||
|
||||
case 'NONE':
|
||||
default:
|
||||
// No casting necessary
|
||||
break;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an associative array of arrays and inverts the array keys to values using the array values as keys.
|
||||
*
|
||||
* Example:
|
||||
* $input = array(
|
||||
* 'New' => array('1000', '1500', '1750'),
|
||||
* 'Used' => array('3000', '4000', '5000', '6000')
|
||||
* );
|
||||
* $output = ArrayHelper::invert($input);
|
||||
*
|
||||
* Output would be equal to:
|
||||
* $output = array(
|
||||
* '1000' => 'New',
|
||||
* '1500' => 'New',
|
||||
* '1750' => 'New',
|
||||
* '3000' => 'Used',
|
||||
* '4000' => 'Used',
|
||||
* '5000' => 'Used',
|
||||
* '6000' => 'Used'
|
||||
* );
|
||||
*
|
||||
* @param array $array The source array.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function invert(array $array)
|
||||
{
|
||||
$return = [];
|
||||
|
||||
foreach ($array as $base => $values)
|
||||
{
|
||||
if (!is_array($values))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($values as $key)
|
||||
{
|
||||
// If the key isn't scalar then ignore it.
|
||||
if (is_scalar($key))
|
||||
{
|
||||
$return[$key] = $base;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to determine if an array is an associative array.
|
||||
*
|
||||
* @param array $array An array to test.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function isAssociative($array)
|
||||
{
|
||||
if (is_array($array))
|
||||
{
|
||||
foreach (array_keys($array) as $k => $v)
|
||||
{
|
||||
if ($k !== $v)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pivots an array to create a reverse lookup of an array of scalars, arrays or objects.
|
||||
*
|
||||
* @param array $source The source array.
|
||||
* @param string $key Where the elements of the source array are objects or arrays, the key to pivot on.
|
||||
*
|
||||
* @return array An array of arrays pivoted either on the value of the keys, or an individual key of an object or
|
||||
* array.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function pivot(array $source, $key = null)
|
||||
{
|
||||
$result = [];
|
||||
$counter = [];
|
||||
|
||||
foreach ($source as $index => $value)
|
||||
{
|
||||
// Determine the name of the pivot key, and its value.
|
||||
if (is_array($value))
|
||||
{
|
||||
// If the key does not exist, ignore it.
|
||||
if (!isset($value[$key]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$resultKey = $value[$key];
|
||||
$resultValue = $source[$index];
|
||||
}
|
||||
elseif (is_object($value))
|
||||
{
|
||||
// If the key does not exist, ignore it.
|
||||
if (!isset($value->$key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$resultKey = $value->$key;
|
||||
$resultValue = $source[$index];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just a scalar value.
|
||||
$resultKey = $value;
|
||||
$resultValue = $index;
|
||||
}
|
||||
|
||||
// The counter tracks how many times a key has been used.
|
||||
if (empty($counter[$resultKey]))
|
||||
{
|
||||
// The first time around we just assign the value to the key.
|
||||
$result[$resultKey] = $resultValue;
|
||||
$counter[$resultKey] = 1;
|
||||
}
|
||||
elseif ($counter[$resultKey] == 1)
|
||||
{
|
||||
// If there is a second time, we convert the value into an array.
|
||||
$result[$resultKey] = [
|
||||
$result[$resultKey],
|
||||
$resultValue,
|
||||
];
|
||||
$counter[$resultKey]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// After the second time, no need to track any more. Just append to the existing array.
|
||||
$result[$resultKey][] = $resultValue;
|
||||
}
|
||||
}
|
||||
|
||||
unset($counter);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multidimensional array safe unique test
|
||||
*
|
||||
* @param array $array The array to make unique.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @see http://php.net/manual/en/function.array-unique.php
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function arrayUnique(array $array)
|
||||
{
|
||||
$array = array_map('serialize', $array);
|
||||
$array = array_unique($array);
|
||||
$array = array_map('unserialize', $array);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* An improved array_search that allows for partial matching of strings values in associative arrays.
|
||||
*
|
||||
* @param string $needle The text to search for within the array.
|
||||
* @param array $haystack Associative array to search in to find $needle.
|
||||
* @param boolean $caseSensitive True to search case sensitive, false otherwise.
|
||||
*
|
||||
* @return mixed Returns the matching array $key if found, otherwise false.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function arraySearch($needle, array $haystack, $caseSensitive = true)
|
||||
{
|
||||
foreach ($haystack as $key => $value)
|
||||
{
|
||||
$searchFunc = ($caseSensitive) ? 'strpos' : 'stripos';
|
||||
|
||||
if ($searchFunc($value, $needle) === 0)
|
||||
{
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to recursively convert data to a one dimension array.
|
||||
*
|
||||
* @param array|object $array The array or object to convert.
|
||||
* @param string $separator The key separator.
|
||||
* @param string $prefix Last level key prefix.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public static function flatten($array, $separator = '.', $prefix = '')
|
||||
{
|
||||
if ($array instanceof Traversable)
|
||||
{
|
||||
$array = iterator_to_array($array);
|
||||
}
|
||||
elseif (is_object($array))
|
||||
{
|
||||
$array = get_object_vars($array);
|
||||
}
|
||||
|
||||
foreach ($array as $k => $v)
|
||||
{
|
||||
$key = $prefix ? $prefix . $separator . $k : $k;
|
||||
|
||||
if (is_object($v) || is_array($v))
|
||||
{
|
||||
$array = array_merge($array, static::flatten($v, $separator, $key));
|
||||
}
|
||||
else
|
||||
{
|
||||
$array[$key] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to map an object or array to an array
|
||||
*
|
||||
* @param mixed $item The source object or array
|
||||
* @param boolean $recurse True to recurse through multi-level objects
|
||||
* @param string $regex An optional regular expression to match on field names
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
private static function arrayFromObject($item, $recurse, $regex)
|
||||
{
|
||||
if (is_object($item))
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach (get_object_vars($item) as $k => $v)
|
||||
{
|
||||
if (!$regex || preg_match($regex, $k))
|
||||
{
|
||||
if ($recurse)
|
||||
{
|
||||
$result[$k] = self::arrayFromObject($v, $recurse, $regex);
|
||||
}
|
||||
else
|
||||
{
|
||||
$result[$k] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (is_array($item))
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($item as $k => $v)
|
||||
{
|
||||
$result[$k] = self::arrayFromObject($v, $recurse, $regex);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
311
libraries/fof30/Utils/Buffer.php
Normal file
311
libraries/fof30/Utils/Buffer.php
Normal file
@ -0,0 +1,311 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Registers a fof:// stream wrapper
|
||||
*/
|
||||
class Buffer
|
||||
{
|
||||
/**
|
||||
* Buffer hash
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $buffers = [];
|
||||
public static $canRegisterWrapper = null;
|
||||
/**
|
||||
* Stream position
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $position = 0;
|
||||
/**
|
||||
* Buffer name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name = null;
|
||||
|
||||
/**
|
||||
* Should I register the fof:// stream wrapper
|
||||
*
|
||||
* @return bool True if the stream wrapper can be registered
|
||||
*/
|
||||
public static function canRegisterWrapper()
|
||||
{
|
||||
if (is_null(static::$canRegisterWrapper))
|
||||
{
|
||||
static::$canRegisterWrapper = false;
|
||||
|
||||
// Maybe the host has disabled registering stream wrappers altogether?
|
||||
if (!function_exists('stream_wrapper_register'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for Suhosin
|
||||
if (function_exists('extension_loaded'))
|
||||
{
|
||||
$hasSuhosin = extension_loaded('suhosin');
|
||||
}
|
||||
else
|
||||
{
|
||||
$hasSuhosin = -1; // Can't detect
|
||||
}
|
||||
|
||||
if ($hasSuhosin !== true)
|
||||
{
|
||||
$hasSuhosin = defined('SUHOSIN_PATCH') ? true : -1;
|
||||
}
|
||||
|
||||
if ($hasSuhosin === -1)
|
||||
{
|
||||
if (function_exists('ini_get'))
|
||||
{
|
||||
$hasSuhosin = false;
|
||||
|
||||
$maxIdLength = ini_get('suhosin.session.max_id_length');
|
||||
|
||||
if ($maxIdLength !== false)
|
||||
{
|
||||
$hasSuhosin = ini_get('suhosin.session.max_id_length') !== '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't detect whether Suhosin is installed we won't proceed to prevent a White Screen of Death
|
||||
if ($hasSuhosin === -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If Suhosin is installed but ini_get is not available we won't proceed to prevent a WSoD
|
||||
if ($hasSuhosin && !function_exists('ini_get'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If Suhosin is installed check if fof:// is whitelisted
|
||||
if ($hasSuhosin)
|
||||
{
|
||||
$whiteList = ini_get('suhosin.executor.include.whitelist');
|
||||
|
||||
// Nothing in the whitelist? I can't go on, sorry.
|
||||
if (empty($whiteList))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$whiteList = explode(',', $whiteList);
|
||||
$whiteList = array_map(function ($x) {
|
||||
return trim($x);
|
||||
}, $whiteList);
|
||||
|
||||
if (!in_array('fof://', $whiteList))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static::$canRegisterWrapper = true;
|
||||
}
|
||||
|
||||
return static::$canRegisterWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to open file or url
|
||||
*
|
||||
* @param string $path The URL that was passed
|
||||
* @param string $mode Mode used to open the file @see fopen
|
||||
* @param integer $options Flags used by the API, may be STREAM_USE_PATH and
|
||||
* STREAM_REPORT_ERRORS
|
||||
* @param string &$opened_path Full path of the resource. Used with STREAM_USE_PATH option
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @see streamWrapper::stream_open
|
||||
*/
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
$url = parse_url($path);
|
||||
$this->name = $url['host'] . $url['path'];
|
||||
$this->position = 0;
|
||||
|
||||
if (!isset(static::$buffers[$this->name]))
|
||||
{
|
||||
static::$buffers[$this->name] = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function unlink($path)
|
||||
{
|
||||
$url = parse_url($path);
|
||||
$name = $url['host'];
|
||||
|
||||
if (isset(static::$buffers[$name]))
|
||||
{
|
||||
unset (static::$buffers[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return [
|
||||
'dev' => 0,
|
||||
'ino' => 0,
|
||||
'mode' => 0644,
|
||||
'nlink' => 0,
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'rdev' => 0,
|
||||
'size' => strlen(static::$buffers[$this->name]),
|
||||
'atime' => 0,
|
||||
'mtime' => 0,
|
||||
'ctime' => 0,
|
||||
'blksize' => -1,
|
||||
'blocks' => -1,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read stream
|
||||
*
|
||||
* @param integer $count How many bytes of data from the current position should be returned.
|
||||
*
|
||||
* @return mixed The data from the stream up to the specified number of bytes (all data if
|
||||
* the total number of bytes in the stream is less than $count. Null if
|
||||
* the stream is empty.
|
||||
*
|
||||
* @see streamWrapper::stream_read
|
||||
* @since 11.1
|
||||
*/
|
||||
public function stream_read($count)
|
||||
{
|
||||
$ret = substr(static::$buffers[$this->name], $this->position, $count);
|
||||
$this->position += strlen($ret);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write stream
|
||||
*
|
||||
* @param string $data The data to write to the stream.
|
||||
*
|
||||
* @return integer
|
||||
*
|
||||
* @see streamWrapper::stream_write
|
||||
* @since 11.1
|
||||
*/
|
||||
public function stream_write($data)
|
||||
{
|
||||
$left = substr(static::$buffers[$this->name], 0, $this->position);
|
||||
$right = substr(static::$buffers[$this->name], $this->position + strlen($data));
|
||||
static::$buffers[$this->name] = $left . $data . $right;
|
||||
$this->position += strlen($data);
|
||||
|
||||
return strlen($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get the current position of the stream
|
||||
*
|
||||
* @return integer
|
||||
*
|
||||
* @see streamWrapper::stream_tell
|
||||
* @since 11.1
|
||||
*/
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to test for end of file pointer
|
||||
*
|
||||
* @return boolean True if the pointer is at the end of the stream
|
||||
*
|
||||
* @see streamWrapper::stream_eof
|
||||
* @since 11.1
|
||||
*/
|
||||
public function stream_eof()
|
||||
{
|
||||
return $this->position >= strlen(static::$buffers[$this->name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The read write position updates in response to $offset and $whence
|
||||
*
|
||||
* @param integer $offset The offset in bytes
|
||||
* @param integer $whence Position the offset is added to
|
||||
* Options are SEEK_SET, SEEK_CUR, and SEEK_END
|
||||
*
|
||||
* @return boolean True if updated
|
||||
*
|
||||
* @see streamWrapper::stream_seek
|
||||
* @since 11.1
|
||||
*/
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
switch ($whence)
|
||||
{
|
||||
case SEEK_SET:
|
||||
if ($offset < strlen(static::$buffers[$this->name]) && $offset >= 0)
|
||||
{
|
||||
$this->position = $offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
if ($offset >= 0)
|
||||
{
|
||||
$this->position += $offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
if (strlen(static::$buffers[$this->name]) + $offset >= 0)
|
||||
{
|
||||
$this->position = strlen(static::$buffers[$this->name]) + $offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Buffer::canRegisterWrapper())
|
||||
{
|
||||
stream_wrapper_register('fof', 'FOF30\\Utils\\Buffer');
|
||||
}
|
||||
215
libraries/fof30/Utils/CacheCleaner.php
Normal file
215
libraries/fof30/Utils/CacheCleaner.php
Normal file
@ -0,0 +1,215 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Application\CMSApplication;
|
||||
use Joomla\CMS\Cache\Cache;
|
||||
use Joomla\CMS\Cache\CacheControllerFactoryInterface;
|
||||
use Joomla\CMS\Cache\Controller\CallbackController;
|
||||
use Joomla\CMS\Cache\Exception\CacheExceptionInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
||||
|
||||
/**
|
||||
* A utility class to help you quickly clean the Joomla! cache
|
||||
*/
|
||||
class CacheCleaner
|
||||
{
|
||||
/**
|
||||
* Clears the com_modules and com_plugins cache. You need to call this whenever you alter the publish state or
|
||||
* parameters of a module or plugin from your code.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearPluginsAndModulesCache()
|
||||
{
|
||||
self::clearPluginsCache();
|
||||
self::clearModulesCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the com_plugins cache. You need to call this whenever you alter the publish state or parameters of a
|
||||
* plugin from your code.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearPluginsCache()
|
||||
{
|
||||
self::clearCacheGroups(['com_plugins'], [0, 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the com_modules cache. You need to call this whenever you alter the publish state or parameters of a
|
||||
* module from your code.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearModulesCache()
|
||||
{
|
||||
self::clearCacheGroups(['com_modules'], [0, 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the specified cache groups.
|
||||
*
|
||||
* @param array $clearGroups Which cache groups to clear. Usually this is com_yourcomponent to clear
|
||||
* your component's cache.
|
||||
* @param array $cacheClients Which cache clients to clear. 0 is the back-end, 1 is the front-end. If you
|
||||
* do not specify anything, both cache clients will be cleared.
|
||||
* @param string|null $event An event to run upon trying to clear the cache. Empty string to disable. If
|
||||
* NULL and the group is "com_content" I will trigger onContentCleanCache.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function clearCacheGroups(array $clearGroups, array $cacheClients = [
|
||||
0, 1,
|
||||
], ?string $event = null): void
|
||||
{
|
||||
// Early return on nonsensical input
|
||||
if (empty($clearGroups) || empty($cacheClients))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure I have a valid CMS application
|
||||
try
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$isJoomla4 = version_compare(JVERSION, '3.9999.9999', 'gt');
|
||||
|
||||
// Loop all groups to clean
|
||||
foreach ($clearGroups as $group)
|
||||
{
|
||||
// Groups must be non-empty strings
|
||||
if (empty($group) || !is_string($group))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Loop all clients (applications)
|
||||
foreach ($cacheClients as $client_id)
|
||||
{
|
||||
$client_id = (int) ($client_id ?? 0);
|
||||
|
||||
$options = $isJoomla4
|
||||
? self::clearCacheGroupJoomla4($group, $client_id, $app)
|
||||
: self::clearCacheGroupJoomla3($group, $client_id, $app);
|
||||
|
||||
// Do not call any events if I failed to clean the cache using the core Joomla API
|
||||
if (!($options['result'] ?? false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If you're cleaning com_content and you have passed no event name I will use onContentCleanCache.
|
||||
*/
|
||||
if ($group === 'com_content')
|
||||
{
|
||||
$cacheCleaningEvent = $event ?: 'onContentCleanCache';
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Joomla's cache cleaning plugin event (e.g. onContentCleanCache) as well.
|
||||
*
|
||||
* @see BaseDatabaseModel::cleanCache()
|
||||
*/
|
||||
if (empty($cacheCleaningEvent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$app->triggerEvent($cacheCleaningEvent, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean a cache group on Joomla 3
|
||||
*
|
||||
* @param string $group The cache to clean, e.g. com_content
|
||||
* @param int $client_id The application ID for which the cache will be cleaned
|
||||
* @param CMSApplication $app The current CMS application
|
||||
*
|
||||
* @return array Cache controller options, including cleaning result
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function clearCacheGroupJoomla3(string $group, int $client_id, CMSApplication $app): array
|
||||
{
|
||||
$options = [
|
||||
'defaultgroup' => $group,
|
||||
'cachebase' => ($client_id) ? JPATH_ADMINISTRATOR . '/cache' : $app->get('cache_path', JPATH_SITE . '/cache'),
|
||||
'result' => true,
|
||||
];
|
||||
|
||||
try
|
||||
{
|
||||
$cache = Cache::getInstance('callback', $options);
|
||||
/** @noinspection PhpUndefinedMethodInspection Available via __call(), not tagged in Joomla core */
|
||||
$cache->clean();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$options['result'] = false;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean a cache group on Joomla 4
|
||||
*
|
||||
* @param string $group The cache to clean, e.g. com_content
|
||||
* @param int $client_id The application ID for which the cache will be cleaned
|
||||
* @param CMSApplication $app The current CMS application
|
||||
*
|
||||
* @return array Cache controller options, including cleaning result
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function clearCacheGroupJoomla4(string $group, int $client_id, CMSApplication $app): array
|
||||
{
|
||||
// Get the default cache folder. Start by using the JPATH_CACHE constant.
|
||||
$cacheBaseDefault = JPATH_CACHE;
|
||||
|
||||
// -- If we are asked to clean cache on the other side of the application we need to find a new cache base
|
||||
if ($client_id != $app->getClientId())
|
||||
{
|
||||
$cacheBaseDefault = (($client_id) ? JPATH_ADMINISTRATOR : JPATH_SITE) . '/cache';
|
||||
}
|
||||
|
||||
// Get the cache controller's options
|
||||
$options = [
|
||||
'defaultgroup' => $group,
|
||||
'cachebase' => $app->get('cache_path', $cacheBaseDefault),
|
||||
'result' => true,
|
||||
];
|
||||
|
||||
try
|
||||
{
|
||||
/** @var CallbackController $cache */
|
||||
$cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController('callback', $options);
|
||||
$cache->clean();
|
||||
}
|
||||
catch (CacheExceptionInterface $exception)
|
||||
{
|
||||
$options['result'] = false;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
164
libraries/fof30/Utils/CliSessionHandler.php
Normal file
164
libraries/fof30/Utils/CliSessionHandler.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\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Encrypt\Randval;
|
||||
use JSessionHandlerInterface;
|
||||
use RuntimeException;
|
||||
|
||||
class CliSessionHandler implements JSessionHandlerInterface
|
||||
{
|
||||
private $id;
|
||||
|
||||
private $name = 'clisession';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->makeId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the session.
|
||||
*
|
||||
* @return boolean True if started.
|
||||
*
|
||||
* @throws RuntimeException If something goes wrong starting the session.
|
||||
* @since 3.4.8
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the session is started.
|
||||
*
|
||||
* @return boolean True if started, false otherwise.
|
||||
*
|
||||
* @since 3.4.8
|
||||
*/
|
||||
public function isStarted()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the session ID
|
||||
*
|
||||
* @return string The session ID
|
||||
*
|
||||
* @since 3.4.8
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session ID
|
||||
*
|
||||
* @param string $id The session ID
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.4.8
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the session name
|
||||
*
|
||||
* @return mixed The session name.
|
||||
*
|
||||
* @since 3.4.8
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session name
|
||||
*
|
||||
* @param string $name The name of the session
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.4.8
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerates ID that represents this storage.
|
||||
*
|
||||
* Note regenerate+destroy should not clear the session data in memory only delete the session data from persistent
|
||||
* storage.
|
||||
*
|
||||
* @param boolean $destroy Destroy session when regenerating?
|
||||
* @param integer $lifetime Sets the cookie lifetime for the session cookie. A null value will leave the system
|
||||
* settings unchanged,
|
||||
* 0 sets the cookie to expire with browser session. Time is in seconds, and is not a
|
||||
* Unix timestamp.
|
||||
*
|
||||
* @return boolean True if session regenerated, false if error
|
||||
*
|
||||
* @since 3.4.8
|
||||
*/
|
||||
public function regenerate($destroy = false, $lifetime = null)
|
||||
{
|
||||
$this->makeId();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the session to be saved and closed.
|
||||
*
|
||||
* This method must invoke session_write_close() unless this interface is used for a storage object design for unit
|
||||
* or functional testing where a real PHP session would interfere with testing, in which case it should actually
|
||||
* persist the session data if required.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws RuntimeException If the session is saved without being started, or if the session is already closed.
|
||||
* @since 3.4.8
|
||||
* @see session_write_close()
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
// No operation. This is a CLI session, we save nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all session data in memory.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.4.8
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->makeId();
|
||||
}
|
||||
|
||||
private function makeId()
|
||||
{
|
||||
$phpfunc = new Phpfunc();
|
||||
$rand = new Randval($phpfunc);
|
||||
|
||||
$this->id = md5($rand->generate(32));
|
||||
}
|
||||
}
|
||||
763
libraries/fof30/Utils/Collection.php
Normal file
763
libraries/fof30/Utils/Collection.php
Normal file
@ -0,0 +1,763 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use ArrayAccess;
|
||||
use ArrayIterator;
|
||||
use CachingIterator;
|
||||
use Closure;
|
||||
use Countable;
|
||||
use IteratorAggregate;
|
||||
use JsonSerializable;
|
||||
|
||||
class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
|
||||
{
|
||||
/**
|
||||
* The items contained in the collection.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $items = [];
|
||||
|
||||
/**
|
||||
* Create a new collection.
|
||||
*
|
||||
* @param array $items
|
||||
*/
|
||||
public function __construct(array $items = [])
|
||||
{
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new collection instance if the value isn't one already.
|
||||
*
|
||||
* @param mixed $items
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function make($items)
|
||||
{
|
||||
if (is_null($items))
|
||||
{
|
||||
return new static;
|
||||
}
|
||||
|
||||
if ($items instanceof Collection)
|
||||
{
|
||||
return $items;
|
||||
}
|
||||
|
||||
return new static(is_array($items) ? $items : [$items]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the items in the collection.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the collection items into a single array.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function collapse()
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($this->items as $values)
|
||||
{
|
||||
$results = array_merge($results, $values);
|
||||
}
|
||||
|
||||
return new static($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff the collection with the given items.
|
||||
*
|
||||
* @param Collection|array $items
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function diff($items)
|
||||
{
|
||||
return new static(array_diff($this->items, $this->getArrayableItems($items)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback over each item.
|
||||
*
|
||||
* @param Closure $callback
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function each(Closure $callback)
|
||||
{
|
||||
array_map($callback, $this->items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a nested element of the collection.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function fetch($key)
|
||||
{
|
||||
return new static(array_fetch($this->items, $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a filter over each of the items.
|
||||
*
|
||||
* @param Closure $callback
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function filter(Closure $callback)
|
||||
{
|
||||
return new static(array_filter($this->items, $callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first item from the collection.
|
||||
*
|
||||
* @param Closure $callback
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function first(Closure $callback = null, $default = null)
|
||||
{
|
||||
if (is_null($callback))
|
||||
{
|
||||
return count($this->items) > 0 ? reset($this->items) : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return array_first($this->items, $callback, $default);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a flattened array of the items in the collection.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function flatten()
|
||||
{
|
||||
return new static(array_flatten($this->items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the collection by key.
|
||||
*
|
||||
* @param mixed $key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function forget($key)
|
||||
{
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item from the collection by key.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
if (array_key_exists($key, $this->items))
|
||||
{
|
||||
return $this->items[$key];
|
||||
}
|
||||
|
||||
return value($default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Group an associative array by a field or Closure value.
|
||||
*
|
||||
* @param callable|string $groupBy
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function groupBy($groupBy)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($this->items as $key => $value)
|
||||
{
|
||||
$key = is_callable($groupBy) ? $groupBy($value, $key) : array_get($value, $groupBy);
|
||||
|
||||
$results[$key][] = $value;
|
||||
}
|
||||
|
||||
return new static($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an item exists in the collection by key.
|
||||
*
|
||||
* @param mixed $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has($key)
|
||||
{
|
||||
return array_key_exists($key, $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate values of a given key as a string.
|
||||
*
|
||||
* @param string $value
|
||||
* @param string $glue
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function implode($value, $glue = null)
|
||||
{
|
||||
if (is_null($glue))
|
||||
{
|
||||
return implode($this->lists($value));
|
||||
}
|
||||
|
||||
return implode($glue, $this->lists($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersect the collection with the given items.
|
||||
*
|
||||
* @param Collection|array $items
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function intersect($items)
|
||||
{
|
||||
return new static(array_intersect($this->items, $this->getArrayableItems($items)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the collection is empty or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return empty($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last item from the collection.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function last()
|
||||
{
|
||||
return count($this->items) > 0 ? end($this->items) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array with the values of a given key.
|
||||
*
|
||||
* @param string $value
|
||||
* @param string $key
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function lists($value, $key = null)
|
||||
{
|
||||
return array_pluck($this->items, $value, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a map over each of the items.
|
||||
*
|
||||
* @param Closure $callback
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function map(Closure $callback)
|
||||
{
|
||||
return new static(array_map($callback, $this->items, array_keys($this->items)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the collection with the given items.
|
||||
*
|
||||
* @param Collection|array $items
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function merge($items)
|
||||
{
|
||||
return new static(array_merge($this->items, $this->getArrayableItems($items)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and remove the last item from the collection.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function pop()
|
||||
{
|
||||
return array_pop($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an item onto the beginning of the collection.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepend($value)
|
||||
{
|
||||
array_unshift($this->items, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an item onto the end of the collection.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function push($value)
|
||||
{
|
||||
$this->items[] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an item in the collection by key.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function put($key, $value)
|
||||
{
|
||||
$this->items[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the collection to a single value.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param mixed $initial
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function reduce($callback, $initial = null)
|
||||
{
|
||||
return array_reduce($this->items, $callback, $initial);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one or more items randomly from the collection.
|
||||
*
|
||||
* @param int $amount
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function random($amount = 1)
|
||||
{
|
||||
$keys = array_rand($this->items, $amount);
|
||||
|
||||
return is_array($keys) ? array_intersect_key($this->items, array_flip($keys)) : $this->items[$keys];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse items order.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function reverse()
|
||||
{
|
||||
return new static(array_reverse($this->items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and remove the first item from the collection.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function shift()
|
||||
{
|
||||
return array_shift($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Slice the underlying collection array.
|
||||
*
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @param bool $preserveKeys
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function slice($offset, $length = null, $preserveKeys = false)
|
||||
{
|
||||
return new static(array_slice($this->items, $offset, $length, $preserveKeys));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort through each item with a callback.
|
||||
*
|
||||
* @param Closure $callback
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function sort(Closure $callback)
|
||||
{
|
||||
uasort($this->items, $callback);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the collection using the given Closure.
|
||||
*
|
||||
* @param Closure|string $callback
|
||||
* @param int $options
|
||||
* @param bool $descending
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
if (is_string($callback))
|
||||
{
|
||||
$callback =
|
||||
$this->valueRetriever($callback);
|
||||
}
|
||||
|
||||
// First we will loop through the items and get the comparator from a callback
|
||||
// function which we were given. Then, we will sort the returned values and
|
||||
// and grab the corresponding values for the sorted keys from this array.
|
||||
foreach ($this->items as $key => $value)
|
||||
{
|
||||
$results[$key] = $callback($value);
|
||||
}
|
||||
|
||||
$descending ? arsort($results, $options)
|
||||
: asort($results, $options);
|
||||
|
||||
// Once we have sorted all of the keys in the array, we will loop through them
|
||||
// and grab the corresponding model so we can set the underlying items list
|
||||
// to the sorted version. Then we'll just return the collection instance.
|
||||
foreach (array_keys($results) as $key)
|
||||
{
|
||||
$results[$key] = $this->items[$key];
|
||||
}
|
||||
|
||||
$this->items = $results;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the collection in descending order using the given Closure.
|
||||
*
|
||||
* @param Closure|string $callback
|
||||
* @param int $options
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function sortByDesc($callback, $options = SORT_REGULAR)
|
||||
{
|
||||
return $this->sortBy($callback, $options, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splice portion of the underlying collection array.
|
||||
*
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @param mixed $replacement
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function splice($offset, $length = 0, $replacement = [])
|
||||
{
|
||||
return new static(array_splice($this->items, $offset, $length, $replacement));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sum of the given values.
|
||||
*
|
||||
* @param Closure|string $callback
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function sum($callback)
|
||||
{
|
||||
if (is_string($callback))
|
||||
{
|
||||
$callback = $this->valueRetriever($callback);
|
||||
}
|
||||
|
||||
return $this->reduce(function ($result, $item) use ($callback) {
|
||||
return $result += $callback($item);
|
||||
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the first or last {$limit} items.
|
||||
*
|
||||
* @param int $limit
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function take($limit = null)
|
||||
{
|
||||
if ($limit < 0)
|
||||
{
|
||||
return $this->slice($limit, abs($limit));
|
||||
}
|
||||
|
||||
return $this->slice(0, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the Collection (removes all items)
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->items = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform each item in the collection using a callback.
|
||||
*
|
||||
* @param callable $callback
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function transform($callback)
|
||||
{
|
||||
$this->items = array_map($callback, $this->items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only unique items from the collection array.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function unique()
|
||||
{
|
||||
return new static(array_unique($this->items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the keys on the underlying array.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function values()
|
||||
{
|
||||
$this->items = array_values($this->items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection of items as a plain array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return array_map(function ($value) {
|
||||
return (is_object($value) && method_exists($value, 'toArray')) ? $value->toArray() : $value;
|
||||
|
||||
}, $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the object into something JSON serializable.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection of items as JSON.
|
||||
*
|
||||
* @param int $options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toJson($options = 0)
|
||||
{
|
||||
return json_encode($this->toArray(), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an iterator for the items.
|
||||
*
|
||||
* @return ArrayIterator
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CachingIterator instance.
|
||||
*
|
||||
* @param integer $flags Caching iterator flags
|
||||
*
|
||||
* @return CachingIterator
|
||||
*/
|
||||
public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING)
|
||||
{
|
||||
return new CachingIterator($this->getIterator(), $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of items in the collection.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an item exists at an offset.
|
||||
*
|
||||
* @param mixed $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($key)
|
||||
{
|
||||
return array_key_exists($key, $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item at a given offset.
|
||||
*
|
||||
* @param mixed $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($key)
|
||||
{
|
||||
return $this->items[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the item at a given offset.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($key, $value)
|
||||
{
|
||||
if (is_null($key))
|
||||
{
|
||||
$this->items[] = $value;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->items[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the item at a given offset.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($key)
|
||||
{
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the collection to its string representation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value retrieving callback.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
protected function valueRetriever($value)
|
||||
{
|
||||
return function ($item) use ($value) {
|
||||
return is_object($item) ? $item->{$value} : array_get($item, $value);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Results array of items from Collection.
|
||||
*
|
||||
* @param Collection|array $items
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getArrayableItems($items)
|
||||
{
|
||||
if ($items instanceof Collection)
|
||||
{
|
||||
$items = $items->all();
|
||||
}
|
||||
elseif (is_object($items) && method_exists($items, 'toArray'))
|
||||
{
|
||||
$items = $items->toArray();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
}
|
||||
158
libraries/fof30/Utils/ComponentVersion.php
Normal file
158
libraries/fof30/Utils/ComponentVersion.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use Joomla\CMS\Factory;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* Retrieve the version of a component from the cached XML manifest or, if it's not present, the version recorded in the
|
||||
* database.
|
||||
*/
|
||||
abstract class ComponentVersion
|
||||
{
|
||||
/**
|
||||
* A cache with the version numbers of components
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @since 3.1.5
|
||||
*/
|
||||
private static $version = [];
|
||||
|
||||
/**
|
||||
* Get a component's version. The XML manifest on disk will be tried first. If it's not there or does not have a
|
||||
* version string the manifest cache in the database is tried. If that fails a fake version number will be returned.
|
||||
*
|
||||
* @param string $component The name of the component, e.g. com_foobar
|
||||
*
|
||||
* @return string The version string
|
||||
*
|
||||
* @since 3.1.5
|
||||
*/
|
||||
public static function getFor($component)
|
||||
{
|
||||
if (!isset(self::$version[$component]))
|
||||
{
|
||||
self::$version[$component] = null;
|
||||
}
|
||||
|
||||
if (is_null(self::$version[$component]))
|
||||
{
|
||||
self::$version[$component] = self::getVersionFromManifest($component);
|
||||
}
|
||||
|
||||
if (is_null(self::$version[$component]))
|
||||
{
|
||||
self::$version[$component] = self::getVersionFromDatabase($component);
|
||||
}
|
||||
|
||||
if (is_null(self::$version[$component]))
|
||||
{
|
||||
self::$version[$component] = 'dev-' . str_replace(' ', '_', microtime(false));
|
||||
}
|
||||
|
||||
return self::$version[$component];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a component's version from the manifest cache in the database
|
||||
*
|
||||
* @param string $component The component's name
|
||||
*
|
||||
* @return string The component version or null if none is defined
|
||||
*
|
||||
* @since 3.1.5
|
||||
*/
|
||||
private static function getVersionFromDatabase($component)
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->qn('manifest_cache'))
|
||||
->from($db->qn('#__extensions'))
|
||||
->where($db->qn('element') . ' = ' . $db->q($component))
|
||||
->where($db->qn('type') . ' = ' . $db->q('component'));
|
||||
|
||||
try
|
||||
{
|
||||
$json = $db->setQuery($query)->loadResult();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (empty($json))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$options = json_decode($json, true);
|
||||
|
||||
if (empty($options))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isset($options['version']))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $options['version'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a component's version from the manifest file on disk. IMPORTANT! The manifest for com_something must be named
|
||||
* something.xml.
|
||||
*
|
||||
* @param string $component The component's name
|
||||
*
|
||||
* @return string The component version or null if none is defined
|
||||
*
|
||||
* @since 1.2.0
|
||||
*/
|
||||
private static function getVersionFromManifest($component)
|
||||
{
|
||||
$bareComponent = str_replace('com_', '', $component);
|
||||
$file = JPATH_ADMINISTRATOR . '/components/' . $component . '/' . $bareComponent . '.xml';
|
||||
|
||||
if (!is_file($file) || !is_readable($file))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = @file_get_contents($file);
|
||||
|
||||
if (empty($data))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$xml = new SimpleXMLElement($data, LIBXML_COMPACT | LIBXML_NONET | LIBXML_ERR_NONE);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$versionNode = $xml->xpath('/extension/version');
|
||||
|
||||
if (empty($versionNode))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (string) ($versionNode[0]);
|
||||
}
|
||||
}
|
||||
199
libraries/fof30/Utils/DynamicGroups.php
Normal file
199
libraries/fof30/Utils/DynamicGroups.php
Normal file
@ -0,0 +1,199 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Container\Container;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionObject;
|
||||
|
||||
/**
|
||||
* Dynamic user to user group assignment.
|
||||
*
|
||||
* This class allows you to add / remove the currently logged in user to a user group without writing the information to
|
||||
* the database. This is useful when you want to allow core and third party code to allow or prohibit display of
|
||||
* information and / or taking actions based on a condition controlled in your code.
|
||||
*/
|
||||
class DynamicGroups
|
||||
{
|
||||
/**
|
||||
* Add the current user to a user group just for this page load.
|
||||
*
|
||||
* @param int $groupID The group ID to add the current user into.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function addGroup($groupID)
|
||||
{
|
||||
self::addRemoveGroup($groupID, true);
|
||||
self::cleanUpUserObjectCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the current user from a user group just for this page load.
|
||||
*
|
||||
* @param int $groupID The group ID to remove the current user from.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function removeGroup($groupID)
|
||||
{
|
||||
self::addRemoveGroup($groupID, false);
|
||||
self::cleanUpUserObjectCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to add or remove the current user from a user group just for this page load.
|
||||
*
|
||||
* @param int $groupID The group ID to add / remove the current user from.
|
||||
* @param bool $add Add (true) or remove (false) the user?
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function addRemoveGroup($groupID, $add)
|
||||
{
|
||||
// Get a fake container (we need it for its platform interface)
|
||||
$container = Container::getInstance('com_FOOBAR');
|
||||
|
||||
/**
|
||||
* Make sure that Joomla has retrieved the user's groups from the database.
|
||||
*
|
||||
* By going through the User object's getAuthorisedGroups we force Joomla to go through Access::getGroupsByUser
|
||||
* which retrieves the information from the database and caches it into the Access helper class.
|
||||
*/
|
||||
$container->platform->getUser()->getAuthorisedGroups();
|
||||
$container->platform->getUser($container->platform->getUser()->id)->getAuthorisedGroups();
|
||||
|
||||
/**
|
||||
* Now we can get a Reflection object into Joomla's Access helper class and manipulate its groupsByUser cache.
|
||||
*/
|
||||
$className = class_exists('Joomla\\CMS\\Access\\Access') ? 'Joomla\\CMS\\Access\\Access' : 'JAccess';
|
||||
|
||||
try
|
||||
{
|
||||
$reflectedAccess = new ReflectionClass($className);
|
||||
}
|
||||
catch (ReflectionException $e)
|
||||
{
|
||||
// This should never happen!
|
||||
$container->platform->logDebug('Cannot locate the Joomla\\CMS\\Access\\Access or JAccess class. Is your Joomla installation broken or too old / too new?');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$groupsByUser = $reflectedAccess->getProperty('groupsByUser');
|
||||
$groupsByUser->setAccessible(true);
|
||||
$rawGroupsByUser = $groupsByUser->getValue();
|
||||
|
||||
/**
|
||||
* Next up, we need to manipulate the keys of the cache which contain user to user group assignments.
|
||||
*
|
||||
* $rawGroupsByUser (JAccess::$groupsByUser) stored the group ownership as userID:recursive e.g. 0:1 for the
|
||||
* default user, recursive. We need to deal with four keys: 0:1, 0:0, myID:1 and myID:0
|
||||
*/
|
||||
$user = $container->platform->getUser();
|
||||
$keys = ['0:1', '0:0', $user->id . ':1', $user->id . ':0'];
|
||||
|
||||
foreach ($keys as $key)
|
||||
{
|
||||
if (!array_key_exists($key, $rawGroupsByUser))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$groups = $rawGroupsByUser[$key];
|
||||
|
||||
if ($add)
|
||||
{
|
||||
if (in_array($groupID, $groups))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$groups[] = $groupID;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!in_array($groupID, $groups))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$removeKey = array_search($groupID, $groups);
|
||||
unset($groups[$removeKey]);
|
||||
}
|
||||
|
||||
$rawGroupsByUser[$key] = $groups;
|
||||
}
|
||||
|
||||
// We can commit our changes back to the cache property and make it publicly inaccessible again.
|
||||
$groupsByUser->setValue(null, $rawGroupsByUser);
|
||||
$groupsByUser->setAccessible(false);
|
||||
|
||||
/**
|
||||
* We are not done. Caching user groups is only one aspect of Joomla access management. Joomla also caches the
|
||||
* identities, i.e. the user group assignment per user, in a different cache. We need to reset it to for our
|
||||
* user.
|
||||
*
|
||||
* Do note that we CAN NOT use clearStatics since that also clears the user group assignment which we assigned
|
||||
* dynamically. Therefore calling it would destroy our work so far.
|
||||
*/
|
||||
$refProperty = $reflectedAccess->getProperty('identities');
|
||||
$refProperty->setAccessible(true);
|
||||
$identities = $refProperty->getValue();
|
||||
|
||||
$keys = [$user->id, 0];
|
||||
|
||||
foreach ($keys as $key)
|
||||
{
|
||||
if (!array_key_exists($key, $identities))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($identities[$key]);
|
||||
}
|
||||
|
||||
$refProperty->setValue(null, $identities);
|
||||
$refProperty->setAccessible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the current user's authenticated groups cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function cleanUpUserObjectCache()
|
||||
{
|
||||
// Get a fake container (we need it for its platform interface)
|
||||
$container = Container::getInstance('com_FOOBAR');
|
||||
|
||||
$user = $container->platform->getUser();
|
||||
$reflectedUser = new ReflectionObject($user);
|
||||
|
||||
// Clear the user group cache
|
||||
$refProperty = $reflectedUser->getProperty('_authGroups');
|
||||
$refProperty->setAccessible(true);
|
||||
$refProperty->setValue($user, []);
|
||||
$refProperty->setAccessible(false);
|
||||
|
||||
// Clear the view access level cache
|
||||
$refProperty = $reflectedUser->getProperty('_authLevels');
|
||||
$refProperty->setAccessible(true);
|
||||
$refProperty->setValue($user, []);
|
||||
$refProperty->setAccessible(false);
|
||||
|
||||
// Clear the authenticated actions cache. I haven't seen it used anywhere but it's there, so...
|
||||
$refProperty = $reflectedUser->getProperty('_authActions');
|
||||
$refProperty->setAccessible(true);
|
||||
$refProperty->setValue($user, []);
|
||||
$refProperty->setAccessible(false);
|
||||
}
|
||||
}
|
||||
915
libraries/fof30/Utils/FEFHelper/BrowseView.php
Normal file
915
libraries/fof30/Utils/FEFHelper/BrowseView.php
Normal file
@ -0,0 +1,915 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils\FEFHelper;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Model\DataModel;
|
||||
use FOF30\Utils\ArrayHelper;
|
||||
use FOF30\Utils\SelectOptions;
|
||||
use FOF30\View\DataView\DataViewInterface;
|
||||
use FOF30\View\View;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* An HTML helper for Browse views.
|
||||
*
|
||||
* It reintroduces a FEF-friendly of some of the functionality found in FOF 3's Header and Field classes. These
|
||||
* helpers are also accessible through Blade, making the transition from XML forms to Blade templates easier.
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
abstract class BrowseView
|
||||
{
|
||||
/**
|
||||
* Caches the results of getOptionsFromModel keyed by a hash. The hash is computed by the model
|
||||
* name, the model state and the options passed to getOptionsFromModel.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $cacheModelOptions = [];
|
||||
|
||||
/**
|
||||
* Get the translation key for a field's label
|
||||
*
|
||||
* @param string $fieldName The field name
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function fieldLabelKey($fieldName)
|
||||
{
|
||||
$view = self::getViewFromBacktrace();
|
||||
|
||||
try
|
||||
{
|
||||
$inflector = $view->getContainer()->inflector;
|
||||
$viewName = $inflector->singularize($view->getName());
|
||||
$altViewName = $inflector->pluralize($view->getName());
|
||||
$componentName = $view->getContainer()->componentName;
|
||||
|
||||
$keys = [
|
||||
strtoupper($componentName . '_' . $viewName . '_FIELD_' . $fieldName),
|
||||
strtoupper($componentName . '_' . $altViewName . '_FIELD_' . $fieldName),
|
||||
strtoupper($componentName . '_' . $viewName . '_' . $fieldName),
|
||||
strtoupper($componentName . '_' . $altViewName . '_' . $fieldName),
|
||||
];
|
||||
|
||||
foreach ($keys as $key)
|
||||
{
|
||||
if (Text::_($key) != $key)
|
||||
{
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $keys[0];
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return ucfirst($fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label for a field (translated)
|
||||
*
|
||||
* @param string $fieldName The field name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function fieldLabel($fieldName)
|
||||
{
|
||||
return Text::_(self::fieldLabelKey($fieldName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a table field header which sorts the table by that field upon clicking
|
||||
*
|
||||
* @param string $field The name of the field
|
||||
* @param string|null $langKey (optional) The language key for the header to be displayed
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function sortgrid($field, $langKey = null)
|
||||
{
|
||||
/** @var DataViewInterface $view */
|
||||
$view = self::getViewFromBacktrace();
|
||||
|
||||
if (is_null($langKey))
|
||||
{
|
||||
$langKey = self::fieldLabelKey($field);
|
||||
}
|
||||
|
||||
return HTMLHelper::_('FEFHelper.browse.sort', $langKey, $field, $view->getLists()->order_Dir, $view->getLists()->order, $view->getTask());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a browse view filter from values returned by a model
|
||||
*
|
||||
* @param string $localField Field name
|
||||
* @param string $modelTitleField Foreign model field for drop-down display values
|
||||
* @param null $modelName Foreign model name
|
||||
* @param string $placeholder Placeholder for no selection
|
||||
* @param array $params Generic select display parameters
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function modelFilter($localField, $modelTitleField = 'title', $modelName = null, $placeholder = null, array $params = [])
|
||||
{
|
||||
/** @var DataModel $model */
|
||||
$model = self::getViewFromBacktrace()->getModel();
|
||||
|
||||
if (empty($modelName))
|
||||
{
|
||||
$modelName = $model->getForeignModelNameFor($localField);
|
||||
}
|
||||
|
||||
if (is_null($placeholder))
|
||||
{
|
||||
$placeholder = self::fieldLabelKey($localField);
|
||||
}
|
||||
|
||||
$params = array_merge([
|
||||
'list.none' => '— ' . Text::_($placeholder) . ' —',
|
||||
'value_field' => $modelTitleField,
|
||||
'fof.autosubmit' => true,
|
||||
], $params);
|
||||
|
||||
return self::modelSelect($localField, $modelName, $model->getState($localField), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a text filter (search box)
|
||||
*
|
||||
* @param string $localField The name of the model field. Used when getting the filter state.
|
||||
* @param string $searchField The INPUT element's name. Default: "filter_$localField".
|
||||
* @param string $placeholder The JText language key for the placeholder. Default: extrapolate from $localField.
|
||||
* @param array $attributes HTML attributes for the INPUT element.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function searchFilter($localField, $searchField = null, $placeholder = null, array $attributes = [])
|
||||
{
|
||||
/** @var DataModel $model */
|
||||
$view = self::getViewFromBacktrace();
|
||||
$model = $view->getModel();
|
||||
$searchField = empty($searchField) ? $localField : $searchField;
|
||||
$placeholder = empty($placeholder) ? self::fieldLabelKey($localField) : $placeholder;
|
||||
$attributes['type'] = $attributes['type'] ?? 'text';
|
||||
$attributes['name'] = $searchField;
|
||||
$attributes['id'] = !isset($attributes['id']) ? "filter_$localField" : $attributes['id'];
|
||||
$attributes['onchange'] = !isset($attributes['onchange']) ? 'document.adminForm.submit()' : null;
|
||||
$attributes['placeholder'] = !isset($attributes['placeholder']) ? $view->escape(Text::_($placeholder)) : $attributes['placeholder'];
|
||||
$attributes['title'] = $attributes['title'] ?? $attributes['placeholder'];
|
||||
$attributes['value'] = $view->escape($model->getState($localField));
|
||||
|
||||
// Remove null attributes and collapse into a string
|
||||
$attributes = array_filter($attributes, function ($v) {
|
||||
return !is_null($v);
|
||||
});
|
||||
$attributes = ArrayHelper::toString($attributes);
|
||||
|
||||
return "<input $attributes />";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a browse view filter with dropdown values
|
||||
*
|
||||
* @param string $localField Field name
|
||||
* @param array $options The JHtml options list to use
|
||||
* @param string $placeholder Placeholder for no selection
|
||||
* @param array $params Generic select display parameters
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function selectFilter($localField, array $options, $placeholder = null, array $params = [])
|
||||
{
|
||||
/** @var DataModel $model */
|
||||
$model = self::getViewFromBacktrace()->getModel();
|
||||
|
||||
if (is_null($placeholder))
|
||||
{
|
||||
$placeholder = self::fieldLabelKey($localField);
|
||||
}
|
||||
|
||||
$params = array_merge([
|
||||
'list.none' => '— ' . Text::_($placeholder) . ' —',
|
||||
'fof.autosubmit' => true,
|
||||
], $params);
|
||||
|
||||
return self::genericSelect($localField, $options, $model->getState($localField), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* View access dropdown filter
|
||||
*
|
||||
* @param string $localField Field name
|
||||
* @param string $placeholder Placeholder for no selection
|
||||
* @param array $params Generic select display parameters
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function accessFilter($localField, $placeholder = null, array $params = [])
|
||||
{
|
||||
return self::selectFilter($localField, SelectOptions::getOptions('access', $params), $placeholder, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Published state dropdown filter
|
||||
*
|
||||
* @param string $localField Field name
|
||||
* @param string $placeholder Placeholder for no selection
|
||||
* @param array $params Generic select display parameters
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function publishedFilter($localField, $placeholder = null, array $params = [])
|
||||
{
|
||||
return self::selectFilter($localField, SelectOptions::getOptions('published', $params), $placeholder, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a select box from the values returned by a model
|
||||
*
|
||||
* @param string $name Field name
|
||||
* @param string $modelName The name of the model, e.g. "items" or "com_foobar.items"
|
||||
* @param string $currentValue The currently selected value
|
||||
* @param array $params Passed to optionsFromModel and genericSelect
|
||||
* @param array $modelState Optional state variables to pass to the model
|
||||
* @param array $options Any JHtml select options you want to add in front of the model's returned values
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @see self::getOptionsFromModel
|
||||
* @see self::getOptionsFromSource
|
||||
* @see self::genericSelect
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function modelSelect($name, $modelName, $currentValue, array $params = [], array $modelState = [], array $options = [])
|
||||
{
|
||||
$params = array_merge([
|
||||
'fof.autosubmit' => true,
|
||||
], $params);
|
||||
|
||||
$options = self::getOptionsFromModel($modelName, $params, $modelState, $options);
|
||||
|
||||
return self::genericSelect($name, $options, $currentValue, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a (human readable) title from a (typically numeric, foreign key) key value using the data
|
||||
* returned by a DataModel.
|
||||
*
|
||||
* @param string $value The key value
|
||||
* @param string $modelName The name of the model, e.g. "items" or "com_foobar.items"
|
||||
* @param array $params Passed to getOptionsFromModel
|
||||
* @param array $modelState Optional state variables to pass to the model
|
||||
* @param array $options Any JHtml select options you want to add in front of the model's returned values
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @see self::getOptionsFromModel
|
||||
* @see self::getOptionsFromSource
|
||||
* @see self::genericSelect
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function modelOptionName($value, $modelName = null, array $params = [], array $modelState = [], array $options = [])
|
||||
{
|
||||
if (!isset($params['cache']))
|
||||
{
|
||||
$params['cache'] = true;
|
||||
}
|
||||
|
||||
if (!isset($params['none_as_zero']))
|
||||
{
|
||||
$params['none_as_zero'] = true;
|
||||
}
|
||||
|
||||
$options = self::getOptionsFromModel($modelName, $params, $modelState, $options);
|
||||
|
||||
return self::getOptionName($value, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the active option's label given an array of JHtml options
|
||||
*
|
||||
* @param mixed $selected The currently selected value
|
||||
* @param array $data The JHtml options to parse
|
||||
* @param string $optKey Key name, default: value
|
||||
* @param string $optText Value name, default: text
|
||||
* @param bool $selectFirst Should I automatically select the first option? Default: true
|
||||
*
|
||||
* @return mixed The label of the currently selected option
|
||||
*/
|
||||
public static function getOptionName($selected, $data, $optKey = 'value', $optText = 'text', $selectFirst = true)
|
||||
{
|
||||
$ret = null;
|
||||
|
||||
foreach ($data as $elementKey => &$element)
|
||||
{
|
||||
if (is_array($element))
|
||||
{
|
||||
$key = $optKey === null ? $elementKey : $element[$optKey];
|
||||
$text = $element[$optText];
|
||||
}
|
||||
elseif (is_object($element))
|
||||
{
|
||||
$key = $optKey === null ? $elementKey : $element->$optKey;
|
||||
$text = $element->$optText;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a simple associative array
|
||||
$key = $elementKey;
|
||||
$text = $element;
|
||||
}
|
||||
|
||||
if (is_null($ret) && $selectFirst && ($selected == $key))
|
||||
{
|
||||
$ret = $text;
|
||||
}
|
||||
elseif ($selected == $key)
|
||||
{
|
||||
$ret = $text;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a generic select list based on a bunch of options. Option sources will be merged into the provided
|
||||
* options automatically.
|
||||
*
|
||||
* Parameters:
|
||||
* - format.depth The current indent depth.
|
||||
* - format.eol The end of line string, default is linefeed.
|
||||
* - format.indent The string to use for indentation, default is tab.
|
||||
* - groups If set, looks for keys with the value "<optgroup>" and synthesizes groups from them. Deprecated.
|
||||
* Default: true.
|
||||
* - list.select Either the value of one selected option or an array of selected options. Default: $currentValue.
|
||||
* - list.translate If true, text and labels are translated via JText::_(). Default is false.
|
||||
* - list.attr HTML element attributes (key/value array or string)
|
||||
* - list.none Placeholder for no selection (creates an option with an empty string key)
|
||||
* - option.id The property in each option array to use as the selection id attribute. Defaults: null.
|
||||
* - option.key The property in each option array to use as the Default: "value". If set to null, the index of the
|
||||
* option array is used.
|
||||
* - option.label The property in each option array to use as the selection label attribute. Default: null
|
||||
* - option.text The property in each option array to use as the displayed text. Default: "text". If set to null,
|
||||
* the option array is assumed to be a list of displayable scalars.
|
||||
* - option.attr The property in each option array to use for additional selection attributes. Defaults: null.
|
||||
* - option.disable: The property that will hold the disabled state. Defaults to "disable".
|
||||
* - fof.autosubmit Should I auto-submit the form on change? Default: true
|
||||
* - fof.formname Form to auto-submit. Default: adminForm
|
||||
* - class CSS class to apply
|
||||
* - size Size attribute for the input
|
||||
* - multiple Is this a multiple select? Default: false.
|
||||
* - required Is this a required field? Default: false.
|
||||
* - autofocus Should I focus this field automatically? Default: false
|
||||
* - disabled Is this a disabled field? Default: false
|
||||
* - readonly Render as a readonly field with hidden inputs? Overrides 'disabled'. Default: false
|
||||
* - onchange Custom onchange handler. Overrides fof.autosubmit. Default: NULL (use fof.autosubmit).
|
||||
*
|
||||
* @param $name
|
||||
* @param array $options
|
||||
* @param $currentValue
|
||||
* @param array $params
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function genericSelect($name, array $options, $currentValue, array $params = [])
|
||||
{
|
||||
$params = array_merge([
|
||||
'format.depth' => 0,
|
||||
'format.eol' => "\n",
|
||||
'format.indent' => "\t",
|
||||
'groups' => true,
|
||||
'list.select' => $currentValue,
|
||||
'list.translate' => false,
|
||||
'option.id' => null,
|
||||
'option.key' => 'value',
|
||||
'option.label' => null,
|
||||
'option.text' => 'text',
|
||||
'option.attr' => null,
|
||||
'option.disable' => 'disable',
|
||||
'list.attr' => '',
|
||||
'list.none' => '',
|
||||
'id' => null,
|
||||
'fof.autosubmit' => true,
|
||||
'fof.formname' => 'adminForm',
|
||||
'class' => '',
|
||||
'size' => '',
|
||||
'multiple' => false,
|
||||
'required' => false,
|
||||
'autofocus' => false,
|
||||
'disabled' => false,
|
||||
'onchange' => null,
|
||||
'readonly' => false,
|
||||
], $params);
|
||||
|
||||
$currentValue = $params['list.select'];
|
||||
|
||||
// If fof.autosubmit is enabled and onchange is not set we will add our own handler
|
||||
if ($params['fof.autosubmit'] && is_null($params['onchange']))
|
||||
{
|
||||
$formName = $params['fof.formname'] ?: 'adminForm';
|
||||
$params['onchange'] = "document.{$formName}.submit()";
|
||||
}
|
||||
|
||||
// Construct SELECT element's attributes
|
||||
$attr = '';
|
||||
$attr .= $params['class'] ? ' class="' . $params['class'] . '"' : '';
|
||||
$attr .= !empty($params['size']) ? ' size="' . $params['size'] . '"' : '';
|
||||
$attr .= $params['multiple'] ? ' multiple' : '';
|
||||
$attr .= $params['required'] ? ' required aria-required="true"' : '';
|
||||
$attr .= $params['autofocus'] ? ' autofocus' : '';
|
||||
$attr .= ($params['disabled'] || $params['readonly']) ? ' disabled="disabled"' : '';
|
||||
$attr .= $params['onchange'] ? ' onchange="' . $params['onchange'] . '"' : '';
|
||||
|
||||
// We use the constructed SELECT element's attributes only if no 'attr' key was provided
|
||||
if (empty($params['list.attr']))
|
||||
{
|
||||
$params['list.attr'] = $attr;
|
||||
}
|
||||
|
||||
// Merge the options with those fetched from a source (e.g. another Helper object)
|
||||
$options = array_merge($options, self::getOptionsFromSource($params));
|
||||
|
||||
if (!empty($params['list.none']))
|
||||
{
|
||||
array_unshift($options, HTMLHelper::_('FEFHelper.select.option', '', Text::_($params['list.none'])));
|
||||
}
|
||||
|
||||
$html = [];
|
||||
|
||||
// Create a read-only list (no name) with hidden input(s) to store the value(s).
|
||||
if ($params['readonly'])
|
||||
{
|
||||
$html[] = HTMLHelper::_('FEFHelper.select.genericlist', $options, $name, $params);
|
||||
|
||||
// E.g. form field type tag sends $this->value as array
|
||||
if ($params['multiple'] && is_array($currentValue))
|
||||
{
|
||||
if (!count($currentValue))
|
||||
{
|
||||
$currentValue[] = '';
|
||||
}
|
||||
|
||||
foreach ($currentValue as $value)
|
||||
{
|
||||
$html[] = '<input type="hidden" name="' . $name . '" value="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"/>';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$html[] = '<input type="hidden" name="' . $name . '" value="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"/>';
|
||||
}
|
||||
}
|
||||
else
|
||||
// Create a regular list.
|
||||
{
|
||||
$html[] = HTMLHelper::_('FEFHelper.select.genericlist', $options, $name, $params);
|
||||
}
|
||||
|
||||
return implode($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace tags that reference fields with their values
|
||||
*
|
||||
* @param string $text Text to process
|
||||
* @param DataModel $item The DataModel instance to get values from
|
||||
*
|
||||
* @return string Text with tags replace
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function parseFieldTags($text, DataModel $item)
|
||||
{
|
||||
$ret = $text;
|
||||
|
||||
if (empty($item))
|
||||
{
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace [ITEM:ID] in the URL with the item's key value (usually: the auto-incrementing numeric ID)
|
||||
*/
|
||||
$replace = $item->getId();
|
||||
$ret = str_replace('[ITEM:ID]', $replace, $ret);
|
||||
|
||||
// Replace the [ITEMID] in the URL with the current Itemid parameter
|
||||
$ret = str_replace('[ITEMID]', $item->getContainer()->input->getInt('Itemid', 0), $ret);
|
||||
|
||||
// Replace the [TOKEN] in the URL with the Joomla! form token
|
||||
$ret = str_replace('[TOKEN]', $item->getContainer()->platform->getToken(true), $ret);
|
||||
|
||||
// Replace other field variables in the URL
|
||||
$data = $item->getData();
|
||||
|
||||
foreach ($data as $field => $value)
|
||||
{
|
||||
// Skip non-processable values
|
||||
if (is_array($value) || is_object($value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$search = '[ITEM:' . strtoupper($field) . ']';
|
||||
$ret = str_replace($search, $value, $ret);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the FOF View from the backtrace of the static call. MAGIC!
|
||||
*
|
||||
* @return View
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function getViewFromBacktrace()
|
||||
{
|
||||
// In case we are on a braindead host
|
||||
if (!function_exists('debug_backtrace'))
|
||||
{
|
||||
throw new RuntimeException("Your host has disabled the <code>debug_backtrace</code> PHP function. Please ask them to re-enable it. It's required for running this software.");
|
||||
}
|
||||
|
||||
/**
|
||||
* For performance reasons I look into the last 4 call stack entries. If I don't find a container I
|
||||
* will expand my search by another 2 entries and so on until I either find a container or I stop
|
||||
* finding new call stack entries.
|
||||
*/
|
||||
$lastNumberOfEntries = 0;
|
||||
$limit = 4;
|
||||
$skip = 0;
|
||||
$container = null;
|
||||
|
||||
while (true)
|
||||
{
|
||||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit);
|
||||
|
||||
if (count($backtrace) == $lastNumberOfEntries)
|
||||
{
|
||||
throw new RuntimeException(__METHOD__ . ": Cannot retrieve FOF View from call stack. You are either calling me from a non-FEF extension or your PHP is broken.");
|
||||
}
|
||||
|
||||
$lastNumberOfEntries = count($backtrace);
|
||||
|
||||
if ($skip)
|
||||
{
|
||||
$backtrace = array_slice($backtrace, $skip);
|
||||
}
|
||||
|
||||
foreach ($backtrace as $bt)
|
||||
{
|
||||
if (!isset($bt['object']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($bt['object'] instanceof View)
|
||||
{
|
||||
return $bt['object'];
|
||||
}
|
||||
}
|
||||
|
||||
$skip = $limit;
|
||||
$limit += 2;
|
||||
}
|
||||
|
||||
throw new RuntimeException(__METHOD__ . ": Cannot retrieve FOF View from call stack. You are either calling me from a non-FEF extension or your PHP is broken.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JHtml options from an alternate source, e.g. a helper. This is useful for adding arbitrary options
|
||||
* which are either dynamic or you do not want to inline to your view, e.g. reusable options across
|
||||
* different views.
|
||||
*
|
||||
* The attribs can be:
|
||||
* source_file The file to load. You can use FOF's URIs such as 'admin:com_foobar/foo/bar'
|
||||
* source_class The class to use
|
||||
* source_method The static method to use on source_class
|
||||
* source_key Use * if you're returning a key/value array. Otherwise the array key for the key (ID)
|
||||
* value.
|
||||
* source_value Use * if you're returning a key/value array. Otherwise the array key for the displayed
|
||||
* value. source_translate Should I pass the value field through JText? Default: true source_format Set
|
||||
* to "optionsobject" if you're returning an array of JHtml options. Ignored otherwise.
|
||||
*
|
||||
* @param array $attribs
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
private static function getOptionsFromSource(array $attribs = [])
|
||||
{
|
||||
$options = [];
|
||||
|
||||
$container = self::getContainerFromBacktrace();
|
||||
|
||||
$attribs = array_merge([
|
||||
'source_file' => '',
|
||||
'source_class' => '',
|
||||
'source_method' => '',
|
||||
'source_key' => '*',
|
||||
'source_value' => '*',
|
||||
'source_translate' => true,
|
||||
'source_format' => '',
|
||||
], $attribs);
|
||||
|
||||
$source_file = $attribs['source_file'];
|
||||
$source_class = $attribs['source_class'];
|
||||
$source_method = $attribs['source_method'];
|
||||
$source_key = $attribs['source_key'];
|
||||
$source_value = $attribs['source_value'];
|
||||
$source_translate = $attribs['source_translate'];
|
||||
$source_format = $attribs['source_format'];
|
||||
|
||||
if ($source_class && $source_method)
|
||||
{
|
||||
// Maybe we have to load a file?
|
||||
if (!empty($source_file))
|
||||
{
|
||||
$source_file = $container->template->parsePath($source_file, true);
|
||||
|
||||
if ($container->filesystem->fileExists($source_file))
|
||||
{
|
||||
include $source_file;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the class exists
|
||||
if (class_exists($source_class, true))
|
||||
{
|
||||
// ...and so does the option
|
||||
if (in_array($source_method, get_class_methods($source_class)))
|
||||
{
|
||||
// Get the data from the class
|
||||
if ($source_format == 'optionsobject')
|
||||
{
|
||||
$options = array_merge($options, $source_class::$source_method());
|
||||
}
|
||||
else
|
||||
{
|
||||
$source_data = $source_class::$source_method();
|
||||
|
||||
// Loop through the data and prime the $options array
|
||||
foreach ($source_data as $k => $v)
|
||||
{
|
||||
$key = (empty($source_key) || ($source_key == '*')) ? $k : @$v[$source_key];
|
||||
$value = (empty($source_value) || ($source_value == '*')) ? $v : @$v[$source_value];
|
||||
|
||||
if ($source_translate)
|
||||
{
|
||||
$value = Text::_($value);
|
||||
}
|
||||
|
||||
$options[] = HTMLHelper::_('FEFHelper.select.option', $key, $value, 'value', 'text');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset($options);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JHtml options from the values returned by a model.
|
||||
*
|
||||
* The params can be:
|
||||
* key_field The model field used for the OPTION's key. Default: the model's ID field.
|
||||
* value_field The model field used for the OPTION's displayed value. You must provide it.
|
||||
* apply_access Should I apply Joomla ACLs to the model? Default: FALSE.
|
||||
* none Placeholder for no selection. Default: NULL (no placeholder).
|
||||
* none_as_zero When true, the 'none' placeholder applies to values '' **AND** '0' (empty string and zero)
|
||||
* translate Should I pass the values through JText? Default: TRUE.
|
||||
* with Array of relation names for eager loading.
|
||||
* cache Cache the results for faster reuse
|
||||
*
|
||||
* @param string $modelName The name of the model, e.g. "items" or "com_foobar.items"
|
||||
* @param array $params Parameters which define which options to get from the model
|
||||
* @param array $modelState Optional state variables to pass to the model
|
||||
* @param array $options Any JHtml select options you want to add in front of the model's returned values
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
private static function getOptionsFromModel($modelName, array $params = [], array $modelState = [], array $options = [])
|
||||
{
|
||||
// Let's find the FOF DI container from the call stack
|
||||
$container = self::getContainerFromBacktrace();
|
||||
|
||||
// Explode model name into component name and prefix
|
||||
$componentName = $container->componentName;
|
||||
$mName = $modelName;
|
||||
|
||||
if (strpos($modelName, '.') !== false)
|
||||
{
|
||||
[$componentName, $mName] = explode('.', $mName, 2);
|
||||
}
|
||||
|
||||
if ($componentName != $container->componentName)
|
||||
{
|
||||
$container = Container::getInstance($componentName);
|
||||
}
|
||||
|
||||
/** @var DataModel $model */
|
||||
$model = $container->factory->model($mName)->setIgnoreRequest(true)->savestate(false);
|
||||
|
||||
$defaultParams = [
|
||||
'key_field' => $model->getKeyName(),
|
||||
'value_field' => 'title',
|
||||
'apply_access' => false,
|
||||
'none' => null,
|
||||
'none_as_zero' => false,
|
||||
'translate' => true,
|
||||
'with' => [],
|
||||
];
|
||||
|
||||
$params = array_merge($defaultParams, $params);
|
||||
|
||||
$cache = isset($params['cache']) && $params['cache'];
|
||||
$cacheKey = null;
|
||||
|
||||
if ($cache)
|
||||
{
|
||||
$cacheKey = sha1(print_r([
|
||||
$model->getContainer()->componentName,
|
||||
$model->getName(),
|
||||
$params['key_field'],
|
||||
$params['value_field'],
|
||||
$params['apply_access'],
|
||||
$params['none'],
|
||||
$params['translate'],
|
||||
$params['with'],
|
||||
$modelState,
|
||||
], true));
|
||||
}
|
||||
|
||||
if ($cache && isset(self::$cacheModelOptions[$cacheKey]))
|
||||
{
|
||||
return self::$cacheModelOptions[$cacheKey];
|
||||
}
|
||||
|
||||
if (empty($params['none']) && !is_null($params['none']))
|
||||
{
|
||||
$langKey = strtoupper($model->getContainer()->componentName . '_TITLE_' . $model->getName());
|
||||
$placeholder = Text::_($langKey);
|
||||
|
||||
if ($langKey != $placeholder)
|
||||
{
|
||||
$params['none'] = '— ' . $placeholder . ' —';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($params['none']))
|
||||
{
|
||||
$options[] = HTMLHelper::_('FEFHelper.select.option', null, Text::_($params['none']));
|
||||
|
||||
if ($params['none_as_zero'])
|
||||
{
|
||||
$options[] = HTMLHelper::_('FEFHelper.select.option', 0, Text::_($params['none']));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($params['apply_access'])
|
||||
{
|
||||
$model->applyAccessFiltering();
|
||||
}
|
||||
|
||||
if (!is_null($params['with']))
|
||||
{
|
||||
$model->with($params['with']);
|
||||
}
|
||||
|
||||
// Set the model's state, if applicable
|
||||
foreach ($modelState as $stateKey => $stateValue)
|
||||
{
|
||||
$model->setState($stateKey, $stateValue);
|
||||
}
|
||||
|
||||
// Set the query and get the result list.
|
||||
$items = $model->get(true);
|
||||
|
||||
// Build the field options.
|
||||
if (!empty($items))
|
||||
{
|
||||
foreach ($items as $item)
|
||||
{
|
||||
$value = $item->{$params['value_field']};
|
||||
|
||||
if ($params['translate'])
|
||||
{
|
||||
$value = Text::_($value);
|
||||
}
|
||||
|
||||
$options[] = HTMLHelper::_('FEFHelper.select.option', $item->{$params['key_field']}, $value);
|
||||
}
|
||||
}
|
||||
|
||||
if ($cache)
|
||||
{
|
||||
self::$cacheModelOptions[$cacheKey] = $options;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the FOF DI container from the backtrace of the static call. MAGIC!
|
||||
*
|
||||
* @return Container
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
private static function getContainerFromBacktrace()
|
||||
{
|
||||
// In case we are on a braindead host
|
||||
if (!function_exists('debug_backtrace'))
|
||||
{
|
||||
throw new RuntimeException("Your host has disabled the <code>debug_backtrace</code> PHP function. Please ask them to re-enable it. It's required for running this software.");
|
||||
}
|
||||
|
||||
/**
|
||||
* For performance reasons I look into the last 4 call stack entries. If I don't find a container I
|
||||
* will expand my search by another 2 entries and so on until I either find a container or I stop
|
||||
* finding new call stack entries.
|
||||
*/
|
||||
$lastNumberOfEntries = 0;
|
||||
$limit = 4;
|
||||
$skip = 0;
|
||||
$container = null;
|
||||
|
||||
while (true)
|
||||
{
|
||||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit);
|
||||
|
||||
if (count($backtrace) == $lastNumberOfEntries)
|
||||
{
|
||||
throw new RuntimeException(__METHOD__ . ": Cannot retrieve FOF container from call stack. You are either calling me from a non-FEF extension or your PHP is broken.");
|
||||
}
|
||||
|
||||
$lastNumberOfEntries = count($backtrace);
|
||||
|
||||
if ($skip)
|
||||
{
|
||||
$backtrace = array_slice($backtrace, $skip);
|
||||
}
|
||||
|
||||
foreach ($backtrace as $bt)
|
||||
{
|
||||
if (!isset($bt['object']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!method_exists($bt['object'], 'getContainer'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return $bt['object']->getContainer();
|
||||
}
|
||||
|
||||
$skip = $limit;
|
||||
$limit += 2;
|
||||
}
|
||||
|
||||
throw new RuntimeException(__METHOD__ . ": Cannot retrieve FOF container from call stack. You are either calling me from a non-FEF extension or your PHP is broken.");
|
||||
}
|
||||
|
||||
}
|
||||
85
libraries/fof30/Utils/FEFHelper/Html.php
Normal file
85
libraries/fof30/Utils/FEFHelper/Html.php
Normal file
@ -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\Utils\FEFHelper;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\View\DataView\DataViewInterface;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Pagination\Pagination;
|
||||
|
||||
/**
|
||||
* Interim FEF helper which was used in FOF 3.2. This is deprecated. Please use the FEFHelper.browse JHtml helper
|
||||
* instead. The implementation of this class should be a good hint on how you can do that.
|
||||
*
|
||||
* @deprecated 4.0
|
||||
*/
|
||||
abstract class Html
|
||||
{
|
||||
/**
|
||||
* Helper function to create Javascript code required for table ordering
|
||||
*
|
||||
* @param string $order Current order
|
||||
*
|
||||
* @return string Javascript to add to the page
|
||||
*/
|
||||
public static function jsOrderingBackend($order)
|
||||
{
|
||||
return HTMLHelper::_('FEFHelper.browse.orderjs', $order, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the required HTML code for backend pagination and sorting
|
||||
*
|
||||
* @param Pagination $pagination Pagination object
|
||||
* @param array $sortFields Fields allowed to be sorted
|
||||
* @param string $order Ordering field
|
||||
* @param string $order_Dir Ordering direction (ASC, DESC)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function selectOrderingBackend($pagination, $sortFields, $order, $order_Dir)
|
||||
{
|
||||
if (is_null($sortFields))
|
||||
{
|
||||
$sortFields = [];
|
||||
}
|
||||
|
||||
if (is_string($sortFields))
|
||||
{
|
||||
$sortFields = [$sortFields];
|
||||
}
|
||||
|
||||
if (!is_array($sortFields))
|
||||
{
|
||||
$sortFields = [];
|
||||
}
|
||||
|
||||
return
|
||||
'<div class="akeeba-filter-bar akeeba-filter-bar--right">' .
|
||||
HTMLHelper::_('FEFHelper.browse.orderheader', null, $sortFields, $pagination, $order, $order_Dir) .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the drag'n'drop reordering field for Browse views
|
||||
*
|
||||
* @param DataViewInterface $view The DataView you're rendering against
|
||||
* @param string $orderingField The name of the field you're ordering by
|
||||
* @param string $order The order value of the current row
|
||||
* @param string $class CSS class for the ordering value INPUT field
|
||||
* @param string $icon CSS class for the d'n'd handle icon
|
||||
* @param string $inactiveIcon CSS class for the d'n'd disabled icon
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function dragDropReordering(DataViewInterface $view, $orderingField, $order, $class = 'input-sm', $icon = 'akion-drag', $inactiveIcon = 'akion-android-more-vertical')
|
||||
{
|
||||
return HTMLHelper::_('FEFHelper.browse.order', $orderingField, $order, $class, $icon, $inactiveIcon, $view);
|
||||
}
|
||||
}
|
||||
831
libraries/fof30/Utils/FEFHelper/browse.php
Normal file
831
libraries/fof30/Utils/FEFHelper/browse.php
Normal file
@ -0,0 +1,831 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Model\DataModel;
|
||||
use FOF30\Utils\ArrayHelper;
|
||||
use FOF30\Utils\FEFHelper\BrowseView;
|
||||
use FOF30\View\DataView\DataViewInterface;
|
||||
use FOF30\View\DataView\Html;
|
||||
use FOF30\View\DataView\Raw as DataViewRaw;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Pagination\Pagination;
|
||||
|
||||
/**
|
||||
* Custom JHtml (HTMLHelper) class. Offers browse view controls compatible with Akeeba Frontend
|
||||
* Framework (FEF).
|
||||
*
|
||||
* Call these methods as JHtml::_('FEFHelper.browse.methodName', $parameter1, $parameter2, ...)
|
||||
*/
|
||||
abstract class FEFHelperBrowse
|
||||
{
|
||||
/**
|
||||
* Returns an action button on the browse view's table
|
||||
*
|
||||
* @param integer $i The row index
|
||||
* @param string $task The task to fire when the button is clicked
|
||||
* @param string|array $prefix An optional task prefix or an array of options
|
||||
* @param string $active_title An optional active tooltip to display if $enable is true
|
||||
* @param string $inactive_title An optional inactive tooltip to display if $enable is true
|
||||
* @param boolean $tip An optional setting for tooltip
|
||||
* @param string $active_class An optional active HTML class
|
||||
* @param string $inactive_class An optional inactive HTML class
|
||||
* @param boolean $enabled An optional setting for access control on the action.
|
||||
* @param boolean $translate An optional setting for translation.
|
||||
* @param string $checkbox An optional prefix for checkboxes.
|
||||
*
|
||||
* @return string The HTML markup
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function action($i, $task, $prefix = '', $active_title = '', $inactive_title = '', $tip = false, $active_class = '',
|
||||
$inactive_class = '', $enabled = true, $translate = true, $checkbox = 'cb')
|
||||
{
|
||||
if (is_array($prefix))
|
||||
{
|
||||
$options = $prefix;
|
||||
$active_title = array_key_exists('active_title', $options) ? $options['active_title'] : $active_title;
|
||||
$inactive_title = array_key_exists('inactive_title', $options) ? $options['inactive_title'] : $inactive_title;
|
||||
$tip = array_key_exists('tip', $options) ? $options['tip'] : $tip;
|
||||
$active_class = array_key_exists('active_class', $options) ? $options['active_class'] : $active_class;
|
||||
$inactive_class = array_key_exists('inactive_class', $options) ? $options['inactive_class'] : $inactive_class;
|
||||
$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
|
||||
$translate = array_key_exists('translate', $options) ? $options['translate'] : $translate;
|
||||
$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
|
||||
$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
|
||||
}
|
||||
|
||||
if ($tip)
|
||||
{
|
||||
$title = $enabled ? $active_title : $inactive_title;
|
||||
$title = $translate ? Text::_($title) : $title;
|
||||
$title = HTMLHelper::_('tooltipText', $title, '', 0);
|
||||
}
|
||||
|
||||
$html = [];
|
||||
|
||||
if ($enabled)
|
||||
{
|
||||
$btnColor = 'grey';
|
||||
|
||||
if (substr($active_class, 0, 2) == '--')
|
||||
{
|
||||
[$btnColor, $active_class] = explode(' ', $active_class, 2);
|
||||
$btnColor = ltrim($btnColor, '-');
|
||||
}
|
||||
|
||||
$html[] = '<a class="akeeba-btn--' . $btnColor . '--mini ' . ($active_class === 'publish' ? ' active' : '') . ($tip ? ' hasTooltip' : '') . '"';
|
||||
$html[] = ' href="javascript:void(0);" onclick="return Joomla.listItemTask(\'' . $checkbox . $i . '\',\'' . $prefix . $task . '\')"';
|
||||
$html[] = $tip ? ' title="' . $title . '"' : '';
|
||||
$html[] = '>';
|
||||
$html[] = '<span class="akion-' . $active_class . '" aria-hidden="true"></span> ';
|
||||
$html[] = '</a>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$btnColor = 'grey';
|
||||
|
||||
if (substr($inactive_class, 0, 2) == '--')
|
||||
{
|
||||
[$btnColor, $inactive_class] = explode(' ', $inactive_class, 2);
|
||||
$btnColor = ltrim($btnColor, '-');
|
||||
}
|
||||
|
||||
$html[] = '<a class="akeeba-btn--' . $btnColor . '--mini disabled akeebagrid' . ($tip ? ' hasTooltip' : '') . '"';
|
||||
$html[] = $tip ? ' title="' . $title . '"' : '';
|
||||
$html[] = '>';
|
||||
|
||||
if ($active_class === 'protected')
|
||||
{
|
||||
$inactive_class = 'locked';
|
||||
}
|
||||
|
||||
$html[] = '<span class="akion-' . $inactive_class . '"></span> ';
|
||||
$html[] = '</a>';
|
||||
}
|
||||
|
||||
return implode($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a state change button on the browse view's table
|
||||
*
|
||||
* @param array $states array of value/state. Each state is an array of the form
|
||||
* (task, text, active title, inactive title, tip (boolean), HTML active class,
|
||||
* HTML inactive class) or ('task'=>task, 'text'=>text, 'active_title'=>active
|
||||
* title,
|
||||
* 'inactive_title'=>inactive title, 'tip'=>boolean, 'active_class'=>html active
|
||||
* class,
|
||||
* 'inactive_class'=>html inactive class)
|
||||
* @param integer $value The state value.
|
||||
* @param integer $i The row index
|
||||
* @param string|array $prefix An optional task prefix or an array of options
|
||||
* @param boolean $enabled An optional setting for access control on the action.
|
||||
* @param boolean $translate An optional setting for translation.
|
||||
* @param string $checkbox An optional prefix for checkboxes.
|
||||
*
|
||||
* @return string The HTML markup
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function state($states, $value, $i, $prefix = '', $enabled = true, $translate = true, $checkbox = 'cb')
|
||||
{
|
||||
if (is_array($prefix))
|
||||
{
|
||||
$options = $prefix;
|
||||
$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
|
||||
$translate = array_key_exists('translate', $options) ? $options['translate'] : $translate;
|
||||
$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
|
||||
$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
|
||||
}
|
||||
|
||||
$state = ArrayHelper::getValue($states, (int) $value, $states[0]);
|
||||
$task = array_key_exists('task', $state) ? $state['task'] : $state[0];
|
||||
$text = array_key_exists('text', $state) ? $state['text'] : (array_key_exists(1, $state) ? $state[1] : '');
|
||||
$active_title = array_key_exists('active_title', $state) ? $state['active_title'] : (array_key_exists(2, $state) ? $state[2] : '');
|
||||
$inactive_title = array_key_exists('inactive_title', $state) ? $state['inactive_title'] : (array_key_exists(3, $state) ? $state[3] : '');
|
||||
$tip = array_key_exists('tip', $state) ? $state['tip'] : (array_key_exists(4, $state) ? $state[4] : false);
|
||||
$active_class = array_key_exists('active_class', $state) ? $state['active_class'] : (array_key_exists(5, $state) ? $state[5] : '');
|
||||
$inactive_class = array_key_exists('inactive_class', $state) ? $state['inactive_class'] : (array_key_exists(6, $state) ? $state[6] : '');
|
||||
|
||||
return static::action(
|
||||
$i, $task, $prefix, $active_title, $inactive_title, $tip,
|
||||
$active_class, $inactive_class, $enabled, $translate, $checkbox
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a published state on the browse view's table
|
||||
*
|
||||
* @param integer $value The state value.
|
||||
* @param integer $i The row index
|
||||
* @param string|array $prefix An optional task prefix or an array of options
|
||||
* @param boolean $enabled An optional setting for access control on the action.
|
||||
* @param string $checkbox An optional prefix for checkboxes.
|
||||
* @param string $publish_up An optional start publishing date.
|
||||
* @param string $publish_down An optional finish publishing date.
|
||||
*
|
||||
* @return string The HTML markup
|
||||
*
|
||||
* @see self::state()
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function published($value, $i, $prefix = '', $enabled = true, $checkbox = 'cb', $publish_up = null, $publish_down = null)
|
||||
{
|
||||
if (is_array($prefix))
|
||||
{
|
||||
$options = $prefix;
|
||||
$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
|
||||
$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
|
||||
$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format:
|
||||
*
|
||||
* (task, text, active title, inactive title, tip (boolean), active icon class (without akion-), inactive icon class (without akion-))
|
||||
*/
|
||||
$states = [
|
||||
1 => [
|
||||
'unpublish', 'JPUBLISHED', 'JLIB_HTML_UNPUBLISH_ITEM', 'JPUBLISHED', true, '--green checkmark',
|
||||
'--green checkmark',
|
||||
],
|
||||
0 => [
|
||||
'publish', 'JUNPUBLISHED', 'JLIB_HTML_PUBLISH_ITEM', 'JUNPUBLISHED', true, '--red close', '--red close',
|
||||
],
|
||||
2 => [
|
||||
'unpublish', 'JARCHIVED', 'JLIB_HTML_UNPUBLISH_ITEM', 'JARCHIVED', true, '--orange ion-ios-box',
|
||||
'--orange ion-ios-box',
|
||||
],
|
||||
-2 => [
|
||||
'publish', 'JTRASHED', 'JLIB_HTML_PUBLISH_ITEM', 'JTRASHED', true, '--dark trash-a', '--dark trash-a',
|
||||
],
|
||||
];
|
||||
|
||||
// Special state for dates
|
||||
if ($publish_up || $publish_down)
|
||||
{
|
||||
$nullDate = Factory::getDbo()->getNullDate();
|
||||
$nowDate = Factory::getDate()->toUnix();
|
||||
|
||||
$tz = Factory::getUser()->getTimezone();
|
||||
|
||||
$publish_up = (!empty($publish_up) && ($publish_up != $nullDate)) ? Factory::getDate($publish_up, 'UTC')->setTimeZone($tz) : false;
|
||||
$publish_down = (!empty($publish_down) && ($publish_down != $nullDate)) ? Factory::getDate($publish_down, 'UTC')->setTimeZone($tz) : false;
|
||||
|
||||
// Create tip text, only we have publish up or down settings
|
||||
$tips = [];
|
||||
|
||||
if ($publish_up)
|
||||
{
|
||||
$tips[] = Text::sprintf('JLIB_HTML_PUBLISHED_START', HTMLHelper::_('date', $publish_up, Text::_('DATE_FORMAT_LC5'), 'UTC'));
|
||||
}
|
||||
|
||||
if ($publish_down)
|
||||
{
|
||||
$tips[] = Text::sprintf('JLIB_HTML_PUBLISHED_FINISHED', HTMLHelper::_('date', $publish_down, Text::_('DATE_FORMAT_LC5'), 'UTC'));
|
||||
}
|
||||
|
||||
$tip = empty($tips) ? false : implode('<br />', $tips);
|
||||
|
||||
// Add tips and special titles
|
||||
foreach ($states as $key => $state)
|
||||
{
|
||||
// Create special titles for published items
|
||||
if ($key == 1)
|
||||
{
|
||||
$states[$key][2] = $states[$key][3] = 'JLIB_HTML_PUBLISHED_ITEM';
|
||||
|
||||
if (!empty($publish_up) && ($publish_up != $nullDate) && $nowDate < $publish_up->toUnix())
|
||||
{
|
||||
$states[$key][2] = $states[$key][3] = 'JLIB_HTML_PUBLISHED_PENDING_ITEM';
|
||||
$states[$key][5] = $states[$key][6] = 'android-time';
|
||||
}
|
||||
|
||||
if (!empty($publish_down) && ($publish_down != $nullDate) && $nowDate > $publish_down->toUnix())
|
||||
{
|
||||
$states[$key][2] = $states[$key][3] = 'JLIB_HTML_PUBLISHED_EXPIRED_ITEM';
|
||||
$states[$key][5] = $states[$key][6] = 'alert';
|
||||
}
|
||||
}
|
||||
|
||||
// Add tips to titles
|
||||
if ($tip)
|
||||
{
|
||||
$states[$key][1] = Text::_($states[$key][1]);
|
||||
$states[$key][2] = Text::_($states[$key][2]) . '<br />' . $tip;
|
||||
$states[$key][3] = Text::_($states[$key][3]) . '<br />' . $tip;
|
||||
$states[$key][4] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return static::state($states, $value, $i, [
|
||||
'prefix' => $prefix, 'translate' => !$tip,
|
||||
], $enabled, true, $checkbox);
|
||||
}
|
||||
|
||||
return static::state($states, $value, $i, $prefix, $enabled, true, $checkbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an isDefault state on the browse view's table
|
||||
*
|
||||
* @param integer $value The state value.
|
||||
* @param integer $i The row index
|
||||
* @param string|array $prefix An optional task prefix or an array of options
|
||||
* @param boolean $enabled An optional setting for access control on the action.
|
||||
* @param string $checkbox An optional prefix for checkboxes.
|
||||
*
|
||||
* @return string The HTML markup
|
||||
*
|
||||
* @see self::state()
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function isdefault($value, $i, $prefix = '', $enabled = true, $checkbox = 'cb')
|
||||
{
|
||||
if (is_array($prefix))
|
||||
{
|
||||
$options = $prefix;
|
||||
$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
|
||||
$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
|
||||
$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
|
||||
}
|
||||
|
||||
$states = [
|
||||
0 => ['setDefault', '', 'JLIB_HTML_SETDEFAULT_ITEM', '', 1, 'android-star-outline', 'android-star-outline'],
|
||||
1 => [
|
||||
'unsetDefault', 'JDEFAULT', 'JLIB_HTML_UNSETDEFAULT_ITEM', 'JDEFAULT', 1, 'android-star',
|
||||
'android-star',
|
||||
],
|
||||
];
|
||||
|
||||
return static::state($states, $value, $i, $prefix, $enabled, true, $checkbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a checked-out icon
|
||||
*
|
||||
* @param integer $i The row index.
|
||||
* @param string $editorName The name of the editor.
|
||||
* @param string $time The time that the object was checked out.
|
||||
* @param string|array $prefix An optional task prefix or an array of options
|
||||
* @param boolean $enabled True to enable the action.
|
||||
* @param string $checkbox An optional prefix for checkboxes.
|
||||
*
|
||||
* @return string The HTML markup
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function checkedout($i, $editorName, $time, $prefix = '', $enabled = false, $checkbox = 'cb')
|
||||
{
|
||||
HTMLHelper::_('bootstrap.tooltip');
|
||||
|
||||
if (is_array($prefix))
|
||||
{
|
||||
$options = $prefix;
|
||||
$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
|
||||
$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
|
||||
$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
|
||||
}
|
||||
|
||||
$text = $editorName . '<br />' . HTMLHelper::_('date', $time, Text::_('DATE_FORMAT_LC')) . '<br />' . HTMLHelper::_('date', $time, 'H:i');
|
||||
$active_title = HTMLHelper::_('tooltipText', Text::_('JLIB_HTML_CHECKIN'), $text, 0);
|
||||
$inactive_title = HTMLHelper::_('tooltipText', Text::_('JLIB_HTML_CHECKED_OUT'), $text, 0);
|
||||
|
||||
return static::action(
|
||||
$i, 'checkin', $prefix, html_entity_decode($active_title, ENT_QUOTES, 'UTF-8'),
|
||||
html_entity_decode($inactive_title, ENT_QUOTES, 'UTF-8'), true, 'locked', 'locked', $enabled, false, $checkbox
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the drag'n'drop reordering field for Browse views
|
||||
*
|
||||
* @param string $orderingField The name of the field you're ordering by
|
||||
* @param string $order The order value of the current row
|
||||
* @param string $class CSS class for the ordering value INPUT field
|
||||
* @param string $icon CSS class for the d'n'd handle icon
|
||||
* @param string $inactiveIcon CSS class for the d'n'd disabled icon
|
||||
* @param DataViewInterface $view The view you're rendering against. Leave null for auto-detection.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function order($orderingField, $order, $class = 'input-sm', $icon = 'akion-android-more-vertical', $inactiveIcon = 'akion-android-more-vertical', DataViewInterface $view = null)
|
||||
{
|
||||
/** @var Html $view */
|
||||
if (is_null($view))
|
||||
{
|
||||
$view = BrowseView::getViewFromBacktrace();
|
||||
}
|
||||
$dndOrderingActive = $view->getLists()->order == $orderingField;
|
||||
|
||||
// Default inactive ordering
|
||||
$html = '<span class="sortable-handler inactive" >';
|
||||
$html .= '<span class="' . $icon . '"></span>';
|
||||
$html .= '</span>';
|
||||
|
||||
// The modern drag'n'drop method
|
||||
if ($view->getPerms()->editstate)
|
||||
{
|
||||
$disableClassName = '';
|
||||
$disabledLabel = '';
|
||||
|
||||
// DO NOT REMOVE! It will initialize Joomla libraries and javascript functions
|
||||
$hasAjaxOrderingSupport = $view->hasAjaxOrderingSupport();
|
||||
|
||||
if (!is_array($hasAjaxOrderingSupport) || !$hasAjaxOrderingSupport['saveOrder'])
|
||||
{
|
||||
$disabledLabel = Text::_('JORDERINGDISABLED');
|
||||
$disableClassName = 'inactive tip-top hasTooltip';
|
||||
}
|
||||
|
||||
$orderClass = $dndOrderingActive ? 'order-enabled' : 'order-disabled';
|
||||
|
||||
$html = '<div class="' . $orderClass . '">';
|
||||
$html .= '<span class="sortable-handler ' . $disableClassName . '" title="' . $disabledLabel . '">';
|
||||
$html .= '<span class="' . ($disableClassName ? $inactiveIcon : $icon) . '"></span>';
|
||||
$html .= '</span>';
|
||||
|
||||
if ($dndOrderingActive)
|
||||
{
|
||||
$joomla35IsBroken = version_compare(JVERSION, '3.5.0', 'ge') ? 'style="display: none"' : '';
|
||||
|
||||
$html .= '<input type="text" name="order[]" ' . $joomla35IsBroken . ' size="5" class="' . $class . ' text-area-order" value="' . $order . '" />';
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the drag'n'drop reordering table header for Browse views
|
||||
*
|
||||
* @param string $orderingField The name of the field you're ordering by
|
||||
* @param string $icon CSS class for the d'n'd handle icon
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function orderfield($orderingField = 'ordering', $icon = 'akion-stats-bars')
|
||||
{
|
||||
$title = Text::_('JGLOBAL_CLICK_TO_SORT_THIS_COLUMN');
|
||||
$orderingLabel = Text::_('JFIELD_ORDERING_LABEL');
|
||||
|
||||
return <<< HTML
|
||||
<a href="#"
|
||||
onclick="Joomla.tableOrdering('{$orderingField}','asc','');return false;"
|
||||
class="hasPopover"
|
||||
title="{$orderingLabel}"
|
||||
data-content="{$title}"
|
||||
data-placement="top"
|
||||
>
|
||||
<span class="{$icon}"></span>
|
||||
</a>
|
||||
|
||||
HTML;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an order-up action icon.
|
||||
*
|
||||
* @param integer $i The row index.
|
||||
* @param string $task An optional task to fire.
|
||||
* @param string|array $prefix An optional task prefix or an array of options
|
||||
* @param string $text An optional text to display
|
||||
* @param boolean $enabled An optional setting for access control on the action.
|
||||
* @param string $checkbox An optional prefix for checkboxes.
|
||||
*
|
||||
* @return string The HTML markup
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function orderUp($i, $task = 'orderup', $prefix = '', $text = 'JLIB_HTML_MOVE_UP', $enabled = true, $checkbox = 'cb')
|
||||
{
|
||||
if (is_array($prefix))
|
||||
{
|
||||
$options = $prefix;
|
||||
$text = array_key_exists('text', $options) ? $options['text'] : $text;
|
||||
$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
|
||||
$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
|
||||
$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
|
||||
}
|
||||
|
||||
return static::action($i, $task, $prefix, $text, $text, false, 'arrow-up-b', 'arrow-up-b', $enabled, true, $checkbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an order-down action icon.
|
||||
*
|
||||
* @param integer $i The row index.
|
||||
* @param string $task An optional task to fire.
|
||||
* @param string|array $prefix An optional task prefix or an array of options
|
||||
* @param string $text An optional text to display
|
||||
* @param boolean $enabled An optional setting for access control on the action.
|
||||
* @param string $checkbox An optional prefix for checkboxes.
|
||||
*
|
||||
* @return string The HTML markup
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function orderDown($i, $task = 'orderdown', $prefix = '', $text = 'JLIB_HTML_MOVE_DOWN', $enabled = true, $checkbox = 'cb')
|
||||
{
|
||||
if (is_array($prefix))
|
||||
{
|
||||
$options = $prefix;
|
||||
$text = array_key_exists('text', $options) ? $options['text'] : $text;
|
||||
$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
|
||||
$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
|
||||
$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
|
||||
}
|
||||
|
||||
return static::action($i, $task, $prefix, $text, $text, false, 'arrow-down-b', 'arrow-down-b', $enabled, true, $checkbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* Table header for a field which changes the sort order when clicked
|
||||
*
|
||||
* @param string $title The link title
|
||||
* @param string $order The order field for the column
|
||||
* @param string $direction The current direction
|
||||
* @param string $selected The selected ordering
|
||||
* @param string $task An optional task override
|
||||
* @param string $new_direction An optional direction for the new column
|
||||
* @param string $tip An optional text shown as tooltip title instead of $title
|
||||
* @param string $form An optional form selector
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function sort($title, $order, $direction = 'asc', $selected = '', $task = null, $new_direction = 'asc', $tip = '', $form = null)
|
||||
{
|
||||
HTMLHelper::_('behavior.core');
|
||||
HTMLHelper::_('bootstrap.popover');
|
||||
|
||||
$direction = strtolower($direction);
|
||||
$icon = ['akion-android-arrow-dropup', 'akion-android-arrow-dropdown'];
|
||||
$index = (int) ($direction === 'desc');
|
||||
|
||||
if ($order != $selected)
|
||||
{
|
||||
$direction = $new_direction;
|
||||
}
|
||||
else
|
||||
{
|
||||
$direction = $direction === 'desc' ? 'asc' : 'desc';
|
||||
}
|
||||
|
||||
if ($form)
|
||||
{
|
||||
$form = ', document.getElementById(\'' . $form . '\')';
|
||||
}
|
||||
|
||||
$html = '<a href="#" onclick="Joomla.tableOrdering(\'' . $order . '\',\'' . $direction . '\',\'' . $task . '\'' . $form . ');return false;"'
|
||||
. ' class="hasPopover" title="' . htmlspecialchars(Text::_($tip ?: $title)) . '"'
|
||||
. ' data-content="' . htmlspecialchars(Text::_('JGLOBAL_CLICK_TO_SORT_THIS_COLUMN')) . '" data-placement="top">';
|
||||
|
||||
if (isset($title['0']) && $title['0'] === '<')
|
||||
{
|
||||
$html .= $title;
|
||||
}
|
||||
else
|
||||
{
|
||||
$html .= Text::_($title);
|
||||
}
|
||||
|
||||
if ($order == $selected)
|
||||
{
|
||||
$html .= '<span class="' . $icon[$index] . '"></span>';
|
||||
}
|
||||
|
||||
$html .= '</a>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check all checkboxes on the browse view's table
|
||||
*
|
||||
* @param string $name The name of the form element
|
||||
* @param string $tip The text shown as tooltip title instead of $tip
|
||||
* @param string $action The action to perform on clicking the checkbox
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function checkall($name = 'checkall-toggle', $tip = 'JGLOBAL_CHECK_ALL', $action = 'Joomla.checkAll(this)')
|
||||
{
|
||||
HTMLHelper::_('behavior.core');
|
||||
HTMLHelper::_('bootstrap.tooltip');
|
||||
|
||||
return '<input type="checkbox" name="' . $name . '" value="" class="hasTooltip" title="' . HTMLHelper::_('tooltipText', $tip)
|
||||
. '" onclick="' . $action . '" />';
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to create a checkbox for a grid row.
|
||||
*
|
||||
* @param integer $rowNum The row index
|
||||
* @param integer $recId The record id
|
||||
* @param boolean $checkedOut True if item is checked out
|
||||
* @param string $name The name of the form element
|
||||
* @param string $stub The name of stub identifier
|
||||
*
|
||||
* @return mixed String of html with a checkbox if item is not checked out, null if checked out.
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function id($rowNum, $recId, $checkedOut = false, $name = 'cid', $stub = 'cb')
|
||||
{
|
||||
return $checkedOut ? '' : '<input type="checkbox" id="' . $stub . $rowNum . '" name="' . $name . '[]" value="' . $recId
|
||||
. '" onclick="Joomla.isChecked(this.checked);" />';
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the necessary JavaScript for the browse view's table order feature
|
||||
*
|
||||
* @param string $orderBy Filed by which we are currently sorting the table.
|
||||
* @param bool $return Should I return the JS? Default: false (= add to the page's head)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function orderjs($orderBy, $return = false)
|
||||
{
|
||||
$js = <<< JS
|
||||
|
||||
Joomla.orderTable = function()
|
||||
{
|
||||
var table = document.getElementById("sortTable");
|
||||
var direction = document.getElementById("directionTable");
|
||||
var order = table.options[table.selectedIndex].value;
|
||||
var dirn = 'asc';
|
||||
|
||||
if (order !== '$orderBy')
|
||||
{
|
||||
dirn = 'asc';
|
||||
}
|
||||
else {
|
||||
dirn = direction.options[direction.selectedIndex].value;
|
||||
}
|
||||
|
||||
Joomla.tableOrdering(order, dirn);
|
||||
};
|
||||
JS;
|
||||
|
||||
if ($return)
|
||||
{
|
||||
return $js;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Factory::getApplication()->getDocument()->addScriptDeclaration($js);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
// If we have no application, well, not having table sorting JS is the least of your worries...
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the table ordering / pagination header for a browse view: number of records to display, order direction,
|
||||
* order by field.
|
||||
*
|
||||
* @param DataViewRaw $view The view you're rendering against. If not provided we
|
||||
* will guess it using MAGIC.
|
||||
* @param array $sortFields Array of field name => description for the ordering
|
||||
* fields in the dropdown. If not provided we will use all
|
||||
* the fields available in the model.
|
||||
* @param Pagination $pagination The Joomla pagination object. If not provided we fetch
|
||||
* it from the view.
|
||||
* @param string $sortBy Order by field name. If not provided we fetch it from
|
||||
* the view.
|
||||
* @param string $order_Dir Order direction. If not provided we fetch it from the
|
||||
* view.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public static function orderheader(DataViewRaw $view = null, array $sortFields = [], Pagination $pagination = null, $sortBy = null, $order_Dir = null)
|
||||
{
|
||||
if (is_null($view))
|
||||
{
|
||||
$view = BrowseView::getViewFromBacktrace();
|
||||
}
|
||||
|
||||
if (empty($sortFields))
|
||||
{
|
||||
/** @var DataModel $model */
|
||||
$model = $view->getModel();
|
||||
$sortFields = $view->getLists()->sortFields ?? [];
|
||||
$sortFields = empty($sortFields) ? self::getSortFields($model) : $sortFields;
|
||||
}
|
||||
|
||||
if (empty($pagination))
|
||||
{
|
||||
$pagination = $view->getPagination();
|
||||
}
|
||||
|
||||
if (empty($sortBy))
|
||||
{
|
||||
$sortBy = $view->getLists()->order;
|
||||
}
|
||||
|
||||
if (empty($order_Dir))
|
||||
{
|
||||
$order_Dir = $view->getLists()->order_Dir;
|
||||
|
||||
if (empty($order_Dir))
|
||||
{
|
||||
$order_Dir = 'asc';
|
||||
}
|
||||
}
|
||||
|
||||
// Static hidden text labels
|
||||
$limitLabel = Text::_('JFIELD_PLG_SEARCH_SEARCHLIMIT_DESC');
|
||||
$orderingDescr = Text::_('JFIELD_ORDERING_DESC');
|
||||
$sortByLabel = Text::_('JGLOBAL_SORT_BY');
|
||||
|
||||
// Order direction dropdown
|
||||
$directionSelect = HTMLHelper::_('FEFHelper.select.genericlist', [
|
||||
'' => $orderingDescr,
|
||||
'asc' => Text::_('JGLOBAL_ORDER_ASCENDING'),
|
||||
'desc' => Text::_('JGLOBAL_ORDER_DESCENDING'),
|
||||
], 'directionTable', [
|
||||
'id' => 'directionTable',
|
||||
'list.select' => $order_Dir,
|
||||
'list.attr' => [
|
||||
'class' => 'input-medium custom-select',
|
||||
'onchange' => 'Joomla.orderTable()',
|
||||
],
|
||||
]);
|
||||
|
||||
// Sort by field dropdown
|
||||
|
||||
$sortTable = HTMLHelper::_('FEFHelper.select.genericlist', array_merge([
|
||||
'' => Text::_('JGLOBAL_SORT_BY'),
|
||||
], $sortFields), 'sortTable', [
|
||||
'id' => 'sortTable',
|
||||
'list.select' => $sortBy,
|
||||
'list.attr' => [
|
||||
'class' => 'input-medium custom-select',
|
||||
'onchange' => 'Joomla.orderTable()',
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
$html = <<<HTML
|
||||
<div class="akeeba-filter-element akeeba-form-group">
|
||||
<label for="limit" class="element-invisible">
|
||||
$limitLabel
|
||||
</label>
|
||||
{$pagination->getLimitBox()}
|
||||
</div>
|
||||
|
||||
<div class="akeeba-filter-element akeeba-form-group">
|
||||
<label for="directionTable" class="element-invisible">
|
||||
$orderingDescr
|
||||
</label>
|
||||
$directionSelect
|
||||
</div>
|
||||
|
||||
<div class="akeeba-filter-element akeeba-form-group">
|
||||
<label for="sortTable" class="element-invisible">
|
||||
{$sortByLabel}
|
||||
</label>
|
||||
$sortTable
|
||||
</div>
|
||||
|
||||
HTML;
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default sort fields from a model. It creates a hash array where the keys are the model's field names and
|
||||
* the values are the translation keys for their names, following FOF's naming conventions.
|
||||
*
|
||||
* @param DataModel $model The model for which we get the sort fields
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
private static function getSortFields(DataModel $model)
|
||||
{
|
||||
$sortFields = [];
|
||||
$idField = $model->getIdFieldName() ?: 'id';
|
||||
$defaultFieldLabels = [
|
||||
'publish_up' => 'JGLOBAL_FIELD_PUBLISH_UP_LABEL',
|
||||
'publish_down' => 'JGLOBAL_FIELD_PUBLISH_DOWN_LABEL',
|
||||
'created_by' => 'JGLOBAL_FIELD_CREATED_BY_LABEL',
|
||||
'created_on' => 'JGLOBAL_FIELD_CREATED_LABEL',
|
||||
'modified_by' => 'JGLOBAL_FIELD_MODIFIED_BY_LABEL',
|
||||
'modified_on' => 'JGLOBAL_FIELD_MODIFIED_LABEL',
|
||||
'ordering' => 'JGLOBAL_FIELD_FIELD_ORDERING_LABEL',
|
||||
'id' => 'JGLOBAL_FIELD_ID_LABEL',
|
||||
'hits' => 'JGLOBAL_HITS',
|
||||
'title' => 'JGLOBAL_TITLE',
|
||||
'user_id' => 'JGLOBAL_USERNAME',
|
||||
'username' => 'JGLOBAL_USERNAME',
|
||||
];
|
||||
$componentName = $model->getContainer()->componentName;
|
||||
$viewNameSingular = $model->getContainer()->inflector->singularize($model->getName());
|
||||
$viewNamePlural = $model->getContainer()->inflector->pluralize($model->getName());
|
||||
|
||||
foreach ($model->getFields() as $field => $fieldDescriptor)
|
||||
{
|
||||
$possibleKeys = [
|
||||
$componentName . '_' . $viewNamePlural . '_FIELD_' . $field,
|
||||
$componentName . '_' . $viewNamePlural . '_' . $field,
|
||||
$componentName . '_' . $viewNameSingular . '_FIELD_' . $field,
|
||||
$componentName . '_' . $viewNameSingular . '_' . $field,
|
||||
];
|
||||
|
||||
if (array_key_exists($field, $defaultFieldLabels))
|
||||
{
|
||||
$possibleKeys[] = $defaultFieldLabels[$field];
|
||||
}
|
||||
|
||||
if ($field === $idField)
|
||||
{
|
||||
$possibleKeys[] = $defaultFieldLabels['id'];
|
||||
}
|
||||
|
||||
$fieldLabel = '';
|
||||
|
||||
foreach ($possibleKeys as $langKey)
|
||||
{
|
||||
$langKey = strtoupper($langKey);
|
||||
$fieldLabel = Text::_($langKey);
|
||||
|
||||
if ($fieldLabel !== $langKey)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
$fieldLabel = '';
|
||||
}
|
||||
|
||||
if (!empty($fieldLabel))
|
||||
{
|
||||
$sortFields[$field] = (new Joomla\Filter\InputFilter())->clean($fieldLabel);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $sortFields;
|
||||
}
|
||||
}
|
||||
60
libraries/fof30/Utils/FEFHelper/edit.php
Normal file
60
libraries/fof30/Utils/FEFHelper/edit.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Joomla\CMS\Editor\Editor;
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
/**
|
||||
* Custom JHtml (HTMLHelper) class. Offers edit (form) view controls compatible with Akeeba Frontend
|
||||
* Framework (FEF).
|
||||
*
|
||||
* Call these methods as JHtml::_('FEFHelper.edit.methodName', $parameter1, $parameter2, ...)
|
||||
*/
|
||||
abstract class FEFHelperEdit
|
||||
{
|
||||
public static function editor($fieldName, $value, array $params = [])
|
||||
{
|
||||
$params = array_merge([
|
||||
'id' => null,
|
||||
'editor' => null,
|
||||
'width' => '100%',
|
||||
'height' => 500,
|
||||
'columns' => 50,
|
||||
'rows' => 20,
|
||||
'created_by' => null,
|
||||
'asset_id' => null,
|
||||
'buttons' => true,
|
||||
'hide' => false,
|
||||
], $params);
|
||||
|
||||
$editorType = $params['editor'];
|
||||
|
||||
if (is_null($editorType))
|
||||
{
|
||||
$editorType = Factory::getConfig()->get('editor');
|
||||
$user = Factory::getUser();
|
||||
|
||||
if (!$user->guest)
|
||||
{
|
||||
$editorType = $user->getParam('editor', $editorType);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($params['id']))
|
||||
{
|
||||
$params['id'] = $fieldName;
|
||||
}
|
||||
|
||||
$editor = Editor::getInstance($editorType);
|
||||
|
||||
return $editor->display($fieldName, $value, $params['width'], $params['height'],
|
||||
$params['columns'], $params['rows'], $params['buttons'], $params['id'],
|
||||
$params['asset_id'], $params['created_by'], $params);
|
||||
}
|
||||
}
|
||||
887
libraries/fof30/Utils/FEFHelper/select.php
Normal file
887
libraries/fof30/Utils/FEFHelper/select.php
Normal file
@ -0,0 +1,887 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Utils\ArrayHelper;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
/**
|
||||
* Custom JHtml (HTMLHelper) class. Offers selects compatible with Akeeba Frontend Framework (FEF)
|
||||
*
|
||||
* Call these methods as JHtml::_('FEFHelper.select.methodName', $parameter1, $parameter2, ...)
|
||||
*/
|
||||
abstract class FEFHelperSelect
|
||||
{
|
||||
/**
|
||||
* Default values for options. Organized by option group.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $optionDefaults = [
|
||||
'option' => [
|
||||
'option.attr' => null,
|
||||
'option.disable' => 'disable',
|
||||
'option.id' => null,
|
||||
'option.key' => 'value',
|
||||
'option.key.toHtml' => true,
|
||||
'option.label' => null,
|
||||
'option.label.toHtml' => true,
|
||||
'option.text' => 'text',
|
||||
'option.text.toHtml' => true,
|
||||
'option.class' => 'class',
|
||||
'option.onclick' => 'onclick',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Generates a yes/no radio list.
|
||||
*
|
||||
* @param string $name The value of the HTML name attribute
|
||||
* @param array $attribs Additional HTML attributes for the `<select>` tag
|
||||
* @param string $selected The key that is selected
|
||||
* @param string $yes Language key for Yes
|
||||
* @param string $no Language key for no
|
||||
* @param mixed $id The id for the field or false for no id
|
||||
*
|
||||
* @return string HTML for the radio list
|
||||
*
|
||||
* @since 1.5
|
||||
* @see JFormFieldRadio
|
||||
*/
|
||||
public static function booleanlist($name, $attribs = [], $selected = null, $yes = 'JYES', $no = 'JNO', $id = false)
|
||||
{
|
||||
$options = [
|
||||
HTMLHelper::_('FEFHelper.select.option', '0', Text::_($no)),
|
||||
HTMLHelper::_('FEFHelper.select.option', '1', Text::_($yes)),
|
||||
];
|
||||
$attribs = array_merge(['forSelect' => 1], $attribs);
|
||||
|
||||
return HTMLHelper::_('FEFHelper.select.radiolist', $options, $name, $attribs, 'value', 'text', (int) $selected, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a searchable HTML selection list (Chosen on J3, Choices.js on J4).
|
||||
*
|
||||
* @param array $data An array of objects, arrays, or scalars.
|
||||
* @param string $name The value of the HTML name attribute.
|
||||
* @param mixed $attribs Additional HTML attributes for the `<select>` tag. This
|
||||
* can be an array of attributes, or an array of options. Treated as options
|
||||
* if it is the last argument passed. Valid options are:
|
||||
* Format options, see {@see JHtml::$formatOptions}.
|
||||
* Selection options, see {@see JHtmlSelect::options()}.
|
||||
* list.attr, string|array: Additional attributes for the select
|
||||
* element.
|
||||
* id, string: Value to use as the select element id attribute.
|
||||
* Defaults to the same as the name.
|
||||
* list.select, string|array: Identifies one or more option elements
|
||||
* to be selected, based on the option key values.
|
||||
* @param string $optKey The name of the object variable for the option value. If
|
||||
* set to null, the index of the value array is used.
|
||||
* @param string $optText The name of the object variable for the option text.
|
||||
* @param mixed $selected The key that is selected (accepts an array or a string).
|
||||
* @param mixed $idtag Value of the field id or null by default
|
||||
* @param boolean $translate True to translate
|
||||
*
|
||||
* @return string HTML for the select list.
|
||||
*
|
||||
* @since 3.7.2
|
||||
*/
|
||||
public static function smartlist($data, $name, $attribs = null, $optKey = 'value', $optText = 'text', $selected = null, $idtag = false, $translate = false)
|
||||
{
|
||||
$innerList = self::genericlist($data, $name, $attribs, $optKey, $optText, $selected, $idtag, $translate);
|
||||
|
||||
// Joomla 3: Use Chosen
|
||||
if (version_compare(JVERSION, '3.999.999', 'le'))
|
||||
{
|
||||
HTMLHelper::_('formbehavior.chosen');
|
||||
|
||||
return $innerList;
|
||||
}
|
||||
|
||||
// Joomla 4: Use the joomla-field-fancy-select using choices.js
|
||||
try
|
||||
{
|
||||
\Joomla\CMS\Factory::getApplication()->getDocument()->getWebAssetManager()
|
||||
->usePreset('choicesjs')
|
||||
->useScript('webcomponent.field-fancy-select');
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return $innerList;
|
||||
}
|
||||
|
||||
$j4Attr = array_filter([
|
||||
'class' => $attribs['class'] ?? null,
|
||||
'placeholder' => $attribs['placeholder'] ?? null,
|
||||
], function ($x) {
|
||||
return !empty($x);
|
||||
});
|
||||
|
||||
$dataAttribute = '';
|
||||
|
||||
if (isset($attribs['dataAttribute']))
|
||||
{
|
||||
$dataAttribute = is_string($attribs['dataAttribute']) ? $attribs['dataAttribute'] : '';
|
||||
}
|
||||
|
||||
if ((bool) ($attribs['allowCustom'] ?? false))
|
||||
{
|
||||
$dataAttribute .= ' allow-custom new-item-prefix="#new#"';
|
||||
}
|
||||
|
||||
$remoteSearchUrl = $attribs['remoteSearchURL'] ?? null;
|
||||
$remoteSearch = ((bool) ($attribs['remoteSearch'] ?? false)) && !empty($remoteSearchUrl);
|
||||
$termKey = $attribs['termKey'] ?? 'like';
|
||||
$minTermLength = $attribs['minTermLength'] ?? 3;
|
||||
|
||||
if ($remoteSearch)
|
||||
{
|
||||
$dataAttribute .= ' remote-search';
|
||||
$j4Attr['url'] = $remoteSearchUrl;
|
||||
$j4Attr['term-key'] = $termKey;
|
||||
$j4Attr['min-term-length'] = $minTermLength;
|
||||
}
|
||||
|
||||
if (isset($attribs['required']))
|
||||
{
|
||||
$j4Attr['class'] = ($j4Attr['class'] ?? '') . ' required';
|
||||
$dataAttribute .= ' required';
|
||||
}
|
||||
|
||||
if (isset($attribs['readonly']))
|
||||
{
|
||||
return $innerList;
|
||||
}
|
||||
|
||||
return sprintf("<joomla-field-fancy-select %s %s>%s</joomla-field-fancy-select>", ArrayHelper::toString($j4Attr), $dataAttribute, $innerList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an HTML selection list.
|
||||
*
|
||||
* @param array $data An array of objects, arrays, or scalars.
|
||||
* @param string $name The value of the HTML name attribute.
|
||||
* @param mixed $attribs Additional HTML attributes for the `<select>` tag. This
|
||||
* can be an array of attributes, or an array of options. Treated as options
|
||||
* if it is the last argument passed. Valid options are:
|
||||
* Format options, see {@see JHtml::$formatOptions}.
|
||||
* Selection options, see {@see JHtmlSelect::options()}.
|
||||
* list.attr, string|array: Additional attributes for the select
|
||||
* element.
|
||||
* id, string: Value to use as the select element id attribute.
|
||||
* Defaults to the same as the name.
|
||||
* list.select, string|array: Identifies one or more option elements
|
||||
* to be selected, based on the option key values.
|
||||
* @param string $optKey The name of the object variable for the option value. If
|
||||
* set to null, the index of the value array is used.
|
||||
* @param string $optText The name of the object variable for the option text.
|
||||
* @param mixed $selected The key that is selected (accepts an array or a string).
|
||||
* @param mixed $idtag Value of the field id or null by default
|
||||
* @param boolean $translate True to translate
|
||||
*
|
||||
* @return string HTML for the select list.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public static function genericlist($data, $name, $attribs = null, $optKey = 'value', $optText = 'text', $selected = null, $idtag = false, $translate = false)
|
||||
{
|
||||
// Set default options
|
||||
$options = array_merge(HTMLHelper::$formatOptions, ['format.depth' => 0, 'id' => false]);
|
||||
|
||||
if (is_array($attribs) && func_num_args() === 3)
|
||||
{
|
||||
// Assume we have an options array
|
||||
$options = array_merge($options, $attribs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get options from the parameters
|
||||
$options['id'] = $idtag;
|
||||
$options['list.attr'] = $attribs;
|
||||
$options['list.translate'] = $translate;
|
||||
$options['option.key'] = $optKey;
|
||||
$options['option.text'] = $optText;
|
||||
$options['list.select'] = $selected;
|
||||
}
|
||||
|
||||
$attribs = '';
|
||||
|
||||
if (isset($options['list.attr']))
|
||||
{
|
||||
if (is_array($options['list.attr']))
|
||||
{
|
||||
$attribs = ArrayHelper::toString($options['list.attr']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$attribs = $options['list.attr'];
|
||||
}
|
||||
|
||||
if ($attribs !== '')
|
||||
{
|
||||
$attribs = ' ' . $attribs;
|
||||
}
|
||||
}
|
||||
|
||||
$id = $options['id'] !== false ? $options['id'] : $name;
|
||||
$id = str_replace(['[', ']', ' '], '', $id);
|
||||
|
||||
$baseIndent = str_repeat($options['format.indent'], $options['format.depth']++);
|
||||
$html = $baseIndent . '<select' . ($id !== '' ? ' id="' . $id . '"' : '') . ' name="' . $name . '"' . $attribs . '>' . $options['format.eol']
|
||||
. static::options($data, $options) . $baseIndent . '</select>' . $options['format.eol'];
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a grouped HTML selection list from nested arrays.
|
||||
*
|
||||
* @param array $data An array of groups, each of which is an array of options.
|
||||
* @param string $name The value of the HTML name attribute
|
||||
* @param array $options Options, an array of key/value pairs. Valid options are:
|
||||
* Format options, {@see JHtml::$formatOptions}.
|
||||
* Selection options. See {@see JHtmlSelect::options()}.
|
||||
* group.id: The property in each group to use as the group id
|
||||
* attribute. Defaults to none.
|
||||
* group.label: The property in each group to use as the group
|
||||
* label. Defaults to "text". If set to null, the data array index key is
|
||||
* used.
|
||||
* group.items: The property in each group to use as the array of
|
||||
* items in the group. Defaults to "items". If set to null, group.id and
|
||||
* group. label are forced to null and the data element is assumed to be a
|
||||
* list of selections.
|
||||
* id: Value to use as the select element id attribute. Defaults to
|
||||
* the same as the name.
|
||||
* list.attr: Attributes for the select element. Can be a string or
|
||||
* an array of key/value pairs. Defaults to none.
|
||||
* list.select: either the value of one selected option or an array
|
||||
* of selected options. Default: none.
|
||||
* list.translate: Boolean. If set, text and labels are translated via
|
||||
* JText::_().
|
||||
*
|
||||
* @return string HTML for the select list
|
||||
*
|
||||
* @throws RuntimeException If a group has contents that cannot be processed.
|
||||
*/
|
||||
public static function groupedlist($data, $name, $options = [])
|
||||
{
|
||||
// Set default options and overwrite with anything passed in
|
||||
$options = array_merge(
|
||||
HTMLHelper::$formatOptions,
|
||||
[
|
||||
'format.depth' => 0, 'group.items' => 'items', 'group.label' => 'text', 'group.label.toHtml' => true,
|
||||
'id' => false,
|
||||
],
|
||||
$options
|
||||
);
|
||||
|
||||
// Apply option rules
|
||||
if ($options['group.items'] === null)
|
||||
{
|
||||
$options['group.label'] = null;
|
||||
}
|
||||
|
||||
$attribs = '';
|
||||
|
||||
if (isset($options['list.attr']))
|
||||
{
|
||||
if (is_array($options['list.attr']))
|
||||
{
|
||||
$attribs = ArrayHelper::toString($options['list.attr']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$attribs = $options['list.attr'];
|
||||
}
|
||||
|
||||
if ($attribs !== '')
|
||||
{
|
||||
$attribs = ' ' . $attribs;
|
||||
}
|
||||
}
|
||||
|
||||
$id = $options['id'] !== false ? $options['id'] : $name;
|
||||
$id = str_replace(['[', ']', ' '], '', $id);
|
||||
|
||||
// Disable groups in the options.
|
||||
$options['groups'] = false;
|
||||
|
||||
$baseIndent = str_repeat($options['format.indent'], $options['format.depth']++);
|
||||
$html = $baseIndent . '<select' . ($id !== '' ? ' id="' . $id . '"' : '') . ' name="' . $name . '"' . $attribs . '>' . $options['format.eol'];
|
||||
$groupIndent = str_repeat($options['format.indent'], $options['format.depth']++);
|
||||
|
||||
foreach ($data as $dataKey => $group)
|
||||
{
|
||||
$label = $dataKey;
|
||||
$id = '';
|
||||
$noGroup = is_int($dataKey);
|
||||
|
||||
if ($options['group.items'] == null)
|
||||
{
|
||||
// Sub-list is an associative array
|
||||
$subList = $group;
|
||||
}
|
||||
elseif (is_array($group))
|
||||
{
|
||||
// Sub-list is in an element of an array.
|
||||
$subList = $group[$options['group.items']];
|
||||
|
||||
if (isset($group[$options['group.label']]))
|
||||
{
|
||||
$label = $group[$options['group.label']];
|
||||
$noGroup = false;
|
||||
}
|
||||
|
||||
if (isset($options['group.id']) && isset($group[$options['group.id']]))
|
||||
{
|
||||
$id = $group[$options['group.id']];
|
||||
$noGroup = false;
|
||||
}
|
||||
}
|
||||
elseif (is_object($group))
|
||||
{
|
||||
// Sub-list is in a property of an object
|
||||
$subList = $group->{$options['group.items']};
|
||||
|
||||
if (isset($group->{$options['group.label']}))
|
||||
{
|
||||
$label = $group->{$options['group.label']};
|
||||
$noGroup = false;
|
||||
}
|
||||
|
||||
if (isset($options['group.id']) && isset($group->{$options['group.id']}))
|
||||
{
|
||||
$id = $group->{$options['group.id']};
|
||||
$noGroup = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException('Invalid group contents.', 1);
|
||||
}
|
||||
|
||||
if ($noGroup)
|
||||
{
|
||||
$html .= static::options($subList, $options);
|
||||
}
|
||||
else
|
||||
{
|
||||
$html .= $groupIndent . '<optgroup' . (empty($id) ? '' : ' id="' . $id . '"') . ' label="'
|
||||
. ($options['group.label.toHtml'] ? htmlspecialchars($label, ENT_COMPAT, 'UTF-8') : $label) . '">' . $options['format.eol']
|
||||
. static::options($subList, $options) . $groupIndent . '</optgroup>' . $options['format.eol'];
|
||||
}
|
||||
}
|
||||
|
||||
$html .= $baseIndent . '</select>' . $options['format.eol'];
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a selection list of integers.
|
||||
*
|
||||
* @param integer $start The start integer
|
||||
* @param integer $end The end integer
|
||||
* @param integer $inc The increment
|
||||
* @param string $name The value of the HTML name attribute
|
||||
* @param mixed $attribs Additional HTML attributes for the `<select>` tag, an array of
|
||||
* attributes, or an array of options. Treated as options if it is the last
|
||||
* argument passed.
|
||||
* @param mixed $selected The key that is selected
|
||||
* @param string $format The printf format to be applied to the number
|
||||
*
|
||||
* @return string HTML for the select list
|
||||
*/
|
||||
public static function integerlist($start, $end, $inc, $name, $attribs = null, $selected = null, $format = '')
|
||||
{
|
||||
// Set default options
|
||||
$options = array_merge(HTMLHelper::$formatOptions, [
|
||||
'format.depth' => 0, 'option.format' => '', 'id' => null,
|
||||
]);
|
||||
|
||||
if (is_array($attribs) && func_num_args() === 5)
|
||||
{
|
||||
// Assume we have an options array
|
||||
$options = array_merge($options, $attribs);
|
||||
|
||||
// Extract the format and remove it from downstream options
|
||||
$format = $options['option.format'];
|
||||
unset($options['option.format']);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get options from the parameters
|
||||
$options['list.attr'] = $attribs;
|
||||
$options['list.select'] = $selected;
|
||||
}
|
||||
|
||||
$start = (int) $start;
|
||||
$end = (int) $end;
|
||||
$inc = (int) $inc;
|
||||
|
||||
$data = [];
|
||||
|
||||
for ($i = $start; $i <= $end; $i += $inc)
|
||||
{
|
||||
$data[$i] = $format ? sprintf($format, $i) : $i;
|
||||
}
|
||||
|
||||
// Tell genericlist() to use array keys
|
||||
$options['option.key'] = null;
|
||||
|
||||
return HTMLHelper::_('FEFHelper.select.genericlist', $data, $name, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an object that represents an option in an option list.
|
||||
*
|
||||
* @param string $value The value of the option
|
||||
* @param string $text The text for the option
|
||||
* @param mixed $optKey If a string, the returned object property name for
|
||||
* the value. If an array, options. Valid options are:
|
||||
* attr: String|array. Additional attributes for this option.
|
||||
* Defaults to none.
|
||||
* disable: Boolean. If set, this option is disabled.
|
||||
* label: String. The value for the option label.
|
||||
* option.attr: The property in each option array to use for
|
||||
* additional selection attributes. Defaults to none.
|
||||
* option.disable: The property that will hold the disabled state.
|
||||
* Defaults to "disable".
|
||||
* option.key: The property that will hold the selection value.
|
||||
* Defaults to "value".
|
||||
* option.label: The property in each option array to use as the
|
||||
* selection label attribute. If a "label" option is provided, defaults to
|
||||
* "label", if no label is given, defaults to null (none).
|
||||
* option.text: The property that will hold the the displayed text.
|
||||
* Defaults to "text". If set to null, the option array is assumed to be a
|
||||
* list of displayable scalars.
|
||||
* @param string $optText The property that will hold the the displayed text. This
|
||||
* parameter is ignored if an options array is passed.
|
||||
* @param boolean $disable Not used.
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public static function option($value, $text = '', $optKey = 'value', $optText = 'text', $disable = false)
|
||||
{
|
||||
$options = [
|
||||
'attr' => null,
|
||||
'disable' => false,
|
||||
'option.attr' => null,
|
||||
'option.disable' => 'disable',
|
||||
'option.key' => 'value',
|
||||
'option.label' => null,
|
||||
'option.text' => 'text',
|
||||
];
|
||||
|
||||
if (is_array($optKey))
|
||||
{
|
||||
// Merge in caller's options
|
||||
$options = array_merge($options, $optKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get options from the parameters
|
||||
$options['option.key'] = $optKey;
|
||||
$options['option.text'] = $optText;
|
||||
$options['disable'] = $disable;
|
||||
}
|
||||
|
||||
$obj = new stdClass;
|
||||
$obj->{$options['option.key']} = $value;
|
||||
$obj->{$options['option.text']} = trim($text) ? $text : $value;
|
||||
|
||||
/*
|
||||
* If a label is provided, save it. If no label is provided and there is
|
||||
* a label name, initialise to an empty string.
|
||||
*/
|
||||
$hasProperty = $options['option.label'] !== null;
|
||||
|
||||
if (isset($options['label']))
|
||||
{
|
||||
$labelProperty = $hasProperty ? $options['option.label'] : 'label';
|
||||
$obj->$labelProperty = $options['label'];
|
||||
}
|
||||
elseif ($hasProperty)
|
||||
{
|
||||
$obj->{$options['option.label']} = '';
|
||||
}
|
||||
|
||||
// Set attributes only if there is a property and a value
|
||||
if ($options['attr'] !== null)
|
||||
{
|
||||
$obj->{$options['option.attr']} = $options['attr'];
|
||||
}
|
||||
|
||||
// Set disable only if it has a property and a value
|
||||
if ($options['disable'] !== null)
|
||||
{
|
||||
$obj->{$options['option.disable']} = $options['disable'];
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the option tags for an HTML select list (with no select tag
|
||||
* surrounding the options).
|
||||
*
|
||||
* @param array $arr An array of objects, arrays, or values.
|
||||
* @param mixed $optKey If a string, this is the name of the object variable for
|
||||
* the option value. If null, the index of the array of objects is used. If
|
||||
* an array, this is a set of options, as key/value pairs. Valid options are:
|
||||
* -Format options, {@see JHtml::$formatOptions}.
|
||||
* -groups: Boolean. If set, looks for keys with the value
|
||||
* "<optgroup>" and synthesizes groups from them. Deprecated. Defaults
|
||||
* true for backwards compatibility.
|
||||
* -list.select: either the value of one selected option or an array
|
||||
* of selected options. Default: none.
|
||||
* -list.translate: Boolean. If set, text and labels are translated via
|
||||
* JText::_(). Default is false.
|
||||
* -option.id: The property in each option array to use as the
|
||||
* selection id attribute. Defaults to none.
|
||||
* -option.key: The property in each option array to use as the
|
||||
* selection value. Defaults to "value". If set to null, the index of the
|
||||
* option array is used.
|
||||
* -option.label: The property in each option array to use as the
|
||||
* selection label attribute. Defaults to null (none).
|
||||
* -option.text: The property in each option array to use as the
|
||||
* displayed text. Defaults to "text". If set to null, the option array is
|
||||
* assumed to be a list of displayable scalars.
|
||||
* -option.attr: The property in each option array to use for
|
||||
* additional selection attributes. Defaults to none.
|
||||
* -option.disable: The property that will hold the disabled state.
|
||||
* Defaults to "disable".
|
||||
* -option.key: The property that will hold the selection value.
|
||||
* Defaults to "value".
|
||||
* -option.text: The property that will hold the the displayed text.
|
||||
* Defaults to "text". If set to null, the option array is assumed to be a
|
||||
* list of displayable scalars.
|
||||
* @param string $optText The name of the object variable for the option text.
|
||||
* @param mixed $selected The key that is selected (accepts an array or a string)
|
||||
* @param boolean $translate Translate the option values.
|
||||
*
|
||||
* @return string HTML for the select list
|
||||
*/
|
||||
public static function options($arr, $optKey = 'value', $optText = 'text', $selected = null, $translate = false)
|
||||
{
|
||||
$options = array_merge(
|
||||
HTMLHelper::$formatOptions,
|
||||
static::$optionDefaults['option'],
|
||||
['format.depth' => 0, 'groups' => true, 'list.select' => null, 'list.translate' => false]
|
||||
);
|
||||
|
||||
if (is_array($optKey))
|
||||
{
|
||||
// Set default options and overwrite with anything passed in
|
||||
$options = array_merge($options, $optKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get options from the parameters
|
||||
$options['option.key'] = $optKey;
|
||||
$options['option.text'] = $optText;
|
||||
$options['list.select'] = $selected;
|
||||
$options['list.translate'] = $translate;
|
||||
}
|
||||
|
||||
$html = '';
|
||||
$baseIndent = str_repeat($options['format.indent'], $options['format.depth']);
|
||||
|
||||
foreach ($arr as $elementKey => &$element)
|
||||
{
|
||||
$attr = '';
|
||||
$extra = '';
|
||||
$label = '';
|
||||
$id = '';
|
||||
|
||||
if (is_array($element))
|
||||
{
|
||||
$key = $options['option.key'] === null ? $elementKey : $element[$options['option.key']];
|
||||
$text = $element[$options['option.text']];
|
||||
|
||||
if (isset($element[$options['option.attr']]))
|
||||
{
|
||||
$attr = $element[$options['option.attr']];
|
||||
}
|
||||
|
||||
if (isset($element[$options['option.id']]))
|
||||
{
|
||||
$id = $element[$options['option.id']];
|
||||
}
|
||||
|
||||
if (isset($element[$options['option.label']]))
|
||||
{
|
||||
$label = $element[$options['option.label']];
|
||||
}
|
||||
|
||||
if (isset($element[$options['option.disable']]) && $element[$options['option.disable']])
|
||||
{
|
||||
$extra .= ' disabled="disabled"';
|
||||
}
|
||||
}
|
||||
elseif (is_object($element))
|
||||
{
|
||||
$key = $options['option.key'] === null ? $elementKey : $element->{$options['option.key']};
|
||||
$text = $element->{$options['option.text']};
|
||||
|
||||
if (isset($element->{$options['option.attr']}))
|
||||
{
|
||||
$attr = $element->{$options['option.attr']};
|
||||
}
|
||||
|
||||
if (isset($element->{$options['option.id']}))
|
||||
{
|
||||
$id = $element->{$options['option.id']};
|
||||
}
|
||||
|
||||
if (isset($element->{$options['option.label']}))
|
||||
{
|
||||
$label = $element->{$options['option.label']};
|
||||
}
|
||||
|
||||
if (isset($element->{$options['option.disable']}) && $element->{$options['option.disable']})
|
||||
{
|
||||
$extra .= ' disabled="disabled"';
|
||||
}
|
||||
|
||||
if (isset($element->{$options['option.class']}) && $element->{$options['option.class']})
|
||||
{
|
||||
$extra .= ' class="' . $element->{$options['option.class']} . '"';
|
||||
}
|
||||
|
||||
if (isset($element->{$options['option.onclick']}) && $element->{$options['option.onclick']})
|
||||
{
|
||||
$extra .= ' onclick="' . $element->{$options['option.onclick']} . '"';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a simple associative array
|
||||
$key = $elementKey;
|
||||
$text = $element;
|
||||
}
|
||||
|
||||
/*
|
||||
* The use of options that contain optgroup HTML elements was
|
||||
* somewhat hacked for J1.5. J1.6 introduces the grouplist() method
|
||||
* to handle this better. The old solution is retained through the
|
||||
* "groups" option, which defaults true in J1.6, but should be
|
||||
* deprecated at some point in the future.
|
||||
*/
|
||||
|
||||
$key = (string) $key;
|
||||
|
||||
if ($key === '<OPTGROUP>' && $options['groups'])
|
||||
{
|
||||
$html .= $baseIndent . '<optgroup label="' . ($options['list.translate'] ? Text::_($text) : $text) . '">' . $options['format.eol'];
|
||||
$baseIndent = str_repeat($options['format.indent'], ++$options['format.depth']);
|
||||
}
|
||||
elseif ($key === '</OPTGROUP>' && $options['groups'])
|
||||
{
|
||||
$baseIndent = str_repeat($options['format.indent'], --$options['format.depth']);
|
||||
$html .= $baseIndent . '</optgroup>' . $options['format.eol'];
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no string after hyphen - take hyphen out
|
||||
$splitText = explode(' - ', $text, 2);
|
||||
$text = $splitText[0];
|
||||
|
||||
if (isset($splitText[1]) && $splitText[1] !== '' && !preg_match('/^[\s]+$/', $splitText[1]))
|
||||
{
|
||||
$text .= ' - ' . $splitText[1];
|
||||
}
|
||||
|
||||
if (!empty($label) && $options['list.translate'])
|
||||
{
|
||||
$label = Text::_($label);
|
||||
}
|
||||
|
||||
if ($options['option.label.toHtml'])
|
||||
{
|
||||
$label = htmlentities($label);
|
||||
}
|
||||
|
||||
if (is_array($attr))
|
||||
{
|
||||
$attr = ArrayHelper::toString($attr);
|
||||
}
|
||||
else
|
||||
{
|
||||
$attr = trim($attr);
|
||||
}
|
||||
|
||||
$extra = ($id ? ' id="' . $id . '"' : '') . ($label ? ' label="' . $label . '"' : '') . ($attr ? ' ' . $attr : '') . $extra;
|
||||
|
||||
if (is_array($options['list.select']))
|
||||
{
|
||||
foreach ($options['list.select'] as $val)
|
||||
{
|
||||
$key2 = is_object($val) ? $val->{$options['option.key']} : $val;
|
||||
|
||||
if ($key == $key2)
|
||||
{
|
||||
$extra .= ' selected="selected"';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ((string) $key === (string) $options['list.select'])
|
||||
{
|
||||
$extra .= ' selected="selected"';
|
||||
}
|
||||
|
||||
if ($options['list.translate'])
|
||||
{
|
||||
$text = Text::_($text);
|
||||
}
|
||||
|
||||
// Generate the option, encoding as required
|
||||
$html .= $baseIndent . '<option value="' . ($options['option.key.toHtml'] ? htmlspecialchars($key, ENT_COMPAT, 'UTF-8') : $key) . '"'
|
||||
. $extra . '>';
|
||||
$html .= $options['option.text.toHtml'] ? htmlentities(html_entity_decode($text, ENT_COMPAT, 'UTF-8'), ENT_COMPAT, 'UTF-8') : $text;
|
||||
$html .= '</option>' . $options['format.eol'];
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an HTML radio list.
|
||||
*
|
||||
* @param array $data An array of objects
|
||||
* @param string $name The value of the HTML name attribute
|
||||
* @param string $attribs Additional HTML attributes for the `<select>` tag
|
||||
* @param mixed $optKey The key that is selected
|
||||
* @param string $optText The name of the object variable for the option value
|
||||
* @param string $selected The name of the object variable for the option text
|
||||
* @param boolean $idtag Value of the field id or null by default
|
||||
* @param boolean $translate True if options will be translated
|
||||
*
|
||||
* @return string HTML for the select list
|
||||
*/
|
||||
public static function radiolist($data, $name, $attribs = null, $optKey = 'value', $optText = 'text', $selected = null, $idtag = false,
|
||||
$translate = false)
|
||||
{
|
||||
|
||||
$forSelect = false;
|
||||
|
||||
if (isset($attribs['forSelect']))
|
||||
{
|
||||
$forSelect = (bool) ($attribs['forSelect']);
|
||||
unset($attribs['forSelect']);
|
||||
}
|
||||
|
||||
if (is_array($attribs))
|
||||
{
|
||||
$attribs = ArrayHelper::toString($attribs);
|
||||
}
|
||||
|
||||
$id_text = empty($idtag) ? $name : $idtag;
|
||||
|
||||
$html = '';
|
||||
|
||||
foreach ($data as $optionObject)
|
||||
{
|
||||
$optionValue = $optionObject->$optKey;
|
||||
$labelText = $translate ? Text::_($optionObject->$optText) : $optionObject->$optText;
|
||||
$id = ($optionObject->id ?? null);
|
||||
|
||||
$extra = '';
|
||||
$id = $id ? $optionObject->id : $id_text . $optionValue;
|
||||
|
||||
if (is_array($selected))
|
||||
{
|
||||
foreach ($selected as $val)
|
||||
{
|
||||
$k2 = is_object($val) ? $val->$optKey : $val;
|
||||
|
||||
if ($optionValue == $k2)
|
||||
{
|
||||
$extra .= ' selected="selected" ';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$extra .= ((string) $optionValue === (string) $selected ? ' checked="checked" ' : '');
|
||||
}
|
||||
|
||||
if ($forSelect)
|
||||
{
|
||||
$html .= "\n\t" . '<input type="radio" name="' . $name . '" id="' . $id . '" value="' . $optionValue . '" ' . $extra
|
||||
. $attribs . ' />';
|
||||
$html .= "\n\t" . '<label for="' . $id . '" id="' . $id . '-lbl">' . $labelText . '</label>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$html .= "\n\t" . '<label for="' . $id . '" id="' . $id . '-lbl">';
|
||||
$html .= "\n\t\n\t" . '<input type="radio" name="' . $name . '" id="' . $id . '" value="' . $optionValue . '" ' . $extra
|
||||
. $attribs . ' />' . $labelText;
|
||||
$html .= "\n\t" . '</label>';
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$html .= "\n";
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates two radio buttons styled with FEF to appear as a YES/NO switch
|
||||
*
|
||||
* @param string $name Name of the field
|
||||
* @param string $selected Selected field
|
||||
* @param array $attribs Additional attributes to add to the switch
|
||||
*
|
||||
* @return string The HTML for the switch
|
||||
*/
|
||||
public static function booleanswitch($name, $selected, array $attribs = [])
|
||||
{
|
||||
if (empty($attribs))
|
||||
{
|
||||
$attribs = ['class' => 'akeeba-toggle'];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isset($attribs['class']))
|
||||
{
|
||||
$attribs['class'] .= ' akeeba-toggle';
|
||||
}
|
||||
else
|
||||
{
|
||||
$attribs['class'] = 'akeeba-toggle';
|
||||
}
|
||||
}
|
||||
|
||||
$temp = '';
|
||||
|
||||
foreach ($attribs as $key => $value)
|
||||
{
|
||||
$temp .= $key . ' = "' . $value . '"';
|
||||
}
|
||||
|
||||
$attribs = $temp;
|
||||
|
||||
$checked_1 = $selected ? '' : 'checked ';
|
||||
$checked_2 = $selected ? 'checked ' : '';
|
||||
|
||||
$html = '<div ' . $attribs . '>';
|
||||
$html .= '<input type="radio" class="radio-yes" name="' . $name . '" ' . $checked_2 . 'id="' . $name . '-2" value="1">';
|
||||
$html .= '<label for="' . $name . '-2" class="green">' . Text::_('JYES') . '</label>';
|
||||
$html .= '<input type="radio" class="radio-no" name="' . $name . '" ' . $checked_1 . 'id="' . $name . '-1" value="0">';
|
||||
$html .= '<label for="' . $name . '-1" class="red">' . Text::_('JNO') . '</label>';
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
285
libraries/fof30/Utils/FilesCheck.php
Normal file
285
libraries/fof30/Utils/FilesCheck.php
Normal file
@ -0,0 +1,285 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Timer\Timer;
|
||||
use Joomla\CMS\Factory;
|
||||
|
||||
/**
|
||||
* A utility class to check that your extension's files are not missing and have not been tampered with.
|
||||
*
|
||||
* You need a file called fileslist.php in your component's administrator root directory with the following contents:
|
||||
*
|
||||
* $phpFileChecker = array(
|
||||
* 'version' => 'revCEE2DAB',
|
||||
* 'date' => '2014-10-16',
|
||||
* 'directories' => array(
|
||||
* 'administrator/components/com_foobar',
|
||||
* ....
|
||||
* ),
|
||||
* 'files' => array(
|
||||
* 'administrator/components/com_foobar/access.xml' => array('705', '09aa0351a316bf011ecc8c1145134761',
|
||||
* 'b95f00c7b49a07a60570dc674f2497c45c4e7152'),
|
||||
* ....
|
||||
* )
|
||||
* );
|
||||
*
|
||||
* All directory and file paths are relative to the site's root
|
||||
*
|
||||
* The directories array is a list of directories which must exist. The files array has the file paths as keys. The
|
||||
* value is a simple array containing the following elements in this order: file size in bytes, MD5 checksum, SHA1
|
||||
* checksum.
|
||||
*/
|
||||
class FilesCheck
|
||||
{
|
||||
/** @var string The name of the component */
|
||||
protected $option = '';
|
||||
|
||||
/** @var string Current component version */
|
||||
protected $version = null;
|
||||
|
||||
/** @var string Current component release date */
|
||||
protected $date = null;
|
||||
|
||||
/** @var array List of files to check as filepath => (filesize, md5, sha1) */
|
||||
protected $fileList = [];
|
||||
|
||||
/** @var array List of directories to check that exist */
|
||||
protected $dirList = [];
|
||||
|
||||
/** @var bool Is the reported component version different than the version of the #__extensions table? */
|
||||
protected $wrongComponentVersion = false;
|
||||
|
||||
/** @var bool Is the fileslist.php reporting a version different than the reported component version? */
|
||||
protected $wrongFilesVersion = false;
|
||||
|
||||
/**
|
||||
* Create and initialise the object
|
||||
*
|
||||
* @param string $option Component name, e.g. com_foobar
|
||||
* @param string $version The current component version, as reported by the component
|
||||
* @param string $date The current component release date, as reported by the component
|
||||
*/
|
||||
public function __construct($option, $version, $date)
|
||||
{
|
||||
// Initialise from parameters
|
||||
$this->option = $option;
|
||||
$this->version = $version;
|
||||
$this->date = $date;
|
||||
|
||||
// Retrieve the date and version from the #__extensions table
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)->select('*')->from($db->qn('#__extensions'))
|
||||
->where($db->qn('element') . ' = ' . $db->q($this->option))
|
||||
->where($db->qn('type') . ' = ' . $db->q('component'));
|
||||
$extension = $db->setQuery($query)->loadObject();
|
||||
|
||||
// Check the version and date against those from #__extensions. I hate heavily nested IFs as much as the next
|
||||
// guy, but what can you do...
|
||||
if (!is_null($extension))
|
||||
{
|
||||
$manifestCache = $extension->manifest_cache;
|
||||
|
||||
if (!empty($manifestCache))
|
||||
{
|
||||
$manifestCache = json_decode($manifestCache, true);
|
||||
|
||||
if (is_array($manifestCache) && isset($manifestCache['creationDate']) && isset($manifestCache['version']))
|
||||
{
|
||||
// Make sure the fileslist.php version and date match the component's version
|
||||
if ($this->version != $manifestCache['version'])
|
||||
{
|
||||
$this->wrongComponentVersion = true;
|
||||
}
|
||||
|
||||
if ($this->date != $manifestCache['creationDate'])
|
||||
{
|
||||
$this->wrongComponentVersion = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to load the fileslist.php file from the component's back-end root
|
||||
$filePath = JPATH_ADMINISTRATOR . '/components/' . $this->option . '/fileslist.php';
|
||||
|
||||
if (!file_exists($filePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$couldInclude = @include($filePath);
|
||||
|
||||
// If we couldn't include the file with the array OR if it didn't define the array we have to quit.
|
||||
if (!$couldInclude || !isset($phpFileChecker))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the fileslist.php version and date match the component's version
|
||||
if ($this->version != $phpFileChecker['version'])
|
||||
{
|
||||
$this->wrongFilesVersion = true;
|
||||
}
|
||||
|
||||
if ($this->date != $phpFileChecker['date'])
|
||||
{
|
||||
$this->wrongFilesVersion = true;
|
||||
}
|
||||
|
||||
// Initialise the files and directories lists
|
||||
$this->fileList = $phpFileChecker['files'];
|
||||
$this->dirList = $phpFileChecker['directories'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the reported component version different than the version of the #__extensions table?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isWrongComponentVersion()
|
||||
{
|
||||
return $this->wrongComponentVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the fileslist.php reporting a version different than the reported component version?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isWrongFilesVersion()
|
||||
{
|
||||
return $this->wrongFilesVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a fast check of file and folders. If even one of the files/folders doesn't exist, or even one file has
|
||||
* the wrong file size it will return false.
|
||||
*
|
||||
* @return bool False when there are mismatched files and directories
|
||||
*/
|
||||
public function fastCheck()
|
||||
{
|
||||
// Check that all directories exist
|
||||
foreach ($this->dirList as $directory)
|
||||
{
|
||||
$directory = JPATH_ROOT . '/' . $directory;
|
||||
|
||||
if (!@is_dir($directory))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all files exist and have the right size
|
||||
foreach ($this->fileList as $filePath => $fileData)
|
||||
{
|
||||
$filePath = JPATH_ROOT . '/' . $filePath;
|
||||
|
||||
if (!@file_exists($filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$fileSize = @filesize($filePath);
|
||||
|
||||
if ($fileSize != $fileData[0])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a slow, thorough check of all files and folders (including MD5/SHA1 sum checks)
|
||||
*
|
||||
* @param int $idx The index from where to start
|
||||
*
|
||||
* @return array Progress report
|
||||
*/
|
||||
public function slowCheck($idx = 0)
|
||||
{
|
||||
$ret = [
|
||||
'done' => false,
|
||||
'files' => [],
|
||||
'folders' => [],
|
||||
'idx' => $idx,
|
||||
];
|
||||
|
||||
$totalFiles = count($this->fileList);
|
||||
$totalFolders = count($this->dirList);
|
||||
$fileKeys = array_keys($this->fileList);
|
||||
|
||||
$timer = new Timer(3.0, 75.0);
|
||||
|
||||
while ($timer->getTimeLeft() && (($idx < $totalFiles) || ($idx < $totalFolders)))
|
||||
{
|
||||
if ($idx < $totalFolders)
|
||||
{
|
||||
$directory = JPATH_ROOT . '/' . $this->dirList[$idx];
|
||||
|
||||
if (!@is_dir($directory))
|
||||
{
|
||||
$ret['folders'][] = $directory;
|
||||
}
|
||||
}
|
||||
|
||||
if ($idx < $totalFiles)
|
||||
{
|
||||
$fileKey = $fileKeys[$idx];
|
||||
$filePath = JPATH_ROOT . '/' . $fileKey;
|
||||
$fileData = $this->fileList[$fileKey];
|
||||
|
||||
if (!@file_exists($filePath))
|
||||
{
|
||||
$ret['files'][] = $fileKey . ' (missing)';
|
||||
}
|
||||
elseif (@filesize($filePath) != $fileData[0])
|
||||
{
|
||||
$ret['files'][] = $fileKey . ' (size ' . @filesize($filePath) . ' ≠ ' . $fileData[0] . ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
if (function_exists('sha1_file'))
|
||||
{
|
||||
$fileSha1 = @sha1_file($filePath);
|
||||
|
||||
if ($fileSha1 != $fileData[2])
|
||||
{
|
||||
$ret['files'][] = $fileKey . ' (SHA1 ' . $fileSha1 . ' ≠ ' . $fileData[2] . ')';
|
||||
}
|
||||
}
|
||||
elseif (function_exists('md5_file'))
|
||||
{
|
||||
$fileMd5 = @md5_file($filePath);
|
||||
|
||||
if ($fileMd5 != $fileData[1])
|
||||
{
|
||||
$ret['files'][] = $fileKey . ' (MD5 ' . $fileMd5 . ' ≠ ' . $fileData[1] . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$idx++;
|
||||
}
|
||||
|
||||
if (($idx >= $totalFiles) && ($idx >= $totalFolders))
|
||||
{
|
||||
$ret['done'] = true;
|
||||
}
|
||||
|
||||
$ret['idx'] = $idx;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
29
libraries/fof30/Utils/InstallScript.php
Normal file
29
libraries/fof30/Utils/InstallScript.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\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Utils\InstallScript\Component;
|
||||
|
||||
// Make sure the new class can be loaded
|
||||
if (!class_exists('FOF30\\Utils\\InstallScript\\Component', true))
|
||||
{
|
||||
require_once __DIR__ . '/InstallScript/Component.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper class which you can use to create component installation scripts.
|
||||
*
|
||||
* This is the old location of the installation script class, maintained for backwards compatibility with FOF 3.0. Please
|
||||
* use the new class FOF30\Utils\InstallScript\Component instead.
|
||||
*/
|
||||
class InstallScript extends Component
|
||||
{
|
||||
|
||||
}
|
||||
758
libraries/fof30/Utils/InstallScript/BaseInstaller.php
Normal file
758
libraries/fof30/Utils/InstallScript/BaseInstaller.php
Normal file
@ -0,0 +1,758 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils\InstallScript;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Exception;
|
||||
use FOFTemplateUtils;
|
||||
use JLoader;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filesystem\File;
|
||||
use Joomla\CMS\Filesystem\Folder;
|
||||
use Joomla\CMS\Log\Log;
|
||||
|
||||
class BaseInstaller
|
||||
{
|
||||
/**
|
||||
* The minimum PHP version required to install this extension
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $minimumPHPVersion = '7.2.0';
|
||||
|
||||
/**
|
||||
* The minimum Joomla! version required to install this extension
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $minimumJoomlaVersion = '3.3.0';
|
||||
|
||||
/**
|
||||
* The maximum Joomla! version this extension can be installed on
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $maximumJoomlaVersion = '4.0.99';
|
||||
|
||||
/**
|
||||
* Post-installation message definitions for Joomla! 3.2 or later.
|
||||
*
|
||||
* This array contains the message definitions for the Post-installation Messages component added in Joomla! 3.2 and
|
||||
* later versions. Each element is also a hashed array. For the keys used in these message definitions please see
|
||||
* addPostInstallationMessage
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $postInstallationMessages = [];
|
||||
|
||||
/**
|
||||
* Recursively copy a bunch of files, but only if the source and target file have a different size.
|
||||
*
|
||||
* @param string $source Path to copy FROM
|
||||
* @param string $dest Path to copy TO
|
||||
* @param array $ignored List of entries to ignore (first level entries are taken into account)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function recursiveConditionalCopy($source, $dest, $ignored = [])
|
||||
{
|
||||
// Make sure source and destination exist
|
||||
if (!@is_dir($source))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!@is_dir($dest))
|
||||
{
|
||||
if (!@mkdir($dest, 0755))
|
||||
{
|
||||
Folder::create($dest, 0755);
|
||||
}
|
||||
}
|
||||
|
||||
if (!@is_dir($dest))
|
||||
{
|
||||
$this->log(__CLASS__ . ": Cannot create folder $dest");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// List the contents of the source folder
|
||||
try
|
||||
{
|
||||
$di = new DirectoryIterator($source);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Process each entry
|
||||
foreach ($di as $entry)
|
||||
{
|
||||
// Ignore dot dirs (. and ..)
|
||||
if ($entry->isDot())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$sourcePath = $entry->getPathname();
|
||||
$fileName = $entry->getFilename();
|
||||
|
||||
// Do not copy ignored files
|
||||
if (!empty($ignored) && in_array($fileName, $ignored))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's a directory do a recursive copy
|
||||
if ($entry->isDir())
|
||||
{
|
||||
$this->recursiveConditionalCopy($sourcePath, $dest . DIRECTORY_SEPARATOR . $fileName);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's a file check if it's missing or identical
|
||||
$mustCopy = false;
|
||||
$targetPath = $dest . DIRECTORY_SEPARATOR . $fileName;
|
||||
|
||||
if (!@is_file($targetPath))
|
||||
{
|
||||
$mustCopy = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sourceSize = @filesize($sourcePath);
|
||||
$targetSize = @filesize($targetPath);
|
||||
|
||||
$mustCopy = $sourceSize != $targetSize;
|
||||
}
|
||||
|
||||
if (!$mustCopy)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!@copy($sourcePath, $targetPath))
|
||||
{
|
||||
if (!File::copy($sourcePath, $targetPath))
|
||||
{
|
||||
$this->log(__CLASS__ . ": Cannot copy $sourcePath to $targetPath");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to log a warning / error with Joomla
|
||||
*
|
||||
* @param string $message The message to write to the log
|
||||
* @param bool $error Is this an error? If not, it's a warning. (default: false)
|
||||
* @param string $category Log category, default jerror
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function log($message, $error = false, $category = 'jerror')
|
||||
{
|
||||
// Just in case...
|
||||
if (!class_exists('JLog', true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$priority = $error ? Log::ERROR : Log::WARNING;
|
||||
|
||||
try
|
||||
{
|
||||
Log::add($message, $priority, $category);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
// Swallow the exception.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the server meets the minimum PHP version requirements.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkPHPVersion()
|
||||
{
|
||||
if (!empty($this->minimumPHPVersion))
|
||||
{
|
||||
if (defined('PHP_VERSION'))
|
||||
{
|
||||
$version = PHP_VERSION;
|
||||
}
|
||||
elseif (function_exists('phpversion'))
|
||||
{
|
||||
$version = phpversion();
|
||||
}
|
||||
else
|
||||
{
|
||||
$version = '5.0.0'; // all bets are off!
|
||||
}
|
||||
|
||||
if (!version_compare($version, $this->minimumPHPVersion, 'ge'))
|
||||
{
|
||||
$msg = "<p>You need PHP $this->minimumPHPVersion or later to install this extension</p>";
|
||||
|
||||
$this->log($msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the minimum and maximum Joomla! versions for this extension
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkJoomlaVersion()
|
||||
{
|
||||
if (!empty($this->minimumJoomlaVersion) && !version_compare(JVERSION, $this->minimumJoomlaVersion, 'ge'))
|
||||
{
|
||||
$msg = "<p>You need Joomla! $this->minimumJoomlaVersion or later to install this extension</p>";
|
||||
|
||||
$this->log($msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the maximum Joomla! version
|
||||
if (!empty($this->maximumJoomlaVersion) && !version_compare(JVERSION, $this->maximumJoomlaVersion, 'le'))
|
||||
{
|
||||
$msg = "<p>You need Joomla! $this->maximumJoomlaVersion or earlier to install this extension</p>";
|
||||
|
||||
$this->log($msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear PHP opcode caches
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function clearOpcodeCaches()
|
||||
{
|
||||
// Always reset the OPcache if it's enabled. Otherwise there's a good chance the server will not know we are
|
||||
// replacing .php scripts. This is a major concern since PHP 5.5 included and enabled OPcache by default.
|
||||
if (function_exists('opcache_reset'))
|
||||
{
|
||||
opcache_reset();
|
||||
}
|
||||
// Also do that for APC cache
|
||||
elseif (function_exists('apc_clear_cache'))
|
||||
{
|
||||
@apc_clear_cache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dependencies for a package from the #__akeeba_common table
|
||||
*
|
||||
* @param string $package The package
|
||||
*
|
||||
* @return array The dependencies
|
||||
*/
|
||||
protected function getDependencies($package)
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->qn('value'))
|
||||
->from($db->qn('#__akeeba_common'))
|
||||
->where($db->qn('key') . ' = ' . $db->q($package));
|
||||
|
||||
try
|
||||
{
|
||||
$dependencies = $db->setQuery($query)->loadResult();
|
||||
$dependencies = json_decode($dependencies, true);
|
||||
|
||||
if (empty($dependencies))
|
||||
{
|
||||
$dependencies = [];
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$dependencies = [];
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dependencies for a package into the #__akeeba_common table
|
||||
*
|
||||
* @param string $package The package
|
||||
* @param array $dependencies The dependencies list
|
||||
*/
|
||||
protected function setDependencies($package, array $dependencies)
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->delete('#__akeeba_common')
|
||||
->where($db->qn('key') . ' = ' . $db->q($package));
|
||||
|
||||
try
|
||||
{
|
||||
$db->setQuery($query)->execute();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
// Do nothing if the old key wasn't found
|
||||
}
|
||||
|
||||
$object = (object) [
|
||||
'key' => $package,
|
||||
'value' => json_encode($dependencies),
|
||||
];
|
||||
|
||||
try
|
||||
{
|
||||
$db->insertObject('#__akeeba_common', $object, 'key');
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
// Do nothing if the old key wasn't found
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a package dependency to #__akeeba_common
|
||||
*
|
||||
* @param string $package The package
|
||||
* @param string $dependency The dependency to add
|
||||
*/
|
||||
protected function addDependency($package, $dependency)
|
||||
{
|
||||
$dependencies = $this->getDependencies($package);
|
||||
|
||||
if (!in_array($dependency, $dependencies))
|
||||
{
|
||||
$dependencies[] = $dependency;
|
||||
|
||||
$this->setDependencies($package, $dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a package dependency from #__akeeba_common
|
||||
*
|
||||
* @param string $package The package
|
||||
* @param string $dependency The dependency to remove
|
||||
*/
|
||||
protected function removeDependency($package, $dependency)
|
||||
{
|
||||
$dependencies = $this->getDependencies($package);
|
||||
|
||||
if (in_array($dependency, $dependencies))
|
||||
{
|
||||
$index = array_search($dependency, $dependencies);
|
||||
unset($dependencies[$index]);
|
||||
|
||||
$this->setDependencies($package, $dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do I have a dependency for a package in #__akeeba_common
|
||||
*
|
||||
* @param string $package The package
|
||||
* @param string $dependency The dependency to check for
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasDependency($package, $dependency)
|
||||
{
|
||||
$dependencies = $this->getDependencies($package);
|
||||
|
||||
return in_array($dependency, $dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates a post-installation message (PIM) definition for Joomla! 3.2 or later. You can use this in your
|
||||
* post-installation script using this code:
|
||||
*
|
||||
* The $options array contains the following mandatory keys:
|
||||
*
|
||||
* extension_id The numeric ID of the extension this message is for (see the #__extensions table)
|
||||
*
|
||||
* type One of message, link or action. Their meaning is:
|
||||
* message Informative message. The user can dismiss it.
|
||||
* link The action button links to a URL. The URL is defined in the action parameter.
|
||||
* action A PHP action takes place when the action button is clicked. You need to specify the
|
||||
* action_file (RAD path to the PHP file) and action (PHP function name) keys. See
|
||||
* below for more information.
|
||||
*
|
||||
* title_key The JText language key for the title of this PIM
|
||||
* Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_TITLE
|
||||
*
|
||||
* description_key The JText language key for the main body (description) of this PIM
|
||||
* Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_DESCRIPTION
|
||||
*
|
||||
* action_key The JText language key for the action button. Ignored and not required when type=message
|
||||
* Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_ACTION
|
||||
*
|
||||
* language_extension The extension name which holds the language keys used above. For example, com_foobar,
|
||||
* mod_something, plg_system_whatever, tpl_mytemplate
|
||||
*
|
||||
* language_client_id Should we load the front-end (0) or back-end (1) language keys?
|
||||
*
|
||||
* version_introduced Which was the version of your extension where this message appeared for the first time?
|
||||
* Example: 3.2.1
|
||||
*
|
||||
* enabled Must be 1 for this message to be enabled. If you omit it, it defaults to 1.
|
||||
*
|
||||
* condition_file The RAD path to a PHP file containing a PHP function which determines whether this message
|
||||
* should be shown to the user. @param array $options See description
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
* @see Template::parsePath() for RAD path format. Joomla! will include this file
|
||||
* before calling the function defined in the action key below.
|
||||
* Example: admin://components/com_foobar/helpers/postinstall.php
|
||||
*
|
||||
* action The name of a PHP function which will be used to run the action of this PIM. This must be
|
||||
* a
|
||||
* simple PHP user function (not a class method, static method etc) which returns no result.
|
||||
* Example: com_foobar_postinstall_messageone_action
|
||||
*
|
||||
* @see Template::parsePath() for RAD path format. Joomla!
|
||||
* will include this file before calling the condition_method.
|
||||
* Example: admin://components/com_foobar/helpers/postinstall.php
|
||||
*
|
||||
* condition_method The name of a PHP function which will be used to determine whether to show this message to
|
||||
* the user. This must be a simple PHP user function (not a class method, static method etc)
|
||||
* which returns true to show the message and false to hide it. This function is defined in
|
||||
* the condition_file. Example: com_foobar_postinstall_messageone_condition
|
||||
*
|
||||
* When type=message no additional keys are required.
|
||||
*
|
||||
* When type=link the following additional keys are required:
|
||||
*
|
||||
* action The URL which will open when the user clicks on the PIM's action button
|
||||
* Example: index.php?option=com_foobar&view=tools&task=installSampleData
|
||||
*
|
||||
* Then type=action the following additional keys are required:
|
||||
*
|
||||
* action_file The RAD path to a PHP file containing a PHP function which performs the action of this
|
||||
* PIM.
|
||||
*
|
||||
*/
|
||||
protected function addPostInstallationMessage(array $options)
|
||||
{
|
||||
// Make sure there are options set
|
||||
if (!is_array($options))
|
||||
{
|
||||
throw new Exception('Post-installation message definitions must be of type array', 500);
|
||||
}
|
||||
|
||||
// Initialise array keys
|
||||
$defaultOptions = [
|
||||
'extension_id' => '',
|
||||
'type' => '',
|
||||
'title_key' => '',
|
||||
'description_key' => '',
|
||||
'action_key' => '',
|
||||
'language_extension' => '',
|
||||
'language_client_id' => '',
|
||||
'action_file' => '',
|
||||
'action' => '',
|
||||
'condition_file' => '',
|
||||
'condition_method' => '',
|
||||
'version_introduced' => '',
|
||||
'enabled' => '1',
|
||||
];
|
||||
|
||||
$options = array_merge($defaultOptions, $options);
|
||||
|
||||
// Array normalisation. Removes array keys not belonging to a definition.
|
||||
$defaultKeys = array_keys($defaultOptions);
|
||||
$allKeys = array_keys($options);
|
||||
$extraKeys = array_diff($allKeys, $defaultKeys);
|
||||
|
||||
if (!empty($extraKeys))
|
||||
{
|
||||
foreach ($extraKeys as $key)
|
||||
{
|
||||
unset($options[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalisation of integer values
|
||||
$options['extension_id'] = (int) $options['extension_id'];
|
||||
$options['language_client_id'] = (int) $options['language_client_id'];
|
||||
$options['enabled'] = (int) $options['enabled'];
|
||||
|
||||
// Normalisation of 0/1 values
|
||||
foreach (['language_client_id', 'enabled'] as $key)
|
||||
{
|
||||
$options[$key] = $options[$key] ? 1 : 0;
|
||||
}
|
||||
|
||||
// Make sure there's an extension_id
|
||||
if (!(int) $options['extension_id'])
|
||||
{
|
||||
throw new Exception('Post-installation message definitions need an extension_id', 500);
|
||||
}
|
||||
|
||||
// Make sure there's a valid type
|
||||
if (!in_array($options['type'], ['message', 'link', 'action']))
|
||||
{
|
||||
throw new Exception('Post-installation message definitions need to declare a type of message, link or action', 500);
|
||||
}
|
||||
|
||||
// Make sure there's a title key
|
||||
if (empty($options['title_key']))
|
||||
{
|
||||
throw new Exception('Post-installation message definitions need a title key', 500);
|
||||
}
|
||||
|
||||
// Make sure there's a description key
|
||||
if (empty($options['description_key']))
|
||||
{
|
||||
throw new Exception('Post-installation message definitions need a description key', 500);
|
||||
}
|
||||
|
||||
// If the type is anything other than message you need an action key
|
||||
if (($options['type'] != 'message') && empty($options['action_key']))
|
||||
{
|
||||
throw new Exception('Post-installation message definitions need an action key when they are of type "' . $options['type'] . '"', 500);
|
||||
}
|
||||
|
||||
// You must specify the language extension
|
||||
if (empty($options['language_extension']))
|
||||
{
|
||||
throw new Exception('Post-installation message definitions need to specify which extension contains their language keys', 500);
|
||||
}
|
||||
|
||||
// The action file and method are only required for the "action" type
|
||||
if ($options['type'] == 'action')
|
||||
{
|
||||
if (empty($options['action_file']))
|
||||
{
|
||||
throw new Exception('Post-installation message definitions need an action file when they are of type "action"', 500);
|
||||
}
|
||||
|
||||
$file_path = FOFTemplateUtils::parsePath($options['action_file'], true);
|
||||
|
||||
if (!@is_file($file_path))
|
||||
{
|
||||
throw new Exception('The action file ' . $options['action_file'] . ' of your post-installation message definition does not exist', 500);
|
||||
}
|
||||
|
||||
if (empty($options['action']))
|
||||
{
|
||||
throw new Exception('Post-installation message definitions need an action (function name) when they are of type "action"', 500);
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['type'] == 'link')
|
||||
{
|
||||
if (empty($options['link']))
|
||||
{
|
||||
throw new Exception('Post-installation message definitions need an action (URL) when they are of type "link"', 500);
|
||||
}
|
||||
}
|
||||
|
||||
// The condition file and method are only required when the type is not "message"
|
||||
if ($options['type'] != 'message')
|
||||
{
|
||||
if (empty($options['condition_file']))
|
||||
{
|
||||
throw new Exception('Post-installation message definitions need a condition file when they are of type "' . $options['type'] . '"', 500);
|
||||
}
|
||||
|
||||
$file_path = FOFTemplateUtils::parsePath($options['condition_file'], true);
|
||||
|
||||
if (!@is_file($file_path))
|
||||
{
|
||||
throw new Exception('The condition file ' . $options['condition_file'] . ' of your post-installation message definition does not exist', 500);
|
||||
}
|
||||
|
||||
if (empty($options['condition_method']))
|
||||
{
|
||||
throw new Exception('Post-installation message definitions need a condition method (function name) when they are of type "' . $options['type'] . '"', 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the definition exists
|
||||
$tableName = '#__postinstall_messages';
|
||||
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->qn($tableName))
|
||||
->where($db->qn('extension_id') . ' = ' . $db->q($options['extension_id']))
|
||||
->where($db->qn('type') . ' = ' . $db->q($options['type']))
|
||||
->where($db->qn('title_key') . ' = ' . $db->q($options['title_key']));
|
||||
$existingRow = $db->setQuery($query)->loadAssoc();
|
||||
|
||||
// Is the existing definition the same as the one we're trying to save (ignore the enabled flag)?
|
||||
if (!empty($existingRow))
|
||||
{
|
||||
$same = true;
|
||||
|
||||
foreach ($options as $k => $v)
|
||||
{
|
||||
if ($k == 'enabled')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existingRow[$k] != $v)
|
||||
{
|
||||
$same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Trying to add the same row as the existing one; quit
|
||||
if ($same)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise it's not the same row. Remove the old row before insert a new one.
|
||||
$query = $db->getQuery(true)
|
||||
->delete($db->qn($tableName))
|
||||
->where($db->q('extension_id') . ' = ' . $db->q($options['extension_id']))
|
||||
->where($db->q('type') . ' = ' . $db->q($options['type']))
|
||||
->where($db->q('title_key') . ' = ' . $db->q($options['title_key']));
|
||||
$db->setQuery($query)->execute();
|
||||
}
|
||||
|
||||
// Insert the new row
|
||||
$options = (object) $options;
|
||||
$db->insertObject($tableName, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the post-installation messages for Joomla! 3.2 or later
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _applyPostInstallationMessages()
|
||||
{
|
||||
// Make sure it's Joomla! 3.2.0 or later
|
||||
if (!version_compare(JVERSION, '3.2.0', 'ge'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure there are post-installation messages
|
||||
if (empty($this->postInstallationMessages))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the extension ID for our component
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true);
|
||||
$query->select('extension_id')
|
||||
->from('#__extensions')
|
||||
->where($db->qn('type') . ' = ' . $db->q('component'))
|
||||
->where($db->qn('element') . ' = ' . $db->q($this->componentName));
|
||||
$db->setQuery($query);
|
||||
|
||||
try
|
||||
{
|
||||
$ids = $db->loadColumn();
|
||||
}
|
||||
catch (Exception $exc)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($ids))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$extension_id = array_shift($ids);
|
||||
|
||||
foreach ($this->postInstallationMessages as $message)
|
||||
{
|
||||
$message['extension_id'] = $extension_id;
|
||||
$this->addPostInstallationMessage($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls the post-installation messages for Joomla! 3.2 or later
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function uninstallPostInstallationMessages()
|
||||
{
|
||||
// Make sure it's Joomla! 3.2.0 or later
|
||||
if (!version_compare(JVERSION, '3.2.0', 'ge'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure there are post-installation messages
|
||||
if (empty($this->postInstallationMessages))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the extension ID for our component
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true);
|
||||
$query->select('extension_id')
|
||||
->from('#__extensions')
|
||||
->where($db->qn('type') . ' = ' . $db->q('component'))
|
||||
->where($db->qn('element') . ' = ' . $db->q($this->componentName));
|
||||
$db->setQuery($query);
|
||||
|
||||
try
|
||||
{
|
||||
$ids = $db->loadColumn();
|
||||
}
|
||||
catch (Exception $exc)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($ids))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$extension_id = array_shift($ids);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->delete($db->qn('#__postinstall_messages'))
|
||||
->where($db->qn('extension_id') . ' = ' . $db->q($extension_id));
|
||||
|
||||
try
|
||||
{
|
||||
$db->setQuery($query)->execute();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
1303
libraries/fof30/Utils/InstallScript/Component.php
Normal file
1303
libraries/fof30/Utils/InstallScript/Component.php
Normal file
File diff suppressed because it is too large
Load Diff
231
libraries/fof30/Utils/InstallScript/Module.php
Normal file
231
libraries/fof30/Utils/InstallScript/Module.php
Normal file
@ -0,0 +1,231 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils\InstallScript;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF30\Database\Installer;
|
||||
use JLoader;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Installer\Adapter\ComponentAdapter;
|
||||
use Joomla\CMS\Log\Log;
|
||||
|
||||
// In case FOF's autoloader is not present yet, e.g. new installation
|
||||
if (!class_exists('FOF30\\Utils\\InstallScript\\BaseInstaller', true))
|
||||
{
|
||||
require_once __DIR__ . '/BaseInstaller.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper class which you can use to create module installation scripts.
|
||||
*
|
||||
* Example usage: class Mod_ExampleInstallerScript extends FOF30\Utils\InstallScript\Module
|
||||
*
|
||||
* This namespace contains more classes for creating installation scripts for other kinds of Joomla! extensions as well.
|
||||
* Do keep in mind that only components, modules and plugins could have post-installation scripts before Joomla! 3.3.
|
||||
*/
|
||||
class Module extends BaseInstaller
|
||||
{
|
||||
/**
|
||||
* Which side of the site is this module installed in? Use 'site' or 'administrator'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $moduleClient = 'site';
|
||||
|
||||
/**
|
||||
* The modules's name, e.g. mod_foobar. Auto-filled from the class name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $moduleName = '';
|
||||
|
||||
/**
|
||||
* The path where the schema XML files are stored. The path is relative to the folder which contains the extension's
|
||||
* files.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $schemaXmlPath = 'sql/xml';
|
||||
|
||||
|
||||
/**
|
||||
* Module installer script constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Get the plugin name and folder from the class name (it's always plgFolderPluginInstallerScript) if necessary.
|
||||
if (empty($this->moduleName))
|
||||
{
|
||||
$class = get_class($this);
|
||||
$words = preg_replace('/(\s)+/', '_', $class);
|
||||
$words = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $words));
|
||||
$classParts = explode('_', $words);
|
||||
|
||||
$this->moduleName = 'mod_' . $classParts[2];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Joomla! pre-flight event. This runs before Joomla! installs or updates the component. This is our last chance to
|
||||
* tell Joomla! if it should abort the installation.
|
||||
*
|
||||
* @param string $type Installation type (install, update,
|
||||
* discover_install)
|
||||
* @param ComponentAdapter $parent Parent object
|
||||
*
|
||||
* @return boolean True to let the installation proceed, false to halt the installation
|
||||
*/
|
||||
public function preflight($type, $parent)
|
||||
{
|
||||
// Check the minimum PHP version
|
||||
if (!$this->checkPHPVersion())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the minimum Joomla! version
|
||||
if (!$this->checkJoomlaVersion())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear op-code caches to prevent any cached code issues
|
||||
$this->clearOpcodeCaches();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs after install, update or discover_update. In other words, it executes after Joomla! has finished installing
|
||||
* or updating your component. This is the last chance you've got to perform any additional installations, clean-up,
|
||||
* database updates and similar housekeeping functions.
|
||||
*
|
||||
* @param string $type install, update or discover_update
|
||||
* @param ComponentAdapter $parent Parent object
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
public function postflight($type, $parent)
|
||||
{
|
||||
/**
|
||||
* We are not doing dependency tracking for modules and plugins because of the way Joomla package uninstallation
|
||||
* works. FOF's uninstall() method would get called before the extensions are uninstalled, therefore its
|
||||
* uninstallation would fail and make the entire package uninstallation to fail (the package is impossible to
|
||||
* uninstall).
|
||||
*/
|
||||
// Add ourselves to the list of extensions depending on FOF30
|
||||
// $this->addDependency('fof30', $this->getDependencyName());
|
||||
// Install or update database
|
||||
$schemaPath = $parent->getParent()->getPath('source') . '/' . $this->schemaXmlPath;
|
||||
|
||||
if (@is_dir($schemaPath))
|
||||
{
|
||||
$dbInstaller = new Installer(Factory::getDbo(), $schemaPath);
|
||||
$dbInstaller->updateSchema();
|
||||
}
|
||||
|
||||
// Make sure everything is copied properly
|
||||
$this->bugfixFilesNotCopiedOnUpdate($parent);
|
||||
|
||||
// Add post-installation messages on Joomla! 3.2 and later
|
||||
$this->_applyPostInstallationMessages();
|
||||
|
||||
// Clear the opcode caches again - in case someone accessed the extension while the files were being upgraded.
|
||||
$this->clearOpcodeCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on uninstallation
|
||||
*
|
||||
* @param ComponentAdapter $parent The parent object
|
||||
*/
|
||||
public function uninstall($parent)
|
||||
{
|
||||
// Uninstall database
|
||||
$schemaPath = $parent->getParent()->getPath('source') . '/' . $this->schemaXmlPath;
|
||||
|
||||
// Uninstall database
|
||||
if (@is_dir($schemaPath))
|
||||
{
|
||||
$dbInstaller = new Installer(Factory::getDbo(), $schemaPath);
|
||||
$dbInstaller->removeSchema();
|
||||
}
|
||||
|
||||
// Uninstall post-installation messages on Joomla! 3.2 and later
|
||||
$this->uninstallPostInstallationMessages();
|
||||
|
||||
/**
|
||||
* We are not doing dependency tracking for modules and plugins because of the way Joomla package uninstallation
|
||||
* works. FOF's uninstall() method would get called before the extensions are uninstalled, therefore its
|
||||
* uninstallation would fail and make the entire package uninstallation to fail (the package is impossible to
|
||||
* uninstall).
|
||||
*/
|
||||
// Remove ourselves from the list of extensions depending on FOF30
|
||||
// $this->removeDependency('fof30', $this->getDependencyName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix for Joomla bug: sometimes files are not copied on update.
|
||||
*
|
||||
* We have observed that ever since Joomla! 1.5.5, when Joomla! is performing an extension update some files /
|
||||
* folders are not copied properly. This seems to be a bit random and seems to be more likely to happen the more
|
||||
* added / modified files and folders you have. We are trying to work around it by retrying the copy operation
|
||||
* ourselves WITHOUT going through the manifest, based entirely on the conventions we follow for Akeeba Ltd's
|
||||
* extensions.
|
||||
*
|
||||
* @param ComponentAdapter $parent
|
||||
*/
|
||||
protected function bugfixFilesNotCopiedOnUpdate($parent)
|
||||
{
|
||||
Log::add("Joomla! extension update workaround for $this->moduleClient module $this->moduleName", Log::INFO, 'fof3_extension_installation');
|
||||
|
||||
$temporarySource = $parent->getParent()->getPath('source');
|
||||
$rootFolder = ($this->moduleClient == 'site') ? JPATH_SITE : JPATH_ADMINISTRATOR;
|
||||
|
||||
$copyMap = [
|
||||
// Module files
|
||||
$temporarySource => $rootFolder . '/modules/' . $this->moduleName,
|
||||
// Language
|
||||
$temporarySource . '/language' => $rootFolder . '/language',
|
||||
// Media files
|
||||
$temporarySource . '/media' => JPATH_ROOT . '/media/' . $this->moduleName,
|
||||
];
|
||||
|
||||
foreach ($copyMap as $source => $target)
|
||||
{
|
||||
Log::add(__CLASS__ . ":: Conditional copy $source to $target", Log::DEBUG, 'fof3_extension_installation');
|
||||
|
||||
$ignored = [];
|
||||
|
||||
if ($source == $temporarySource)
|
||||
{
|
||||
$ignored = [
|
||||
'index.html', 'index.htm', 'LICENSE.txt', 'license.txt', 'readme.htm', 'readme.html', 'README.md',
|
||||
'script.php', 'language', 'media',
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
$this->recursiveConditionalCopy($source, $target, $ignored);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extension name for FOF dependency tracking, e.g. mod_site_foobar
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDependencyName()
|
||||
{
|
||||
return 'mod_' . strtolower($this->moduleClient) . '_' . substr($this->moduleName, 4);
|
||||
}
|
||||
}
|
||||
238
libraries/fof30/Utils/InstallScript/Plugin.php
Normal file
238
libraries/fof30/Utils/InstallScript/Plugin.php
Normal file
@ -0,0 +1,238 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils\InstallScript;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF30\Database\Installer;
|
||||
use JLoader;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Installer\Adapter\ComponentAdapter;
|
||||
use Joomla\CMS\Log\Log;
|
||||
|
||||
// In case FOF's autoloader is not present yet, e.g. new installation
|
||||
if (!class_exists('FOF30\\Utils\\InstallScript\\BaseInstaller', true))
|
||||
{
|
||||
require_once __DIR__ . '/BaseInstaller.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper class which you can use to create plugin installation scripts.
|
||||
*
|
||||
* Example usage: class PlgSystemExampleInstallerScript extends FOF30\Utils\InstallScript\Module
|
||||
*
|
||||
* NB: The class name is always Plg<Plugin Folder><Plugin Name>InstallerScript per Joomla's conventions.
|
||||
*
|
||||
* This namespace contains more classes for creating installation scripts for other kinds of Joomla! extensions as well.
|
||||
* Do keep in mind that only components, modules and plugins could have post-installation scripts before Joomla! 3.3.
|
||||
*/
|
||||
class Plugin extends BaseInstaller
|
||||
{
|
||||
/**
|
||||
* The plugins's name, e.g. foobar (for plg_system_foobar). Auto-filled from the class name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pluginName = '';
|
||||
|
||||
/**
|
||||
* The plugins's folder, e.g. system (for plg_system_foobar). Auto-filled from the class name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pluginFolder = '';
|
||||
|
||||
/**
|
||||
* The path where the schema XML files are stored. The path is relative to the folder which contains the extension's
|
||||
* files.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $schemaXmlPath = 'sql/xml';
|
||||
|
||||
/**
|
||||
* Plugin installer script constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Get the plugin name and folder from the class name (it's always plgFolderPluginInstallerScript) if necessary.
|
||||
if (empty($this->pluginFolder) || empty($this->pluginName))
|
||||
{
|
||||
$class = get_class($this);
|
||||
$words = preg_replace('/(\s)+/', '_', $class);
|
||||
$words = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $words));
|
||||
$classParts = explode('_', $words);
|
||||
|
||||
if (empty($this->pluginFolder))
|
||||
{
|
||||
$this->pluginFolder = $classParts[1];
|
||||
}
|
||||
|
||||
if (empty($this->pluginName))
|
||||
{
|
||||
$this->pluginName = $classParts[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Joomla! pre-flight event. This runs before Joomla! installs or updates the component. This is our last chance to
|
||||
* tell Joomla! if it should abort the installation.
|
||||
*
|
||||
* @param string $type Installation type (install, update,
|
||||
* discover_install)
|
||||
* @param ComponentAdapter $parent Parent object
|
||||
*
|
||||
* @return boolean True to let the installation proceed, false to halt the installation
|
||||
*/
|
||||
public function preflight($type, $parent)
|
||||
{
|
||||
// Check the minimum PHP version
|
||||
if (!$this->checkPHPVersion())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the minimum Joomla! version
|
||||
if (!$this->checkJoomlaVersion())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear op-code caches to prevent any cached code issues
|
||||
$this->clearOpcodeCaches();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs after install, update or discover_update. In other words, it executes after Joomla! has finished installing
|
||||
* or updating your component. This is the last chance you've got to perform any additional installations, clean-up,
|
||||
* database updates and similar housekeeping functions.
|
||||
*
|
||||
* @param string $type install, update or discover_update
|
||||
* @param ComponentAdapter $parent Parent object
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
public function postflight($type, $parent)
|
||||
{
|
||||
/**
|
||||
* We are not doing dependency tracking for modules and plugins because of the way Joomla package uninstallation
|
||||
* works. FOF's uninstall() method would get called before the extensions are uninstalled, therefore its
|
||||
* uninstallation would fail and make the entire package uninstallation to fail (the package is impossible to
|
||||
* uninstall).
|
||||
*/
|
||||
// Add ourselves to the list of extensions depending on FOF30
|
||||
// $this->addDependency('fof30', $this->getDependencyName());
|
||||
// Install or update database
|
||||
$schemaPath = $parent->getParent()->getPath('source') . '/' . $this->schemaXmlPath;
|
||||
|
||||
if (@is_dir($schemaPath))
|
||||
{
|
||||
$dbInstaller = new Installer(Factory::getDbo(), $schemaPath);
|
||||
$dbInstaller->updateSchema();
|
||||
}
|
||||
|
||||
// Make sure everything is copied properly
|
||||
$this->bugfixFilesNotCopiedOnUpdate($parent);
|
||||
|
||||
// Add post-installation messages on Joomla! 3.2 and later
|
||||
$this->_applyPostInstallationMessages();
|
||||
|
||||
// Clear the opcode caches again - in case someone accessed the extension while the files were being upgraded.
|
||||
$this->clearOpcodeCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs on uninstallation
|
||||
*
|
||||
* @param ComponentAdapter $parent The parent object
|
||||
*/
|
||||
public function uninstall($parent)
|
||||
{
|
||||
// Uninstall database
|
||||
$schemaPath = $parent->getParent()->getPath('source') . '/' . $this->schemaXmlPath;
|
||||
|
||||
// Uninstall database
|
||||
if (@is_dir($schemaPath))
|
||||
{
|
||||
$dbInstaller = new Installer(Factory::getDbo(), $schemaPath);
|
||||
$dbInstaller->removeSchema();
|
||||
}
|
||||
|
||||
// Uninstall post-installation messages on Joomla! 3.2 and later
|
||||
$this->uninstallPostInstallationMessages();
|
||||
|
||||
/**
|
||||
* We are not doing dependency tracking for modules and plugins because of the way Joomla package uninstallation
|
||||
* works. FOF's uninstall() method would get called before the extensions are uninstalled, therefore its
|
||||
* uninstallation would fail and make the entire package uninstallation to fail (the package is impossible to
|
||||
* uninstall).
|
||||
*/
|
||||
// Remove ourselves from the list of extensions depending on FOF30
|
||||
// $this->removeDependency('fof30', $this->getDependencyName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix for Joomla bug: sometimes files are not copied on update.
|
||||
*
|
||||
* We have observed that ever since Joomla! 1.5.5, when Joomla! is performing an extension update some files /
|
||||
* folders are not copied properly. This seems to be a bit random and seems to be more likely to happen the more
|
||||
* added / modified files and folders you have. We are trying to work around it by retrying the copy operation
|
||||
* ourselves WITHOUT going through the manifest, based entirely on the conventions we follow for Akeeba Ltd's
|
||||
* extensions.
|
||||
*
|
||||
* @param ComponentAdapter $parent
|
||||
*/
|
||||
protected function bugfixFilesNotCopiedOnUpdate($parent)
|
||||
{
|
||||
Log::add("Joomla! extension update workaround for $this->pluginFolder plugin $this->pluginName", Log::INFO, 'fof3_extension_installation');
|
||||
|
||||
$temporarySource = $parent->getParent()->getPath('source');
|
||||
|
||||
$copyMap = [
|
||||
// Plugin files
|
||||
$temporarySource => JPATH_ROOT . '/plugins/' . $this->pluginFolder . '/' . $this->pluginName,
|
||||
// Language (always stored in administrator for plugins)
|
||||
$temporarySource . '/language' => JPATH_ADMINISTRATOR . '/language',
|
||||
// Media files, e.g. /media/plg_system_foobar
|
||||
$temporarySource . '/media' => JPATH_ROOT . '/media/' . $this->getDependencyName(),
|
||||
];
|
||||
|
||||
foreach ($copyMap as $source => $target)
|
||||
{
|
||||
Log::add(__CLASS__ . ":: Conditional copy $source to $target", Log::DEBUG, 'fof3_extension_installation');
|
||||
|
||||
$ignored = [];
|
||||
|
||||
if ($source == $temporarySource)
|
||||
{
|
||||
$ignored = [
|
||||
'index.html', 'index.htm', 'LICENSE.txt', 'license.txt', 'readme.htm', 'readme.html', 'README.md',
|
||||
'script.php', 'language', 'media',
|
||||
];
|
||||
}
|
||||
|
||||
$this->recursiveConditionalCopy($source, $target, $ignored);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extension name for FOF dependency tracking, e.g. plg_system_foobar
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getDependencyName()
|
||||
{
|
||||
return 'plg_' . strtolower($this->pluginFolder) . '_' . $this->pluginName;
|
||||
}
|
||||
}
|
||||
628
libraries/fof30/Utils/Ip.php
Normal file
628
libraries/fof30/Utils/Ip.php
Normal file
@ -0,0 +1,628 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* IP address helper
|
||||
*
|
||||
* Makes sure that we get the real IP of the user
|
||||
*/
|
||||
class Ip
|
||||
{
|
||||
/**
|
||||
* The IP address of the current visitor
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $ip = null;
|
||||
|
||||
/**
|
||||
* Should I allow IP overrides through X-Forwarded-For or Client-Ip HTTP headers?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $allowIpOverrides = true;
|
||||
|
||||
/**
|
||||
* See self::detectAndCleanIP and setUseFirstIpInChain
|
||||
*
|
||||
* If this is enabled (default) self::detectAndCleanIP will return the FIRST IP in case there is an IP chain coming
|
||||
* for example from an X-Forwarded-For HTTP header. When set to false it will simulate the old behavior in FOF up to
|
||||
* and including 3.1.1 which returned the LAST IP in the list.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $useFirstIpInChain = true;
|
||||
|
||||
/**
|
||||
* List of headers we should check to know if the user is behind a proxy. PLEASE NOTE: ORDER MATTERS!
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $proxyHeaders = [
|
||||
'HTTP_CF_CONNECTING_IP', // CloudFlare
|
||||
'HTTP_X_FORWARDED_FOR', // Standard for transparent proxy (e.g. NginX)
|
||||
'HTTP_X_SUCURI_CLIENTIP', // Sucuri firewall uses its own header
|
||||
];
|
||||
|
||||
/**
|
||||
* Set the $useFirstIpInChain flag. See above.
|
||||
*
|
||||
* @param bool $value
|
||||
*/
|
||||
public static function setUseFirstIpInChain($value = true)
|
||||
{
|
||||
self::$useFirstIpInChain = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the list of proxy headers we can check
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getProxyHeaders()
|
||||
{
|
||||
return static::$proxyHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current visitor's IP address
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getIp()
|
||||
{
|
||||
if (is_null(static::$ip))
|
||||
{
|
||||
$ip = self::detectAndCleanIP();
|
||||
|
||||
if (!empty($ip) && ($ip != '0.0.0.0') && function_exists('inet_pton') && function_exists('inet_ntop'))
|
||||
{
|
||||
$myIP = @inet_pton($ip);
|
||||
|
||||
if ($myIP !== false)
|
||||
{
|
||||
$ip = inet_ntop($myIP);
|
||||
}
|
||||
}
|
||||
|
||||
static::setIp($ip);
|
||||
}
|
||||
|
||||
return static::$ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IP address of the current visitor
|
||||
*
|
||||
* @param string $ip
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setIp($ip)
|
||||
{
|
||||
static::$ip = $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an IP is contained in a list of IPs or IP expressions
|
||||
*
|
||||
* @param string $ip The IPv4/IPv6 address to check
|
||||
* @param array|string $ipTable An IP expression (or a comma-separated or array list of IP expressions) to
|
||||
* check against
|
||||
*
|
||||
* @return null|boolean True if it's in the list
|
||||
*/
|
||||
public static function IPinList($ip, $ipTable = '')
|
||||
{
|
||||
// No point proceeding with an empty IP list
|
||||
if (empty($ipTable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the IP list is not an array, convert it to an array
|
||||
if (!is_array($ipTable))
|
||||
{
|
||||
if (strpos($ipTable, ',') !== false)
|
||||
{
|
||||
$ipTable = explode(',', $ipTable);
|
||||
$ipTable = array_map(function ($x) {
|
||||
return trim($x);
|
||||
}, $ipTable);
|
||||
}
|
||||
else
|
||||
{
|
||||
$ipTable = trim($ipTable);
|
||||
$ipTable = [$ipTable];
|
||||
}
|
||||
}
|
||||
|
||||
// If no IP address is found, return false
|
||||
if ($ip == '0.0.0.0')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no IP is given, return false
|
||||
if (empty($ip))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (!function_exists('inet_pton'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the IP's in_adds representation
|
||||
$myIP = @inet_pton($ip);
|
||||
|
||||
// If the IP is in an unrecognisable format, quite
|
||||
if ($myIP === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$ipv6 = self::isIPv6($ip);
|
||||
|
||||
foreach ($ipTable as $ipExpression)
|
||||
{
|
||||
$ipExpression = trim($ipExpression);
|
||||
|
||||
// Inclusive IP range, i.e. 123.123.123.123-124.125.126.127
|
||||
if (strstr($ipExpression, '-'))
|
||||
{
|
||||
[$from, $to] = explode('-', $ipExpression, 2);
|
||||
|
||||
if ($ipv6 && (!self::isIPv6($from) || !self::isIPv6($to)))
|
||||
{
|
||||
// Do not apply IPv4 filtering on an IPv6 address
|
||||
continue;
|
||||
}
|
||||
elseif (!$ipv6 && (self::isIPv6($from) || self::isIPv6($to)))
|
||||
{
|
||||
// Do not apply IPv6 filtering on an IPv4 address
|
||||
continue;
|
||||
}
|
||||
|
||||
$from = @inet_pton(trim($from));
|
||||
$to = @inet_pton(trim($to));
|
||||
|
||||
// Sanity check
|
||||
if (($from === false) || ($to === false))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Swap from/to if they're in the wrong order
|
||||
if ($from > $to)
|
||||
{
|
||||
[$from, $to] = [$to, $from];
|
||||
}
|
||||
|
||||
if (($myIP >= $from) && ($myIP <= $to))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Netmask or CIDR provided
|
||||
elseif (strstr($ipExpression, '/'))
|
||||
{
|
||||
$binaryip = self::inet_to_bits($myIP);
|
||||
|
||||
[$net, $maskbits] = explode('/', $ipExpression, 2);
|
||||
if ($ipv6 && !self::isIPv6($net))
|
||||
{
|
||||
// Do not apply IPv4 filtering on an IPv6 address
|
||||
continue;
|
||||
}
|
||||
elseif (!$ipv6 && self::isIPv6($net))
|
||||
{
|
||||
// Do not apply IPv6 filtering on an IPv4 address
|
||||
continue;
|
||||
}
|
||||
elseif ($ipv6 && strstr($maskbits, ':'))
|
||||
{
|
||||
// Perform an IPv6 CIDR check
|
||||
if (self::checkIPv6CIDR($myIP, $ipExpression))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we didn't match it proceed to the next expression
|
||||
continue;
|
||||
}
|
||||
elseif (!$ipv6 && strstr($maskbits, '.'))
|
||||
{
|
||||
// Convert IPv4 netmask to CIDR
|
||||
$long = ip2long($maskbits);
|
||||
$base = ip2long('255.255.255.255');
|
||||
$maskbits = 32 - log(($long ^ $base) + 1, 2);
|
||||
}
|
||||
|
||||
// Convert network IP to in_addr representation
|
||||
$net = @inet_pton($net);
|
||||
|
||||
// Sanity check
|
||||
if ($net === false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the network's binary representation
|
||||
$binarynet = self::inet_to_bits($net);
|
||||
$expectedNumberOfBits = $ipv6 ? 128 : 24;
|
||||
$binarynet = str_pad($binarynet, $expectedNumberOfBits, '0', STR_PAD_RIGHT);
|
||||
|
||||
// Check the corresponding bits of the IP and the network
|
||||
$ip_net_bits = substr($binaryip, 0, $maskbits);
|
||||
$net_bits = substr($binarynet, 0, $maskbits);
|
||||
|
||||
if ($ip_net_bits == $net_bits)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// IPv6: Only single IPs are supported
|
||||
if ($ipv6)
|
||||
{
|
||||
$ipExpression = trim($ipExpression);
|
||||
|
||||
if (!self::isIPv6($ipExpression))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$ipCheck = @inet_pton($ipExpression);
|
||||
if ($ipCheck === false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($ipCheck == $myIP)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Standard IPv4 address, i.e. 123.123.123.123 or partial IP address, i.e. 123.[123.][123.][123]
|
||||
$dots = 0;
|
||||
if (substr($ipExpression, -1) == '.')
|
||||
{
|
||||
// Partial IP address. Convert to CIDR and re-match
|
||||
foreach (count_chars($ipExpression, 1) as $i => $val)
|
||||
{
|
||||
if ($i == 46)
|
||||
{
|
||||
$dots = $val;
|
||||
}
|
||||
}
|
||||
|
||||
$netmask = '255.255.255.255';
|
||||
|
||||
switch ($dots)
|
||||
{
|
||||
case 1:
|
||||
$netmask = '255.0.0.0';
|
||||
$ipExpression .= '0.0.0';
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$netmask = '255.255.0.0';
|
||||
$ipExpression .= '0.0';
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$netmask = '255.255.255.0';
|
||||
$ipExpression .= '0';
|
||||
break;
|
||||
|
||||
default:
|
||||
$dots = 0;
|
||||
}
|
||||
|
||||
if ($dots)
|
||||
{
|
||||
$binaryip = self::inet_to_bits($myIP);
|
||||
|
||||
// Convert netmask to CIDR
|
||||
$long = ip2long($netmask);
|
||||
$base = ip2long('255.255.255.255');
|
||||
$maskbits = 32 - log(($long ^ $base) + 1, 2);
|
||||
|
||||
$net = @inet_pton($ipExpression);
|
||||
|
||||
// Sanity check
|
||||
if ($net === false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the network's binary representation
|
||||
$binarynet = self::inet_to_bits($net);
|
||||
$expectedNumberOfBits = $ipv6 ? 128 : 24;
|
||||
$binarynet = str_pad($binarynet, $expectedNumberOfBits, '0', STR_PAD_RIGHT);
|
||||
|
||||
// Check the corresponding bits of the IP and the network
|
||||
$ip_net_bits = substr($binaryip, 0, $maskbits);
|
||||
$net_bits = substr($binarynet, 0, $maskbits);
|
||||
|
||||
if ($ip_net_bits == $net_bits)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$dots)
|
||||
{
|
||||
$ip = @inet_pton(trim($ipExpression));
|
||||
if ($ip == $myIP)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Works around the REMOTE_ADDR not containing the user's IP
|
||||
*/
|
||||
public static function workaroundIPIssues()
|
||||
{
|
||||
$ip = self::getIp();
|
||||
|
||||
if (array_key_exists('REMOTE_ADDR', $_SERVER) && ($_SERVER['REMOTE_ADDR'] == $ip))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (array_key_exists('REMOTE_ADDR', $_SERVER))
|
||||
{
|
||||
$_SERVER['FOF_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
elseif (function_exists('getenv'))
|
||||
{
|
||||
if (getenv('REMOTE_ADDR'))
|
||||
{
|
||||
$_SERVER['FOF_REMOTE_ADDR'] = getenv('REMOTE_ADDR');
|
||||
}
|
||||
}
|
||||
|
||||
$_SERVER['REMOTE_ADDR'] = $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should I allow the remote client's IP to be overridden by an X-Forwarded-For or Client-Ip HTTP header?
|
||||
*
|
||||
* @param bool $newState True to allow the override
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setAllowIpOverrides($newState)
|
||||
{
|
||||
self::$allowIpOverrides = $newState ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is it an IPv6 IP address?
|
||||
*
|
||||
* @param string $ip An IPv4 or IPv6 address
|
||||
*
|
||||
* @return boolean True if it's IPv6
|
||||
*/
|
||||
protected static function isIPv6($ip)
|
||||
{
|
||||
if (strstr($ip, ':'))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the visitor's IP address. Automatically handles reverse proxies
|
||||
* reporting the IPs of intermediate devices, like load balancers. Examples:
|
||||
* https://www.akeeba.com/support/admin-tools/13743-double-ip-adresses-in-security-exception-log-warnings.html
|
||||
* http://stackoverflow.com/questions/2422395/why-is-request-envremote-addr-returning-two-ips
|
||||
* The solution used is assuming that the first IP address is the external one (unless $useFirstIpInChain is set to
|
||||
* false)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function detectAndCleanIP()
|
||||
{
|
||||
$ip = static::detectIP();
|
||||
|
||||
if ((strstr($ip, ',') !== false) || (strstr($ip, ' ') !== false))
|
||||
{
|
||||
$ip = str_replace(' ', ',', $ip);
|
||||
$ip = str_replace(',,', ',', $ip);
|
||||
$ips = explode(',', $ip);
|
||||
$ip = '';
|
||||
|
||||
// Loop until we're running out of parts or we have a hit
|
||||
while ($ips)
|
||||
{
|
||||
$ip = array_shift($ips);
|
||||
$ip = trim($ip);
|
||||
|
||||
if (self::$useFirstIpInChain)
|
||||
{
|
||||
return self::cleanIP($ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::cleanIP($ip);
|
||||
}
|
||||
|
||||
protected static function cleanIP($ip)
|
||||
{
|
||||
$ip = trim($ip);
|
||||
$ip = strtoupper($ip);
|
||||
|
||||
/**
|
||||
* Work around IPv4-mapped addresses.
|
||||
*
|
||||
* IPv4 addresses may be embedded in an IPv6 address. This is always 80 zeroes, 16 ones and the IPv4 address.
|
||||
* In all possible IPv6 notations this is:
|
||||
* 0:0:0:0:0:FFFF:192.168.1.1
|
||||
* ::FFFF:192.168.1.1
|
||||
* ::FFFF:C0A8:0101
|
||||
*
|
||||
* @see http://www.tcpipguide.com/free/t_IPv6IPv4AddressEmbedding-2.htm
|
||||
*/
|
||||
if ((strpos($ip, '::FFFF:') === 0) || (strpos($ip, '0:0:0:0:0:FFFF:') === 0))
|
||||
{
|
||||
// Fast path: the embedded IPv4 is in decimal notation.
|
||||
if (strstr($ip, '.') !== false)
|
||||
{
|
||||
return substr($ip, strrpos($ip, ':') + 1);
|
||||
}
|
||||
|
||||
// Get the embedded IPv4 (in hex notation)
|
||||
$ip = substr($ip, strpos($ip, ':FFFF:') + 6);
|
||||
// Convert each 16-bit WORD to decimal
|
||||
[$word1, $word2] = explode(':', $ip);
|
||||
$word1 = hexdec($word1);
|
||||
$word2 = hexdec($word2);
|
||||
$longIp = $word1 * 65536 + $word2;
|
||||
|
||||
return long2ip($longIp);
|
||||
}
|
||||
|
||||
return $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the visitor's IP address
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function detectIP()
|
||||
{
|
||||
// Normally the $_SERVER superglobal is set
|
||||
if (isset($_SERVER))
|
||||
{
|
||||
// Do we have IP overrides enabled?
|
||||
if (static::$allowIpOverrides)
|
||||
{
|
||||
// If so, check for every proxy header
|
||||
foreach (static::$proxyHeaders as $header)
|
||||
{
|
||||
if (array_key_exists($header, $_SERVER))
|
||||
{
|
||||
return $_SERVER[$header];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CLI applications
|
||||
if (!array_key_exists('REMOTE_ADDR', $_SERVER))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// Normal, non-proxied server or server behind a transparent proxy
|
||||
return $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
|
||||
// This part is executed on PHP running as CGI, or on SAPIs which do
|
||||
// not set the $_SERVER superglobal
|
||||
// If getenv() is disabled, you're screwed
|
||||
if (!function_exists('getenv'))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// Do we have IP overrides enabled?
|
||||
if (static::$allowIpOverrides)
|
||||
{
|
||||
// If so, check for every proxy header
|
||||
foreach (static::$proxyHeaders as $header)
|
||||
{
|
||||
if (getenv($header))
|
||||
{
|
||||
return getenv($header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal, non-proxied server or server behind a transparent proxy
|
||||
if (getenv('REMOTE_ADDR'))
|
||||
{
|
||||
return getenv('REMOTE_ADDR');
|
||||
}
|
||||
|
||||
// Catch-all case for broken servers and CLI applications
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts inet_pton output to bits string
|
||||
*
|
||||
* @param string $inet The in_addr representation of an IPv4 or IPv6 address
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function inet_to_bits($inet)
|
||||
{
|
||||
if (strlen($inet) == 4)
|
||||
{
|
||||
$unpacked = unpack('C4', $inet);
|
||||
}
|
||||
else
|
||||
{
|
||||
$unpacked = unpack('C16', $inet);
|
||||
}
|
||||
|
||||
$binaryip = '';
|
||||
|
||||
foreach ($unpacked as $byte)
|
||||
{
|
||||
$binaryip .= str_pad(decbin($byte), 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
return $binaryip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an IPv6 address $ip is part of the IPv6 CIDR block $cidrnet
|
||||
*
|
||||
* @param string $ip The IPv6 address to check, e.g. 21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A
|
||||
* @param string $cidrnet The IPv6 CIDR block, e.g. 21DA:00D3:0000:2F3B::/64
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function checkIPv6CIDR($ip, $cidrnet)
|
||||
{
|
||||
$ip = inet_pton($ip);
|
||||
$binaryip = self::inet_to_bits($ip);
|
||||
|
||||
[$net, $maskbits] = explode('/', $cidrnet);
|
||||
$net = inet_pton($net);
|
||||
$binarynet = self::inet_to_bits($net);
|
||||
|
||||
$ip_net_bits = substr($binaryip, 0, $maskbits);
|
||||
$net_bits = substr($binarynet, 0, $maskbits);
|
||||
|
||||
return $ip_net_bits === $net_bits;
|
||||
}
|
||||
}
|
||||
215
libraries/fof30/Utils/MediaVersion.php
Normal file
215
libraries/fof30/Utils/MediaVersion.php
Normal file
@ -0,0 +1,215 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF30\Container\Container;
|
||||
use JDatabaseDriver;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
/**
|
||||
* Class MediaVersion
|
||||
* @package FOF30\Utils
|
||||
*
|
||||
* @since 3.5.3
|
||||
*/
|
||||
class MediaVersion
|
||||
{
|
||||
/**
|
||||
* Cached the version and date of FOF-powered components
|
||||
*
|
||||
* @var array
|
||||
* @since 3.5.3
|
||||
*/
|
||||
protected static $componentVersionCache = [];
|
||||
|
||||
/**
|
||||
* The current component's container
|
||||
*
|
||||
* @var Container
|
||||
* @since 3.5.3
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* The configured media query version
|
||||
*
|
||||
* @var string|null;
|
||||
* @since 3.5.3
|
||||
*/
|
||||
protected $mediaVersion;
|
||||
|
||||
/**
|
||||
* MediaVersion constructor.
|
||||
*
|
||||
* @param Container $c The component container
|
||||
*
|
||||
* @since 3.5.3
|
||||
*/
|
||||
public function __construct(Container $c)
|
||||
{
|
||||
$this->container = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a component's version and date
|
||||
*
|
||||
* @param string $component
|
||||
* @param JDatabaseDriver $db
|
||||
*
|
||||
* @return array
|
||||
* @since 3.5.3
|
||||
*/
|
||||
protected static function getComponentVersionAndDate($component, $db)
|
||||
{
|
||||
if (array_key_exists($component, self::$componentVersionCache))
|
||||
{
|
||||
return self::$componentVersionCache[$component];
|
||||
}
|
||||
|
||||
$version = '0.0.0';
|
||||
$date = date('Y-m-d H:i:s');
|
||||
|
||||
try
|
||||
{
|
||||
$query = $db->getQuery(true)
|
||||
->select([
|
||||
$db->qn('manifest_cache'),
|
||||
])->from($db->qn('#__extensions'))
|
||||
->where($db->qn('type') . ' = ' . $db->q('component'))
|
||||
->where($db->qn('name') . ' = ' . $db->q($component));
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
$json = $db->loadResult();
|
||||
|
||||
if (class_exists('JRegistry'))
|
||||
{
|
||||
$params = new Registry($json);
|
||||
}
|
||||
else
|
||||
{
|
||||
$params = new Registry($json);
|
||||
}
|
||||
|
||||
$version = $params->get('version', $version);
|
||||
$date = $params->get('creationDate', $date);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
}
|
||||
|
||||
self::$componentVersionCache[$component] = [$version, $date];
|
||||
|
||||
return self::$componentVersionCache[$component];
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialization helper
|
||||
*
|
||||
* This is for the benefit of legacy components which might use Joomla's JS/CSS inclusion directly passing
|
||||
* $container->mediaVersion as the version argument. In FOF 3.5.2 and lower that was always string or null, making
|
||||
* it a safe bet. In FOF 3.5.3 and later it's an object. It's not converted to a string until Joomla builds its
|
||||
* template header. However, Joomla's cache system will try to serialize all CSS and JS definitions, including their
|
||||
* parameters of which version is one. Therefore, for those legacy applications, Joomla would be trying to serialize
|
||||
* the MediaVersion object which would try to serialize the container. That would cause an immediate failure since
|
||||
* we protect the Container from being serialized.
|
||||
*
|
||||
* Our Template service knows about this and stringifies the MediaVersion before passing it to Joomla. Legacy apps
|
||||
* may not do that. Using the __sleep and __wakeup methods in this class we make sure that we are essentially
|
||||
* storing nothing but strings in the serialized representation and we reconstruct the container upon
|
||||
* unseralization. That said, it's a good idea to use the Template service instead of $container->mediaVersion
|
||||
* directly or, at the very least, use (string) $container->mediaVersion when using the Template service is not a
|
||||
* viable option.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
$this->componentName = $this->container->componentName;
|
||||
|
||||
return [
|
||||
'mediaVersion',
|
||||
'componentName',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialization helper
|
||||
*
|
||||
* @return void
|
||||
* @see __sleep
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
if (isset($this->componentName))
|
||||
{
|
||||
$this->container = Container::getInstance($this->componentName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media query version string
|
||||
*
|
||||
* @return string
|
||||
* @since 3.5.3
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
if (empty($this->mediaVersion))
|
||||
{
|
||||
$this->mediaVersion = $this->getDefaultMediaVersion();
|
||||
}
|
||||
|
||||
return $this->mediaVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media query version string
|
||||
*
|
||||
* @param mixed $mediaVersion
|
||||
*
|
||||
* @since 3.5.3
|
||||
*/
|
||||
public function setMediaVersion($mediaVersion)
|
||||
{
|
||||
$this->mediaVersion = $mediaVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default media query version string if none is already defined
|
||||
*
|
||||
* @return string
|
||||
* @since 3.5.3
|
||||
*/
|
||||
protected function getDefaultMediaVersion()
|
||||
{
|
||||
// Initialise
|
||||
[$version, $date] = self::getComponentVersionAndDate($this->container->componentName, $this->container->db);
|
||||
|
||||
// Get the site's secret
|
||||
try
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
if (method_exists($app, 'get'))
|
||||
{
|
||||
$secret = $app->get('secret');
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
}
|
||||
|
||||
// Generate the version string
|
||||
return md5($version . $date . $secret);
|
||||
}
|
||||
}
|
||||
315
libraries/fof30/Utils/ModelTypeHints.php
Normal file
315
libraries/fof30/Utils/ModelTypeHints.php
Normal file
@ -0,0 +1,315 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF30\Model\DataModel;
|
||||
use FOF30\Model\DataModel\Relation\Exception\RelationNotFound;
|
||||
|
||||
/**
|
||||
* Generate phpDoc type hints for the magic fields of your DataModels
|
||||
*
|
||||
* @package FOF30\Utils
|
||||
*/
|
||||
class ModelTypeHints
|
||||
{
|
||||
/**
|
||||
* The model for which to create type hints
|
||||
*
|
||||
* @var DataModel
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* Name of the class. If empty will be inferred from the current object
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $className = null;
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param DataModel $model The model to create hints for
|
||||
*/
|
||||
public function __construct(DataModel $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->className = get_class($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the database field type into a PHP base type
|
||||
*
|
||||
* @param string $type The type of the field
|
||||
*
|
||||
* @return string The PHP base type
|
||||
*/
|
||||
public static function getFieldType($type)
|
||||
{
|
||||
// Remove parentheses, indicating field options / size (they don't matter in type detection)
|
||||
if (!empty($type))
|
||||
{
|
||||
[$type, ] = explode('(', $type);
|
||||
}
|
||||
|
||||
$detectedType = null;
|
||||
|
||||
switch (trim($type))
|
||||
{
|
||||
case 'varchar':
|
||||
case 'text':
|
||||
case 'smalltext':
|
||||
case 'longtext':
|
||||
case 'char':
|
||||
case 'mediumtext':
|
||||
case 'character varying':
|
||||
case 'nvarchar':
|
||||
case 'nchar':
|
||||
$detectedType = 'string';
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
case 'time':
|
||||
case 'year':
|
||||
case 'timestamp':
|
||||
case 'timestamp without time zone':
|
||||
case 'timestamp with time zone':
|
||||
$detectedType = 'string';
|
||||
break;
|
||||
|
||||
case 'tinyint':
|
||||
case 'smallint':
|
||||
$detectedType = 'bool';
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
case 'currency':
|
||||
case 'single':
|
||||
case 'double':
|
||||
$detectedType = 'float';
|
||||
break;
|
||||
}
|
||||
|
||||
// Sometimes we have character types followed by a space and some cruft. Let's handle them.
|
||||
if (is_null($detectedType) && !empty($type))
|
||||
{
|
||||
[$type, ] = explode(' ', $type);
|
||||
|
||||
switch (trim($type))
|
||||
{
|
||||
case 'varchar':
|
||||
case 'text':
|
||||
case 'smalltext':
|
||||
case 'longtext':
|
||||
case 'char':
|
||||
case 'mediumtext':
|
||||
case 'nvarchar':
|
||||
case 'nchar':
|
||||
$detectedType = 'string';
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
case 'time':
|
||||
case 'year':
|
||||
case 'timestamp':
|
||||
case 'enum':
|
||||
$detectedType = 'string';
|
||||
break;
|
||||
|
||||
case 'tinyint':
|
||||
case 'smallint':
|
||||
$detectedType = 'bool';
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
case 'currency':
|
||||
case 'single':
|
||||
case 'double':
|
||||
$detectedType = 'float';
|
||||
break;
|
||||
|
||||
default:
|
||||
$detectedType = 'int';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If all else fails assume it's an int and hope for the best
|
||||
if (empty($detectedType))
|
||||
{
|
||||
$detectedType = 'int';
|
||||
}
|
||||
|
||||
return $detectedType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*/
|
||||
public function setClassName($className)
|
||||
{
|
||||
$this->className = $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the raw hints array
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws RelationNotFound
|
||||
*/
|
||||
public function getRawHints()
|
||||
{
|
||||
$model = $this->model;
|
||||
|
||||
$hints = [
|
||||
'property' => [],
|
||||
'method' => [],
|
||||
'property-read' => [],
|
||||
];
|
||||
|
||||
$hasFilters = $model->getBehavioursDispatcher()->hasObserverClass('FOF30\Model\DataModel\Behaviour\Filters');
|
||||
|
||||
$magicFields = [
|
||||
'enabled', 'ordering', 'created_on', 'created_by', 'modified_on', 'modified_by', 'locked_on', 'locked_by',
|
||||
];
|
||||
|
||||
foreach ($model->getTableFields() as $fieldName => $fieldMeta)
|
||||
{
|
||||
$fieldType = self::getFieldType($fieldMeta->Type);
|
||||
|
||||
if (!in_array($fieldName, $magicFields))
|
||||
{
|
||||
$hints['property'][] = [$fieldType, '$' . $fieldName];
|
||||
}
|
||||
|
||||
if ($hasFilters)
|
||||
{
|
||||
$hints['method'][] = [
|
||||
'$this',
|
||||
$fieldName . '()',
|
||||
$fieldName . '(' . $fieldType . ' $v)',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$relations = $model->getRelations()->getRelationNames();
|
||||
|
||||
$modelType = get_class($model);
|
||||
$modelTypeParts = explode('\\', $modelType);
|
||||
array_pop($modelTypeParts);
|
||||
$modelType = implode('\\', $modelTypeParts) . '\\';
|
||||
|
||||
if ($relations)
|
||||
{
|
||||
foreach ($relations as $relationName)
|
||||
{
|
||||
$relationObject = $model->getRelations()->getRelation($relationName)->getForeignModel();
|
||||
$relationType = get_class($relationObject);
|
||||
$relationType = str_replace($modelType, '', $relationType);
|
||||
|
||||
$hints['property-read'][] = [
|
||||
$relationType,
|
||||
'$' . $relationName,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the docblock with the magic field hints for the model class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHints()
|
||||
{
|
||||
$modelName = $this->className;
|
||||
|
||||
$text = "/**\n * Model $modelName\n *\n";
|
||||
|
||||
$hints = $this->getRawHints();
|
||||
|
||||
if (!empty($hints['property']))
|
||||
{
|
||||
$text .= " * Fields:\n *\n";
|
||||
|
||||
$colWidth = 0;
|
||||
|
||||
foreach ($hints['property'] as $hintLine)
|
||||
{
|
||||
$colWidth = max($colWidth, strlen($hintLine[0]));
|
||||
}
|
||||
|
||||
$colWidth += 2;
|
||||
|
||||
foreach ($hints['property'] as $hintLine)
|
||||
{
|
||||
$text .= " * @property " . str_pad($hintLine[0], $colWidth, ' ') . $hintLine[1] . "\n";
|
||||
}
|
||||
|
||||
$text .= " *\n";
|
||||
}
|
||||
|
||||
if (!empty($hints['method']))
|
||||
{
|
||||
$text .= " * Filters:\n *\n";
|
||||
|
||||
$colWidth = 0;
|
||||
$col2Width = 0;
|
||||
|
||||
foreach ($hints['method'] as $hintLine)
|
||||
{
|
||||
$colWidth = max($colWidth, strlen($hintLine[0]));
|
||||
$col2Width = max($col2Width, strlen($hintLine[1]));
|
||||
}
|
||||
|
||||
$colWidth += 2;
|
||||
$col2Width += 2;
|
||||
|
||||
foreach ($hints['method'] as $hintLine)
|
||||
{
|
||||
$text .= " * @method " . str_pad($hintLine[0], $colWidth, ' ')
|
||||
. str_pad($hintLine[1], $col2Width, ' ')
|
||||
. $hintLine[2] . "\n";
|
||||
}
|
||||
|
||||
$text .= " *\n";
|
||||
}
|
||||
|
||||
if (!empty($hints['property-read']))
|
||||
{
|
||||
$text .= " * Relations:\n *\n";
|
||||
|
||||
$colWidth = 0;
|
||||
|
||||
foreach ($hints['property-read'] as $hintLine)
|
||||
{
|
||||
$colWidth = max($colWidth, strlen($hintLine[0]));
|
||||
}
|
||||
|
||||
$colWidth += 2;
|
||||
|
||||
foreach ($hints['property-read'] as $hintLine)
|
||||
{
|
||||
$text .= " * @property " . str_pad($hintLine[0], $colWidth, ' ') . $hintLine[1] . "\n";
|
||||
}
|
||||
|
||||
$text .= " *\n";
|
||||
}
|
||||
|
||||
$text .= "**/\n";
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
37
libraries/fof30/Utils/Phpfunc.php
Normal file
37
libraries/fof30/Utils/Phpfunc.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Intercept calls to PHP functions.
|
||||
*
|
||||
* Based on the Session package of Aura for PHP – https://github.com/auraphp/Aura.Session
|
||||
*
|
||||
* @method function_exists(string $function)
|
||||
* @method hash_algos()
|
||||
*/
|
||||
class Phpfunc
|
||||
{
|
||||
/**
|
||||
*
|
||||
* Magic call to intercept any function pass to it.
|
||||
*
|
||||
* @param string $func The function to call.
|
||||
*
|
||||
* @param array $args Arguments passed to the function.
|
||||
*
|
||||
* @return mixed The result of the function call.
|
||||
*
|
||||
*/
|
||||
public function __call($func, $args)
|
||||
{
|
||||
return call_user_func_array($func, $args);
|
||||
}
|
||||
}
|
||||
440
libraries/fof30/Utils/SelectOptions.php
Normal file
440
libraries/fof30/Utils/SelectOptions.php
Normal file
@ -0,0 +1,440 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Joomla\CMS\Cache\Cache;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Helper\UserGroupsHelper;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\LanguageHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Returns arrays of JHtml select options for Joomla-specific information such as access levels.
|
||||
*/
|
||||
class SelectOptions
|
||||
{
|
||||
private static $cache = [];
|
||||
|
||||
/**
|
||||
* Magic method to handle static calls
|
||||
*
|
||||
* @param string $name The name of the static method being called
|
||||
* @param string $arguments Ignored.
|
||||
*
|
||||
* @return mixed
|
||||
* @since 3.3.0
|
||||
*
|
||||
*/
|
||||
public static function __callStatic($name, $arguments)
|
||||
{
|
||||
return self::getOptions($name, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of Joomla options of the type you specify. Supported types
|
||||
* - access View access levels
|
||||
* - usergroups User groups
|
||||
* - cachehandlers Cache handlers
|
||||
* - components Installed components accessible by the current user
|
||||
* - languages Site or administrator languages
|
||||
* - published Published status
|
||||
*
|
||||
* Global params:
|
||||
* - cache Should I returned cached data? Default: true.
|
||||
*
|
||||
* See the private static methods of this class for more information on params.
|
||||
*
|
||||
* @param string $type The options type to get
|
||||
* @param array $params Optional arguments, if they are supported by the options type.
|
||||
*
|
||||
* @return stdClass[]
|
||||
* @since 3.3.0
|
||||
*
|
||||
*/
|
||||
public static function getOptions($type, array $params = [])
|
||||
{
|
||||
if ((substr($type, 0, 1) == '_') || !method_exists(__CLASS__, $type))
|
||||
{
|
||||
throw new InvalidArgumentException(__CLASS__ . "does not support option type '$type'.");
|
||||
}
|
||||
|
||||
$useCache = true;
|
||||
|
||||
if (isset($params['cache']))
|
||||
{
|
||||
$useCache = isset($params['cache']);
|
||||
unset($params['cache']);
|
||||
}
|
||||
|
||||
$cacheKey = sha1($type . '--' . print_r($params, true));
|
||||
$fetchNew = !$useCache || ($useCache && !isset(self::$cache[$cacheKey]));
|
||||
|
||||
if ($fetchNew)
|
||||
{
|
||||
$ret = forward_static_call_array([__CLASS__, $type], [$params]);
|
||||
}
|
||||
|
||||
if (!$useCache)
|
||||
{
|
||||
return $ret;
|
||||
}
|
||||
|
||||
if ($fetchNew)
|
||||
{
|
||||
self::$cache[$cacheKey] = $ret;
|
||||
}
|
||||
|
||||
return self::$cache[$cacheKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Joomla! Access Levels (previously: view access levels)
|
||||
*
|
||||
* Available params:
|
||||
* - allLevels: Show an option for all levels (default: false)
|
||||
*
|
||||
* @param array $params Parameters
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @since 3.3.0
|
||||
*
|
||||
* @see \JHtmlAccess::level()
|
||||
*/
|
||||
private static function access(array $params = [])
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('a.id', 'value') . ', ' . $db->quoteName('a.title', 'text'))
|
||||
->from($db->quoteName('#__viewlevels', 'a'))
|
||||
->group($db->quoteName(['a.id', 'a.title', 'a.ordering']))
|
||||
->order($db->quoteName('a.ordering') . ' ASC')
|
||||
->order($db->quoteName('title') . ' ASC');
|
||||
|
||||
// Get the options.
|
||||
$db->setQuery($query);
|
||||
$options = $db->loadObjectList();
|
||||
|
||||
if (isset($params['allLevels']) && $params['allLevels'])
|
||||
{
|
||||
array_unshift($options, HTMLHelper::_('select.option', '', Text::_('JOPTION_ACCESS_SHOW_ALL_LEVELS')));
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joomla! User Groups
|
||||
*
|
||||
* Available params:
|
||||
* - allGroups: Show an option for all groups (default: false)
|
||||
*
|
||||
* @param array $params Parameters
|
||||
*
|
||||
* @return stdClass[]
|
||||
*
|
||||
* @since 3.3.0
|
||||
*
|
||||
* @see \JHtmlAccess::usergroup()
|
||||
*/
|
||||
private static function usergroups(array $params = [])
|
||||
{
|
||||
$options = array_values(UserGroupsHelper::getInstance()->getAll());
|
||||
|
||||
for ($i = 0, $n = count($options); $i < $n; $i++)
|
||||
{
|
||||
$options[$i]->value = $options[$i]->id;
|
||||
$options[$i]->text = str_repeat('- ', $options[$i]->level) . $options[$i]->title;
|
||||
}
|
||||
|
||||
// If all usergroups is allowed, push it into the array.
|
||||
if (isset($params['allGroups']) && $params['allGroups'])
|
||||
{
|
||||
array_unshift($options, HTMLHelper::_('select.option', '', Text::_('JOPTION_ACCESS_SHOW_ALL_GROUPS')));
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joomla cache handlers
|
||||
*
|
||||
* @return stdClass[]
|
||||
* @since 3.3.0
|
||||
*
|
||||
*/
|
||||
private static function cachehandlers()
|
||||
{
|
||||
$options = [];
|
||||
|
||||
// Convert to name => name array.
|
||||
foreach (Cache::getStores() as $store)
|
||||
{
|
||||
$options[] = HTMLHelper::_('select.option', $store, Text::_('JLIB_FORM_VALUE_CACHE_' . $store), 'value', 'text');
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all installed components and also translates them.
|
||||
*
|
||||
* Available params:
|
||||
* - client_ids Array of Joomla application client IDs
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return stdClass[]
|
||||
* @since 3.3.0
|
||||
*
|
||||
*/
|
||||
private static function components(array $params)
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Check for client_ids override
|
||||
$client_ids = $params['client_ids'] ?? [0, 1];
|
||||
|
||||
if (is_string($client_ids))
|
||||
{
|
||||
$client_ids = explode(',', $client_ids);
|
||||
}
|
||||
|
||||
// Calculate client_ids where clause
|
||||
$client_ids = array_map(function ($client_id) use ($db) {
|
||||
return $db->q((int) trim($client_id));
|
||||
}, $client_ids);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select(
|
||||
[
|
||||
$db->qn('name'),
|
||||
$db->qn('element'),
|
||||
$db->qn('client_id'),
|
||||
$db->qn('manifest_cache'),
|
||||
]
|
||||
)
|
||||
->from($db->qn('#__extensions'))
|
||||
->where($db->qn('type') . ' = ' . $db->q('component'))
|
||||
->where($db->qn('client_id') . ' IN (' . implode(',', $client_ids) . ')');
|
||||
$components = $db->setQuery($query)->loadObjectList('element');
|
||||
|
||||
// Convert to array of objects, so we can use sortObjects()
|
||||
// Also translate component names with JText::_()
|
||||
$aComponents = [];
|
||||
$user = Factory::getUser();
|
||||
|
||||
foreach ($components as $component)
|
||||
{
|
||||
// Don't show components in the list where the user doesn't have access for
|
||||
// TODO: perhaps add an option for this
|
||||
if (!$user->authorise('core.manage', $component->element))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$aComponents[$component->element] = (object) [
|
||||
'value' => $component->element,
|
||||
'text' => self::_translateComponentName($component),
|
||||
];
|
||||
}
|
||||
|
||||
// Reorder the components array, because the alphabetical
|
||||
// ordering changed due to the JText::_() translation
|
||||
uasort(
|
||||
$aComponents,
|
||||
function ($a, $b) {
|
||||
return strcasecmp($a->text, $b->text);
|
||||
}
|
||||
);
|
||||
|
||||
return $aComponents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the field options.
|
||||
*
|
||||
* Available params:
|
||||
* - client 'site' (default) or 'administrator'
|
||||
* - none Text to show for "all languages" option, use empty string to remove it
|
||||
*
|
||||
* @return array Languages for the specified client
|
||||
* @since 3.3.0
|
||||
*
|
||||
*/
|
||||
private static function languages($params)
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$client = $params['client'] ?? 'site';
|
||||
|
||||
if (!in_array($client, ['site', 'administrator']))
|
||||
{
|
||||
$client = 'site';
|
||||
}
|
||||
|
||||
// Make sure the languages are sorted base on locale instead of random sorting
|
||||
$options = LanguageHelper::createLanguageList(null, constant('JPATH_' . strtoupper($client)), true, true);
|
||||
|
||||
if (count($options) > 1)
|
||||
{
|
||||
usort(
|
||||
$options,
|
||||
function ($a, $b) {
|
||||
return strcmp($a['value'], $b['value']);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$none = $params['none'] ?? '*';
|
||||
|
||||
if (!empty($none))
|
||||
{
|
||||
array_unshift($options, HTMLHelper::_('select.option', '*', Text::_($none)));
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for a Published field
|
||||
*
|
||||
* Params:
|
||||
* - none Placeholder for no selection (empty key). Default: null.
|
||||
* - published Show "Published"? Default: true
|
||||
* - unpublished Show "Unpublished"? Default: true
|
||||
* - archived Show "Archived"? Default: false
|
||||
* - trash Show "Trashed"? Default: false
|
||||
* - all Show "All" option? This is different than none, the key is '*'. Default: false
|
||||
*
|
||||
* @param $params
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function published(array $params = [])
|
||||
{
|
||||
$config = array_merge([
|
||||
'none' => '',
|
||||
'published' => true,
|
||||
'unpublished' => true,
|
||||
'archived' => false,
|
||||
'trash' => false,
|
||||
'all' => false,
|
||||
], $params);
|
||||
|
||||
$options = [];
|
||||
|
||||
if (!empty($config['none']))
|
||||
{
|
||||
$options[] = HTMLHelper::_('select.option', '', Text::_($config['none']));
|
||||
}
|
||||
|
||||
if ($config['published'])
|
||||
{
|
||||
$options[] = HTMLHelper::_('select.option', '1', Text::_('JPUBLISHED'));
|
||||
}
|
||||
|
||||
if ($config['unpublished'])
|
||||
{
|
||||
$options[] = HTMLHelper::_('select.option', '0', Text::_('JUNPUBLISHED'));
|
||||
}
|
||||
|
||||
if ($config['archived'])
|
||||
{
|
||||
$options[] = HTMLHelper::_('select.option', '2', Text::_('JARCHIVED'));
|
||||
}
|
||||
|
||||
if ($config['trash'])
|
||||
{
|
||||
$options[] = HTMLHelper::_('select.option', '-2', Text::_('JTRASHED'));
|
||||
}
|
||||
|
||||
if ($config['all'])
|
||||
{
|
||||
$options[] = HTMLHelper::_('select.option', '*', Text::_('JALL'));
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for a Published field
|
||||
*
|
||||
* Params:
|
||||
* - none Placeholder for no selection (empty key). Default: null.
|
||||
*
|
||||
* @param $params
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function boolean(array $params = [])
|
||||
{
|
||||
$config = array_merge([
|
||||
'none' => '',
|
||||
], $params);
|
||||
|
||||
$options = [];
|
||||
|
||||
if (!empty($config['none']))
|
||||
{
|
||||
$options[] = HTMLHelper::_('select.option', '', Text::_($config['none']));
|
||||
}
|
||||
|
||||
$options[] = HTMLHelper::_('select.option', '1', Text::_('JYES'));
|
||||
$options[] = HTMLHelper::_('select.option', '0', Text::_('JNO'));
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translate a component name
|
||||
*
|
||||
* @param stdClass $item The component object
|
||||
*
|
||||
* @return string $text The translated name of the extension
|
||||
*
|
||||
* @since 3.3.0
|
||||
*
|
||||
* @see administrator/com_installer/models/extension.php
|
||||
*/
|
||||
private static function _translateComponentName($item)
|
||||
{
|
||||
// Map the manifest cache to $item. This is needed to get the name from the
|
||||
// manifest_cache and NOT from the name column, else some JText::_() translations fails.
|
||||
$mData = json_decode($item->manifest_cache);
|
||||
|
||||
if ($mData)
|
||||
{
|
||||
foreach ($mData as $key => $value)
|
||||
{
|
||||
if ($key == 'type')
|
||||
{
|
||||
// Ignore the type field
|
||||
continue;
|
||||
}
|
||||
|
||||
$item->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$lang = Factory::getLanguage();
|
||||
$source = JPATH_ADMINISTRATOR . '/components/' . $item->element;
|
||||
$lang->load("$item->element.sys", JPATH_ADMINISTRATOR, null, false, false)
|
||||
|| $lang->load("$item->element.sys", $source, null, false, false)
|
||||
|| $lang->load("$item->element.sys", JPATH_ADMINISTRATOR, $lang->getDefault(), false, false)
|
||||
|| $lang->load("$item->element.sys", $source, $lang->getDefault(), false, false);
|
||||
|
||||
return Text::_($item->name);
|
||||
}
|
||||
}
|
||||
89
libraries/fof30/Utils/StringHelper.php
Normal file
89
libraries/fof30/Utils/StringHelper.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use JLoader;
|
||||
use Joomla\CMS\Application\ApplicationHelper;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Log\Log;
|
||||
|
||||
abstract class StringHelper
|
||||
{
|
||||
/**
|
||||
* Convert a string into a slug (alias), suitable for use in URLs. Please
|
||||
* note that transliteration support is rudimentary at this stage.
|
||||
*
|
||||
* @param string $value A string to convert to slug
|
||||
*
|
||||
* @return string The slug
|
||||
*
|
||||
* @deprecated 3.0 Use \JApplicationHelper::stringURLSafe instead
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function toSlug($value)
|
||||
{
|
||||
if (class_exists('\JLog'))
|
||||
{
|
||||
Log::add('FOF30\\Utils\\StringHelper::toSlug is deprecated. Use \\JApplicationHelper::stringURLSafe instead', Log::WARNING, 'deprecated');
|
||||
}
|
||||
|
||||
return ApplicationHelper::stringURLSafe($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert common northern European languages' letters into plain ASCII. This
|
||||
* is a rudimentary transliteration.
|
||||
*
|
||||
* @param string $value The value to convert to ASCII
|
||||
*
|
||||
* @return string The converted string
|
||||
*
|
||||
* @deprecated 3.0 Use JFactory::getLanguage()->transliterate instead
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function toASCII($value)
|
||||
{
|
||||
if (class_exists('\JLog'))
|
||||
{
|
||||
Log::add('FOF30\\Utils\\StringHelper::toASCII is deprecated. Use JFactory::getLanguage()->transliterate instead', Log::WARNING, 'deprecated');
|
||||
}
|
||||
|
||||
$lang = Factory::getLanguage();
|
||||
|
||||
return $lang->transliterate($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to a boolean.
|
||||
*
|
||||
* @param string $string The string.
|
||||
*
|
||||
* @return boolean The converted string
|
||||
*/
|
||||
public static function toBool($string)
|
||||
{
|
||||
$string = trim((string) $string);
|
||||
$string = strtolower($string);
|
||||
|
||||
if (in_array($string, [1, 'true', 'yes', 'on', 'enabled'], true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($string, [0, 'false', 'no', 'off', 'disabled'], true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $string;
|
||||
}
|
||||
}
|
||||
351
libraries/fof30/Utils/TimezoneWrangler.php
Normal file
351
libraries/fof30/Utils/TimezoneWrangler.php
Normal file
@ -0,0 +1,351 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
namespace FOF30\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use FOF30\Container\Container;
|
||||
use FOF30\Date\Date;
|
||||
use Joomla\CMS\User\User;
|
||||
|
||||
/**
|
||||
* A helper class to wrangle timezones, as used by Joomla!.
|
||||
*
|
||||
* @package FOF30\Utils
|
||||
*
|
||||
* @since 3.1.3
|
||||
*/
|
||||
class TimezoneWrangler
|
||||
{
|
||||
/**
|
||||
* The default timestamp format string to use when one is not provided
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultFormat = 'Y-m-d H:i:s T';
|
||||
|
||||
/**
|
||||
* When set, this timezone will be used instead of the Joomla! applicable timezone for the user.
|
||||
*
|
||||
* @var DateTimeZone
|
||||
*/
|
||||
protected $forcedTimezone = null;
|
||||
|
||||
/**
|
||||
* Cache of user IDs to applicable timezones
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $userToTimezone = [];
|
||||
|
||||
/**
|
||||
* The component container for which we are created
|
||||
*
|
||||
* @var Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default timestamp format to use when one is not provided
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultFormat()
|
||||
{
|
||||
return $this->defaultFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default timestamp format to use when one is not provided
|
||||
*
|
||||
* @param string $defaultFormat
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultFormat($defaultFormat)
|
||||
{
|
||||
$this->defaultFormat = $defaultFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the forced timezone which is used instead of the applicable Joomla! timezone.
|
||||
*
|
||||
* @return DateTimeZone
|
||||
*/
|
||||
public function getForcedTimezone()
|
||||
{
|
||||
return $this->forcedTimezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the forced timezone which is used instead of the applicable Joomla! timezone. If the new timezone is
|
||||
* different than the existing one we will also reset the user to timezone cache.
|
||||
*
|
||||
* @param DateTimeZone|string $forcedTimezone
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setForcedTimezone($forcedTimezone)
|
||||
{
|
||||
// Are we unsetting the forced TZ?
|
||||
if (empty($forcedTimezone))
|
||||
{
|
||||
$this->forcedTimezone = null;
|
||||
$this->resetCache();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If the new TZ is a string we have to create an object
|
||||
if (is_string($forcedTimezone))
|
||||
{
|
||||
$forcedTimezone = new DateTimeZone($forcedTimezone);
|
||||
}
|
||||
|
||||
$oldTZ = '';
|
||||
|
||||
if (is_object($this->forcedTimezone) && ($this->forcedTimezone instanceof DateTimeZone))
|
||||
{
|
||||
$oldTZ = $this->forcedTimezone->getName();
|
||||
}
|
||||
|
||||
if ($oldTZ == $forcedTimezone->getName())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->forcedTimezone = $forcedTimezone;
|
||||
|
||||
$this->resetCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the user to timezone cache. This is done automatically every time you change the forced timezone.
|
||||
*/
|
||||
public function resetCache()
|
||||
{
|
||||
$this->userToTimezone = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the applicable timezone for a user. If the user is not a guest and they have a timezone set up in their
|
||||
* profile it will be used. Otherwise we fall back to the Server Timezone as set up in Global Configuration. If that
|
||||
* fails, we use GMT. However, if you have used a non-blank forced timezone that will be used instead, circumventing
|
||||
* this calculation. Therefore the returned timezone is one of the following, by descending order of priority:
|
||||
* - Forced timezone
|
||||
* - User's timezone (explicitly set in their user profile)
|
||||
* - Server Timezone (from Joomla's Global Configuration)
|
||||
* - GMT
|
||||
*
|
||||
* @param User|null $user
|
||||
*
|
||||
* @return DateTimeZone
|
||||
*/
|
||||
public function getApplicableTimezone($user = null)
|
||||
{
|
||||
// If we have a forced timezone use it instead of trying to figure anything out.
|
||||
if (is_object($this->forcedTimezone))
|
||||
{
|
||||
return $this->forcedTimezone;
|
||||
}
|
||||
|
||||
// No user? Get the current user.
|
||||
if (is_null($user))
|
||||
{
|
||||
$user = $this->container->platform->getUser();
|
||||
}
|
||||
|
||||
// If there is a cached timezone return that instead.
|
||||
if (isset($this->userToTimezone[$user->id]))
|
||||
{
|
||||
return $this->userToTimezone[$user->id];
|
||||
}
|
||||
|
||||
// Prefer the user timezone if it's set.
|
||||
if (!$user->guest)
|
||||
{
|
||||
$tz = $user->getParam('timezone', null);
|
||||
|
||||
if (!empty($tz))
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->userToTimezone[$user->id] = new DateTimeZone($tz);
|
||||
|
||||
return $this->userToTimezone[$user->id];
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the Server Timezone from Global Configuration with a fallback to GMT
|
||||
$tz = $this->container->platform->getConfig()->get('offset', 'GMT');
|
||||
|
||||
try
|
||||
{
|
||||
$this->userToTimezone[$user->id] = new DateTimeZone($tz);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
// If an invalid timezone was set we get to use GMT
|
||||
$this->userToTimezone[$user->id] = new DateTimeZone('GMT');
|
||||
}
|
||||
|
||||
return $this->userToTimezone[$user->id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FOF Date object with its timezone set to the user's applicable timezone.
|
||||
*
|
||||
* If no user is specified the current user will be used.
|
||||
*
|
||||
* $time can be a DateTime object (including Date and JDate), an integer (UNIX timestamp) or a date string. If no
|
||||
* timezone is specified in a date string we assume it's GMT.
|
||||
*
|
||||
* @param User $user Applicable user for timezone calculation. Null = current user.
|
||||
* @param mixed $time Source time. Leave blank for current date/time.
|
||||
*
|
||||
* @return Date
|
||||
*/
|
||||
public function getLocalDateTime($user, $time = null)
|
||||
{
|
||||
$time = empty($time) ? 'now' : $time;
|
||||
$date = new Date($time);
|
||||
$tz = $this->getApplicableTimezone($user);
|
||||
$date->setTimezone($tz);
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a FOF Date object with its timezone set to GMT.
|
||||
*
|
||||
* If no user is specified the current user will be used.
|
||||
*
|
||||
* $time can be a DateTime object (including Date and JDate), an integer (UNIX timestamp) or a date string. If no
|
||||
* timezone is specified in a date string we assume it's the user's applicable timezone.
|
||||
*
|
||||
* @param User $user
|
||||
* @param mixed $time
|
||||
*
|
||||
* @return Date
|
||||
*/
|
||||
public function getGMTDateTime($user, $time)
|
||||
{
|
||||
$time = empty($time) ? 'now' : $time;
|
||||
$tz = $this->getApplicableTimezone($user);
|
||||
$date = new Date($time, $tz);
|
||||
$gmtTimezone = new DateTimeZone('GMT');
|
||||
$date->setTimezone($gmtTimezone);
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted date string in the user's applicable timezone.
|
||||
*
|
||||
* If no format is specified we will use $defaultFormat.
|
||||
*
|
||||
* If no user is specified the current user will be used.
|
||||
*
|
||||
* $time can be a DateTime object (including Date and JDate), an integer (UNIX timestamp) or a date string. If no
|
||||
* timezone is specified in a date string we assume it's GMT.
|
||||
*
|
||||
* $translate requires you to have loaded the relevant translation file (e.g. en-GB.ini). JApplicationCms does that
|
||||
* for you automatically. If you're under CLI, a custom JApplicationWeb etc you will probably have to load this
|
||||
* file
|
||||
* manually.
|
||||
*
|
||||
* @param string|null $format Timestamp format. If empty $defaultFormat is used.
|
||||
* @param User|null $user Applicable user for timezone calculation. Null = current
|
||||
* user.
|
||||
* @param DateTime|Date|string|int|null $time Source time. Leave blank for current date/time.
|
||||
* @param bool $translate Translate day of week and month names?
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalTimeStamp($format = null, $user = null, $time = null, $translate = false)
|
||||
{
|
||||
$date = $this->getLocalDateTime($user, $time);
|
||||
$format = empty($format) ? $this->defaultFormat : $format;
|
||||
|
||||
return $date->format($format, true, $translate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted date string in the GMT timezone.
|
||||
*
|
||||
* If no format is specified we will use $defaultFormat.
|
||||
*
|
||||
* If no user is specified the current user will be used.
|
||||
*
|
||||
* $time can be a DateTime object (including Date and JDate), an integer (UNIX timestamp) or a date string. If no
|
||||
* timezone is specified in a date string we assume it's the user's applicable timezone.
|
||||
*
|
||||
* $translate requires you to have loaded the relevant translation file (e.g. en-GB.ini). JApplicationCms does that
|
||||
* for you automatically. If you're under CLI, a custom JApplicationWeb etc you will probably have to load this
|
||||
* file
|
||||
* manually.
|
||||
*
|
||||
* @param string|null $format Timestamp format. If empty $defaultFormat is used.
|
||||
* @param User|null $user Applicable user for timezone calculation. Null = current
|
||||
* user.
|
||||
* @param DateTime|Date|string|int|null $time Source time. Leave blank for current date/time.
|
||||
* @param bool $translate Translate day of week and month names?
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getGMTTimeStamp($format = null, $user = null, $time = null, $translate = false)
|
||||
{
|
||||
$date = $this->localToGMT($user, $time);
|
||||
$format = empty($format) ? $this->defaultFormat : $format;
|
||||
|
||||
return $date->format($format, true, $translate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a local time back to GMT. Returns a Date object with its timezone set to GMT.
|
||||
*
|
||||
* This is an alias to getGMTDateTime
|
||||
*
|
||||
* @param string|Date $time
|
||||
* @param User|null $user
|
||||
*
|
||||
* @return Date
|
||||
*/
|
||||
public function localToGMT($time, $user = null)
|
||||
{
|
||||
return $this->getGMTDateTime($user, $time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a GMT time to local timezone. Returns a Date object with its timezone set to the applicable user's TZ.
|
||||
*
|
||||
* This is an alias to getLocalDateTime
|
||||
*
|
||||
* @param string|Date $time
|
||||
* @param User|null $user
|
||||
*
|
||||
* @return Date
|
||||
*/
|
||||
public function GMTToLocal($time, $user = null)
|
||||
{
|
||||
return $this->getLocalDateTime($user, $time);
|
||||
}
|
||||
}
|
||||
585
libraries/fof30/Utils/helpers.php
Normal file
585
libraries/fof30/Utils/helpers.php
Normal file
@ -0,0 +1,585 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 2, or later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* This is a modified copy of Laravel 4's "helpers.php"
|
||||
*
|
||||
* Laravel 4 is distributed under the MIT license, see https://github.com/laravel/framework/blob/master/LICENSE.txt
|
||||
*/
|
||||
if (!function_exists('array_add'))
|
||||
{
|
||||
/**
|
||||
* Add an element to an array if it doesn't exist.
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_add($array, $key, $value)
|
||||
{
|
||||
if (!isset($array[$key]))
|
||||
{
|
||||
$array[$key] = $value;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_build'))
|
||||
{
|
||||
/**
|
||||
* Build a new array using a callback.
|
||||
*
|
||||
* @param array $array
|
||||
* @param Closure $callback
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_build($array, Closure $callback)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($array as $key => $value)
|
||||
{
|
||||
[$innerKey, $innerValue] = call_user_func($callback, $key, $value);
|
||||
|
||||
$results[$innerKey] = $innerValue;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_divide'))
|
||||
{
|
||||
/**
|
||||
* Divide an array into two arrays. One with keys and the other with values.
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_divide($array)
|
||||
{
|
||||
return [array_keys($array), array_values($array)];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_dot'))
|
||||
{
|
||||
/**
|
||||
* Flatten a multi-dimensional associative array with dots.
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $prepend
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_dot($array, $prepend = '')
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($array as $key => $value)
|
||||
{
|
||||
if (is_array($value))
|
||||
{
|
||||
$results = array_merge($results, array_dot($value, $prepend . $key . '.'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$results[$prepend . $key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_except'))
|
||||
{
|
||||
/**
|
||||
* Get all of the given array except for a specified array of items.
|
||||
*
|
||||
* @param array $array
|
||||
* @param array $keys
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_except($array, $keys)
|
||||
{
|
||||
return array_diff_key($array, array_flip((array) $keys));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_fetch'))
|
||||
{
|
||||
/**
|
||||
* Fetch a flattened array of a nested array element.
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $key
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_fetch($array, $key)
|
||||
{
|
||||
foreach (explode('.', $key) as $segment)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($array as $value)
|
||||
{
|
||||
$value = (array) $value;
|
||||
|
||||
$results[] = $value[$segment];
|
||||
}
|
||||
|
||||
$array = array_values($results);
|
||||
}
|
||||
|
||||
return array_values($results);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_first'))
|
||||
{
|
||||
/**
|
||||
* Return the first element in an array passing a given truth test.
|
||||
*
|
||||
* @param array $array
|
||||
* @param Closure $callback
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function array_first($array, $callback, $default = null)
|
||||
{
|
||||
foreach ($array as $key => $value)
|
||||
{
|
||||
if (call_user_func($callback, $key, $value))
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return value($default);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_last'))
|
||||
{
|
||||
/**
|
||||
* Return the last element in an array passing a given truth test.
|
||||
*
|
||||
* @param array $array
|
||||
* @param Closure $callback
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function array_last($array, $callback, $default = null)
|
||||
{
|
||||
return array_first(array_reverse($array), $callback, $default);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_flatten'))
|
||||
{
|
||||
/**
|
||||
* Flatten a multi-dimensional array into a single level.
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_flatten($array)
|
||||
{
|
||||
$return = [];
|
||||
|
||||
array_walk_recursive($array, function ($x) use (&$return) {
|
||||
$return[] = $x;
|
||||
});
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_forget'))
|
||||
{
|
||||
/**
|
||||
* Remove an array item from a given array using "dot" notation.
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function array_forget(&$array, $key)
|
||||
{
|
||||
$keys = explode('.', $key);
|
||||
|
||||
while (count($keys) > 1)
|
||||
{
|
||||
$key = array_shift($keys);
|
||||
|
||||
if (!isset($array[$key]) || !is_array($array[$key]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$array =& $array[$key];
|
||||
}
|
||||
|
||||
unset($array[array_shift($keys)]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_get'))
|
||||
{
|
||||
/**
|
||||
* Get an item from an array using "dot" notation.
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function array_get($array, $key, $default = null)
|
||||
{
|
||||
if (is_null($key))
|
||||
{
|
||||
return $array;
|
||||
}
|
||||
|
||||
if (isset($array[$key]))
|
||||
{
|
||||
return $array[$key];
|
||||
}
|
||||
|
||||
foreach (explode('.', $key) as $segment)
|
||||
{
|
||||
if (!is_array($array) || !array_key_exists($segment, $array))
|
||||
{
|
||||
return value($default);
|
||||
}
|
||||
|
||||
$array = $array[$segment];
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_only'))
|
||||
{
|
||||
/**
|
||||
* Get a subset of the items from the given array.
|
||||
*
|
||||
* @param array $array
|
||||
* @param array $keys
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_only($array, $keys)
|
||||
{
|
||||
return array_intersect_key($array, array_flip((array) $keys));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_pluck'))
|
||||
{
|
||||
/**
|
||||
* Pluck an array of values from an array.
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $value
|
||||
* @param string $key
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_pluck($array, $value, $key = null)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($array as $item)
|
||||
{
|
||||
$itemValue = is_object($item) ? $item->{$value} : $item[$value];
|
||||
|
||||
// If the key is "null", we will just append the value to the array and keep
|
||||
// looping. Otherwise we will key the array using the value of the key we
|
||||
// received from the developer. Then we'll return the final array form.
|
||||
if (is_null($key))
|
||||
{
|
||||
$results[] = $itemValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
$itemKey = is_object($item) ? $item->{$key} : $item[$key];
|
||||
|
||||
$results[$itemKey] = $itemValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_pull'))
|
||||
{
|
||||
/**
|
||||
* Get a value from the array, and remove it.
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function array_pull(&$array, $key)
|
||||
{
|
||||
$value = array_get($array, $key);
|
||||
|
||||
array_forget($array, $key);
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_set'))
|
||||
{
|
||||
/**
|
||||
* Set an array item to a given value using "dot" notation.
|
||||
*
|
||||
* If no key is given to the method, the entire array will be replaced.
|
||||
*
|
||||
* @param array $array
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_set(&$array, $key, $value)
|
||||
{
|
||||
if (is_null($key))
|
||||
{
|
||||
return $array = $value;
|
||||
}
|
||||
|
||||
$keys = explode('.', $key);
|
||||
|
||||
while (count($keys) > 1)
|
||||
{
|
||||
$key = array_shift($keys);
|
||||
|
||||
// If the key doesn't exist at this depth, we will just create an empty array
|
||||
// to hold the next value, allowing us to create the arrays to hold final
|
||||
// values at the correct depth. Then we'll keep digging into the array.
|
||||
if (!isset($array[$key]) || !is_array($array[$key]))
|
||||
{
|
||||
$array[$key] = [];
|
||||
}
|
||||
|
||||
$array =& $array[$key];
|
||||
}
|
||||
|
||||
$array[array_shift($keys)] = $value;
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_sort'))
|
||||
{
|
||||
/**
|
||||
* Sort the array using the given Closure.
|
||||
*
|
||||
* @param array $array
|
||||
* @param Closure $callback
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_sort($array, Closure $callback)
|
||||
{
|
||||
return FOF30\Utils\Collection::make($array)->sortBy($callback)->all();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('array_where'))
|
||||
{
|
||||
/**
|
||||
* Filter the array using the given Closure.
|
||||
*
|
||||
* @param array $array
|
||||
* @param Closure $callback
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function array_where($array, Closure $callback)
|
||||
{
|
||||
$filtered = [];
|
||||
|
||||
foreach ($array as $key => $value)
|
||||
{
|
||||
if (call_user_func($callback, $key, $value))
|
||||
{
|
||||
$filtered[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('ends_with'))
|
||||
{
|
||||
/**
|
||||
* Determine if a given string ends with a given substring.
|
||||
*
|
||||
* @param string $haystack
|
||||
* @param string|array $needles
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function ends_with($haystack, $needles)
|
||||
{
|
||||
foreach ((array) $needles as $needle)
|
||||
{
|
||||
if ((string) $needle === substr($haystack, -strlen($needle)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('last'))
|
||||
{
|
||||
/**
|
||||
* Get the last element from an array.
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function last($array)
|
||||
{
|
||||
return end($array);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('object_get'))
|
||||
{
|
||||
/**
|
||||
* Get an item from an object using "dot" notation.
|
||||
*
|
||||
* @param object $object
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function object_get($object, $key, $default = null)
|
||||
{
|
||||
if (is_null($key) || trim($key) == '')
|
||||
{
|
||||
return $object;
|
||||
}
|
||||
|
||||
foreach (explode('.', $key) as $segment)
|
||||
{
|
||||
if (!is_object($object) || !isset($object->{$segment}))
|
||||
{
|
||||
return value($default);
|
||||
}
|
||||
|
||||
$object = $object->{$segment};
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('preg_replace_sub'))
|
||||
{
|
||||
/**
|
||||
* Replace a given pattern with each value in the array in sequentially.
|
||||
*
|
||||
* @param string $pattern
|
||||
* @param array $replacements
|
||||
* @param string $subject
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function preg_replace_sub($pattern, &$replacements, $subject)
|
||||
{
|
||||
return preg_replace_callback($pattern, function ($match) use (&$replacements) {
|
||||
return array_shift($replacements);
|
||||
|
||||
}, $subject);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('starts_with'))
|
||||
{
|
||||
/**
|
||||
* Determine if a given string starts with a given substring.
|
||||
*
|
||||
* @param string $haystack
|
||||
* @param string|array $needles
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function starts_with($haystack, $needles)
|
||||
{
|
||||
foreach ((array) $needles as $needle)
|
||||
{
|
||||
if ($needle != '' && strpos($haystack, $needle) === 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('value'))
|
||||
{
|
||||
/**
|
||||
* Return the default value of the given value.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function value($value)
|
||||
{
|
||||
return $value instanceof Closure ? $value() : $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('with'))
|
||||
{
|
||||
/**
|
||||
* Return the given object. Useful for chaining.
|
||||
*
|
||||
* @param mixed $object
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function with($object)
|
||||
{
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user