primo commit
This commit is contained in:
547
libraries/fof40/Utils/ArrayHelper.php
Normal file
547
libraries/fof40/Utils/ArrayHelper.php
Normal file
@ -0,0 +1,547 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace FOF40\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* 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 int|array|null $default A default value (int|array) to assign if $array is not an array
|
||||
*
|
||||
* @return int[]
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function toInteger(array $array, $default = null): array
|
||||
{
|
||||
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, string $class = 'stdClass', bool $recursive = true)
|
||||
{
|
||||
$obj = new $class;
|
||||
|
||||
foreach ($array as $k => $v)
|
||||
{
|
||||
$obj->$k = ($recursive && is_array($v)) ? static::toObject($v, $class) : $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, string $inner_glue = '=', string $outer_glue = ' ', bool $keepOuterKey = false): string
|
||||
{
|
||||
$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|null $regex An optional regular expression to match on field names
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public static function fromObject($p_obj, bool $recurse = true, ?string $regex = null): array
|
||||
{
|
||||
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|null $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, string $valueCol, ?string $keyCol = null): array
|
||||
{
|
||||
$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 $array, string $name, $default = null, string $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('/-?\d+/', $result, $matches);
|
||||
$result = @(int) $matches[0];
|
||||
break;
|
||||
|
||||
case 'FLOAT':
|
||||
case 'DOUBLE':
|
||||
// Only use the first floating point value
|
||||
@preg_match('/-?\d+(\.\d+)?/', $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): 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 $array): bool
|
||||
{
|
||||
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|null $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, ?string $key = null): array
|
||||
{
|
||||
$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 = array_map('serialize', $array);
|
||||
$array = array_unique($array);
|
||||
|
||||
return array_map('unserialize', $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(string $needle, array $haystack, bool $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, string $separator = '.', string $prefix = ''): array
|
||||
{
|
||||
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, bool $recurse, ?string $regex): array
|
||||
{
|
||||
if (is_object($item))
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach (get_object_vars($item) as $k => $v)
|
||||
{
|
||||
if (!$regex || preg_match($regex, $k))
|
||||
{
|
||||
$result[$k] = $recurse ? self::arrayFromObject($v, $recurse, $regex) : $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (is_array($item))
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($item as $k => $v)
|
||||
{
|
||||
$result[$k] = self::arrayFromObject($v, $recurse, $regex);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
309
libraries/fof40/Utils/Buffer.php
Normal file
309
libraries/fof40/Utils/Buffer.php
Normal file
@ -0,0 +1,309 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace FOF40\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
/**
|
||||
* Registers a fof:// stream wrapper
|
||||
*/
|
||||
class Buffer
|
||||
{
|
||||
/**
|
||||
* Buffer hash
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $buffers = [];
|
||||
|
||||
public static $canRegisterWrapper;
|
||||
|
||||
/**
|
||||
* Stream position
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $position = 0;
|
||||
|
||||
/**
|
||||
* Buffer name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Should I register the fof:// stream wrapper
|
||||
*
|
||||
* @return bool True if the stream wrapper can be registered
|
||||
*/
|
||||
public static function canRegisterWrapper(): bool
|
||||
{
|
||||
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) && 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(string $path, string $mode, ?int $options, ?string &$opened_path): bool
|
||||
{
|
||||
$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(string $path): void
|
||||
{
|
||||
$url = parse_url($path);
|
||||
$name = $url['host'];
|
||||
|
||||
if (isset(static::$buffers[$name]))
|
||||
{
|
||||
unset (static::$buffers[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
public function stream_stat(): array
|
||||
{
|
||||
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(int $count): ?string
|
||||
{
|
||||
$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(string $data): int
|
||||
{
|
||||
$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(): int
|
||||
{
|
||||
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(): bool
|
||||
{
|
||||
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(int $offset, int $whence): bool
|
||||
{
|
||||
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', 'FOF40\\Utils\\Buffer');
|
||||
}
|
||||
166
libraries/fof40/Utils/CliSessionHandler.php
Normal file
166
libraries/fof40/Utils/CliSessionHandler.php
Normal file
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace FOF40\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF40\Encrypt\Randval;
|
||||
use JSessionHandlerInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* CLI session handler for Joomla 3.x
|
||||
*/
|
||||
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()
|
||||
{
|
||||
$rand = new Randval();
|
||||
|
||||
$this->id = md5($rand->generate(32));
|
||||
}
|
||||
}
|
||||
772
libraries/fof40/Utils/Collection.php
Normal file
772
libraries/fof40/Utils/Collection.php
Normal file
@ -0,0 +1,772 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace FOF40\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. If given an array then the array is wrapped in
|
||||
* a Collection. Otherwise we return a Collection with only one item, whatever was passed in $items. If you pass
|
||||
* null you get an empty Collection.
|
||||
*
|
||||
* @param array|mixed $items
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function make($items): Collection
|
||||
{
|
||||
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(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the collection items into a single array. This assumes that the Collection is composed of arrays. We
|
||||
* are essentially merging all of these arrays and creating a new Collection out of them.
|
||||
*
|
||||
* If $this->items = [ ['a','b'], ['c','d'], ['e'] ] after collapse it becomes [ 'a','b','c','d','e' ]
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function collapse(): Collection
|
||||
{
|
||||
$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 Collection
|
||||
*/
|
||||
public function diff($items): Collection
|
||||
{
|
||||
return new static(array_diff($this->items, $this->getIterableItemsAsArray($items)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback over each item.
|
||||
*
|
||||
* @param Closure $callback
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function each(Closure $callback): Collection
|
||||
{
|
||||
array_map($callback, $this->items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a nested element of the collection.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function fetch(string $key): Collection
|
||||
{
|
||||
return new static(array_fetch($this->items, $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a filter over each of the items.
|
||||
*
|
||||
* @param Closure $callback
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function filter(Closure $callback): Collection
|
||||
{
|
||||
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 Collection
|
||||
*/
|
||||
public function flatten(): Collection
|
||||
{
|
||||
return new static(array_flatten($this->items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the collection by key.
|
||||
*
|
||||
* @param mixed $key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function forget(string $key): void
|
||||
{
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item from the collection by key.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $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 Collection
|
||||
*/
|
||||
public function groupBy($groupBy): Collection
|
||||
{
|
||||
$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 string $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return array_key_exists($key, $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate values of a given key as a string.
|
||||
*
|
||||
* @param string $value
|
||||
* @param string|null $glue
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function implode(string $value, ?string $glue = null): string
|
||||
{
|
||||
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 Collection
|
||||
*/
|
||||
public function intersect($items): Collection
|
||||
{
|
||||
return new static(array_intersect($this->items, $this->getIterableItemsAsArray($items)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the collection is empty or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
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|null $key
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function lists(string $value, ?string $key = null)
|
||||
{
|
||||
return array_pluck($this->items, $value, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a map over each of the items.
|
||||
*
|
||||
* @param Closure $callback
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function map(Closure $callback): Collection
|
||||
{
|
||||
return new static(array_map($callback, $this->items, array_keys($this->items)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the collection with the given items.
|
||||
*
|
||||
* @param Collection|array $items
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function merge($items): Collection
|
||||
{
|
||||
return new static(array_merge($this->items, $this->getIterableItemsAsArray($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): void
|
||||
{
|
||||
array_unshift($this->items, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an item onto the end of the collection.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function push($value): void
|
||||
{
|
||||
$this->items[] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an item in the collection by key.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function put(string $key, $value): void
|
||||
{
|
||||
$this->items[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the collection to a single value.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param mixed $initial
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function reduce(callable $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(int $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 Collection
|
||||
*/
|
||||
public function reverse(): Collection
|
||||
{
|
||||
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|null $length
|
||||
* @param bool $preserveKeys
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function slice(int $offset, ?int $length = null, bool $preserveKeys = false): Collection
|
||||
{
|
||||
return new static(array_slice($this->items, $offset, $length, $preserveKeys));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort through each item with a callback.
|
||||
*
|
||||
* @param Closure $callback
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function sort(Closure $callback): Collection
|
||||
{
|
||||
uasort($this->items, $callback);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the collection using the given Closure.
|
||||
*
|
||||
* @param \Closure|string $callback
|
||||
* @param int $options
|
||||
* @param bool $descending
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function sortBy($callback, int $options = SORT_REGULAR, bool $descending = false): Collection
|
||||
{
|
||||
$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 Collection
|
||||
*/
|
||||
public function sortByDesc($callback, int $options = SORT_REGULAR): Collection
|
||||
{
|
||||
return $this->sortBy($callback, $options, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splice portion of the underlying collection array.
|
||||
*
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @param mixed $replacement
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function splice(int $offset, int $length = 0, $replacement = []): Collection
|
||||
{
|
||||
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|null $limit
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function take(?int $limit = null): Collection
|
||||
{
|
||||
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(): Collection
|
||||
{
|
||||
$this->items = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform each item in the collection using a callback.
|
||||
*
|
||||
* @param callable $callback
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function transform(callable $callback): Collection
|
||||
{
|
||||
$this->items = array_map($callback, $this->items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only unique items from the collection array.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function unique(): Collection
|
||||
{
|
||||
return new static(array_unique($this->items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the keys on the underlying array.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function values(): Collection
|
||||
{
|
||||
$this->items = array_values($this->items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection of items as a plain array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
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
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection of items as JSON.
|
||||
*
|
||||
* @param int $options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toJson(int $options = 0): string
|
||||
{
|
||||
return json_encode($this->toArray(), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an iterator for the items.
|
||||
*
|
||||
* @return ArrayIterator
|
||||
*/
|
||||
public function getIterator(): ArrayIterator
|
||||
{
|
||||
return new ArrayIterator($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a CachingIterator instance.
|
||||
*
|
||||
* @param integer $flags Caching iterator flags
|
||||
*
|
||||
* @return \CachingIterator
|
||||
*/
|
||||
public function getCachingIterator(int $flags = CachingIterator::CALL_TOSTRING): CachingIterator
|
||||
{
|
||||
return new \CachingIterator($this->getIterator(), $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of items in the collection.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an item exists at an offset.
|
||||
*
|
||||
* @param mixed $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($key): bool
|
||||
{
|
||||
return array_key_exists($key, $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item at a given offset.
|
||||
*
|
||||
* @param mixed $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($key)
|
||||
{
|
||||
return $this->items[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the item at a given offset.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
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
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($key)
|
||||
{
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the collection to its string representation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value retrieving callback.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
protected function valueRetriever(string $value): callable
|
||||
{
|
||||
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 getIterableItemsAsArray($items): array
|
||||
{
|
||||
if ($items instanceof Collection)
|
||||
{
|
||||
$items = $items->all();
|
||||
}
|
||||
elseif (is_object($items) && method_exists($items, 'toArray'))
|
||||
{
|
||||
$items = $items->toArray();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
}
|
||||
282
libraries/fof40/Utils/FilesCheck.php
Normal file
282
libraries/fof40/Utils/FilesCheck.php
Normal file
@ -0,0 +1,282 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace FOF40\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF40\Timer\Timer;
|
||||
use Joomla\CMS\Factory as JoomlaFactory;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/** @var string Current component release date */
|
||||
protected $date;
|
||||
|
||||
/** @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 $componentName 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(string $componentName, string $version, string $date)
|
||||
{
|
||||
// Initialise from parameters
|
||||
$this->option = $componentName;
|
||||
$this->version = $version;
|
||||
$this->date = $date;
|
||||
|
||||
// Retrieve the date and version from the #__extensions table
|
||||
$db = JoomlaFactory::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(): bool
|
||||
{
|
||||
return $this->wrongComponentVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the fileslist.php reporting a version different than the reported component version?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isWrongFilesVersion(): bool
|
||||
{
|
||||
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(): bool
|
||||
{
|
||||
// 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(int $idx = 0): array
|
||||
{
|
||||
$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] . ')';
|
||||
}
|
||||
elseif (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;
|
||||
}
|
||||
}
|
||||
211
libraries/fof40/Utils/MediaVersion.php
Normal file
211
libraries/fof40/Utils/MediaVersion.php
Normal file
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace FOF40\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use Exception;
|
||||
use FOF40\Container\Container;
|
||||
use JDatabaseDriver;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
/**
|
||||
* Class MediaVersion
|
||||
*
|
||||
* @since 3.5.3
|
||||
*/
|
||||
class MediaVersion
|
||||
{
|
||||
/**
|
||||
* Cached the version and date of FOF-powered components
|
||||
*
|
||||
* @var array
|
||||
* @since 3.5.3
|
||||
*/
|
||||
protected static $componentVersionCache = [];
|
||||
|
||||
/**
|
||||
* @var string|mixed
|
||||
*/
|
||||
public $componentName;
|
||||
|
||||
/**
|
||||
* 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(string $component, JDatabaseDriver $db): array
|
||||
{
|
||||
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() ?? '{}';
|
||||
$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(): array
|
||||
{
|
||||
$this->componentName = $this->container->componentName;
|
||||
|
||||
return [
|
||||
'mediaVersion',
|
||||
'componentName',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserialization helper
|
||||
*
|
||||
* @return void
|
||||
* @see __sleep
|
||||
*/
|
||||
public function __wakeup(): void
|
||||
{
|
||||
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(): string
|
||||
{
|
||||
if (empty($this->mediaVersion))
|
||||
{
|
||||
$this->mediaVersion = $this->getDefaultMediaVersion();
|
||||
}
|
||||
|
||||
return $this->mediaVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media query version string
|
||||
*
|
||||
* @param string|null $mediaVersion
|
||||
*
|
||||
* @since 3.5.3
|
||||
*/
|
||||
public function setMediaVersion(?string $mediaVersion): void
|
||||
{
|
||||
$this->mediaVersion = $mediaVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default media query version string if none is already defined
|
||||
*
|
||||
* @return string
|
||||
* @since 3.5.3
|
||||
*/
|
||||
protected function getDefaultMediaVersion(): string
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
319
libraries/fof40/Utils/ModelTypeHints.php
Normal file
319
libraries/fof40/Utils/ModelTypeHints.php
Normal file
@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace FOF40\Utils;
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
use FOF40\Model\DataModel;
|
||||
|
||||
/**
|
||||
* Generate phpDoc type hints for the magic properties and methods of your DataModels.
|
||||
*
|
||||
* Usage:
|
||||
* $typeHinter = new ModelTypeHints($instanceOfYourFOFDataModel);
|
||||
* var_dump($typeHinter->getHints());
|
||||
*
|
||||
* This will dump the type hints you should add to the DocBlock of your DataModel class to allow IDEs such as phpStorm
|
||||
* to provide smart type hinting for magic property and method access.
|
||||
*
|
||||
* @package FOF40\Utils
|
||||
*/
|
||||
class ModelTypeHints
|
||||
{
|
||||
/**
|
||||
* The model for which to create type hints
|
||||
*
|
||||
* @var DataModel
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
/**
|
||||
* Name of the class. If empty will be inferred from the current object
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $className;
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param \FOF40\Model\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(string $type): string
|
||||
{
|
||||
// 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(string $className): void
|
||||
{
|
||||
$this->className = $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the raw hints array
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \FOF40\Model\DataModel\Relation\Exception\RelationNotFound
|
||||
*/
|
||||
public function getRawHints(): array
|
||||
{
|
||||
$model = $this->model;
|
||||
|
||||
$hints = [
|
||||
'property' => [],
|
||||
'method' => [],
|
||||
'property-read' => [],
|
||||
];
|
||||
|
||||
$hasFilters = $model->getBehavioursDispatcher()->hasObserverClass('FOF40\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 = static::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(): string
|
||||
{
|
||||
$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";
|
||||
}
|
||||
|
||||
return $text . "**/\n";
|
||||
}
|
||||
}
|
||||
173
libraries/fof40/Utils/ViewManifestMigration.php
Normal file
173
libraries/fof40/Utils/ViewManifestMigration.php
Normal file
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
namespace FOF40\Utils;
|
||||
|
||||
|
||||
use FOF40\Container\Container;
|
||||
use Joomla\CMS\Filesystem\File;
|
||||
use Joomla\Filesystem\Folder;
|
||||
|
||||
class ViewManifestMigration
|
||||
{
|
||||
/**
|
||||
* Migrates Joomla 4 view XML manifests into their Joomla 3 locations
|
||||
*
|
||||
* @param Container $container The FOF 4 container of the component we'll be migrating.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function migrateJoomla4MenuXMLFiles(Container $container): void
|
||||
{
|
||||
self::migrateJoomla4MenuXMLFiles_real($container->frontEndPath, $container->backEndPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates Joomla 4 view XML manifests into their Joomla 3 locations
|
||||
*
|
||||
* @param string $frontendPath Component's frontend path
|
||||
* @param string $backendPath Component's backend path
|
||||
*
|
||||
* @return void
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public static function migrateJoomla4MenuXMLFiles_real($frontendPath, $backendPath): void
|
||||
{
|
||||
// This only applies to Joomla 3
|
||||
if (version_compare(JVERSION, '3.999.999', 'gt'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Map modern to legacy locations
|
||||
$maps = [
|
||||
$frontendPath . '/tmpl' => $frontendPath . '/views',
|
||||
$frontendPath . '/ViewTemplates' => $frontendPath . '/views',
|
||||
$backendPath . '/tmpl' => $backendPath . '/views',
|
||||
$backendPath . '/ViewTemplates' => $backendPath . '/views',
|
||||
];
|
||||
|
||||
foreach ($maps as $source => $dest)
|
||||
{
|
||||
try
|
||||
{
|
||||
self::migrateViewXMLManifests($source, $dest);
|
||||
}
|
||||
catch (\UnexpectedValueException $e)
|
||||
{
|
||||
// This means the source folder doesn't exist. No problem!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the legacy `views` paths from the front- and backend of the component on Joomla 4 and later versions.
|
||||
*
|
||||
* @param Container $container
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function removeJoomla3LegacyViews(Container $container): void
|
||||
{
|
||||
self::removeJoomla3LegacyViews_real($container->frontEndPath, $container->backEndPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the legacy `views` paths from the front- and backend of the component on Joomla 4 and later versions.
|
||||
*
|
||||
* @param string $frontendPath Component's frontend path
|
||||
* @param string $backendPath Component's backend path
|
||||
*
|
||||
* @return void
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
public static function removeJoomla3LegacyViews_real($frontendPath, $backendPath): void
|
||||
{
|
||||
// This only applies to Joomla 4
|
||||
if (version_compare(JVERSION, '3.999.999', 'le'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$legacyLocations = [
|
||||
$frontendPath . '/views',
|
||||
$backendPath . '/views',
|
||||
];
|
||||
|
||||
foreach ($legacyLocations as $path)
|
||||
{
|
||||
if (!is_dir($path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Folder::delete($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates view manifest XML files from the source to the dest folder.
|
||||
*
|
||||
* @param string $source Source folder to scan, i.e. the `tmpl` or `ViewTemplates` folder.
|
||||
* @param string $dest Target folder to copy the files to, i.e. the legacy `views` folder.
|
||||
*/
|
||||
private static function migrateViewXMLManifests(string $source, string $dest): void
|
||||
{
|
||||
$di = new \DirectoryIterator($source);
|
||||
|
||||
/** @var \DirectoryIterator $folderItem */
|
||||
foreach ($di as $folderItem)
|
||||
{
|
||||
if ($folderItem->isDot() || !$folderItem->isDir())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete the metadata.xml and tmpl/*.xml files in the corresponding `views` subfolder
|
||||
$killLegacyFile = $dest . '/' . $folderItem->getFilename() . '/metadata.xml';
|
||||
$killLegacyFolder = $dest . '/' . $folderItem->getFilename() . '/tmpl';
|
||||
|
||||
if (!@is_file($killLegacyFile))
|
||||
{
|
||||
File::delete($killLegacyFile);
|
||||
}
|
||||
|
||||
if (@file_exists($killLegacyFolder) && @is_dir($killLegacyFolder))
|
||||
{
|
||||
$files = Folder::files($killLegacyFolder, '\.xml$', false, true);
|
||||
|
||||
if (!empty($files))
|
||||
{
|
||||
File::delete($files);
|
||||
}
|
||||
}
|
||||
|
||||
$filesIterator = new \DirectoryIterator($folderItem->getPathname());
|
||||
|
||||
/** @var \DirectoryIterator $fileItem */
|
||||
foreach ($filesIterator as $fileItem)
|
||||
{
|
||||
if ($fileItem->isDir())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($fileItem->getExtension() != 'xml')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$destPath = $dest . '/' . $folderItem->getFilename() . (($fileItem->getFilename() == 'metadata.xml') ? '' : '/tmpl');
|
||||
|
||||
$destPathName = $destPath . '/' . $fileItem->getFilename();
|
||||
|
||||
Folder::create($destPath);
|
||||
File::copy($fileItem->getPathname(), $destPathName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
616
libraries/fof40/Utils/helpers.php
Normal file
616
libraries/fof40/Utils/helpers.php
Normal file
@ -0,0 +1,616 @@
|
||||
<?php
|
||||
/**
|
||||
* @package FOF
|
||||
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
|
||||
* @license GNU General Public License version 3, or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
defined('_JEXEC') || die;
|
||||
|
||||
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 $array, string $key, $value): array
|
||||
{
|
||||
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 $array, Closure $callback): array
|
||||
{
|
||||
$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 $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 $array, string $prepend = ''): array
|
||||
{
|
||||
$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 $array, array $keys): array
|
||||
{
|
||||
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 $array, string $key): array
|
||||
{
|
||||
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 $array, callable $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 $array, callable $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 $array): 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 &$array, string $key): void
|
||||
{
|
||||
$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 $array, string $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 $array, array $keys): array
|
||||
{
|
||||
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 $array, string $value, ?string $key = null): array
|
||||
{
|
||||
$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 &$array, string $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 &$array, string $key, $value): array
|
||||
{
|
||||
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 $array, callable $callback): array
|
||||
{
|
||||
return FOF40\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 $array, callable $callback): array
|
||||
{
|
||||
$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(string $haystack, $needles): bool
|
||||
{
|
||||
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 $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, string $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(string $pattern, array &$replacements, string $subject): string
|
||||
{
|
||||
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(string $haystack, $needles): bool
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('fofStringToBool'))
|
||||
{
|
||||
/**
|
||||
* Convert a string to a boolean. It understands the following human-readable boolean notations 0, 1, true, false,
|
||||
* yes, no, on, off, enabled, disabled. If the value is anything else it will delegate it to PHP's `(bool)` type
|
||||
* casting operator.
|
||||
*
|
||||
* @param string $string The string with the human-readable boolean value.
|
||||
*
|
||||
* @return boolean The converted string
|
||||
*/
|
||||
function fofStringToBool(string $string): bool
|
||||
{
|
||||
$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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user