add trashes
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Helpers\Array;
|
||||
|
||||
use CodeIgniter\Exceptions\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @interal This is internal implementation for the framework.
|
||||
*
|
||||
* If there are any methods that should be provided, make them
|
||||
* public APIs via helper functions.
|
||||
*
|
||||
* @see \CodeIgniter\Helpers\Array\ArrayHelperDotKeyExistsTest
|
||||
* @see \CodeIgniter\Helpers\Array\ArrayHelperRecursiveDiffTest
|
||||
* @see \CodeIgniter\Helpers\Array\ArrayHelperSortValuesByNaturalTest
|
||||
*/
|
||||
final class ArrayHelper
|
||||
{
|
||||
/**
|
||||
* Searches an array through dot syntax. Supports wildcard searches,
|
||||
* like `foo.*.bar`.
|
||||
*
|
||||
* @used-by dot_array_search()
|
||||
*
|
||||
* @param string $index The index as dot array syntax.
|
||||
*
|
||||
* @return array|bool|int|object|string|null
|
||||
*/
|
||||
public static function dotSearch(string $index, array $array)
|
||||
{
|
||||
return self::arraySearchDot(self::convertToArray($index), $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $index The index as dot array syntax.
|
||||
*
|
||||
* @return list<string> The index as an array.
|
||||
*/
|
||||
private static function convertToArray(string $index): array
|
||||
{
|
||||
// See https://regex101.com/r/44Ipql/1
|
||||
$segments = preg_split(
|
||||
'/(?<!\\\\)\./',
|
||||
rtrim($index, '* '),
|
||||
0,
|
||||
PREG_SPLIT_NO_EMPTY,
|
||||
);
|
||||
|
||||
return array_map(
|
||||
static fn ($key): string => str_replace('\.', '.', $key),
|
||||
$segments,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively search the array with wildcards.
|
||||
*
|
||||
* @used-by dotSearch()
|
||||
*
|
||||
* @return array|bool|float|int|object|string|null
|
||||
*/
|
||||
private static function arraySearchDot(array $indexes, array $array)
|
||||
{
|
||||
// If index is empty, returns null.
|
||||
if ($indexes === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Grab the current index
|
||||
$currentIndex = array_shift($indexes);
|
||||
|
||||
if (! isset($array[$currentIndex]) && $currentIndex !== '*') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle Wildcard (*)
|
||||
if ($currentIndex === '*') {
|
||||
$answer = [];
|
||||
|
||||
foreach ($array as $value) {
|
||||
if (! is_array($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$answer[] = self::arraySearchDot($indexes, $value);
|
||||
}
|
||||
|
||||
$answer = array_filter($answer, static fn ($value): bool => $value !== null);
|
||||
|
||||
if ($answer !== []) {
|
||||
// If array only has one element, we return that element for BC.
|
||||
return count($answer) === 1 ? current($answer) : $answer;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// If this is the last index, make sure to return it now,
|
||||
// and not try to recurse through things.
|
||||
if ($indexes === []) {
|
||||
return $array[$currentIndex];
|
||||
}
|
||||
|
||||
// Do we need to recursively search this value?
|
||||
if (is_array($array[$currentIndex]) && $array[$currentIndex] !== []) {
|
||||
return self::arraySearchDot($indexes, $array[$currentIndex]);
|
||||
}
|
||||
|
||||
// Otherwise, not found.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* array_key_exists() with dot array syntax.
|
||||
*
|
||||
* If wildcard `*` is used, all items for the key after it must have the key.
|
||||
*/
|
||||
public static function dotKeyExists(string $index, array $array): bool
|
||||
{
|
||||
if (str_ends_with($index, '*') || str_contains($index, '*.*')) {
|
||||
throw new InvalidArgumentException(
|
||||
'You must set key right after "*". Invalid index: "' . $index . '"',
|
||||
);
|
||||
}
|
||||
|
||||
$indexes = self::convertToArray($index);
|
||||
|
||||
// If indexes is empty, returns false.
|
||||
if ($indexes === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentArray = $array;
|
||||
|
||||
// Grab the current index
|
||||
while ($currentIndex = array_shift($indexes)) {
|
||||
if ($currentIndex === '*') {
|
||||
$currentIndex = array_shift($indexes);
|
||||
|
||||
foreach ($currentArray as $item) {
|
||||
if (! array_key_exists($currentIndex, $item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If indexes is empty, all elements are checked.
|
||||
if ($indexes === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$currentArray = self::dotSearch('*.' . $currentIndex, $currentArray);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! array_key_exists($currentIndex, $currentArray)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentArray = $currentArray[$currentIndex];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups all rows by their index values. Result's depth equals number of indexes
|
||||
*
|
||||
* @used-by array_group_by()
|
||||
*
|
||||
* @param array $array Data array (i.e. from query result)
|
||||
* @param array $indexes Indexes to group by. Dot syntax used. Returns $array if empty
|
||||
* @param bool $includeEmpty If true, null and '' are also added as valid keys to group
|
||||
*
|
||||
* @return array Result array where rows are grouped together by indexes values.
|
||||
*/
|
||||
public static function groupBy(array $array, array $indexes, bool $includeEmpty = false): array
|
||||
{
|
||||
if ($indexes === []) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($array as $row) {
|
||||
$result = self::arrayAttachIndexedValue($result, $row, $indexes, $includeEmpty);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively attach $row to the $indexes path of values found by
|
||||
* `dot_array_search()`.
|
||||
*
|
||||
* @used-by groupBy()
|
||||
*/
|
||||
private static function arrayAttachIndexedValue(
|
||||
array $result,
|
||||
array $row,
|
||||
array $indexes,
|
||||
bool $includeEmpty,
|
||||
): array {
|
||||
if (($index = array_shift($indexes)) === null) {
|
||||
$result[] = $row;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$value = dot_array_search($index, $row);
|
||||
|
||||
if (! is_scalar($value)) {
|
||||
$value = '';
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
$value = (int) $value;
|
||||
}
|
||||
|
||||
if (! $includeEmpty && $value === '') {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (! array_key_exists($value, $result)) {
|
||||
$result[$value] = [];
|
||||
}
|
||||
|
||||
$result[$value] = self::arrayAttachIndexedValue($result[$value], $row, $indexes, $includeEmpty);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare recursively two associative arrays and return difference as new array.
|
||||
* Returns keys that exist in `$original` but not in `$compareWith`.
|
||||
*/
|
||||
public static function recursiveDiff(array $original, array $compareWith): array
|
||||
{
|
||||
$difference = [];
|
||||
|
||||
if ($original === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($compareWith === []) {
|
||||
return $original;
|
||||
}
|
||||
|
||||
foreach ($original as $originalKey => $originalValue) {
|
||||
if ($originalValue === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($originalValue)) {
|
||||
$diffArrays = [];
|
||||
|
||||
if (isset($compareWith[$originalKey]) && is_array($compareWith[$originalKey])) {
|
||||
$diffArrays = self::recursiveDiff($originalValue, $compareWith[$originalKey]);
|
||||
} else {
|
||||
$difference[$originalKey] = $originalValue;
|
||||
}
|
||||
|
||||
if ($diffArrays !== []) {
|
||||
$difference[$originalKey] = $diffArrays;
|
||||
}
|
||||
} elseif (is_string($originalValue) && ! array_key_exists($originalKey, $compareWith)) {
|
||||
$difference[$originalKey] = $originalValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $difference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively count all keys.
|
||||
*/
|
||||
public static function recursiveCount(array $array, int $counter = 0): int
|
||||
{
|
||||
foreach ($array as $value) {
|
||||
if (is_array($value)) {
|
||||
$counter = self::recursiveCount($value, $counter);
|
||||
}
|
||||
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts array values in natural order
|
||||
* If the value is an array, you need to specify the $sortByIndex of the key to sort
|
||||
*
|
||||
* @param list<int|list<int|string>|string> $array
|
||||
* @param int|string|null $sortByIndex
|
||||
*/
|
||||
public static function sortValuesByNatural(array &$array, $sortByIndex = null): bool
|
||||
{
|
||||
return usort($array, static function ($currentValue, $nextValue) use ($sortByIndex): int {
|
||||
if ($sortByIndex !== null) {
|
||||
return strnatcmp((string) $currentValue[$sortByIndex], (string) $nextValue[$sortByIndex]);
|
||||
}
|
||||
|
||||
return strnatcmp((string) $currentValue, (string) $nextValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use CodeIgniter\Helpers\Array\ArrayHelper;
|
||||
|
||||
// CodeIgniter Array Helpers
|
||||
|
||||
if (! function_exists('dot_array_search')) {
|
||||
/**
|
||||
* Searches an array through dot syntax. Supports
|
||||
* wildcard searches, like foo.*.bar
|
||||
*
|
||||
* @return array|bool|int|object|string|null
|
||||
*/
|
||||
function dot_array_search(string $index, array $array)
|
||||
{
|
||||
return ArrayHelper::dotSearch($index, $array);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('array_deep_search')) {
|
||||
/**
|
||||
* Returns the value of an element at a key in an array of uncertain depth.
|
||||
*
|
||||
* @param int|string $key
|
||||
*
|
||||
* @return array|bool|float|int|object|string|null
|
||||
*/
|
||||
function array_deep_search($key, array $array)
|
||||
{
|
||||
if (isset($array[$key])) {
|
||||
return $array[$key];
|
||||
}
|
||||
|
||||
foreach ($array as $value) {
|
||||
if (is_array($value) && ($result = array_deep_search($key, $value))) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('array_sort_by_multiple_keys')) {
|
||||
/**
|
||||
* Sorts a multidimensional array by its elements values. The array
|
||||
* columns to be used for sorting are passed as an associative
|
||||
* array of key names and sorting flags.
|
||||
*
|
||||
* Both arrays of objects and arrays of array can be sorted.
|
||||
*
|
||||
* Example:
|
||||
* array_sort_by_multiple_keys($players, [
|
||||
* 'team.hierarchy' => SORT_ASC,
|
||||
* 'position' => SORT_ASC,
|
||||
* 'name' => SORT_STRING,
|
||||
* ]);
|
||||
*
|
||||
* The '.' dot operator in the column name indicates a deeper array or
|
||||
* object level. In principle, any number of sublevels could be used,
|
||||
* as long as the level and column exist in every array element.
|
||||
*
|
||||
* For information on multi-level array sorting, refer to Example #3 here:
|
||||
* https://www.php.net/manual/de/function.array-multisort.php
|
||||
*
|
||||
* @param array $array the reference of the array to be sorted
|
||||
* @param array $sortColumns an associative array of columns to sort
|
||||
* after and their sorting flags
|
||||
*/
|
||||
function array_sort_by_multiple_keys(array &$array, array $sortColumns): bool
|
||||
{
|
||||
// Check if there really are columns to sort after
|
||||
if ($sortColumns === [] || $array === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Group sorting indexes and data
|
||||
$tempArray = [];
|
||||
|
||||
foreach ($sortColumns as $key => $sortFlag) {
|
||||
// Get sorting values
|
||||
$carry = $array;
|
||||
|
||||
// The '.' operator separates nested elements
|
||||
foreach (explode('.', $key) as $keySegment) {
|
||||
// Loop elements if they are objects
|
||||
if (is_object(reset($carry))) {
|
||||
// Extract the object attribute
|
||||
foreach ($carry as $index => $object) {
|
||||
$carry[$index] = $object->{$keySegment};
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract the target column if elements are arrays
|
||||
$carry = array_column($carry, $keySegment);
|
||||
}
|
||||
|
||||
// Store the collected sorting parameters
|
||||
$tempArray[] = $carry;
|
||||
$tempArray[] = $sortFlag;
|
||||
}
|
||||
|
||||
// Append the array as reference
|
||||
$tempArray[] = &$array;
|
||||
|
||||
// Pass sorting arrays and flags as an argument list.
|
||||
return array_multisort(...$tempArray);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('array_flatten_with_dots')) {
|
||||
/**
|
||||
* Flatten a multidimensional array using dots as separators.
|
||||
*
|
||||
* @param iterable $array The multi-dimensional array
|
||||
* @param string $id Something to initially prepend to the flattened keys
|
||||
*
|
||||
* @return array The flattened array
|
||||
*/
|
||||
function array_flatten_with_dots(iterable $array, string $id = ''): array
|
||||
{
|
||||
$flattened = [];
|
||||
|
||||
foreach ($array as $key => $value) {
|
||||
$newKey = $id . $key;
|
||||
|
||||
if (is_array($value) && $value !== []) {
|
||||
$flattened = array_merge($flattened, array_flatten_with_dots($value, $newKey . '.'));
|
||||
} else {
|
||||
$flattened[$newKey] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $flattened;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('array_group_by')) {
|
||||
/**
|
||||
* Groups all rows by their index values. Result's depth equals number of indexes
|
||||
*
|
||||
* @param array $array Data array (i.e. from query result)
|
||||
* @param array $indexes Indexes to group by. Dot syntax used. Returns $array if empty
|
||||
* @param bool $includeEmpty If true, null and '' are also added as valid keys to group
|
||||
*
|
||||
* @return array Result array where rows are grouped together by indexes values.
|
||||
*/
|
||||
function array_group_by(array $array, array $indexes, bool $includeEmpty = false): array
|
||||
{
|
||||
return ArrayHelper::groupBy($array, $indexes, $includeEmpty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use CodeIgniter\Cookie\Cookie;
|
||||
use Config\Cookie as CookieConfig;
|
||||
|
||||
// =============================================================================
|
||||
// CodeIgniter Cookie Helpers
|
||||
// =============================================================================
|
||||
|
||||
if (! function_exists('set_cookie')) {
|
||||
/**
|
||||
* Set cookie
|
||||
*
|
||||
* Accepts seven parameters, or you can submit an associative
|
||||
* array in the first parameter containing all the values.
|
||||
*
|
||||
* @param array|Cookie|string $name Cookie name / array containing binds / Cookie object
|
||||
* @param string $value The value of the cookie
|
||||
* @param int $expire The number of seconds until expiration
|
||||
* @param string $domain For site-wide cookie. Usually: .yourdomain.com
|
||||
* @param string $path The cookie path
|
||||
* @param string $prefix The cookie prefix ('': the default prefix)
|
||||
* @param bool|null $secure True makes the cookie secure
|
||||
* @param bool|null $httpOnly True makes the cookie accessible via http(s) only (no javascript)
|
||||
* @param string|null $sameSite The cookie SameSite value
|
||||
*
|
||||
* @see \CodeIgniter\HTTP\Response::setCookie()
|
||||
*/
|
||||
function set_cookie(
|
||||
$name,
|
||||
string $value = '',
|
||||
int $expire = 0,
|
||||
string $domain = '',
|
||||
string $path = '/',
|
||||
string $prefix = '',
|
||||
?bool $secure = null,
|
||||
?bool $httpOnly = null,
|
||||
?string $sameSite = null,
|
||||
): void {
|
||||
$response = service('response');
|
||||
$response->setCookie($name, $value, $expire, $domain, $path, $prefix, $secure, $httpOnly, $sameSite);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_cookie')) {
|
||||
/**
|
||||
* Fetch an item from the $_COOKIE array
|
||||
*
|
||||
* @param string $index
|
||||
* @param string|null $prefix Cookie name prefix.
|
||||
* '': the prefix in Config\Cookie
|
||||
* null: no prefix
|
||||
*
|
||||
* @return array|string|null
|
||||
*
|
||||
* @see \CodeIgniter\HTTP\IncomingRequest::getCookie()
|
||||
*/
|
||||
function get_cookie($index, bool $xssClean = false, ?string $prefix = '')
|
||||
{
|
||||
if ($prefix === '') {
|
||||
$cookie = config(CookieConfig::class);
|
||||
|
||||
$prefix = $cookie->prefix;
|
||||
}
|
||||
|
||||
$request = service('request');
|
||||
$filter = $xssClean ? FILTER_SANITIZE_FULL_SPECIAL_CHARS : FILTER_DEFAULT;
|
||||
|
||||
return $request->getCookie($prefix . $index, $filter);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('delete_cookie')) {
|
||||
/**
|
||||
* Delete a cookie
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $domain the cookie domain. Usually: .yourdomain.com
|
||||
* @param string $path the cookie path
|
||||
* @param string $prefix the cookie prefix
|
||||
*
|
||||
* @see \CodeIgniter\HTTP\Response::deleteCookie()
|
||||
*/
|
||||
function delete_cookie($name, string $domain = '', string $path = '/', string $prefix = ''): void
|
||||
{
|
||||
service('response')->deleteCookie($name, $domain, $path, $prefix);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('has_cookie')) {
|
||||
/**
|
||||
* Checks if a cookie exists by name.
|
||||
*/
|
||||
function has_cookie(string $name, ?string $value = null, string $prefix = ''): bool
|
||||
{
|
||||
return service('response')->hasCookie($name, $value, $prefix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use CodeIgniter\I18n\Time;
|
||||
|
||||
// CodeIgniter Date Helpers
|
||||
|
||||
if (! function_exists('now')) {
|
||||
/**
|
||||
* Get "now" time
|
||||
*
|
||||
* Returns Time::now()->getTimestamp() based on the timezone parameter or on the
|
||||
* app_timezone() setting
|
||||
*
|
||||
* @param non-empty-string|null $timezone
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function now(?string $timezone = null): int
|
||||
{
|
||||
$timezone = ($timezone === null || $timezone === '') ? app_timezone() : $timezone;
|
||||
|
||||
if ($timezone === 'local' || $timezone === date_default_timezone_get()) {
|
||||
return Time::now()->getTimestamp();
|
||||
}
|
||||
|
||||
$time = Time::now($timezone);
|
||||
sscanf(
|
||||
$time->format('j-n-Y G:i:s'),
|
||||
'%d-%d-%d %d:%d:%d',
|
||||
$day,
|
||||
$month,
|
||||
$year,
|
||||
$hour,
|
||||
$minute,
|
||||
$second,
|
||||
);
|
||||
|
||||
return mktime($hour, $minute, $second, $month, $day, $year);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('timezone_select')) {
|
||||
/**
|
||||
* Generates a select field of all available timezones
|
||||
*
|
||||
* Returns a string with the formatted HTML
|
||||
*
|
||||
* @param string $class Optional class to apply to the select field
|
||||
* @param string $default Default value for initial selection
|
||||
* @param int $what One of the DateTimeZone class constants (for listIdentifiers)
|
||||
* @param string $country A two-letter ISO 3166-1 compatible country code (for listIdentifiers)
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function timezone_select(string $class = '', string $default = '', int $what = DateTimeZone::ALL, ?string $country = null): string
|
||||
{
|
||||
$timezones = DateTimeZone::listIdentifiers($what, $country);
|
||||
|
||||
$buffer = "<select name='timezone' class='{$class}'>\n";
|
||||
|
||||
foreach ($timezones as $timezone) {
|
||||
$selected = ($timezone === $default) ? 'selected' : '';
|
||||
$buffer .= "<option value='{$timezone}' {$selected}>{$timezone}</option>\n";
|
||||
}
|
||||
|
||||
return $buffer . ("</select>\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use CodeIgniter\Exceptions\InvalidArgumentException;
|
||||
|
||||
// CodeIgniter File System Helpers
|
||||
|
||||
if (! function_exists('directory_map')) {
|
||||
/**
|
||||
* Create a Directory Map
|
||||
*
|
||||
* Reads the specified directory and builds an array
|
||||
* representation of it. Sub-folders contained with the
|
||||
* directory will be mapped as well.
|
||||
*
|
||||
* @param string $sourceDir Path to source
|
||||
* @param int $directoryDepth Depth of directories to traverse
|
||||
* (0 = fully recursive, 1 = current dir, etc)
|
||||
* @param bool $hidden Whether to show hidden files
|
||||
*/
|
||||
function directory_map(string $sourceDir, int $directoryDepth = 0, bool $hidden = false): array
|
||||
{
|
||||
try {
|
||||
$fp = opendir($sourceDir);
|
||||
|
||||
$fileData = [];
|
||||
$newDepth = $directoryDepth - 1;
|
||||
$sourceDir = rtrim($sourceDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
|
||||
while (false !== ($file = readdir($fp))) {
|
||||
// Remove '.', '..', and hidden files [optional]
|
||||
if ($file === '.' || $file === '..' || ($hidden === false && $file[0] === '.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_dir($sourceDir . $file)) {
|
||||
$file .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
if (($directoryDepth < 1 || $newDepth > 0) && is_dir($sourceDir . $file)) {
|
||||
$fileData[$file] = directory_map($sourceDir . $file, $newDepth, $hidden);
|
||||
} else {
|
||||
$fileData[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
closedir($fp);
|
||||
|
||||
return $fileData;
|
||||
} catch (Throwable) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('directory_mirror')) {
|
||||
/**
|
||||
* Recursively copies the files and directories of the origin directory
|
||||
* into the target directory, i.e. "mirror" its contents.
|
||||
*
|
||||
* @param bool $overwrite Whether individual files overwrite on collision
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
function directory_mirror(string $originDir, string $targetDir, bool $overwrite = true): void
|
||||
{
|
||||
if (! is_dir($originDir = rtrim($originDir, '\\/'))) {
|
||||
throw new InvalidArgumentException(sprintf('The origin directory "%s" was not found.', $originDir));
|
||||
}
|
||||
|
||||
if (! is_dir($targetDir = rtrim($targetDir, '\\/'))) {
|
||||
@mkdir($targetDir, 0755, true);
|
||||
}
|
||||
|
||||
$dirLen = strlen($originDir);
|
||||
|
||||
/**
|
||||
* @var SplFileInfo $file
|
||||
*/
|
||||
foreach (new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($originDir, FilesystemIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST,
|
||||
) as $file) {
|
||||
$origin = $file->getPathname();
|
||||
$target = $targetDir . substr($origin, $dirLen);
|
||||
|
||||
if ($file->isDir()) {
|
||||
if (! is_dir($target)) {
|
||||
mkdir($target, 0755);
|
||||
}
|
||||
} elseif ($overwrite || ! is_file($target)) {
|
||||
copy($origin, $target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('write_file')) {
|
||||
/**
|
||||
* Write File
|
||||
*
|
||||
* Writes data to the file specified in the path.
|
||||
* Creates a new file if non-existent.
|
||||
*
|
||||
* @param string $path File path
|
||||
* @param string $data Data to write
|
||||
* @param string $mode fopen() mode (default: 'wb')
|
||||
*/
|
||||
function write_file(string $path, string $data, string $mode = 'wb'): bool
|
||||
{
|
||||
try {
|
||||
$fp = fopen($path, $mode);
|
||||
|
||||
flock($fp, LOCK_EX);
|
||||
|
||||
$result = 0;
|
||||
|
||||
for ($written = 0, $length = strlen($data); $written < $length; $written += $result) {
|
||||
if (($result = fwrite($fp, substr($data, $written))) === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
|
||||
return is_int($result);
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('delete_files')) {
|
||||
/**
|
||||
* Delete Files
|
||||
*
|
||||
* Deletes all files contained in the supplied directory path.
|
||||
* Files must be writable or owned by the system in order to be deleted.
|
||||
* If the second parameter is set to true, any directories contained
|
||||
* within the supplied base directory will be nuked as well.
|
||||
*
|
||||
* @param string $path File path
|
||||
* @param bool $delDir Whether to delete any directories found in the path
|
||||
* @param bool $htdocs Whether to skip deleting .htaccess and index page files
|
||||
* @param bool $hidden Whether to include hidden files (files beginning with a period)
|
||||
*/
|
||||
function delete_files(string $path, bool $delDir = false, bool $htdocs = false, bool $hidden = false): bool
|
||||
{
|
||||
$path = realpath($path) ?: $path;
|
||||
$path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
|
||||
try {
|
||||
foreach (new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::CHILD_FIRST,
|
||||
) as $object) {
|
||||
$filename = $object->getFilename();
|
||||
if (! $hidden && $filename[0] === '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $htdocs || preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename) !== 1) {
|
||||
$isDir = $object->isDir();
|
||||
if ($isDir && $delDir) {
|
||||
rmdir($object->getPathname());
|
||||
|
||||
continue;
|
||||
}
|
||||
if (! $isDir) {
|
||||
unlink($object->getPathname());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_filenames')) {
|
||||
/**
|
||||
* Get Filenames
|
||||
*
|
||||
* Reads the specified directory and builds an array containing the filenames.
|
||||
* Any sub-folders contained within the specified path are read as well.
|
||||
*
|
||||
* @param string $sourceDir Path to source
|
||||
* @param bool|null $includePath Whether to include the path as part of the filename; false for no path, null for a relative path, true for full path
|
||||
* @param bool $hidden Whether to include hidden files (files beginning with a period)
|
||||
* @param bool $includeDir Whether to include directories
|
||||
*/
|
||||
function get_filenames(
|
||||
string $sourceDir,
|
||||
?bool $includePath = false,
|
||||
bool $hidden = false,
|
||||
bool $includeDir = true,
|
||||
): array {
|
||||
$files = [];
|
||||
|
||||
$sourceDir = realpath($sourceDir) ?: $sourceDir;
|
||||
$sourceDir = rtrim($sourceDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
|
||||
try {
|
||||
foreach (new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS),
|
||||
RecursiveIteratorIterator::SELF_FIRST,
|
||||
) as $name => $object) {
|
||||
$basename = pathinfo($name, PATHINFO_BASENAME);
|
||||
if (! $hidden && $basename[0] === '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($includeDir || ! $object->isDir()) {
|
||||
if ($includePath === false) {
|
||||
$files[] = $basename;
|
||||
} elseif ($includePath === null) {
|
||||
$files[] = str_replace($sourceDir, '', $name);
|
||||
} else {
|
||||
$files[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable) {
|
||||
return [];
|
||||
}
|
||||
|
||||
sort($files);
|
||||
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_dir_file_info')) {
|
||||
/**
|
||||
* Get Directory File Information
|
||||
*
|
||||
* Reads the specified directory and builds an array containing the filenames,
|
||||
* filesize, dates, and permissions
|
||||
*
|
||||
* Any sub-folders contained within the specified path are read as well.
|
||||
*
|
||||
* @param string $sourceDir Path to source
|
||||
* @param bool $topLevelOnly Look only at the top level directory specified?
|
||||
* @param bool $recursion Internal variable to determine recursion status - do not use in calls
|
||||
*/
|
||||
function get_dir_file_info(string $sourceDir, bool $topLevelOnly = true, bool $recursion = false): array
|
||||
{
|
||||
static $fileData = [];
|
||||
$relativePath = $sourceDir;
|
||||
|
||||
try {
|
||||
$fp = opendir($sourceDir);
|
||||
|
||||
// reset the array and make sure $sourceDir has a trailing slash on the initial call
|
||||
if ($recursion === false) {
|
||||
$fileData = [];
|
||||
$sourceDir = rtrim(realpath($sourceDir), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
// Used to be foreach (scandir($sourceDir, 1) as $file), but scandir() is simply not as fast
|
||||
while (false !== ($file = readdir($fp))) {
|
||||
if (is_dir($sourceDir . $file) && $file[0] !== '.' && $topLevelOnly === false) {
|
||||
get_dir_file_info($sourceDir . $file . DIRECTORY_SEPARATOR, $topLevelOnly, true);
|
||||
} elseif ($file[0] !== '.') {
|
||||
$fileData[$file] = get_file_info($sourceDir . $file);
|
||||
$fileData[$file]['relative_path'] = $relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
closedir($fp);
|
||||
|
||||
return $fileData;
|
||||
} catch (Throwable) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('get_file_info')) {
|
||||
/**
|
||||
* Get File Info
|
||||
*
|
||||
* Given a file and path, returns the name, path, size, date modified
|
||||
* Second parameter allows you to explicitly declare what information you want returned
|
||||
* Options are: name, server_path, size, date, readable, writable, executable, fileperms
|
||||
* Returns false if the file cannot be found.
|
||||
*
|
||||
* @param string $file Path to file
|
||||
* @param array|string $returnedValues Array or comma separated string of information returned
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
function get_file_info(string $file, $returnedValues = ['name', 'server_path', 'size', 'date'])
|
||||
{
|
||||
if (! is_file($file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$fileInfo = [];
|
||||
|
||||
if (is_string($returnedValues)) {
|
||||
$returnedValues = explode(',', $returnedValues);
|
||||
}
|
||||
|
||||
foreach ($returnedValues as $key) {
|
||||
switch ($key) {
|
||||
case 'name':
|
||||
$fileInfo['name'] = basename($file);
|
||||
break;
|
||||
|
||||
case 'server_path':
|
||||
$fileInfo['server_path'] = $file;
|
||||
break;
|
||||
|
||||
case 'size':
|
||||
$fileInfo['size'] = filesize($file);
|
||||
break;
|
||||
|
||||
case 'date':
|
||||
$fileInfo['date'] = filemtime($file);
|
||||
break;
|
||||
|
||||
case 'readable':
|
||||
$fileInfo['readable'] = is_readable($file);
|
||||
break;
|
||||
|
||||
case 'writable':
|
||||
$fileInfo['writable'] = is_really_writable($file);
|
||||
break;
|
||||
|
||||
case 'executable':
|
||||
$fileInfo['executable'] = is_executable($file);
|
||||
break;
|
||||
|
||||
case 'fileperms':
|
||||
$fileInfo['fileperms'] = fileperms($file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $fileInfo;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('symbolic_permissions')) {
|
||||
/**
|
||||
* Symbolic Permissions
|
||||
*
|
||||
* Takes a numeric value representing a file's permissions and returns
|
||||
* standard symbolic notation representing that value
|
||||
*
|
||||
* @param int $perms Permissions
|
||||
*/
|
||||
function symbolic_permissions(int $perms): string
|
||||
{
|
||||
if (($perms & 0xC000) === 0xC000) {
|
||||
$symbolic = 's'; // Socket
|
||||
} elseif (($perms & 0xA000) === 0xA000) {
|
||||
$symbolic = 'l'; // Symbolic Link
|
||||
} elseif (($perms & 0x8000) === 0x8000) {
|
||||
$symbolic = '-'; // Regular
|
||||
} elseif (($perms & 0x6000) === 0x6000) {
|
||||
$symbolic = 'b'; // Block special
|
||||
} elseif (($perms & 0x4000) === 0x4000) {
|
||||
$symbolic = 'd'; // Directory
|
||||
} elseif (($perms & 0x2000) === 0x2000) {
|
||||
$symbolic = 'c'; // Character special
|
||||
} elseif (($perms & 0x1000) === 0x1000) {
|
||||
$symbolic = 'p'; // FIFO pipe
|
||||
} else {
|
||||
$symbolic = 'u'; // Unknown
|
||||
}
|
||||
|
||||
// Owner
|
||||
$symbolic .= ((($perms & 0x0100) !== 0) ? 'r' : '-')
|
||||
. ((($perms & 0x0080) !== 0) ? 'w' : '-')
|
||||
. ((($perms & 0x0040) !== 0) ? ((($perms & 0x0800) !== 0) ? 's' : 'x') : ((($perms & 0x0800) !== 0) ? 'S' : '-'));
|
||||
|
||||
// Group
|
||||
$symbolic .= ((($perms & 0x0020) !== 0) ? 'r' : '-')
|
||||
. ((($perms & 0x0010) !== 0) ? 'w' : '-')
|
||||
. ((($perms & 0x0008) !== 0) ? ((($perms & 0x0400) !== 0) ? 's' : 'x') : ((($perms & 0x0400) !== 0) ? 'S' : '-'));
|
||||
|
||||
// World
|
||||
$symbolic .= ((($perms & 0x0004) !== 0) ? 'r' : '-')
|
||||
. ((($perms & 0x0002) !== 0) ? 'w' : '-')
|
||||
. ((($perms & 0x0001) !== 0) ? ((($perms & 0x0200) !== 0) ? 't' : 'x') : ((($perms & 0x0200) !== 0) ? 'T' : '-'));
|
||||
|
||||
return $symbolic;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('octal_permissions')) {
|
||||
/**
|
||||
* Octal Permissions
|
||||
*
|
||||
* Takes a numeric value representing a file's permissions and returns
|
||||
* a three character string representing the file's octal permissions
|
||||
*
|
||||
* @param int $perms Permissions
|
||||
*/
|
||||
function octal_permissions(int $perms): string
|
||||
{
|
||||
return substr(sprintf('%o', $perms), -3);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('same_file')) {
|
||||
/**
|
||||
* Checks if two files both exist and have identical hashes
|
||||
*
|
||||
* @return bool Same or not
|
||||
*/
|
||||
function same_file(string $file1, string $file2): bool
|
||||
{
|
||||
return is_file($file1) && is_file($file2) && md5_file($file1) === md5_file($file2);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('set_realpath')) {
|
||||
/**
|
||||
* Set Realpath
|
||||
*
|
||||
* @param bool $checkExistence Checks to see if the path exists
|
||||
*/
|
||||
function set_realpath(string $path, bool $checkExistence = false): string
|
||||
{
|
||||
// Security check to make sure the path is NOT a URL. No remote file inclusion!
|
||||
if (preg_match('#^(http:\/\/|https:\/\/|www\.|ftp)#i', $path) || filter_var($path, FILTER_VALIDATE_IP) === $path) {
|
||||
throw new InvalidArgumentException('The path you submitted must be a local server path, not a URL');
|
||||
}
|
||||
|
||||
// Resolve the path
|
||||
if (realpath($path) !== false) {
|
||||
$path = realpath($path);
|
||||
} elseif ($checkExistence && ! is_dir($path) && ! is_file($path)) {
|
||||
throw new InvalidArgumentException('Not a valid path: ' . $path);
|
||||
}
|
||||
|
||||
// Add a trailing slash, if this is a directory
|
||||
return is_dir($path) ? rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,802 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use CodeIgniter\Validation\Exceptions\ValidationException;
|
||||
use Config\App;
|
||||
use Config\Validation;
|
||||
|
||||
// CodeIgniter Form Helpers
|
||||
|
||||
if (! function_exists('form_open')) {
|
||||
/**
|
||||
* Form Declaration
|
||||
*
|
||||
* Creates the opening portion of the form.
|
||||
*
|
||||
* @param string $action the URI segments of the form destination
|
||||
* @param array|string $attributes a key/value pair of attributes, or string representation
|
||||
* @param array $hidden a key/value pair hidden data
|
||||
*/
|
||||
function form_open(string $action = '', $attributes = [], array $hidden = []): string
|
||||
{
|
||||
// If no action is provided then set to the current url
|
||||
if ($action === '') {
|
||||
$action = (string) current_url(true);
|
||||
} // If an action is not a full URL then turn it into one
|
||||
elseif (! str_contains($action, '://')) {
|
||||
// If an action has {locale}
|
||||
if (str_contains($action, '{locale}')) {
|
||||
$action = str_replace('{locale}', service('request')->getLocale(), $action);
|
||||
}
|
||||
|
||||
$action = site_url($action);
|
||||
}
|
||||
|
||||
if (is_array($attributes) && array_key_exists('csrf_id', $attributes)) {
|
||||
$csrfId = $attributes['csrf_id'];
|
||||
unset($attributes['csrf_id']);
|
||||
}
|
||||
|
||||
$attributes = stringify_attributes($attributes);
|
||||
|
||||
if (! str_contains(strtolower($attributes), 'method=')) {
|
||||
$attributes .= ' method="post"';
|
||||
}
|
||||
if (! str_contains(strtolower($attributes), 'accept-charset=')) {
|
||||
$config = config(App::class);
|
||||
$attributes .= ' accept-charset="' . strtolower($config->charset) . '"';
|
||||
}
|
||||
|
||||
$form = '<form action="' . $action . '"' . $attributes . ">\n";
|
||||
|
||||
// Add CSRF field if enabled, but leave it out for GET requests and requests to external websites
|
||||
$before = service('filters')->getFilters()['before'];
|
||||
|
||||
if ((in_array('csrf', $before, true) || array_key_exists('csrf', $before)) && str_contains($action, base_url()) && ! str_contains(strtolower($form), strtolower('method="get"'))) {
|
||||
$form .= csrf_field($csrfId ?? null);
|
||||
}
|
||||
|
||||
foreach ($hidden as $name => $value) {
|
||||
$form .= form_hidden($name, $value);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_open_multipart')) {
|
||||
/**
|
||||
* Form Declaration - Multipart type
|
||||
*
|
||||
* Creates the opening portion of the form, but with "multipart/form-data".
|
||||
*
|
||||
* @param string $action The URI segments of the form destination
|
||||
* @param array|string $attributes A key/value pair of attributes, or the same as a string
|
||||
* @param array $hidden A key/value pair hidden data
|
||||
*/
|
||||
function form_open_multipart(string $action = '', $attributes = [], array $hidden = []): string
|
||||
{
|
||||
if (is_string($attributes)) {
|
||||
$attributes .= ' enctype="' . esc('multipart/form-data') . '"';
|
||||
} else {
|
||||
$attributes['enctype'] = 'multipart/form-data';
|
||||
}
|
||||
|
||||
return form_open($action, $attributes, $hidden);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_hidden')) {
|
||||
/**
|
||||
* Hidden Input Field
|
||||
*
|
||||
* Generates hidden fields. You can pass a simple key/value string or
|
||||
* an associative array with multiple values.
|
||||
*
|
||||
* @param array|string $name Field name or associative array to create multiple fields
|
||||
* @param array|string $value Field value
|
||||
*/
|
||||
function form_hidden($name, $value = '', bool $recursing = false): string
|
||||
{
|
||||
static $form;
|
||||
|
||||
if ($recursing === false) {
|
||||
$form = "\n";
|
||||
}
|
||||
|
||||
if (is_array($name)) {
|
||||
foreach ($name as $key => $val) {
|
||||
form_hidden($key, $val, true);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
if (! is_array($value)) {
|
||||
$form .= form_input($name, $value, '', 'hidden');
|
||||
} else {
|
||||
foreach ($value as $k => $v) {
|
||||
$k = is_int($k) ? '' : $k;
|
||||
form_hidden($name . '[' . $k . ']', $v, true);
|
||||
}
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_input')) {
|
||||
/**
|
||||
* Text Input Field. If 'type' is passed in the $type field, it will be
|
||||
* used as the input type, for making 'email', 'phone', etc input fields.
|
||||
*
|
||||
* @param array|string $data
|
||||
* @param array|object|string $extra string, array, object that can be cast to array
|
||||
*/
|
||||
function form_input($data = '', string $value = '', $extra = '', string $type = 'text'): string
|
||||
{
|
||||
$defaults = [
|
||||
'type' => $type,
|
||||
'name' => is_array($data) ? '' : $data,
|
||||
'value' => $value,
|
||||
];
|
||||
|
||||
return '<input ' . parse_form_attributes($data, $defaults) . stringify_attributes($extra) . _solidus() . ">\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_password')) {
|
||||
/**
|
||||
* Password Field
|
||||
*
|
||||
* Identical to the input function but adds the "password" type
|
||||
*
|
||||
* @param array|string $data
|
||||
* @param array|object|string $extra string, array, object that can be cast to array
|
||||
*/
|
||||
function form_password($data = '', string $value = '', $extra = ''): string
|
||||
{
|
||||
if (! is_array($data)) {
|
||||
$data = ['name' => $data];
|
||||
}
|
||||
$data['type'] = 'password';
|
||||
|
||||
return form_input($data, $value, $extra);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_upload')) {
|
||||
/**
|
||||
* Upload Field
|
||||
*
|
||||
* Identical to the input function but adds the "file" type
|
||||
*
|
||||
* @param array|string $data
|
||||
* @param array|object|string $extra string, array, object that can be cast to array
|
||||
*/
|
||||
function form_upload($data = '', string $value = '', $extra = ''): string
|
||||
{
|
||||
$defaults = [
|
||||
'type' => 'file',
|
||||
'name' => '',
|
||||
];
|
||||
|
||||
if (! is_array($data)) {
|
||||
$data = ['name' => $data];
|
||||
}
|
||||
|
||||
$data['type'] = 'file';
|
||||
|
||||
return '<input ' . parse_form_attributes($data, $defaults) . stringify_attributes($extra) . _solidus() . ">\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_textarea')) {
|
||||
/**
|
||||
* Textarea field
|
||||
*
|
||||
* @param array|string $data
|
||||
* @param array|object|string $extra string, array, object that can be cast to array
|
||||
*/
|
||||
function form_textarea($data = '', string $value = '', $extra = ''): string
|
||||
{
|
||||
$defaults = [
|
||||
'name' => is_array($data) ? '' : $data,
|
||||
'cols' => '40',
|
||||
'rows' => '10',
|
||||
];
|
||||
if (! is_array($data) || ! isset($data['value'])) {
|
||||
$val = $value;
|
||||
} else {
|
||||
$val = $data['value'];
|
||||
unset($data['value']); // textareas don't use the value attribute
|
||||
}
|
||||
|
||||
// Unsets default rows and cols if defined in extra field as array or string.
|
||||
if ((is_array($extra) && array_key_exists('rows', $extra)) || (is_string($extra) && str_contains(strtolower(preg_replace('/\s+/', '', $extra)), 'rows='))) {
|
||||
unset($defaults['rows']);
|
||||
}
|
||||
|
||||
if ((is_array($extra) && array_key_exists('cols', $extra)) || (is_string($extra) && str_contains(strtolower(preg_replace('/\s+/', '', $extra)), 'cols='))) {
|
||||
unset($defaults['cols']);
|
||||
}
|
||||
|
||||
return '<textarea ' . rtrim(parse_form_attributes($data, $defaults)) . stringify_attributes($extra) . '>'
|
||||
. htmlspecialchars($val)
|
||||
. "</textarea>\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_multiselect')) {
|
||||
/**
|
||||
* Multi-select menu
|
||||
*
|
||||
* @param array|string $name
|
||||
* @param array|object|string $extra string, array, object that can be cast to array
|
||||
*/
|
||||
function form_multiselect($name = '', array $options = [], array $selected = [], $extra = ''): string
|
||||
{
|
||||
$extra = stringify_attributes($extra);
|
||||
|
||||
if (! str_contains(strtolower($extra), strtolower('multiple'))) {
|
||||
$extra .= ' multiple="multiple"';
|
||||
}
|
||||
|
||||
return form_dropdown($name, $options, $selected, $extra);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_dropdown')) {
|
||||
/**
|
||||
* Drop-down Menu
|
||||
*
|
||||
* @param array|string $data
|
||||
* @param array|string $options
|
||||
* @param array|string $selected
|
||||
* @param array|object|string $extra string, array, object that can be cast to array
|
||||
*/
|
||||
function form_dropdown($data = '', $options = [], $selected = [], $extra = ''): string
|
||||
{
|
||||
$defaults = [];
|
||||
if (is_array($data)) {
|
||||
if (isset($data['selected'])) {
|
||||
$selected = $data['selected'];
|
||||
unset($data['selected']); // select tags don't have a selected attribute
|
||||
}
|
||||
if (isset($data['options'])) {
|
||||
$options = $data['options'];
|
||||
unset($data['options']); // select tags don't use an options attribute
|
||||
}
|
||||
} else {
|
||||
$defaults = ['name' => $data];
|
||||
}
|
||||
|
||||
if (! is_array($selected)) {
|
||||
$selected = [$selected];
|
||||
}
|
||||
if (! is_array($options)) {
|
||||
$options = [$options];
|
||||
}
|
||||
|
||||
// If no selected state was submitted we will attempt to set it automatically
|
||||
if ($selected === []) {
|
||||
if (is_array($data)) {
|
||||
if (isset($data['name'], $_POST[$data['name']])) {
|
||||
$selected = [$_POST[$data['name']]];
|
||||
}
|
||||
} elseif (isset($_POST[$data])) {
|
||||
$selected = [$_POST[$data]];
|
||||
}
|
||||
}
|
||||
|
||||
// Standardize selected as strings, like the option keys will be
|
||||
foreach ($selected as $key => $item) {
|
||||
$selected[$key] = (string) $item;
|
||||
}
|
||||
|
||||
$extra = stringify_attributes($extra);
|
||||
$multiple = (count($selected) > 1 && ! str_contains(strtolower($extra), 'multiple')) ? ' multiple="multiple"' : '';
|
||||
$form = '<select ' . rtrim(parse_form_attributes($data, $defaults)) . $extra . $multiple . ">\n";
|
||||
|
||||
foreach ($options as $key => $val) {
|
||||
// Keys should always be strings for strict comparison
|
||||
$key = (string) $key;
|
||||
|
||||
if (is_array($val)) {
|
||||
if ($val === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$form .= '<optgroup label="' . $key . "\">\n";
|
||||
|
||||
foreach ($val as $optgroupKey => $optgroupVal) {
|
||||
// Keys should always be strings for strict comparison
|
||||
$optgroupKey = (string) $optgroupKey;
|
||||
|
||||
$sel = in_array($optgroupKey, $selected, true) ? ' selected="selected"' : '';
|
||||
$form .= '<option value="' . htmlspecialchars($optgroupKey) . '"' . $sel . '>' . $optgroupVal . "</option>\n";
|
||||
}
|
||||
|
||||
$form .= "</optgroup>\n";
|
||||
} else {
|
||||
$form .= '<option value="' . htmlspecialchars($key) . '"'
|
||||
. (in_array($key, $selected, true) ? ' selected="selected"' : '') . '>'
|
||||
. $val . "</option>\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $form . "</select>\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_checkbox')) {
|
||||
/**
|
||||
* Checkbox Field
|
||||
*
|
||||
* @param array|string $data
|
||||
* @param array|object|string $extra string, array, object that can be cast to array
|
||||
*/
|
||||
function form_checkbox($data = '', string $value = '', bool $checked = false, $extra = ''): string
|
||||
{
|
||||
$defaults = [
|
||||
'type' => 'checkbox',
|
||||
'name' => (! is_array($data) ? $data : ''),
|
||||
'value' => $value,
|
||||
];
|
||||
|
||||
if (is_array($data) && array_key_exists('checked', $data)) {
|
||||
$checked = $data['checked'];
|
||||
|
||||
if ($checked === false) {
|
||||
unset($data['checked']);
|
||||
} else {
|
||||
$data['checked'] = 'checked';
|
||||
}
|
||||
}
|
||||
|
||||
if ($checked === true) {
|
||||
$defaults['checked'] = 'checked';
|
||||
}
|
||||
|
||||
return '<input ' . parse_form_attributes($data, $defaults) . stringify_attributes($extra) . _solidus() . ">\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_radio')) {
|
||||
/**
|
||||
* Radio Button
|
||||
*
|
||||
* @param array|string $data
|
||||
* @param array|object|string $extra string, array, object that can be cast to array
|
||||
*/
|
||||
function form_radio($data = '', string $value = '', bool $checked = false, $extra = ''): string
|
||||
{
|
||||
if (! is_array($data)) {
|
||||
$data = ['name' => $data];
|
||||
}
|
||||
$data['type'] = 'radio';
|
||||
|
||||
return form_checkbox($data, $value, $checked, $extra);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_submit')) {
|
||||
/**
|
||||
* Submit Button
|
||||
*
|
||||
* @param array|string $data
|
||||
* @param array|object|string $extra string, array, object that can be cast to array
|
||||
*/
|
||||
function form_submit($data = '', string $value = '', $extra = ''): string
|
||||
{
|
||||
return form_input($data, $value, $extra, 'submit');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_reset')) {
|
||||
/**
|
||||
* Reset Button
|
||||
*
|
||||
* @param array|string $data
|
||||
* @param array|object|string $extra string, array, object that can be cast to array
|
||||
*/
|
||||
function form_reset($data = '', string $value = '', $extra = ''): string
|
||||
{
|
||||
return form_input($data, $value, $extra, 'reset');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_button')) {
|
||||
/**
|
||||
* Form Button
|
||||
*
|
||||
* @param array|string $data
|
||||
* @param array|object|string $extra string, array, object that can be cast to array
|
||||
*/
|
||||
function form_button($data = '', string $content = '', $extra = ''): string
|
||||
{
|
||||
$defaults = [
|
||||
'name' => is_array($data) ? '' : $data,
|
||||
'type' => 'button',
|
||||
];
|
||||
|
||||
if (is_array($data) && isset($data['content'])) {
|
||||
$content = $data['content'];
|
||||
unset($data['content']); // content is not an attribute
|
||||
}
|
||||
|
||||
return '<button ' . parse_form_attributes($data, $defaults) . stringify_attributes($extra) . '>'
|
||||
. $content
|
||||
. "</button>\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_label')) {
|
||||
/**
|
||||
* Form Label Tag
|
||||
*
|
||||
* @param string $labelText The text to appear onscreen
|
||||
* @param string $id The id the label applies to
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
function form_label(string $labelText = '', string $id = '', array $attributes = []): string
|
||||
{
|
||||
$label = '<label';
|
||||
|
||||
if ($id !== '') {
|
||||
$label .= ' for="' . $id . '"';
|
||||
}
|
||||
|
||||
foreach ($attributes as $key => $val) {
|
||||
$label .= ' ' . $key . '="' . $val . '"';
|
||||
}
|
||||
|
||||
return $label . '>' . $labelText . '</label>';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_datalist')) {
|
||||
/**
|
||||
* Datalist
|
||||
*
|
||||
* The <datalist> element specifies a list of pre-defined options for an <input> element.
|
||||
* Users will see a drop-down list of pre-defined options as they input data.
|
||||
* The list attribute of the <input> element, must refer to the id attribute of the <datalist> element.
|
||||
*/
|
||||
function form_datalist(string $name, string $value, array $options): string
|
||||
{
|
||||
$data = [
|
||||
'type' => 'text',
|
||||
'name' => $name,
|
||||
'list' => $name . '_list',
|
||||
'value' => $value,
|
||||
];
|
||||
|
||||
$out = form_input($data) . "\n";
|
||||
|
||||
$out .= "<datalist id='" . $name . "_list'>";
|
||||
|
||||
foreach ($options as $option) {
|
||||
$out .= "<option value='{$option}'>\n";
|
||||
}
|
||||
|
||||
return $out . ("</datalist>\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_fieldset')) {
|
||||
/**
|
||||
* Fieldset Tag
|
||||
*
|
||||
* Used to produce <fieldset><legend>text</legend>. To close fieldset
|
||||
* use form_fieldset_close()
|
||||
*
|
||||
* @param string $legendText The legend text
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
function form_fieldset(string $legendText = '', array $attributes = []): string
|
||||
{
|
||||
$fieldset = '<fieldset' . stringify_attributes($attributes) . ">\n";
|
||||
|
||||
if ($legendText !== '') {
|
||||
return $fieldset . '<legend>' . $legendText . "</legend>\n";
|
||||
}
|
||||
|
||||
return $fieldset;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_fieldset_close')) {
|
||||
/**
|
||||
* Fieldset Close Tag
|
||||
*/
|
||||
function form_fieldset_close(string $extra = ''): string
|
||||
{
|
||||
return '</fieldset>' . $extra;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('form_close')) {
|
||||
/**
|
||||
* Form Close Tag
|
||||
*/
|
||||
function form_close(string $extra = ''): string
|
||||
{
|
||||
return '</form>' . $extra;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('set_value')) {
|
||||
/**
|
||||
* Form Value
|
||||
*
|
||||
* Grabs a value from the POST array for the specified field so you can
|
||||
* re-populate an input field or textarea
|
||||
*
|
||||
* @param string $field Field name
|
||||
* @param list<string>|string $default Default value
|
||||
* @param bool $htmlEscape Whether to escape HTML special characters or not
|
||||
*
|
||||
* @return list<string>|string
|
||||
*/
|
||||
function set_value(string $field, $default = '', bool $htmlEscape = true)
|
||||
{
|
||||
$request = service('request');
|
||||
|
||||
// Try any old input data we may have first
|
||||
$value = $request->getOldInput($field);
|
||||
|
||||
if ($value === null) {
|
||||
$value = $request->getPost($field) ?? $default;
|
||||
}
|
||||
|
||||
return ($htmlEscape) ? esc($value) : $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('set_select')) {
|
||||
/**
|
||||
* Set Select
|
||||
*
|
||||
* Let's you set the selected value of a <select> menu via data in the POST array.
|
||||
*/
|
||||
function set_select(string $field, string $value = '', bool $default = false): string
|
||||
{
|
||||
$request = service('request');
|
||||
|
||||
// Try any old input data we may have first
|
||||
$input = $request->getOldInput($field);
|
||||
|
||||
if ($input === null) {
|
||||
$input = $request->getPost($field);
|
||||
}
|
||||
|
||||
if ($input === null) {
|
||||
return $default ? ' selected="selected"' : '';
|
||||
}
|
||||
|
||||
if (is_array($input)) {
|
||||
// Note: in_array('', array(0)) returns TRUE, do not use it
|
||||
foreach ($input as &$v) {
|
||||
if ($value === $v) {
|
||||
return ' selected="selected"';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return ($input === $value) ? ' selected="selected"' : '';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('set_checkbox')) {
|
||||
/**
|
||||
* Set Checkbox
|
||||
*
|
||||
* Let's you set the selected value of a checkbox via the value in the POST array.
|
||||
*/
|
||||
function set_checkbox(string $field, string $value = '', bool $default = false): string
|
||||
{
|
||||
$request = service('request');
|
||||
|
||||
// Try any old input data we may have first
|
||||
$input = $request->getOldInput($field);
|
||||
|
||||
if ($input === null) {
|
||||
$input = $request->getPost($field);
|
||||
}
|
||||
|
||||
if (is_array($input)) {
|
||||
// Note: in_array('', array(0)) returns TRUE, do not use it
|
||||
foreach ($input as &$v) {
|
||||
if ($value === $v) {
|
||||
return ' checked="checked"';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$session = service('session');
|
||||
$hasOldInput = $session->has('_ci_old_input');
|
||||
|
||||
// Unchecked checkbox and radio inputs are not even submitted by browsers ...
|
||||
if ((string) $input === '0' || ! empty($request->getPost()) || $hasOldInput) {
|
||||
return ($input === $value) ? ' checked="checked"' : '';
|
||||
}
|
||||
|
||||
return $default ? ' checked="checked"' : '';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('set_radio')) {
|
||||
/**
|
||||
* Set Radio
|
||||
*
|
||||
* Let's you set the selected value of a radio field via info in the POST array.
|
||||
*/
|
||||
function set_radio(string $field, string $value = '', bool $default = false): string
|
||||
{
|
||||
$request = service('request');
|
||||
|
||||
// Try any old input data we may have first
|
||||
$oldInput = $request->getOldInput($field);
|
||||
|
||||
$postInput = $request->getPost($field);
|
||||
|
||||
$input = $oldInput ?? $postInput ?? $default;
|
||||
|
||||
if (is_array($input)) {
|
||||
// Note: in_array('', array(0)) returns TRUE, do not use it
|
||||
foreach ($input as $v) {
|
||||
if ($value === $v) {
|
||||
return ' checked="checked"';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// Unchecked checkbox and radio inputs are not even submitted by browsers ...
|
||||
if ($oldInput !== null || $postInput !== null) {
|
||||
return ((string) $input === $value) ? ' checked="checked"' : '';
|
||||
}
|
||||
|
||||
return $default ? ' checked="checked"' : '';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('validation_errors')) {
|
||||
/**
|
||||
* Returns the validation errors.
|
||||
*
|
||||
* First, checks the validation errors that are stored in the session.
|
||||
* To store the errors in the session, you need to use `withInput()` with `redirect()`.
|
||||
*
|
||||
* The returned array should be in the following format:
|
||||
* [
|
||||
* 'field1' => 'error message',
|
||||
* 'field2' => 'error message',
|
||||
* ]
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
function validation_errors()
|
||||
{
|
||||
$errors = session('_ci_validation_errors');
|
||||
|
||||
// Check the session to see if any were
|
||||
// passed along from a redirect withErrors() request.
|
||||
if ($errors !== null && (ENVIRONMENT === 'testing' || ! is_cli())) {
|
||||
return $errors;
|
||||
}
|
||||
|
||||
$validation = service('validation');
|
||||
|
||||
return $validation->getErrors();
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('validation_list_errors')) {
|
||||
/**
|
||||
* Returns the rendered HTML of the validation errors.
|
||||
*
|
||||
* See Validation::listErrors()
|
||||
*/
|
||||
function validation_list_errors(string $template = 'list'): string
|
||||
{
|
||||
$config = config(Validation::class);
|
||||
$view = service('renderer');
|
||||
|
||||
if (! array_key_exists($template, $config->templates)) {
|
||||
throw ValidationException::forInvalidTemplate($template);
|
||||
}
|
||||
|
||||
return $view->setVar('errors', validation_errors())
|
||||
->render($config->templates[$template]);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('validation_show_error')) {
|
||||
/**
|
||||
* Returns a single error for the specified field in formatted HTML.
|
||||
*
|
||||
* See Validation::showError()
|
||||
*/
|
||||
function validation_show_error(string $field, string $template = 'single'): string
|
||||
{
|
||||
$config = config(Validation::class);
|
||||
$view = service('renderer');
|
||||
|
||||
$errors = array_filter(validation_errors(), static fn ($key): bool => preg_match(
|
||||
'/^' . str_replace(['\.\*', '\*\.'], ['\..+', '.+\.'], preg_quote($field, '/')) . '$/',
|
||||
$key,
|
||||
) === 1, ARRAY_FILTER_USE_KEY);
|
||||
|
||||
if ($errors === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (! array_key_exists($template, $config->templates)) {
|
||||
throw ValidationException::forInvalidTemplate($template);
|
||||
}
|
||||
|
||||
return $view->setVar('error', implode("\n", $errors))
|
||||
->render($config->templates[$template]);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('parse_form_attributes')) {
|
||||
/**
|
||||
* Parse the form attributes
|
||||
*
|
||||
* Helper function used by some of the form helpers
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param array|string $attributes List of attributes
|
||||
* @param array $default Default values
|
||||
*/
|
||||
function parse_form_attributes($attributes, array $default): string
|
||||
{
|
||||
if (is_array($attributes)) {
|
||||
foreach (array_keys($default) as $key) {
|
||||
if (isset($attributes[$key])) {
|
||||
$default[$key] = $attributes[$key];
|
||||
unset($attributes[$key]);
|
||||
}
|
||||
}
|
||||
if ($attributes !== []) {
|
||||
$default = array_merge($default, $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
$att = '';
|
||||
|
||||
foreach ($default as $key => $val) {
|
||||
if (! is_bool($val)) {
|
||||
if ($key === 'value') {
|
||||
$val = esc($val);
|
||||
} elseif ($key === 'name' && $default['name'] === '') {
|
||||
continue;
|
||||
}
|
||||
$att .= $key . '="' . $val . '"' . ($key === array_key_last($default) ? '' : ' ');
|
||||
} else {
|
||||
$att .= $key . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return $att;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,559 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use CodeIgniter\Files\Exceptions\FileNotFoundException;
|
||||
use Config\DocTypes;
|
||||
use Config\Mimes;
|
||||
|
||||
// CodeIgniter HTML Helpers
|
||||
|
||||
if (! function_exists('ul')) {
|
||||
/**
|
||||
* Unordered List
|
||||
*
|
||||
* Generates an HTML unordered list from a single or
|
||||
* multidimensional array.
|
||||
*
|
||||
* @param array $list List entries
|
||||
* @param array|object|string $attributes HTML attributes string, array, object
|
||||
*/
|
||||
function ul(array $list, $attributes = ''): string
|
||||
{
|
||||
return _list('ul', $list, $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('ol')) {
|
||||
/**
|
||||
* Ordered List
|
||||
*
|
||||
* Generates an HTML ordered list from a single or multidimensional array.
|
||||
*
|
||||
* @param array $list List entries
|
||||
* @param array|object|string $attributes HTML attributes string, array, object
|
||||
*/
|
||||
function ol(array $list, $attributes = ''): string
|
||||
{
|
||||
return _list('ol', $list, $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('_list')) {
|
||||
/**
|
||||
* Generates the list
|
||||
*
|
||||
* Generates an HTML ordered list from a single or multidimensional array.
|
||||
*
|
||||
* @param array $list List entries
|
||||
* @param array|object|string $attributes HTML attributes string, array, object
|
||||
*/
|
||||
function _list(string $type = 'ul', $list = [], $attributes = '', int $depth = 0): string
|
||||
{
|
||||
// Set the indentation based on the depth
|
||||
$out = str_repeat(' ', $depth)
|
||||
// Write the opening list tag
|
||||
. '<' . $type . stringify_attributes($attributes) . ">\n";
|
||||
|
||||
// Cycle through the list elements. If an array is
|
||||
// encountered we will recursively call _list()
|
||||
|
||||
foreach ($list as $key => $val) {
|
||||
$out .= str_repeat(' ', $depth + 2) . '<li>';
|
||||
|
||||
if (! is_array($val)) {
|
||||
$out .= $val;
|
||||
} else {
|
||||
$out .= $key
|
||||
. "\n"
|
||||
. _list($type, $val, '', $depth + 4)
|
||||
. str_repeat(' ', $depth + 2);
|
||||
}
|
||||
|
||||
$out .= "</li>\n";
|
||||
}
|
||||
|
||||
// Set the indentation for the closing tag and apply it
|
||||
return $out . str_repeat(' ', $depth) . '</' . $type . ">\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('img')) {
|
||||
/**
|
||||
* Image
|
||||
*
|
||||
* Generates an image element
|
||||
*
|
||||
* @param array|string $src Image source URI, or array of attributes and values
|
||||
* @param bool $indexPage Should `Config\App::$indexPage` be added to the source path
|
||||
* @param array|object|string $attributes Additional HTML attributes
|
||||
*/
|
||||
function img($src = '', bool $indexPage = false, $attributes = ''): string
|
||||
{
|
||||
if (! is_array($src)) {
|
||||
$src = ['src' => $src];
|
||||
}
|
||||
if (! isset($src['src'])) {
|
||||
$src['src'] = $attributes['src'] ?? '';
|
||||
}
|
||||
if (! isset($src['alt'])) {
|
||||
$src['alt'] = $attributes['alt'] ?? '';
|
||||
}
|
||||
|
||||
$img = '<img';
|
||||
|
||||
// Check for a relative URI
|
||||
if (preg_match('#^([a-z]+:)?//#i', $src['src']) !== 1 && ! str_starts_with($src['src'], 'data:')) {
|
||||
if ($indexPage) {
|
||||
$img .= ' src="' . site_url($src['src']) . '"';
|
||||
} else {
|
||||
$img .= ' src="' . slash_item('baseURL') . $src['src'] . '"';
|
||||
}
|
||||
|
||||
unset($src['src']);
|
||||
}
|
||||
|
||||
// Append any other values
|
||||
foreach ($src as $key => $value) {
|
||||
$img .= ' ' . $key . '="' . $value . '"';
|
||||
}
|
||||
|
||||
// Prevent passing completed values to stringify_attributes
|
||||
if (is_array($attributes)) {
|
||||
unset($attributes['alt'], $attributes['src']);
|
||||
}
|
||||
|
||||
return $img . stringify_attributes($attributes) . _solidus() . '>';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('img_data')) {
|
||||
/**
|
||||
* Image (data)
|
||||
*
|
||||
* Generates a src-ready string from an image using the "data:" protocol
|
||||
*
|
||||
* @param string $path Image source path
|
||||
* @param string|null $mime MIME type to use, or null to guess
|
||||
*/
|
||||
function img_data(string $path, ?string $mime = null): string
|
||||
{
|
||||
if (! is_file($path) || ! is_readable($path)) {
|
||||
throw FileNotFoundException::forFileNotFound($path);
|
||||
}
|
||||
|
||||
// Read in file binary data
|
||||
$handle = fopen($path, 'rb');
|
||||
$data = fread($handle, filesize($path));
|
||||
fclose($handle);
|
||||
|
||||
// Encode as base64
|
||||
$data = base64_encode($data);
|
||||
|
||||
// Figure out the type (Hail Mary to JPEG)
|
||||
$mime ??= Mimes::guessTypeFromExtension(pathinfo($path, PATHINFO_EXTENSION)) ?? 'image/jpg';
|
||||
|
||||
return 'data:' . $mime . ';base64,' . $data;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('doctype')) {
|
||||
/**
|
||||
* Doctype
|
||||
*
|
||||
* Generates a page document type declaration
|
||||
*
|
||||
* Examples of valid options: html5, xhtml-11, xhtml-strict, xhtml-trans,
|
||||
* xhtml-frame, html4-strict, html4-trans, and html4-frame.
|
||||
* All values are saved in the doctypes config file.
|
||||
*
|
||||
* @param string $type The doctype to be generated
|
||||
*/
|
||||
function doctype(string $type = 'html5'): string
|
||||
{
|
||||
$config = new DocTypes();
|
||||
$doctypes = $config->list;
|
||||
|
||||
return $doctypes[$type] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('script_tag')) {
|
||||
/**
|
||||
* Script
|
||||
*
|
||||
* Generates link to a JS file
|
||||
*
|
||||
* @param array|string $src Script source or an array of attributes
|
||||
* @param bool $indexPage Should `Config\App::$indexPage` be added to the JS path
|
||||
*/
|
||||
function script_tag($src = '', bool $indexPage = false): string
|
||||
{
|
||||
$cspNonce = csp_script_nonce();
|
||||
$cspNonce = $cspNonce !== '' ? ' ' . $cspNonce : $cspNonce;
|
||||
$script = '<script' . $cspNonce . ' ';
|
||||
if (! is_array($src)) {
|
||||
$src = ['src' => $src];
|
||||
}
|
||||
|
||||
foreach ($src as $k => $v) {
|
||||
if ($k === 'src' && preg_match('#^([a-z]+:)?//#i', $v) !== 1) {
|
||||
if ($indexPage) {
|
||||
$script .= 'src="' . site_url($v) . '" ';
|
||||
} else {
|
||||
$script .= 'src="' . slash_item('baseURL') . $v . '" ';
|
||||
}
|
||||
} else {
|
||||
// for attributes without values, like async or defer, use NULL.
|
||||
$script .= $k . (null === $v ? ' ' : '="' . $v . '" ');
|
||||
}
|
||||
}
|
||||
|
||||
return rtrim($script) . '></script>';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('link_tag')) {
|
||||
/**
|
||||
* Link
|
||||
*
|
||||
* Generates link tag
|
||||
*
|
||||
* @param array<string, bool|string>|string $href Stylesheet href or an array
|
||||
* @param bool $indexPage Should `Config\App::$indexPage` be added to the CSS path.
|
||||
*/
|
||||
function link_tag(
|
||||
$href = '',
|
||||
string $rel = 'stylesheet',
|
||||
string $type = 'text/css',
|
||||
string $title = '',
|
||||
string $media = '',
|
||||
bool $indexPage = false,
|
||||
string $hreflang = '',
|
||||
): string {
|
||||
$attributes = [];
|
||||
// extract fields if needed
|
||||
if (is_array($href)) {
|
||||
$rel = $href['rel'] ?? $rel;
|
||||
$type = $href['type'] ?? $type;
|
||||
$title = $href['title'] ?? $title;
|
||||
$media = $href['media'] ?? $media;
|
||||
$hreflang = $href['hreflang'] ?? '';
|
||||
$indexPage = $href['indexPage'] ?? $indexPage;
|
||||
$href = $href['href'] ?? '';
|
||||
}
|
||||
|
||||
if (preg_match('#^([a-z]+:)?//#i', $href) !== 1) {
|
||||
$attributes['href'] = $indexPage ? site_url($href) : slash_item('baseURL') . $href;
|
||||
} else {
|
||||
$attributes['href'] = $href;
|
||||
}
|
||||
|
||||
if ($hreflang !== '') {
|
||||
$attributes['hreflang'] = $hreflang;
|
||||
}
|
||||
|
||||
$attributes['rel'] = $rel;
|
||||
|
||||
if ($type !== '' && $rel !== 'canonical' && $hreflang === '' && ! ($rel === 'alternate' && $media !== '')) {
|
||||
$attributes['type'] = $type;
|
||||
}
|
||||
|
||||
if ($media !== '') {
|
||||
$attributes['media'] = $media;
|
||||
}
|
||||
|
||||
if ($title !== '') {
|
||||
$attributes['title'] = $title;
|
||||
}
|
||||
|
||||
return '<link' . stringify_attributes($attributes) . _solidus() . '>';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('video')) {
|
||||
/**
|
||||
* Video
|
||||
*
|
||||
* Generates a video element to embed videos. The video element can
|
||||
* contain one or more video sources
|
||||
*
|
||||
* @param array|string $src Either a source string or an array of sources
|
||||
* @param string $unsupportedMessage The message to display if the media tag is not supported by the browser
|
||||
* @param string $attributes HTML attributes
|
||||
* @param bool $indexPage Should `Config\App::$indexPage` be added to the source path
|
||||
*/
|
||||
function video($src, string $unsupportedMessage = '', string $attributes = '', array $tracks = [], bool $indexPage = false): string
|
||||
{
|
||||
if (is_array($src)) {
|
||||
return _media('video', $src, $unsupportedMessage, $attributes, $tracks);
|
||||
}
|
||||
|
||||
$video = '<video';
|
||||
|
||||
if (_has_protocol($src)) {
|
||||
$video .= ' src="' . $src . '"';
|
||||
} elseif ($indexPage) {
|
||||
$video .= ' src="' . site_url($src) . '"';
|
||||
} else {
|
||||
$video .= ' src="' . slash_item('baseURL') . $src . '"';
|
||||
}
|
||||
|
||||
if ($attributes !== '') {
|
||||
$video .= ' ' . $attributes;
|
||||
}
|
||||
|
||||
$video .= ">\n";
|
||||
|
||||
foreach ($tracks as $track) {
|
||||
$video .= _space_indent() . $track . "\n";
|
||||
}
|
||||
|
||||
if ($unsupportedMessage !== '') {
|
||||
$video .= _space_indent()
|
||||
. $unsupportedMessage
|
||||
. "\n";
|
||||
}
|
||||
|
||||
return $video . "</video>\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('audio')) {
|
||||
/**
|
||||
* Audio
|
||||
*
|
||||
* Generates an audio element to embed sounds
|
||||
*
|
||||
* @param array|string $src Either a source string or an array of sources
|
||||
* @param string $unsupportedMessage The message to display if the media tag is not supported by the browser.
|
||||
* @param string $attributes HTML attributes
|
||||
* @param bool $indexPage Should `Config\App::$indexPage` be added to the source path
|
||||
*/
|
||||
function audio($src, string $unsupportedMessage = '', string $attributes = '', array $tracks = [], bool $indexPage = false): string
|
||||
{
|
||||
if (is_array($src)) {
|
||||
return _media('audio', $src, $unsupportedMessage, $attributes, $tracks);
|
||||
}
|
||||
|
||||
$audio = '<audio';
|
||||
|
||||
if (_has_protocol($src)) {
|
||||
$audio .= ' src="' . $src . '"';
|
||||
} elseif ($indexPage) {
|
||||
$audio .= ' src="' . site_url($src) . '"';
|
||||
} else {
|
||||
$audio .= ' src="' . slash_item('baseURL') . $src . '"';
|
||||
}
|
||||
|
||||
if ($attributes !== '') {
|
||||
$audio .= ' ' . $attributes;
|
||||
}
|
||||
|
||||
$audio .= '>';
|
||||
|
||||
foreach ($tracks as $track) {
|
||||
$audio .= "\n" . _space_indent() . $track;
|
||||
}
|
||||
|
||||
if ($unsupportedMessage !== '') {
|
||||
$audio .= "\n" . _space_indent() . $unsupportedMessage . "\n";
|
||||
}
|
||||
|
||||
return $audio . "</audio>\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('_media')) {
|
||||
/**
|
||||
* Generate media based tag
|
||||
*
|
||||
* @param string $unsupportedMessage The message to display if the media tag is not supported by the browser.
|
||||
*/
|
||||
function _media(string $name, array $types = [], string $unsupportedMessage = '', string $attributes = '', array $tracks = []): string
|
||||
{
|
||||
$media = '<' . $name;
|
||||
|
||||
if ($attributes === '') {
|
||||
$media .= '>';
|
||||
} else {
|
||||
$media .= ' ' . $attributes . '>';
|
||||
}
|
||||
|
||||
$media .= "\n";
|
||||
|
||||
foreach ($types as $option) {
|
||||
$media .= _space_indent() . $option . "\n";
|
||||
}
|
||||
|
||||
foreach ($tracks as $track) {
|
||||
$media .= _space_indent() . $track . "\n";
|
||||
}
|
||||
|
||||
if ($unsupportedMessage !== '') {
|
||||
$media .= _space_indent() . $unsupportedMessage . "\n";
|
||||
}
|
||||
|
||||
return $media . ('</' . $name . ">\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('source')) {
|
||||
/**
|
||||
* Source
|
||||
*
|
||||
* Generates a source element that specifies multiple media resources
|
||||
* for either audio or video element
|
||||
*
|
||||
* @param string $src The path of the media resource
|
||||
* @param string $type The MIME-type of the resource with optional codecs parameters
|
||||
* @param string $attributes HTML attributes
|
||||
* @param bool $indexPage Should `Config\App::$indexPage` be added to the source path
|
||||
*/
|
||||
function source(string $src, string $type = 'unknown', string $attributes = '', bool $indexPage = false): string
|
||||
{
|
||||
if (! _has_protocol($src)) {
|
||||
$src = $indexPage ? site_url($src) : slash_item('baseURL') . $src;
|
||||
}
|
||||
|
||||
$source = '<source src="' . $src
|
||||
. '" type="' . $type . '"';
|
||||
|
||||
if ($attributes !== '') {
|
||||
$source .= ' ' . $attributes;
|
||||
}
|
||||
|
||||
return $source . _solidus() . '>';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('track')) {
|
||||
/**
|
||||
* Track
|
||||
*
|
||||
* Generates a track element to specify timed tracks. The tracks are
|
||||
* formatted in WebVTT format.
|
||||
*
|
||||
* @param string $src The path of the .VTT file
|
||||
* @param string $kind How the text track is meant to be used
|
||||
* @param string $srcLanguage Language of the track text data
|
||||
* @param string $label A user-readable title of the text track
|
||||
*/
|
||||
function track(string $src, string $kind, string $srcLanguage, string $label): string
|
||||
{
|
||||
return '<track src="' . $src
|
||||
. '" kind="' . $kind
|
||||
. '" srclang="' . $srcLanguage
|
||||
. '" label="' . $label
|
||||
. '"' . _solidus() . '>';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('object')) {
|
||||
/**
|
||||
* Object
|
||||
*
|
||||
* Generates an object element that represents the media
|
||||
* as either image or a resource plugin such as audio, video,
|
||||
* Java applets, ActiveX, PDF and Flash
|
||||
*
|
||||
* @param string $data A resource URL
|
||||
* @param string $type Content-type of the resource
|
||||
* @param string $attributes HTML attributes
|
||||
* @param bool $indexPage Should `Config\App::$indexPage` be added to the data path
|
||||
*/
|
||||
function object(string $data, string $type = 'unknown', string $attributes = '', array $params = [], bool $indexPage = false): string
|
||||
{
|
||||
if (! _has_protocol($data)) {
|
||||
$data = $indexPage ? site_url($data) : slash_item('baseURL') . $data;
|
||||
}
|
||||
|
||||
$object = '<object data="' . $data . '" '
|
||||
. $attributes . '>';
|
||||
|
||||
if ($params !== []) {
|
||||
$object .= "\n";
|
||||
}
|
||||
|
||||
foreach ($params as $param) {
|
||||
$object .= _space_indent() . $param . "\n";
|
||||
}
|
||||
|
||||
return $object . "</object>\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('param')) {
|
||||
/**
|
||||
* Param
|
||||
*
|
||||
* Generates a param element that defines parameters
|
||||
* for the object element.
|
||||
*
|
||||
* @param string $name The name of the parameter
|
||||
* @param string $value The value of the parameter
|
||||
* @param string $type The MIME-type
|
||||
* @param string $attributes HTML attributes
|
||||
*/
|
||||
function param(string $name, string $value, string $type = 'ref', string $attributes = ''): string
|
||||
{
|
||||
return '<param name="' . $name
|
||||
. '" type="' . $type
|
||||
. '" value="' . $value
|
||||
. '" ' . $attributes . _solidus() . '>';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('embed')) {
|
||||
/**
|
||||
* Embed
|
||||
*
|
||||
* Generates an embed element
|
||||
*
|
||||
* @param string $src The path of the resource to embed
|
||||
* @param string $type MIME-type
|
||||
* @param string $attributes HTML attributes
|
||||
* @param bool $indexPage Should `Config\App::$indexPage` be added to the source path
|
||||
*/
|
||||
function embed(string $src, string $type = 'unknown', string $attributes = '', bool $indexPage = false): string
|
||||
{
|
||||
if (! _has_protocol($src)) {
|
||||
$src = $indexPage ? site_url($src) : slash_item('baseURL') . $src;
|
||||
}
|
||||
|
||||
return '<embed src="' . $src
|
||||
. '" type="' . $type . '" '
|
||||
. $attributes . _solidus() . ">\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('_has_protocol')) {
|
||||
/**
|
||||
* Test the protocol of a URI.
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
function _has_protocol(string $url)
|
||||
{
|
||||
return preg_match('#^([a-z]+:)?//#i', $url);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('_space_indent')) {
|
||||
/**
|
||||
* Provide space indenting.
|
||||
*/
|
||||
function _space_indent(int $depth = 2): string
|
||||
{
|
||||
return str_repeat(' ', $depth);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
// CodeIgniter Inflector Helpers
|
||||
|
||||
if (! function_exists('singular')) {
|
||||
/**
|
||||
* Singular
|
||||
*
|
||||
* Takes a plural word and makes it singular
|
||||
*
|
||||
* @param string $string Input string
|
||||
*/
|
||||
function singular(string $string): string
|
||||
{
|
||||
$result = $string;
|
||||
|
||||
if (! is_pluralizable($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Arranged in order.
|
||||
$singularRules = [
|
||||
'/(matr)ices$/' => '\1ix',
|
||||
'/(vert|ind)ices$/' => '\1ex',
|
||||
'/^(ox)en/' => '\1',
|
||||
'/(alias)es$/' => '\1',
|
||||
'/([octop|vir])i$/' => '\1us',
|
||||
'/(cris|ax|test)es$/' => '\1is',
|
||||
'/(shoe)s$/' => '\1',
|
||||
'/(o)es$/' => '\1',
|
||||
'/(bus|campus)es$/' => '\1',
|
||||
'/([m|l])ice$/' => '\1ouse',
|
||||
'/(x|ch|ss|sh)es$/' => '\1',
|
||||
'/(m)ovies$/' => '\1\2ovie',
|
||||
'/(s)eries$/' => '\1\2eries',
|
||||
'/([^aeiouy]|qu)ies$/' => '\1y',
|
||||
'/([lr])ves$/' => '\1f',
|
||||
'/(tive)s$/' => '\1',
|
||||
'/(hive)s$/' => '\1',
|
||||
'/([^f])ves$/' => '\1fe',
|
||||
'/(^analy)ses$/' => '\1sis',
|
||||
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/' => '\1\2sis',
|
||||
'/([ti])a$/' => '\1um',
|
||||
'/(p)eople$/' => '\1\2erson',
|
||||
'/(m)en$/' => '\1an',
|
||||
'/(s)tatuses$/' => '\1\2tatus',
|
||||
'/(c)hildren$/' => '\1\2hild',
|
||||
'/(n)ews$/' => '\1\2ews',
|
||||
'/(quiz)zes$/' => '\1',
|
||||
'/([^us])s$/' => '\1',
|
||||
];
|
||||
|
||||
foreach ($singularRules as $rule => $replacement) {
|
||||
if (preg_match($rule, $result)) {
|
||||
$result = preg_replace($rule, $replacement, $result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('plural')) {
|
||||
/**
|
||||
* Plural
|
||||
*
|
||||
* Takes a singular word and makes it plural
|
||||
*
|
||||
* @param string $string Input string
|
||||
*/
|
||||
function plural(string $string): string
|
||||
{
|
||||
$result = $string;
|
||||
|
||||
if (! is_pluralizable($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$pluralRules = [
|
||||
'/(quiz)$/' => '\1zes', // quizzes
|
||||
'/^(ox)$/' => '\1\2en', // ox
|
||||
'/([m|l])ouse$/' => '\1ice', // mouse, louse
|
||||
'/(matr|vert|ind)ix|ex$/' => '\1ices', // matrix, vertex, index
|
||||
'/(x|ch|ss|sh)$/' => '\1es', // search, switch, fix, box, process, address
|
||||
'/([^aeiouy]|qu)y$/' => '\1ies', // query, ability, agency
|
||||
'/(hive)$/' => '\1s', // archive, hive
|
||||
'/(?:([^f])fe|([lr])f)$/' => '\1\2ves', // half, safe, wife
|
||||
'/sis$/' => 'ses', // basis, diagnosis
|
||||
'/([ti])um$/' => '\1a', // datum, medium
|
||||
'/(p)erson$/' => '\1eople', // person, salesperson
|
||||
'/(m)an$/' => '\1en', // man, woman, spokesman
|
||||
'/(c)hild$/' => '\1hildren', // child
|
||||
'/(buffal|tomat)o$/' => '\1\2oes', // buffalo, tomato
|
||||
'/(bu|campu)s$/' => '\1\2ses', // bus, campus
|
||||
'/(alias|status|virus)$/' => '\1es', // alias
|
||||
'/(octop)us$/' => '\1i', // octopus
|
||||
'/(ax|cris|test)is$/' => '\1es', // axis, crisis
|
||||
'/s$/' => 's', // no change (compatibility)
|
||||
'/$/' => 's',
|
||||
];
|
||||
|
||||
foreach ($pluralRules as $rule => $replacement) {
|
||||
if (preg_match($rule, $result)) {
|
||||
$result = preg_replace($rule, $replacement, $result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('counted')) {
|
||||
/**
|
||||
* Counted
|
||||
*
|
||||
* Takes a number and a word to return the plural or not
|
||||
* E.g. 0 cats, 1 cat, 2 cats, ...
|
||||
*
|
||||
* @param int $count Number of items
|
||||
* @param string $string Input string
|
||||
*/
|
||||
function counted(int $count, string $string): string
|
||||
{
|
||||
$result = "{$count} ";
|
||||
|
||||
return $result . ($count === 1 ? singular($string) : plural($string));
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('camelize')) {
|
||||
/**
|
||||
* Camelize
|
||||
*
|
||||
* Takes multiple words separated by spaces or
|
||||
* underscores and converts them to camel case.
|
||||
*
|
||||
* @param string $string Input string
|
||||
*/
|
||||
function camelize(string $string): string
|
||||
{
|
||||
return lcfirst(str_replace(' ', '', ucwords(preg_replace('/[\s_]+/', ' ', $string))));
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('pascalize')) {
|
||||
/**
|
||||
* Pascalize
|
||||
*
|
||||
* Takes multiple words separated by spaces or
|
||||
* underscores and converts them to Pascal case,
|
||||
* which is camel case with an uppercase first letter.
|
||||
*
|
||||
* @param string $string Input string
|
||||
*/
|
||||
function pascalize(string $string): string
|
||||
{
|
||||
return ucfirst(camelize($string));
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('underscore')) {
|
||||
/**
|
||||
* Underscore
|
||||
*
|
||||
* Takes multiple words separated by spaces and underscores them
|
||||
*
|
||||
* @param string $string Input string
|
||||
*/
|
||||
function underscore(string $string): string
|
||||
{
|
||||
$replacement = trim($string);
|
||||
|
||||
return preg_replace('/[\s]+/', '_', $replacement);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('decamelize')) {
|
||||
/**
|
||||
* Decamelize
|
||||
*
|
||||
* Takes multiple words separated by camel case and
|
||||
* underscores them.
|
||||
*
|
||||
* @param string $string Input string
|
||||
*/
|
||||
function decamelize(string $string): string
|
||||
{
|
||||
return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', trim($string)));
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('humanize')) {
|
||||
/**
|
||||
* Humanize
|
||||
*
|
||||
* Takes multiple words separated by the separator,
|
||||
* camelizes and changes them to spaces
|
||||
*
|
||||
* @param string $string Input string
|
||||
* @param string $separator Input separator
|
||||
*/
|
||||
function humanize(string $string, string $separator = '_'): string
|
||||
{
|
||||
$replacement = trim($string);
|
||||
|
||||
return ucwords(preg_replace('/[' . $separator . ']+/', ' ', $replacement));
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('is_pluralizable')) {
|
||||
/**
|
||||
* Checks if the given word has a plural version.
|
||||
*
|
||||
* @param string $word Word to check
|
||||
*/
|
||||
function is_pluralizable(string $word): bool
|
||||
{
|
||||
$uncountables = in_array(
|
||||
strtolower($word),
|
||||
[
|
||||
'advice',
|
||||
'bravery',
|
||||
'butter',
|
||||
'chaos',
|
||||
'clarity',
|
||||
'coal',
|
||||
'courage',
|
||||
'cowardice',
|
||||
'curiosity',
|
||||
'education',
|
||||
'equipment',
|
||||
'evidence',
|
||||
'fish',
|
||||
'fun',
|
||||
'furniture',
|
||||
'greed',
|
||||
'help',
|
||||
'homework',
|
||||
'honesty',
|
||||
'information',
|
||||
'insurance',
|
||||
'jewelry',
|
||||
'knowledge',
|
||||
'livestock',
|
||||
'love',
|
||||
'luck',
|
||||
'marketing',
|
||||
'meta',
|
||||
'money',
|
||||
'mud',
|
||||
'news',
|
||||
'patriotism',
|
||||
'racism',
|
||||
'rice',
|
||||
'satisfaction',
|
||||
'scenery',
|
||||
'series',
|
||||
'sexism',
|
||||
'silence',
|
||||
'species',
|
||||
'spelling',
|
||||
'sugar',
|
||||
'water',
|
||||
'weather',
|
||||
'wisdom',
|
||||
'work',
|
||||
],
|
||||
true,
|
||||
);
|
||||
|
||||
return ! $uncountables;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('dasherize')) {
|
||||
/**
|
||||
* Replaces underscores with dashes in the string.
|
||||
*
|
||||
* @param string $string Input string
|
||||
*/
|
||||
function dasherize(string $string): string
|
||||
{
|
||||
return str_replace('_', '-', $string);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('ordinal')) {
|
||||
/**
|
||||
* Returns the suffix that should be added to a
|
||||
* number to denote the position in an ordered
|
||||
* sequence such as 1st, 2nd, 3rd, 4th.
|
||||
*
|
||||
* @param int $integer The integer to determine the suffix
|
||||
*/
|
||||
function ordinal(int $integer): string
|
||||
{
|
||||
$suffixes = [
|
||||
'th',
|
||||
'st',
|
||||
'nd',
|
||||
'rd',
|
||||
'th',
|
||||
'th',
|
||||
'th',
|
||||
'th',
|
||||
'th',
|
||||
'th',
|
||||
];
|
||||
|
||||
return $integer % 100 >= 11 && $integer % 100 <= 13 ? 'th' : $suffixes[$integer % 10];
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('ordinalize')) {
|
||||
/**
|
||||
* Turns a number into an ordinal string used
|
||||
* to denote the position in an ordered sequence
|
||||
* such as 1st, 2nd, 3rd, 4th.
|
||||
*
|
||||
* @param int $integer The integer to ordinalize
|
||||
*/
|
||||
function ordinalize(int $integer): string
|
||||
{
|
||||
return $integer . ordinal($integer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
// This helper is autoloaded by CodeIgniter.
|
||||
|
||||
if (! function_exists('dd')) {
|
||||
if (class_exists(Kint::class)) {
|
||||
/**
|
||||
* Prints a Kint debug report and exits.
|
||||
*
|
||||
* @param array $vars
|
||||
*
|
||||
* @return never
|
||||
*
|
||||
* @codeCoverageIgnore Can't be tested ... exits
|
||||
*/
|
||||
function dd(...$vars): void
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
Kint::$aliases[] = 'dd';
|
||||
Kint::dump(...$vars);
|
||||
|
||||
exit;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
} else {
|
||||
// In case that Kint is not loaded.
|
||||
/**
|
||||
* dd function
|
||||
*
|
||||
* @param array $vars
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function dd(...$vars)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('d') && ! class_exists(Kint::class)) {
|
||||
// In case that Kint is not loaded.
|
||||
/**
|
||||
* d function
|
||||
*
|
||||
* @param array $vars
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function d(...$vars)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('trace')) {
|
||||
if (class_exists(Kint::class)) {
|
||||
/**
|
||||
* Provides a backtrace to the current execution point, from Kint.
|
||||
*/
|
||||
/**
|
||||
* trace function
|
||||
*/
|
||||
function trace(): void
|
||||
{
|
||||
Kint::$aliases[] = 'trace';
|
||||
Kint::trace();
|
||||
}
|
||||
} else {
|
||||
// In case that Kint is not loaded.
|
||||
/**
|
||||
* trace function
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function trace()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use CodeIgniter\Exceptions\BadFunctionCallException;
|
||||
|
||||
// CodeIgniter Number Helpers
|
||||
|
||||
if (! function_exists('number_to_size')) {
|
||||
/**
|
||||
* Formats a numbers as bytes, based on size, and adds the appropriate suffix
|
||||
*
|
||||
* @param int|string $num Will be cast as int
|
||||
* @param non-empty-string|null $locale [optional]
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
function number_to_size($num, int $precision = 1, ?string $locale = null)
|
||||
{
|
||||
// Strip any formatting & ensure numeric input
|
||||
try {
|
||||
// @phpstan-ignore-next-line
|
||||
$num = 0 + str_replace(',', '', (string) $num);
|
||||
} catch (ErrorException) {
|
||||
// Catch "Warning: A non-numeric value encountered"
|
||||
return false;
|
||||
}
|
||||
|
||||
// ignore sub part
|
||||
$generalLocale = $locale;
|
||||
if ($locale !== null && $locale !== '' && ($underscorePos = strpos($locale, '_'))) {
|
||||
$generalLocale = substr($locale, 0, $underscorePos);
|
||||
}
|
||||
|
||||
if ($num >= 1_000_000_000_000) {
|
||||
$num = round($num / 1_099_511_627_776, $precision);
|
||||
$unit = lang('Number.terabyteAbbr', [], $generalLocale);
|
||||
} elseif ($num >= 1_000_000_000) {
|
||||
$num = round($num / 1_073_741_824, $precision);
|
||||
$unit = lang('Number.gigabyteAbbr', [], $generalLocale);
|
||||
} elseif ($num >= 1_000_000) {
|
||||
$num = round($num / 1_048_576, $precision);
|
||||
$unit = lang('Number.megabyteAbbr', [], $generalLocale);
|
||||
} elseif ($num >= 1000) {
|
||||
$num = round($num / 1024, $precision);
|
||||
$unit = lang('Number.kilobyteAbbr', [], $generalLocale);
|
||||
} else {
|
||||
$unit = lang('Number.bytes', [], $generalLocale);
|
||||
}
|
||||
|
||||
return format_number($num, $precision, $locale, ['after' => ' ' . $unit]);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('number_to_amount')) {
|
||||
/**
|
||||
* Converts numbers to a more readable representation
|
||||
* when dealing with very large numbers (in the thousands or above),
|
||||
* up to the quadrillions, because you won't often deal with numbers
|
||||
* larger than that.
|
||||
*
|
||||
* It uses the "short form" numbering system as this is most commonly
|
||||
* used within most English-speaking countries today.
|
||||
*
|
||||
* @see https://simple.wikipedia.org/wiki/Names_for_large_numbers
|
||||
*
|
||||
* @param int|string $num Will be cast as int
|
||||
* @param int $precision [optional] The optional number of decimal digits to round to.
|
||||
* @param non-empty-string|null $locale [optional]
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
function number_to_amount($num, int $precision = 0, ?string $locale = null)
|
||||
{
|
||||
// Strip any formatting & ensure numeric input
|
||||
try {
|
||||
// @phpstan-ignore-next-line
|
||||
$num = 0 + str_replace(',', '', (string) $num);
|
||||
} catch (ErrorException) {
|
||||
// Catch "Warning: A non-numeric value encountered"
|
||||
return false;
|
||||
}
|
||||
|
||||
$suffix = '';
|
||||
|
||||
// ignore sub part
|
||||
$generalLocale = $locale;
|
||||
if ($locale !== null && $locale !== '' && ($underscorePos = strpos($locale, '_'))) {
|
||||
$generalLocale = substr($locale, 0, $underscorePos);
|
||||
}
|
||||
|
||||
if ($num >= 1_000_000_000_000_000) {
|
||||
$suffix = lang('Number.quadrillion', [], $generalLocale);
|
||||
$num = round(($num / 1_000_000_000_000_000), $precision);
|
||||
} elseif ($num >= 1_000_000_000_000) {
|
||||
$suffix = lang('Number.trillion', [], $generalLocale);
|
||||
$num = round(($num / 1_000_000_000_000), $precision);
|
||||
} elseif ($num >= 1_000_000_000) {
|
||||
$suffix = lang('Number.billion', [], $generalLocale);
|
||||
$num = round(($num / 1_000_000_000), $precision);
|
||||
} elseif ($num >= 1_000_000) {
|
||||
$suffix = lang('Number.million', [], $generalLocale);
|
||||
$num = round(($num / 1_000_000), $precision);
|
||||
} elseif ($num >= 1000) {
|
||||
$suffix = lang('Number.thousand', [], $generalLocale);
|
||||
$num = round(($num / 1000), $precision);
|
||||
}
|
||||
|
||||
return format_number($num, $precision, $locale, ['after' => $suffix]);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('number_to_currency')) {
|
||||
function number_to_currency(float $num, string $currency, ?string $locale = null, int $fraction = 0): string
|
||||
{
|
||||
return format_number($num, 1, $locale, [
|
||||
'type' => NumberFormatter::CURRENCY,
|
||||
'currency' => $currency,
|
||||
'fraction' => $fraction,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('format_number')) {
|
||||
/**
|
||||
* A general purpose, locale-aware, number_format method.
|
||||
* Used by all of the functions of the number_helper.
|
||||
*/
|
||||
function format_number(float $num, int $precision = 1, ?string $locale = null, array $options = []): string
|
||||
{
|
||||
// If locale is not passed, get from the default locale that is set from our config file
|
||||
// or set by HTTP content negotiation.
|
||||
$locale ??= Locale::getDefault();
|
||||
|
||||
// Type can be any of the NumberFormatter options, but provide a default.
|
||||
$type = (int) ($options['type'] ?? NumberFormatter::DECIMAL);
|
||||
|
||||
$formatter = new NumberFormatter($locale, $type);
|
||||
|
||||
// Try to format it per the locale
|
||||
if ($type === NumberFormatter::CURRENCY) {
|
||||
$formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, (float) $options['fraction']);
|
||||
$output = $formatter->formatCurrency($num, $options['currency']);
|
||||
} else {
|
||||
// In order to specify a precision, we'll have to modify
|
||||
// the pattern used by NumberFormatter.
|
||||
$pattern = '#,##0.' . str_repeat('#', $precision);
|
||||
|
||||
$formatter->setPattern($pattern);
|
||||
$output = $formatter->format($num);
|
||||
}
|
||||
|
||||
// This might lead a trailing period if $precision == 0
|
||||
$output = trim($output, '. ');
|
||||
|
||||
if (intl_is_failure($formatter->getErrorCode())) {
|
||||
throw new BadFunctionCallException($formatter->getErrorMessage());
|
||||
}
|
||||
|
||||
// Add on any before/after text.
|
||||
if (isset($options['before']) && is_string($options['before'])) {
|
||||
$output = $options['before'] . $output;
|
||||
}
|
||||
|
||||
if (isset($options['after']) && is_string($options['after'])) {
|
||||
$output .= $options['after'];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('number_to_roman')) {
|
||||
/**
|
||||
* Convert a number to a roman numeral.
|
||||
*
|
||||
* @param int|string $num it will convert to int
|
||||
*/
|
||||
function number_to_roman($num): ?string
|
||||
{
|
||||
static $map = [
|
||||
'M' => 1000,
|
||||
'CM' => 900,
|
||||
'D' => 500,
|
||||
'CD' => 400,
|
||||
'C' => 100,
|
||||
'XC' => 90,
|
||||
'L' => 50,
|
||||
'XL' => 40,
|
||||
'X' => 10,
|
||||
'IX' => 9,
|
||||
'V' => 5,
|
||||
'IV' => 4,
|
||||
'I' => 1,
|
||||
];
|
||||
|
||||
$num = (int) $num;
|
||||
|
||||
if ($num < 1 || $num > 3999) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = '';
|
||||
|
||||
foreach ($map as $roman => $arabic) {
|
||||
$repeat = (int) floor($num / $arabic);
|
||||
$result .= str_repeat($roman, $repeat);
|
||||
$num %= $arabic;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
// CodeIgniter Security Helpers
|
||||
|
||||
if (! function_exists('sanitize_filename')) {
|
||||
/**
|
||||
* Sanitize a filename to use in a URI.
|
||||
*/
|
||||
function sanitize_filename(string $filename): string
|
||||
{
|
||||
return service('security')->sanitizeFilename($filename);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('strip_image_tags')) {
|
||||
/**
|
||||
* Strip Image Tags
|
||||
*/
|
||||
function strip_image_tags(string $str): string
|
||||
{
|
||||
return preg_replace(
|
||||
[
|
||||
'#<img[\s/]+.*?src\s*=\s*(["\'])([^\\1]+?)\\1.*?\>#i',
|
||||
'#<img[\s/]+.*?src\s*=\s*?(([^\s"\'=<>`]+)).*?\>#i',
|
||||
],
|
||||
'\\2',
|
||||
$str,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('encode_php_tags')) {
|
||||
/**
|
||||
* Convert PHP tags to entities
|
||||
*/
|
||||
function encode_php_tags(string $str): string
|
||||
{
|
||||
return str_replace(['<?', '?>'], ['<?', '?>'], $str);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use CodeIgniter\Exceptions\TestException;
|
||||
use CodeIgniter\Model;
|
||||
use CodeIgniter\Test\Fabricator;
|
||||
use Config\Services;
|
||||
|
||||
// CodeIgniter Test Helpers
|
||||
|
||||
if (! function_exists('fake')) {
|
||||
/**
|
||||
* Creates a single item using Fabricator.
|
||||
*
|
||||
* @param Model|object|string $model Instance or name of the model
|
||||
* @param array|null $overrides Overriding data to pass to Fabricator::setOverrides()
|
||||
* @param bool $persist
|
||||
*
|
||||
* @return array|object
|
||||
*/
|
||||
function fake($model, ?array $overrides = null, $persist = true)
|
||||
{
|
||||
$fabricator = new Fabricator($model);
|
||||
|
||||
if ($overrides !== null) {
|
||||
$fabricator->setOverrides($overrides);
|
||||
}
|
||||
|
||||
if ($persist) {
|
||||
return $fabricator->create();
|
||||
}
|
||||
|
||||
return $fabricator->make();
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('mock')) {
|
||||
/**
|
||||
* Used within our test suite to mock certain system tools.
|
||||
*
|
||||
* @param string $className Fully qualified class name
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
function mock(string $className)
|
||||
{
|
||||
$mockClass = $className::$mockClass;
|
||||
$mockService = $className::$mockServiceName ?? '';
|
||||
|
||||
if (empty($mockClass) || ! class_exists($mockClass)) {
|
||||
throw TestException::forInvalidMockClass($mockClass);
|
||||
}
|
||||
|
||||
$mock = new $mockClass();
|
||||
|
||||
if (! empty($mockService)) {
|
||||
Services::injectMock($mockService, $mock);
|
||||
}
|
||||
|
||||
return $mock;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use CodeIgniter\Exceptions\InvalidArgumentException;
|
||||
use Config\ForeignCharacters;
|
||||
|
||||
// CodeIgniter Text Helpers
|
||||
|
||||
if (! function_exists('word_limiter')) {
|
||||
/**
|
||||
* Word Limiter
|
||||
*
|
||||
* Limits a string to X number of words.
|
||||
*
|
||||
* @param string $endChar the end character. Usually an ellipsis
|
||||
*/
|
||||
function word_limiter(string $str, int $limit = 100, string $endChar = '…'): string
|
||||
{
|
||||
if (trim($str) === '') {
|
||||
return $str;
|
||||
}
|
||||
|
||||
preg_match('/^\s*+(?:\S++\s*+){1,' . $limit . '}/', $str, $matches);
|
||||
|
||||
if (strlen($str) === strlen($matches[0])) {
|
||||
$endChar = '';
|
||||
}
|
||||
|
||||
return rtrim($matches[0]) . $endChar;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('character_limiter')) {
|
||||
/**
|
||||
* Character Limiter
|
||||
*
|
||||
* Limits the string based on the character count. Preserves complete words
|
||||
* so the character count may not be exactly as specified.
|
||||
*
|
||||
* @param string $endChar the end character. Usually an ellipsis
|
||||
*/
|
||||
function character_limiter(string $string, int $limit = 500, string $endChar = '…'): string
|
||||
{
|
||||
if (mb_strlen($string) < $limit) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
// a bit complicated, but faster than preg_replace with \s+
|
||||
$string = preg_replace('/ {2,}/', ' ', str_replace(["\r", "\n", "\t", "\x0B", "\x0C"], ' ', $string));
|
||||
$stringLength = mb_strlen($string);
|
||||
|
||||
if ($stringLength <= $limit) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
$output = '';
|
||||
$outputLength = 0;
|
||||
$words = explode(' ', trim($string));
|
||||
|
||||
foreach ($words as $word) {
|
||||
$output .= $word . ' ';
|
||||
$outputLength = mb_strlen($output);
|
||||
|
||||
if ($outputLength >= $limit) {
|
||||
$output = trim($output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ($outputLength === $stringLength) ? $output : $output . $endChar;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('ascii_to_entities')) {
|
||||
/**
|
||||
* High ASCII to Entities
|
||||
*
|
||||
* Converts high ASCII text and MS Word special characters to character entities
|
||||
*/
|
||||
function ascii_to_entities(string $str): string
|
||||
{
|
||||
$out = '';
|
||||
|
||||
for ($i = 0, $s = strlen($str) - 1, $count = 1, $temp = []; $i <= $s; $i++) {
|
||||
$ordinal = ord($str[$i]);
|
||||
|
||||
if ($ordinal < 128) {
|
||||
/*
|
||||
If the $temp array has a value but we have moved on, then it seems only
|
||||
fair that we output that entity and restart $temp before continuing.
|
||||
*/
|
||||
if (count($temp) === 1) {
|
||||
$out .= '&#' . array_shift($temp) . ';';
|
||||
$count = 1;
|
||||
}
|
||||
|
||||
$out .= $str[$i];
|
||||
} else {
|
||||
if ($temp === []) {
|
||||
$count = ($ordinal < 224) ? 2 : 3;
|
||||
}
|
||||
|
||||
$temp[] = $ordinal;
|
||||
|
||||
if (count($temp) === $count) {
|
||||
$number = ($count === 3) ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64) : (($temp[0] % 32) * 64) + ($temp[1] % 64);
|
||||
$out .= '&#' . $number . ';';
|
||||
$count = 1;
|
||||
$temp = [];
|
||||
}
|
||||
// If this is the last iteration, just output whatever we have
|
||||
elseif ($i === $s) {
|
||||
$out .= '&#' . implode(';', $temp) . ';';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('entities_to_ascii')) {
|
||||
/**
|
||||
* Entities to ASCII
|
||||
*
|
||||
* Converts character entities back to ASCII
|
||||
*/
|
||||
function entities_to_ascii(string $str, bool $all = true): string
|
||||
{
|
||||
if (preg_match_all('/\&#(\d+)\;/', $str, $matches) >= 1) {
|
||||
for ($i = 0, $s = count($matches[0]); $i < $s; $i++) {
|
||||
$digits = (int) $matches[1][$i];
|
||||
$out = '';
|
||||
if ($digits < 128) {
|
||||
$out .= chr($digits);
|
||||
} elseif ($digits < 2048) {
|
||||
$out .= chr(192 + (($digits - ($digits % 64)) / 64)) . chr(128 + ($digits % 64));
|
||||
} else {
|
||||
$out .= chr(224 + (($digits - ($digits % 4096)) / 4096))
|
||||
. chr(128 + ((($digits % 4096) - ($digits % 64)) / 64))
|
||||
. chr(128 + ($digits % 64));
|
||||
}
|
||||
$str = str_replace($matches[0][$i], $out, $str);
|
||||
}
|
||||
}
|
||||
|
||||
if ($all) {
|
||||
return str_replace(
|
||||
['&', '<', '>', '"', ''', '-'],
|
||||
['&', '<', '>', '"', "'", '-'],
|
||||
$str,
|
||||
);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('word_censor')) {
|
||||
/**
|
||||
* Word Censoring Function
|
||||
*
|
||||
* Supply a string and an array of disallowed words and any
|
||||
* matched words will be converted to #### or to the replacement
|
||||
* word you've submitted.
|
||||
*
|
||||
* @param string $str the text string
|
||||
* @param array $censored the array of censored words
|
||||
* @param string $replacement the optional replacement value
|
||||
*/
|
||||
function word_censor(string $str, array $censored, string $replacement = ''): string
|
||||
{
|
||||
if ($censored === []) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
$str = ' ' . $str . ' ';
|
||||
|
||||
// \w, \b and a few others do not match on a unicode character
|
||||
// set for performance reasons. As a result words like über
|
||||
// will not match on a word boundary. Instead, we'll assume that
|
||||
// a bad word will be bookended by any of these characters.
|
||||
$delim = '[-_\'\"`(){}<>\[\]|!?@#%&,.:;^~*+=\/ 0-9\n\r\t]';
|
||||
|
||||
foreach ($censored as $badword) {
|
||||
$badword = str_replace('\*', '\w*?', preg_quote($badword, '/'));
|
||||
|
||||
if ($replacement !== '') {
|
||||
$str = preg_replace(
|
||||
"/({$delim})(" . $badword . ")({$delim})/i",
|
||||
"\\1{$replacement}\\3",
|
||||
$str,
|
||||
);
|
||||
} elseif (preg_match_all("/{$delim}(" . $badword . "){$delim}/i", $str, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE) >= 1) {
|
||||
$matches = $matches[1];
|
||||
|
||||
for ($i = count($matches) - 1; $i >= 0; $i--) {
|
||||
$length = strlen($matches[$i][0]);
|
||||
|
||||
$str = substr_replace(
|
||||
$str,
|
||||
str_repeat('#', $length),
|
||||
$matches[$i][1],
|
||||
$length,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trim($str);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('highlight_code')) {
|
||||
/**
|
||||
* Code Highlighter
|
||||
*
|
||||
* Colorizes code strings
|
||||
*
|
||||
* @param string $str the text string
|
||||
*/
|
||||
function highlight_code(string $str): string
|
||||
{
|
||||
/* The highlight string function encodes and highlights
|
||||
* brackets so we need them to start raw.
|
||||
*
|
||||
* Also replace any existing PHP tags to temporary markers
|
||||
* so they don't accidentally break the string out of PHP,
|
||||
* and thus, thwart the highlighting.
|
||||
*/
|
||||
$str = str_replace(
|
||||
['<', '>', '<?', '?>', '<%', '%>', '\\', '</script>'],
|
||||
['<', '>', 'phptagopen', 'phptagclose', 'asptagopen', 'asptagclose', 'backslashtmp', 'scriptclose'],
|
||||
$str,
|
||||
);
|
||||
|
||||
// The highlight_string function requires that the text be surrounded
|
||||
// by PHP tags, which we will remove later
|
||||
$str = highlight_string('<?php ' . $str . ' ?>', true);
|
||||
|
||||
// Remove our artificially added PHP, and the syntax highlighting that came with it
|
||||
$str = preg_replace(
|
||||
[
|
||||
'/<span style="color: #([A-Z0-9]+)"><\?php( | )/i',
|
||||
'/(<span style="color: #[A-Z0-9]+">.*?)\?><\/span>\n<\/span>\n<\/code>/is',
|
||||
'/<span style="color: #[A-Z0-9]+"\><\/span>/i',
|
||||
],
|
||||
[
|
||||
'<span style="color: #$1">',
|
||||
"$1</span>\n</span>\n</code>",
|
||||
'',
|
||||
],
|
||||
$str,
|
||||
);
|
||||
|
||||
// Replace our markers back to PHP tags.
|
||||
return str_replace(
|
||||
[
|
||||
'phptagopen',
|
||||
'phptagclose',
|
||||
'asptagopen',
|
||||
'asptagclose',
|
||||
'backslashtmp',
|
||||
'scriptclose',
|
||||
],
|
||||
[
|
||||
'<?',
|
||||
'?>',
|
||||
'<%',
|
||||
'%>',
|
||||
'\\',
|
||||
'</script>',
|
||||
],
|
||||
$str,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('highlight_phrase')) {
|
||||
/**
|
||||
* Phrase Highlighter
|
||||
*
|
||||
* Highlights a phrase within a text string
|
||||
*
|
||||
* @param string $str the text string
|
||||
* @param string $phrase the phrase you'd like to highlight
|
||||
* @param string $tagOpen the opening tag to precede the phrase with
|
||||
* @param string $tagClose the closing tag to end the phrase with
|
||||
*/
|
||||
function highlight_phrase(string $str, string $phrase, string $tagOpen = '<mark>', string $tagClose = '</mark>'): string
|
||||
{
|
||||
return ($str !== '' && $phrase !== '') ? preg_replace('/(' . preg_quote($phrase, '/') . ')/i', $tagOpen . '\\1' . $tagClose, $str) : $str;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('convert_accented_characters')) {
|
||||
/**
|
||||
* Convert Accented Foreign Characters to ASCII
|
||||
*
|
||||
* @param string $str Input string
|
||||
*/
|
||||
function convert_accented_characters(string $str): string
|
||||
{
|
||||
static $arrayFrom, $arrayTo;
|
||||
|
||||
if (! is_array($arrayFrom)) {
|
||||
$config = new ForeignCharacters();
|
||||
|
||||
if ($config->characterList === [] || ! is_array($config->characterList)) {
|
||||
$arrayFrom = [];
|
||||
$arrayTo = [];
|
||||
|
||||
return $str;
|
||||
}
|
||||
$arrayFrom = array_keys($config->characterList);
|
||||
$arrayTo = array_values($config->characterList);
|
||||
|
||||
unset($config);
|
||||
}
|
||||
|
||||
return preg_replace($arrayFrom, $arrayTo, $str);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('word_wrap')) {
|
||||
/**
|
||||
* Word Wrap
|
||||
*
|
||||
* Wraps text at the specified character. Maintains the integrity of words.
|
||||
* Anything placed between {unwrap}{/unwrap} will not be word wrapped, nor
|
||||
* will URLs.
|
||||
*
|
||||
* @param string $str the text string
|
||||
* @param int $charlim = 76 the number of characters to wrap at
|
||||
*/
|
||||
function word_wrap(string $str, int $charlim = 76): string
|
||||
{
|
||||
// Reduce multiple spaces
|
||||
$str = preg_replace('| +|', ' ', $str);
|
||||
|
||||
// Standardize newlines
|
||||
if (str_contains($str, "\r")) {
|
||||
$str = str_replace(["\r\n", "\r"], "\n", $str);
|
||||
}
|
||||
|
||||
// If the current word is surrounded by {unwrap} tags we'll
|
||||
// strip the entire chunk and replace it with a marker.
|
||||
$unwrap = [];
|
||||
|
||||
if (preg_match_all('|\{unwrap\}(.+?)\{/unwrap\}|s', $str, $matches) >= 1) {
|
||||
for ($i = 0, $c = count($matches[0]); $i < $c; $i++) {
|
||||
$unwrap[] = $matches[1][$i];
|
||||
$str = str_replace($matches[0][$i], '{{unwrapped' . $i . '}}', $str);
|
||||
}
|
||||
}
|
||||
|
||||
// Use PHP's native function to do the initial wordwrap.
|
||||
// We set the cut flag to FALSE so that any individual words that are
|
||||
// too long get left alone. In the next step we'll deal with them.
|
||||
$str = wordwrap($str, $charlim, "\n", false);
|
||||
|
||||
// Split the string into individual lines of text and cycle through them
|
||||
$output = '';
|
||||
|
||||
foreach (explode("\n", $str) as $line) {
|
||||
// Is the line within the allowed character count?
|
||||
// If so we'll join it to the output and continue
|
||||
if (mb_strlen($line) <= $charlim) {
|
||||
$output .= $line . "\n";
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$temp = '';
|
||||
|
||||
while (mb_strlen($line) > $charlim) {
|
||||
// If the over-length word is a URL we won't wrap it
|
||||
if (preg_match('!\[url.+\]|://|www\.!', $line)) {
|
||||
break;
|
||||
}
|
||||
// Trim the word down
|
||||
$temp .= mb_substr($line, 0, $charlim - 1);
|
||||
$line = mb_substr($line, $charlim - 1);
|
||||
}
|
||||
|
||||
// If $temp contains data it means we had to split up an over-length
|
||||
// word into smaller chunks so we'll add it back to our current line
|
||||
if ($temp !== '') {
|
||||
$output .= $temp . "\n" . $line . "\n";
|
||||
} else {
|
||||
$output .= $line . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Put our markers back
|
||||
foreach ($unwrap as $key => $val) {
|
||||
$output = str_replace('{{unwrapped' . $key . '}}', $val, $output);
|
||||
}
|
||||
|
||||
// remove any trailing newline
|
||||
return rtrim($output);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('ellipsize')) {
|
||||
/**
|
||||
* Ellipsize String
|
||||
*
|
||||
* This function will strip tags from a string, split it at its max_length and ellipsize
|
||||
*
|
||||
* @param string $str String to ellipsize
|
||||
* @param int $maxLength Max length of string
|
||||
* @param float|int $position int (1|0) or float, .5, .2, etc for position to split
|
||||
* @param string $ellipsis ellipsis ; Default '...'
|
||||
*
|
||||
* @return string Ellipsized string
|
||||
*/
|
||||
function ellipsize(string $str, int $maxLength, $position = 1, string $ellipsis = '…'): string
|
||||
{
|
||||
// Strip tags
|
||||
$str = trim(strip_tags($str));
|
||||
|
||||
// Is the string long enough to ellipsize?
|
||||
if (mb_strlen($str) <= $maxLength) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
$beg = mb_substr($str, 0, (int) floor($maxLength * $position));
|
||||
$position = ($position > 1) ? 1 : $position;
|
||||
|
||||
if ($position === 1) {
|
||||
$end = mb_substr($str, 0, -($maxLength - mb_strlen($beg)));
|
||||
} else {
|
||||
$end = mb_substr($str, -($maxLength - mb_strlen($beg)));
|
||||
}
|
||||
|
||||
return $beg . $ellipsis . $end;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('strip_slashes')) {
|
||||
/**
|
||||
* Strip Slashes
|
||||
*
|
||||
* Removes slashes contained in a string or in an array
|
||||
*
|
||||
* @param array|string $str string or array
|
||||
*
|
||||
* @return array|string string or array
|
||||
*/
|
||||
function strip_slashes($str)
|
||||
{
|
||||
if (! is_array($str)) {
|
||||
return stripslashes($str);
|
||||
}
|
||||
|
||||
foreach ($str as $key => $val) {
|
||||
$str[$key] = strip_slashes($val);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('strip_quotes')) {
|
||||
/**
|
||||
* Strip Quotes
|
||||
*
|
||||
* Removes single and double quotes from a string
|
||||
*/
|
||||
function strip_quotes(string $str): string
|
||||
{
|
||||
return str_replace(['"', "'"], '', $str);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('quotes_to_entities')) {
|
||||
/**
|
||||
* Quotes to Entities
|
||||
*
|
||||
* Converts single and double quotes to entities
|
||||
*/
|
||||
function quotes_to_entities(string $str): string
|
||||
{
|
||||
return str_replace(["\\'", '"', "'", '"'], [''', '"', ''', '"'], $str);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('reduce_double_slashes')) {
|
||||
/**
|
||||
* Reduce Double Slashes
|
||||
*
|
||||
* Converts double slashes in a string to a single slash,
|
||||
* except those found in http://
|
||||
*
|
||||
* http://www.some-site.com//index.php
|
||||
*
|
||||
* becomes:
|
||||
*
|
||||
* http://www.some-site.com/index.php
|
||||
*/
|
||||
function reduce_double_slashes(string $str): string
|
||||
{
|
||||
return preg_replace('#(^|[^:])//+#', '\\1/', $str);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('reduce_multiples')) {
|
||||
/**
|
||||
* Reduce Multiples
|
||||
*
|
||||
* Reduces multiple instances of a particular character. Example:
|
||||
*
|
||||
* Fred, Bill,, Joe, Jimmy
|
||||
*
|
||||
* becomes:
|
||||
*
|
||||
* Fred, Bill, Joe, Jimmy
|
||||
*
|
||||
* @param string $character the character you wish to reduce
|
||||
* @param bool $trim TRUE/FALSE - whether to trim the character from the beginning/end
|
||||
*/
|
||||
function reduce_multiples(string $str, string $character = ',', bool $trim = false): string
|
||||
{
|
||||
$pattern = '#' . preg_quote($character, '#') . '{2,}#';
|
||||
$str = preg_replace($pattern, $character, $str);
|
||||
|
||||
return $trim ? trim($str, $character) : $str;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('random_string')) {
|
||||
/**
|
||||
* Create a Random String
|
||||
*
|
||||
* Useful for generating passwords or hashes.
|
||||
*
|
||||
* @param string $type Type of random string. basic, alpha, alnum, numeric, nozero, md5, sha1, and crypto
|
||||
* @param int $len Number of characters
|
||||
*
|
||||
* @deprecated The type 'basic', 'md5', and 'sha1' are deprecated. They are not cryptographically secure.
|
||||
*/
|
||||
function random_string(string $type = 'alnum', int $len = 8): string
|
||||
{
|
||||
switch ($type) {
|
||||
case 'alnum':
|
||||
case 'nozero':
|
||||
case 'alpha':
|
||||
switch ($type) {
|
||||
case 'alpha':
|
||||
$pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
break;
|
||||
|
||||
case 'alnum':
|
||||
$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
break;
|
||||
|
||||
case 'nozero':
|
||||
$pool = '123456789';
|
||||
break;
|
||||
}
|
||||
|
||||
return _from_random($len, $pool);
|
||||
|
||||
case 'numeric':
|
||||
$max = 10 ** $len - 1;
|
||||
$rand = random_int(0, $max);
|
||||
|
||||
return sprintf('%0' . $len . 'd', $rand);
|
||||
|
||||
case 'md5':
|
||||
return md5(uniqid((string) mt_rand(), true));
|
||||
|
||||
case 'sha1':
|
||||
return sha1(uniqid((string) mt_rand(), true));
|
||||
|
||||
case 'crypto':
|
||||
if ($len % 2 !== 0) {
|
||||
throw new InvalidArgumentException(
|
||||
'You must set an even number to the second parameter when you use `crypto`.',
|
||||
);
|
||||
}
|
||||
|
||||
return bin2hex(random_bytes($len / 2));
|
||||
}
|
||||
|
||||
// 'basic' type treated as default
|
||||
return (string) mt_rand();
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('_from_random')) {
|
||||
/**
|
||||
* The following function was derived from code of Symfony (v6.2.7 - 2023-02-28)
|
||||
* https://github.com/symfony/symfony/blob/80cac46a31d4561804c17d101591a4f59e6db3a2/src/Symfony/Component/String/ByteString.php#L45
|
||||
* Code subject to the MIT license (https://github.com/symfony/symfony/blob/v6.2.7/LICENSE).
|
||||
* Copyright (c) 2004-present Fabien Potencier
|
||||
*
|
||||
* The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03)
|
||||
* https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16
|
||||
* Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE).
|
||||
* Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/)
|
||||
*
|
||||
* @internal Outside the framework this should not be used directly.
|
||||
*/
|
||||
function _from_random(int $length, string $pool): string
|
||||
{
|
||||
if ($length <= 0) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf('A strictly positive length is expected, "%d" given.', $length),
|
||||
);
|
||||
}
|
||||
|
||||
$poolSize = \strlen($pool);
|
||||
$bits = (int) ceil(log($poolSize, 2.0));
|
||||
if ($bits <= 0 || $bits > 56) {
|
||||
throw new InvalidArgumentException(
|
||||
'The length of the alphabet must in the [2^1, 2^56] range.',
|
||||
);
|
||||
}
|
||||
|
||||
$string = '';
|
||||
|
||||
while ($length > 0) {
|
||||
$urandomLength = (int) ceil(2 * $length * $bits / 8.0);
|
||||
$data = random_bytes($urandomLength);
|
||||
$unpackedData = 0;
|
||||
$unpackedBits = 0;
|
||||
|
||||
for ($i = 0; $i < $urandomLength && $length > 0; $i++) {
|
||||
// Unpack 8 bits
|
||||
$unpackedData = ($unpackedData << 8) | \ord($data[$i]);
|
||||
$unpackedBits += 8;
|
||||
|
||||
// While we have enough bits to select a character from the alphabet, keep
|
||||
// consuming the random data
|
||||
for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) {
|
||||
$index = ($unpackedData & ((1 << $bits) - 1));
|
||||
$unpackedData >>= $bits;
|
||||
// Unfortunately, the alphabet size is not necessarily a power of two.
|
||||
// Worst case, it is 2^k + 1, which means we need (k+1) bits and we
|
||||
// have around a 50% chance of missing as k gets larger
|
||||
if ($index < $poolSize) {
|
||||
$string .= $pool[$index];
|
||||
$length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('increment_string')) {
|
||||
/**
|
||||
* Add's _1 to a string or increment the ending number to allow _2, _3, etc
|
||||
*
|
||||
* @param string $str Required
|
||||
* @param string $separator What should the duplicate number be appended with
|
||||
* @param int $first Which number should be used for the first dupe increment
|
||||
*/
|
||||
function increment_string(string $str, string $separator = '_', int $first = 1): string
|
||||
{
|
||||
preg_match('/(.+)' . preg_quote($separator, '/') . '([0-9]+)$/', $str, $match);
|
||||
|
||||
return isset($match[2]) ? $match[1] . $separator . ((int) $match[2] + 1) : $str . $separator . $first;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('alternator')) {
|
||||
/**
|
||||
* Alternator
|
||||
*
|
||||
* Allows strings to be alternated. See docs...
|
||||
*
|
||||
* @param string ...$args (as many parameters as needed)
|
||||
*/
|
||||
function alternator(...$args): string
|
||||
{
|
||||
static $i;
|
||||
|
||||
if (func_num_args() === 0) {
|
||||
$i = 0;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return $args[($i++ % count($args))];
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('excerpt')) {
|
||||
/**
|
||||
* Excerpt.
|
||||
*
|
||||
* Allows to extract a piece of text surrounding a word or phrase.
|
||||
*
|
||||
* @param string $text String to search the phrase
|
||||
* @param string $phrase Phrase that will be searched for.
|
||||
* @param int $radius The amount of characters returned around the phrase.
|
||||
* @param string $ellipsis Ending that will be appended
|
||||
*
|
||||
* If no $phrase is passed, will generate an excerpt of $radius characters
|
||||
* from the beginning of $text.
|
||||
*/
|
||||
function excerpt(string $text, ?string $phrase = null, int $radius = 100, string $ellipsis = '...'): string
|
||||
{
|
||||
if (isset($phrase)) {
|
||||
$phrasePosition = mb_stripos($text, $phrase);
|
||||
$phraseLength = mb_strlen($phrase);
|
||||
} else {
|
||||
$phrasePosition = $radius / 2;
|
||||
$phraseLength = 1;
|
||||
}
|
||||
|
||||
$beforeWords = explode(' ', mb_substr($text, 0, $phrasePosition));
|
||||
$afterWords = explode(' ', mb_substr($text, $phrasePosition + $phraseLength));
|
||||
|
||||
$firstPartOutput = ' ';
|
||||
$endPartOutput = ' ';
|
||||
$count = 0;
|
||||
|
||||
foreach (array_reverse($beforeWords) as $beforeWord) {
|
||||
$beforeWordLength = mb_strlen($beforeWord);
|
||||
|
||||
if (($beforeWordLength + $count + 1) < $radius) {
|
||||
$firstPartOutput = ' ' . $beforeWord . $firstPartOutput;
|
||||
}
|
||||
|
||||
$count = ++$count + $beforeWordLength;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
|
||||
foreach ($afterWords as $afterWord) {
|
||||
$afterWordLength = mb_strlen($afterWord);
|
||||
|
||||
if (($afterWordLength + $count + 1) < $radius) {
|
||||
$endPartOutput .= $afterWord . ' ';
|
||||
}
|
||||
|
||||
$count = ++$count + $afterWordLength;
|
||||
}
|
||||
|
||||
$ellPre = $phrase !== null ? $ellipsis : '';
|
||||
|
||||
return str_replace(' ', ' ', $ellPre . $firstPartOutput . $phrase . $endPartOutput . $ellipsis);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,536 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use CodeIgniter\HTTP\CLIRequest;
|
||||
use CodeIgniter\HTTP\IncomingRequest;
|
||||
use CodeIgniter\HTTP\SiteURI;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\Router\Exceptions\RouterException;
|
||||
use Config\App;
|
||||
|
||||
// CodeIgniter URL Helpers
|
||||
|
||||
if (! function_exists('site_url')) {
|
||||
/**
|
||||
* Returns a site URL as defined by the App config.
|
||||
*
|
||||
* @param array|string $relativePath URI string or array of URI segments.
|
||||
* @param string|null $scheme URI scheme. E.g., http, ftp. If empty
|
||||
* string '' is set, a protocol-relative
|
||||
* link is returned.
|
||||
* @param App|null $config Alternate configuration to use.
|
||||
*/
|
||||
function site_url($relativePath = '', ?string $scheme = null, ?App $config = null): string
|
||||
{
|
||||
$currentURI = service('request')->getUri();
|
||||
|
||||
assert($currentURI instanceof SiteURI);
|
||||
|
||||
return $currentURI->siteUrl($relativePath, $scheme, $config);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('base_url')) {
|
||||
/**
|
||||
* Returns the base URL as defined by the App config.
|
||||
* Base URLs are trimmed site URLs without the index page.
|
||||
*
|
||||
* @param array|string $relativePath URI string or array of URI segments.
|
||||
* @param string|null $scheme URI scheme. E.g., http, ftp. If empty
|
||||
* string '' is set, a protocol-relative
|
||||
* link is returned.
|
||||
*/
|
||||
function base_url($relativePath = '', ?string $scheme = null): string
|
||||
{
|
||||
$currentURI = service('request')->getUri();
|
||||
|
||||
assert($currentURI instanceof SiteURI);
|
||||
|
||||
return $currentURI->baseUrl($relativePath, $scheme);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('current_url')) {
|
||||
/**
|
||||
* Returns the current full URL based on the Config\App settings and IncomingRequest.
|
||||
*
|
||||
* @param bool $returnObject True to return an object instead of a string
|
||||
* @param IncomingRequest|null $request A request to use when retrieving the path
|
||||
*
|
||||
* @return string|URI When returning string, the query and fragment parts are removed.
|
||||
* When returning URI, the query and fragment parts are preserved.
|
||||
*/
|
||||
function current_url(bool $returnObject = false, ?IncomingRequest $request = null)
|
||||
{
|
||||
$request ??= service('request');
|
||||
/** @var CLIRequest|IncomingRequest $request */
|
||||
$uri = $request->getUri();
|
||||
|
||||
return $returnObject ? $uri : URI::createURIString($uri->getScheme(), $uri->getAuthority(), $uri->getPath());
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('previous_url')) {
|
||||
/**
|
||||
* Returns the previous URL the current visitor was on. For security reasons
|
||||
* we first check in a saved session variable, if it exists, and use that.
|
||||
* If that's not available, however, we'll use a sanitized url from $_SERVER['HTTP_REFERER']
|
||||
* which can be set by the user so is untrusted and not set by certain browsers/servers.
|
||||
*
|
||||
* @return string|URI
|
||||
*/
|
||||
function previous_url(bool $returnObject = false)
|
||||
{
|
||||
// Grab from the session first, if we have it,
|
||||
// since it's more reliable and safer.
|
||||
if (isset($_SESSION)) {
|
||||
$referer = session('_ci_previous_url');
|
||||
}
|
||||
|
||||
// Otherwise, grab a sanitized version from $_SERVER.
|
||||
$referer ??= request()->getServer('HTTP_REFERER', FILTER_SANITIZE_URL) ?? site_url('/');
|
||||
|
||||
return $returnObject ? new URI($referer) : $referer;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('uri_string')) {
|
||||
/**
|
||||
* URL String
|
||||
*
|
||||
* Returns the path part (relative to baseURL) of the current URL
|
||||
*/
|
||||
function uri_string(): string
|
||||
{
|
||||
// The value of service('request')->getUri()->getPath() returns
|
||||
// full URI path.
|
||||
$uri = service('request')->getUri();
|
||||
|
||||
$path = $uri instanceof SiteURI ? $uri->getRoutePath() : $uri->getPath();
|
||||
|
||||
return ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('index_page')) {
|
||||
/**
|
||||
* Index page
|
||||
*
|
||||
* Returns the "index_page" from your config file
|
||||
*
|
||||
* @param App|null $altConfig Alternate configuration to use
|
||||
*/
|
||||
function index_page(?App $altConfig = null): string
|
||||
{
|
||||
// use alternate config if provided, else default one
|
||||
$config = $altConfig ?? config(App::class);
|
||||
|
||||
return $config->indexPage;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('anchor')) {
|
||||
/**
|
||||
* Anchor Link
|
||||
*
|
||||
* Creates an anchor based on the local URL.
|
||||
*
|
||||
* @param array|string $uri URI string or array of URI segments
|
||||
* @param string $title The link title
|
||||
* @param array|object|string $attributes Any attributes
|
||||
* @param App|null $altConfig Alternate configuration to use
|
||||
*/
|
||||
function anchor($uri = '', string $title = '', $attributes = '', ?App $altConfig = null): string
|
||||
{
|
||||
// use alternate config if provided, else default one
|
||||
$config = $altConfig ?? config(App::class);
|
||||
|
||||
$siteUrl = is_array($uri) ? site_url($uri, null, $config) : (preg_match('#^(\w+:)?//#i', $uri) ? $uri : site_url($uri, null, $config));
|
||||
// eliminate trailing slash
|
||||
$siteUrl = rtrim($siteUrl, '/');
|
||||
|
||||
if ($title === '') {
|
||||
$title = $siteUrl;
|
||||
}
|
||||
|
||||
if ($attributes !== '') {
|
||||
$attributes = stringify_attributes($attributes);
|
||||
}
|
||||
|
||||
return '<a href="' . $siteUrl . '"' . $attributes . '>' . $title . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('anchor_popup')) {
|
||||
/**
|
||||
* Anchor Link - Pop-up version
|
||||
*
|
||||
* Creates an anchor based on the local URL. The link
|
||||
* opens a new window based on the attributes specified.
|
||||
*
|
||||
* @param string $uri the URL
|
||||
* @param string $title the link title
|
||||
* @param array|false|object|string $attributes any attributes
|
||||
* @param App|null $altConfig Alternate configuration to use
|
||||
*/
|
||||
function anchor_popup($uri = '', string $title = '', $attributes = false, ?App $altConfig = null): string
|
||||
{
|
||||
// use alternate config if provided, else default one
|
||||
$config = $altConfig ?? config(App::class);
|
||||
|
||||
$siteUrl = preg_match('#^(\w+:)?//#i', $uri) ? $uri : site_url($uri, null, $config);
|
||||
$siteUrl = rtrim($siteUrl, '/');
|
||||
|
||||
if ($title === '') {
|
||||
$title = $siteUrl;
|
||||
}
|
||||
|
||||
if ($attributes === false) {
|
||||
return '<a href="' . $siteUrl . '" onclick="window.open(\'' . $siteUrl . "', '_blank'); return false;\">" . $title . '</a>';
|
||||
}
|
||||
|
||||
if (! is_array($attributes)) {
|
||||
$attributes = [$attributes];
|
||||
|
||||
// Ref: http://www.w3schools.com/jsref/met_win_open.asp
|
||||
$windowName = '_blank';
|
||||
} elseif (! empty($attributes['window_name'])) {
|
||||
$windowName = $attributes['window_name'];
|
||||
unset($attributes['window_name']);
|
||||
} else {
|
||||
$windowName = '_blank';
|
||||
}
|
||||
|
||||
$atts = [];
|
||||
|
||||
foreach (['width' => '800', 'height' => '600', 'scrollbars' => 'yes', 'menubar' => 'no', 'status' => 'yes', 'resizable' => 'yes', 'screenx' => '0', 'screeny' => '0'] as $key => $val) {
|
||||
$atts[$key] = $attributes[$key] ?? $val;
|
||||
unset($attributes[$key]);
|
||||
}
|
||||
|
||||
$attributes = stringify_attributes($attributes);
|
||||
|
||||
return '<a href="' . $siteUrl
|
||||
. '" onclick="window.open(\'' . $siteUrl . "', '" . $windowName . "', '" . stringify_attributes($atts, true) . "'); return false;\""
|
||||
. $attributes . '>' . $title . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('mailto')) {
|
||||
/**
|
||||
* Mailto Link
|
||||
*
|
||||
* @param string $email the email address
|
||||
* @param string $title the link title
|
||||
* @param array|object|string $attributes any attributes
|
||||
*/
|
||||
function mailto(string $email, string $title = '', $attributes = ''): string
|
||||
{
|
||||
if (trim($title) === '') {
|
||||
$title = $email;
|
||||
}
|
||||
|
||||
return '<a href="mailto:' . $email . '"' . stringify_attributes($attributes) . '>' . $title . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('safe_mailto')) {
|
||||
/**
|
||||
* Encoded Mailto Link
|
||||
*
|
||||
* Create a spam-protected mailto link written in Javascript
|
||||
*
|
||||
* @param string $email the email address
|
||||
* @param string $title the link title
|
||||
* @param array|object|string $attributes any attributes
|
||||
*/
|
||||
function safe_mailto(string $email, string $title = '', $attributes = ''): string
|
||||
{
|
||||
$count = 0;
|
||||
if (trim($title) === '') {
|
||||
$title = $email;
|
||||
}
|
||||
|
||||
$x = str_split('<a href="mailto:', 1);
|
||||
|
||||
for ($i = 0, $l = strlen($email); $i < $l; $i++) {
|
||||
$x[] = '|' . ord($email[$i]);
|
||||
}
|
||||
|
||||
$x[] = '"';
|
||||
|
||||
if ($attributes !== '') {
|
||||
if (is_array($attributes)) {
|
||||
foreach ($attributes as $key => $val) {
|
||||
$x[] = ' ' . $key . '="';
|
||||
|
||||
for ($i = 0, $l = strlen($val); $i < $l; $i++) {
|
||||
$x[] = '|' . ord($val[$i]);
|
||||
}
|
||||
|
||||
$x[] = '"';
|
||||
}
|
||||
} else {
|
||||
for ($i = 0, $l = mb_strlen($attributes); $i < $l; $i++) {
|
||||
$x[] = mb_substr($attributes, $i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$x[] = '>';
|
||||
|
||||
$temp = [];
|
||||
|
||||
for ($i = 0, $l = strlen($title); $i < $l; $i++) {
|
||||
$ordinal = ord($title[$i]);
|
||||
|
||||
if ($ordinal < 128) {
|
||||
$x[] = '|' . $ordinal;
|
||||
} else {
|
||||
if ($temp === []) {
|
||||
$count = ($ordinal < 224) ? 2 : 3;
|
||||
}
|
||||
|
||||
$temp[] = $ordinal;
|
||||
|
||||
if (count($temp) === $count) {
|
||||
$number = ($count === 3) ? (($temp[0] % 16) * 4096) + (($temp[1] % 64) * 64) + ($temp[2] % 64) : (($temp[0] % 32) * 64) + ($temp[1] % 64);
|
||||
$x[] = '|' . $number;
|
||||
$count = 1;
|
||||
$temp = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$x[] = '<';
|
||||
$x[] = '/';
|
||||
$x[] = 'a';
|
||||
$x[] = '>';
|
||||
|
||||
$x = array_reverse($x);
|
||||
|
||||
// improve obfuscation by eliminating newlines & whitespace
|
||||
$cspNonce = csp_script_nonce();
|
||||
$cspNonce = $cspNonce !== '' ? ' ' . $cspNonce : $cspNonce;
|
||||
$output = '<script' . $cspNonce . '>'
|
||||
. 'var l=new Array();';
|
||||
|
||||
foreach ($x as $i => $value) {
|
||||
$output .= 'l[' . $i . "] = '" . $value . "';";
|
||||
}
|
||||
|
||||
return $output . ('for (var i = l.length-1; i >= 0; i=i-1) {'
|
||||
. "if (l[i].substring(0, 1) === '|') document.write(\"&#\"+unescape(l[i].substring(1))+\";\");"
|
||||
. 'else document.write(unescape(l[i]));'
|
||||
. '}'
|
||||
. '</script>');
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('auto_link')) {
|
||||
/**
|
||||
* Auto-linker
|
||||
*
|
||||
* Automatically links URL and Email addresses.
|
||||
* Note: There's a bit of extra code here to deal with
|
||||
* URLs or emails that end in a period. We'll strip these
|
||||
* off and add them after the link.
|
||||
*
|
||||
* @param string $str the string
|
||||
* @param string $type the type: email, url, or both
|
||||
* @param bool $popup whether to create pop-up links
|
||||
*/
|
||||
function auto_link(string $str, string $type = 'both', bool $popup = false): string
|
||||
{
|
||||
// Find and replace any URLs.
|
||||
if (
|
||||
$type !== 'email'
|
||||
&& preg_match_all(
|
||||
'#([a-z][a-z0-9+\-.]*://|www\.)[a-z0-9]+(-+[a-z0-9]+)*(\.[a-z0-9]+(-+[a-z0-9]+)*)+(/([^\s()<>;]+\w)?/?)?#i',
|
||||
$str,
|
||||
$matches,
|
||||
PREG_OFFSET_CAPTURE | PREG_SET_ORDER,
|
||||
) >= 1
|
||||
) {
|
||||
// Set our target HTML if using popup links.
|
||||
$target = ($popup) ? ' target="_blank"' : '';
|
||||
|
||||
// We process the links in reverse order (last -> first) so that
|
||||
// the returned string offsets from preg_match_all() are not
|
||||
// moved as we add more HTML.
|
||||
foreach (array_reverse($matches) as $match) {
|
||||
// $match[0] is the matched string/link
|
||||
// $match[1] is either a protocol prefix or 'www.'
|
||||
//
|
||||
// With PREG_OFFSET_CAPTURE, both of the above is an array,
|
||||
// where the actual value is held in [0] and its offset at the [1] index.
|
||||
$a = '<a href="' . (strpos($match[1][0], '/') ? '' : 'http://') . $match[0][0] . '"' . $target . '>' . $match[0][0] . '</a>';
|
||||
$str = substr_replace($str, $a, $match[0][1], strlen($match[0][0]));
|
||||
}
|
||||
}
|
||||
|
||||
// Find and replace any emails.
|
||||
if (
|
||||
$type !== 'url'
|
||||
&& preg_match_all(
|
||||
'#([\w\.\-\+]+@[a-z0-9\-]+\.[a-z0-9\-\.]+[^[:punct:]\s])#i',
|
||||
$str,
|
||||
$matches,
|
||||
PREG_OFFSET_CAPTURE,
|
||||
) >= 1
|
||||
) {
|
||||
foreach (array_reverse($matches[0]) as $match) {
|
||||
if (filter_var($match[0], FILTER_VALIDATE_EMAIL) !== false) {
|
||||
$str = substr_replace($str, safe_mailto($match[0]), $match[1], strlen($match[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('prep_url')) {
|
||||
/**
|
||||
* Prep URL - Simply adds the http:// or https:// part if no scheme is included.
|
||||
*
|
||||
* Formerly used URI, but that does not play nicely with URIs missing
|
||||
* the scheme.
|
||||
*
|
||||
* @param string $str the URL
|
||||
* @param bool $secure set true if you want to force https://
|
||||
*/
|
||||
function prep_url(string $str = '', bool $secure = false): string
|
||||
{
|
||||
if (in_array($str, ['http://', 'https://', '//', ''], true)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (parse_url($str, PHP_URL_SCHEME) === null) {
|
||||
$str = 'http://' . ltrim($str, '/');
|
||||
}
|
||||
|
||||
// force replace http:// with https://
|
||||
if ($secure) {
|
||||
$str = preg_replace('/^(?:http):/i', 'https:', $str);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('url_title')) {
|
||||
/**
|
||||
* Create URL Title
|
||||
*
|
||||
* Takes a "title" string as input and creates a
|
||||
* human-friendly URL string with a "separator" string
|
||||
* as the word separator.
|
||||
*
|
||||
* @param string $str Input string
|
||||
* @param string $separator Word separator (usually '-' or '_')
|
||||
* @param bool $lowercase Whether to transform the output string to lowercase
|
||||
*/
|
||||
function url_title(string $str, string $separator = '-', bool $lowercase = false): string
|
||||
{
|
||||
$qSeparator = preg_quote($separator, '#');
|
||||
|
||||
$trans = [
|
||||
'&.+?;' => '',
|
||||
'[^\w\d\pL\pM _-]' => '',
|
||||
'\s+' => $separator,
|
||||
'(' . $qSeparator . ')+' => $separator,
|
||||
];
|
||||
|
||||
$str = strip_tags($str);
|
||||
|
||||
foreach ($trans as $key => $val) {
|
||||
$str = preg_replace('#' . $key . '#iu', $val, $str);
|
||||
}
|
||||
|
||||
if ($lowercase) {
|
||||
$str = mb_strtolower($str);
|
||||
}
|
||||
|
||||
return trim(trim($str, $separator));
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('mb_url_title')) {
|
||||
/**
|
||||
* Create URL Title that takes into account accented characters
|
||||
*
|
||||
* Takes a "title" string as input and creates a
|
||||
* human-friendly URL string with a "separator" string
|
||||
* as the word separator.
|
||||
*
|
||||
* @param string $str Input string
|
||||
* @param string $separator Word separator (usually '-' or '_')
|
||||
* @param bool $lowercase Whether to transform the output string to lowercase
|
||||
*/
|
||||
function mb_url_title(string $str, string $separator = '-', bool $lowercase = false): string
|
||||
{
|
||||
helper('text');
|
||||
|
||||
return url_title(convert_accented_characters($str), $separator, $lowercase);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('url_to')) {
|
||||
/**
|
||||
* Get the full, absolute URL to a route name or controller method
|
||||
* (with additional arguments)
|
||||
*
|
||||
* NOTE: This requires the controller/method to
|
||||
* have a route defined in the routes Config file.
|
||||
*
|
||||
* @param string $controller Route name or Controller::method
|
||||
* @param int|string ...$args One or more parameters to be passed to the route.
|
||||
* The last parameter allows you to set the locale.
|
||||
*
|
||||
* @throws RouterException
|
||||
*/
|
||||
function url_to(string $controller, ...$args): string
|
||||
{
|
||||
if (! $route = route_to($controller, ...$args)) {
|
||||
$explode = explode('::', $controller);
|
||||
|
||||
if (isset($explode[1])) {
|
||||
throw RouterException::forControllerNotFound($explode[0], $explode[1]);
|
||||
}
|
||||
|
||||
throw RouterException::forInvalidRoute($controller);
|
||||
}
|
||||
|
||||
return site_url($route);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('url_is')) {
|
||||
/**
|
||||
* Determines if current url path contains
|
||||
* the given path. It may contain a wildcard (*)
|
||||
* which will allow any valid character.
|
||||
*
|
||||
* Example:
|
||||
* if (url_is('admin*')) ...
|
||||
*/
|
||||
function url_is(string $path): bool
|
||||
{
|
||||
// Setup our regex to allow wildcards
|
||||
$path = '/' . trim(str_replace('*', '(\S)*', $path), '/ ');
|
||||
$currentPath = '/' . trim(uri_string(), '/ ');
|
||||
|
||||
return (bool) preg_match("|^{$path}$|", $currentPath, $matches);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
// CodeIgniter XML Helpers
|
||||
|
||||
if (! function_exists('xml_convert')) {
|
||||
/**
|
||||
* Convert Reserved XML characters to Entities
|
||||
*/
|
||||
function xml_convert(string $str, bool $protectAll = false): string
|
||||
{
|
||||
$temp = '__TEMP_AMPERSANDS__';
|
||||
|
||||
// Replace entities to temporary markers so that
|
||||
// ampersands won't get messed up
|
||||
$str = preg_replace('/&#(\d+);/', $temp . '\\1;', $str);
|
||||
|
||||
if ($protectAll) {
|
||||
$str = preg_replace('/&(\w+);/', $temp . '\\1;', $str);
|
||||
}
|
||||
|
||||
$original = [
|
||||
'&',
|
||||
'<',
|
||||
'>',
|
||||
'"',
|
||||
"'",
|
||||
'-',
|
||||
];
|
||||
|
||||
$replacement = [
|
||||
'&',
|
||||
'<',
|
||||
'>',
|
||||
'"',
|
||||
''',
|
||||
'-',
|
||||
];
|
||||
|
||||
$str = str_replace($original, $replacement, $str);
|
||||
|
||||
// Decode the temp markers back to entities
|
||||
$str = preg_replace('/' . $temp . '(\d+);/', '&#\\1;', $str);
|
||||
|
||||
if ($protectAll) {
|
||||
return preg_replace('/' . $temp . '(\w+);/', '&\\1;', $str);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user