1417 lines
51 KiB
PHP
1417 lines
51 KiB
PHP
<?php namespace ProcessWire;
|
||
|
||
/**
|
||
* ProcessWire Functions
|
||
*
|
||
* Common API functions useful outside of class scope
|
||
*
|
||
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
|
||
* https://processwire.com
|
||
*
|
||
* #pw-summary-arrays These shortcuts for creating WireArray types are available in ProcessWire 3.0.123 and newer.
|
||
* #pw-summary-files These file system functions are procedural versions of some of those provided by the `$files` API variable.
|
||
*
|
||
*/
|
||
|
||
/**
|
||
* Return an API variable, or return current ProcessWire instance if given no arguments
|
||
*
|
||
* - Call `wire()` with no arguments returns the current ProcessWire instance.
|
||
* - Call `wire('var')` to return the API variable represented by 'var', or null if not present.
|
||
* - Call `wire('all')` or `wire('*')` to return an iterable object of all API variables.
|
||
* - Call `wire($object)` to attach $object to the current instance ($object must be Wire-derived object).
|
||
*
|
||
* #pw-group-common
|
||
* #pw-group-Functions-API
|
||
*
|
||
* @param string $name If omitted, returns current ProcessWire instance.
|
||
* @return null|ProcessWire|Wire|Session|Page|Pages|Modules|User|Users|Roles|Permissions|Templates|Fields|Fieldtypes|Sanitizer|Config|Notices|WireDatabasePDO|WireHooks|WireDateTime|WireFileTools|WireMailTools|WireInput|string|mixed Requested API variable or null if it does not exist
|
||
*
|
||
*/
|
||
function wire($name = 'wire') {
|
||
return ProcessWire::getCurrentInstance()->wire($name);
|
||
}
|
||
|
||
/**
|
||
* Get or set the current ProcessWire instance
|
||
*
|
||
* #pw-group-common
|
||
*
|
||
* @param ProcessWire|Wire|null $wire To set specify ProcessWire instance or any Wire-derived object in it, or omit to get current instance.
|
||
* @return ProcessWire
|
||
* @since 3.0.125
|
||
*
|
||
*/
|
||
function wireInstance(Wire $wire = null) {
|
||
if($wire === null) return ProcessWire::getCurrentInstance();
|
||
if(!$wire instanceof ProcessWire) $wire = $wire->wire();
|
||
ProcessWire::setCurrentInstance($wire);
|
||
return $wire;
|
||
}
|
||
|
||
/**
|
||
* Return all Fuel, or specified ProcessWire API variable, or NULL if it doesn't exist.
|
||
*
|
||
* Same as Wire::getFuel($name) and Wire::getAllFuel();
|
||
* When a $name is specified, this function is identical to the wire() function.
|
||
* Both functions exist more for consistent naming depending on usage.
|
||
*
|
||
* #pw-internal
|
||
*
|
||
* @deprecated
|
||
* @param string $name If omitted, returns a Fuel object with references to all the fuel.
|
||
* @return mixed Fuel value if available, NULL if not.
|
||
*
|
||
*/
|
||
function fuel($name = '') {
|
||
return wire($name);
|
||
}
|
||
|
||
|
||
if(!function_exists("tabIndent")):
|
||
/**
|
||
* Indent the given string with $numTabs tab characters
|
||
*
|
||
* Newlines are assumed to be \n
|
||
*
|
||
* Watch out when using this function with strings that have a <textarea>, you may want to have it use \r newlines, at least temporarily.
|
||
*
|
||
* #pw-internal
|
||
*
|
||
* @param string $str String that needs the tabs
|
||
* @param int $numTabs Number of tabs to insert per line (note any existing tabs are left as-is, so indentation is retained)
|
||
* @param string $str The provided string but with tabs inserted
|
||
* @return string
|
||
* @deprecated
|
||
*
|
||
*/
|
||
function tabIndent($str, $numTabs) {
|
||
$tabs = str_repeat("\t", $numTabs);
|
||
$str = str_replace("\n", "\n$tabs", $str);
|
||
return $str;
|
||
}
|
||
endif;
|
||
|
||
/**
|
||
* Encode array for storage and remove empty values
|
||
*
|
||
* Uses json_encode and works the same way except this function clears out empty root-level values.
|
||
* It also forces number strings that can be integers to be integers.
|
||
*
|
||
* The end result of all this is more optimized JSON.
|
||
*
|
||
* Use json_encode() instead if you don't want any empty values removed.
|
||
*
|
||
* #pw-internal
|
||
*
|
||
* @param array $data Array to be encoded to JSON
|
||
* @param bool|array $allowEmpty Should empty values be allowed in the encoded data?
|
||
* - Specify false to exclude all empty values (this is the default if not specified).
|
||
* - Specify true to allow all empty values to be retained.
|
||
* - Specify an array of keys (from data) that should be retained if you want some retained and not others.
|
||
* - Specify array of literal empty value types to retain, i.e. [ 0, '0', array(), false, null ].
|
||
* - Specify the digit 0 to retain values that are 0, but not other types of empty values.
|
||
* @param bool $beautify Beautify the encoded data when possible for better human readability? (requires PHP 5.4+)
|
||
* @return string String of JSON data
|
||
*
|
||
*/
|
||
function wireEncodeJSON(array $data, $allowEmpty = false, $beautify = false) {
|
||
if($allowEmpty !== true) {
|
||
/** @var Sanitizer $sanitizer */
|
||
$sanitizer = wire('sanitizer');
|
||
$data = $sanitizer->minArray($data, $allowEmpty, true);
|
||
}
|
||
if(!count($data)) return '';
|
||
$flags = 0;
|
||
if($beautify && defined("JSON_PRETTY_PRINT")) $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
|
||
return json_encode($data, $flags);
|
||
}
|
||
|
||
/**
|
||
* Decode JSON to array
|
||
*
|
||
* Uses json_decode and works the same way except that arrays are forced.
|
||
* This is the counterpart to the wireEncodeJSON() function.
|
||
*
|
||
* #pw-internal
|
||
*
|
||
* @param string $json A JSON encoded string
|
||
* @return array
|
||
*
|
||
*/
|
||
function wireDecodeJSON($json) {
|
||
if(empty($json) || $json == '[]') return array();
|
||
return json_decode($json, true);
|
||
}
|
||
|
||
|
||
/**
|
||
* Minimize an array to remove empty values
|
||
*
|
||
* #pw-internal
|
||
*
|
||
* @param array $data Array to reduce
|
||
* @param bool|array $allowEmpty Should empty values be allowed in the encoded data?
|
||
* - Specify false to exclude all empty values (this is the default if not specified).
|
||
* - Specify true to allow all empty values to be retained (thus no point in calling this function).
|
||
* - Specify an array of keys (from data) that should be retained if you want some retained and not others.
|
||
* - Specify the digit 0 to retain values that are 0, but not other types of empty values.
|
||
* @param bool $convert Perform type conversions where appropriate: i.e. convert digit-only string to integer
|
||
* @return array
|
||
* @deprecated Use $sanitizer->minArray() instead
|
||
*
|
||
*/
|
||
function wireMinArray(array $data, $allowEmpty = false, $convert = false) {
|
||
/** @var Sanitizer $sanitizer */
|
||
$sanitizer = wire('sanitizer');
|
||
return $sanitizer->minArray($data, $allowEmpty, $convert);
|
||
}
|
||
|
||
/**
|
||
* Create a directory (optionally recursively) that is writable to ProcessWire and uses the $config chmod settings
|
||
*
|
||
* This is procedural version of the `$files->mkdir()` method.
|
||
*
|
||
* #pw-group-files
|
||
*
|
||
* @param string $path
|
||
* @param bool $recursive If set to true, all directories will be created as needed to reach the end.
|
||
* @param string $chmod Optional mode to set directory to (default: $config->chmodDir), format must be a string i.e. "0755"
|
||
* If omitted, then ProcessWire’s $config->chmodDir setting is used instead.
|
||
* @return bool
|
||
* @see WireFileTools::mkdir()
|
||
*
|
||
*/
|
||
function wireMkdir($path, $recursive = false, $chmod = null) {
|
||
/** @var WireFileTools $files */
|
||
$files = wire('files');
|
||
return $files->mkdir($path, $recursive, $chmod);
|
||
}
|
||
|
||
/**
|
||
* Remove a directory (optionally recursively)
|
||
*
|
||
* This is procedural version of the `$files->rmdir()` method. See that method for more options.
|
||
*
|
||
* #pw-group-files
|
||
*
|
||
* @param string $path
|
||
* @param bool $recursive If set to true, all files and directories in $path will be recursively removed as well.
|
||
* @return bool
|
||
* @see WireFileTools::rmdir()
|
||
*
|
||
*/
|
||
function wireRmdir($path, $recursive = false) {
|
||
/** @var WireFileTools $files */
|
||
$files = wire('files');
|
||
return $files->rmdir($path, $recursive);
|
||
}
|
||
|
||
/**
|
||
* Change the mode of a file or directory (optionally recursive)
|
||
*
|
||
* If no `$chmod` mode argument is specified the `$config->chmodFile` or $config->chmodDir` settings will be used.
|
||
*
|
||
* This is procedural version of the `$files->chmod()` method.
|
||
*
|
||
* #pw-group-files
|
||
*
|
||
* @param string $path May be a directory or a filename
|
||
* @param bool $recursive If set to true, all files and directories in $path will be recursively set as well.
|
||
* @param string $chmod If you want to set the mode to something other than PW's chmodFile/chmodDir settings,
|
||
* you may override it by specifying it here. Ignored otherwise. Format should be a string, like "0755".
|
||
* @return bool Returns true if all changes were successful, or false if at least one chmod failed.
|
||
* @throws WireException when it receives incorrect chmod format
|
||
* @see WireFileTools::chmod()
|
||
*
|
||
*/
|
||
function wireChmod($path, $recursive = false, $chmod = null) {
|
||
/** @var WireFileTools $files */
|
||
$files = wire('files');
|
||
return $files->chmod($path, $recursive, $chmod);
|
||
}
|
||
|
||
/**
|
||
* Copy all files recursively from one directory to another
|
||
*
|
||
* This is procedural version of the `$files->copy()` method.
|
||
*
|
||
* #pw-group-files
|
||
*
|
||
* @param string $src Path to copy files from
|
||
* @param string $dst Path to copy files to. Directory is created if it doesn’t already exist.
|
||
* @param bool|array Array of options:
|
||
* - `recursive` (bool): Whether to copy directories within recursively. (default=true)
|
||
* - `allowEmptyDirs` (bool): Copy directories even if they are empty? (default=true)
|
||
* - If a boolean is specified for $options, it is assumed to be the 'recursive' option.
|
||
* @return bool True on success, false on failure.
|
||
* @see WireFileTools::copy()
|
||
*
|
||
*/
|
||
function wireCopy($src, $dst, $options = array()) {
|
||
/** @var WireFileTools $files */
|
||
$files = wire('files');
|
||
return $files->copy($src, $dst, $options);
|
||
}
|
||
|
||
/**
|
||
* Unzips the given ZIP file to the destination directory
|
||
*
|
||
* This is procedural version of the `$files->unzip()` method. See that method for more details.
|
||
*
|
||
* #pw-group-files
|
||
*
|
||
* @param string $file ZIP file to extract
|
||
* @param string $dst Directory where files should be unzipped into. Directory is created if it doesn’t exist.
|
||
* @return array Returns an array of filenames (excluding $dst) that were unzipped.
|
||
* @throws WireException All error conditions result in WireException being thrown.
|
||
* @see WireFileTools::unzip()
|
||
*
|
||
*/
|
||
function wireUnzipFile($file, $dst) {
|
||
/** @var WireFileTools $files */
|
||
$files = wire('files');
|
||
return $files->unzip($file, $dst);
|
||
}
|
||
|
||
/**
|
||
* Create a ZIP file from given files
|
||
*
|
||
* This is procedural version of the `$files->zip()` method. See that method for all options.
|
||
*
|
||
* #pw-group-files
|
||
*
|
||
* @param string $zipfile Full path and filename to create or update (i.e. /path/to/myfile.zip)
|
||
* @param array|string $files Array of files to add (full path and filename), or directory (string) to add.
|
||
* If given a directory, it will recursively add everything in that directory.
|
||
* @param array $options Options modify default behavior:
|
||
* - `allowHidden` (bool|array): allow hidden files? May be boolean, or array of hidden files (basenames) you allow. (default=false)
|
||
* Note that if you actually specify a hidden file in your $files argument, then that overrides this.
|
||
* - `allowEmptyDirs` (bool): allow empty directories in the ZIP file? (default=true)
|
||
* - `overwrite` (bool): Replaces ZIP file if already present (rather than adding to it) (default=false)
|
||
* - `exclude` (array): Files or directories to exclude
|
||
* - `dir` (string): Directory name to prepend to added files in the ZIP
|
||
* @return array Returns associative array of:
|
||
* - `files` (array): all files that were added
|
||
* - `errors` (array): files that failed to add, if any
|
||
* @throws WireException Original ZIP file creation error conditions result in WireException being thrown.
|
||
* @see WireFileTools::zip()
|
||
*
|
||
*/
|
||
function wireZipFile($zipfile, $files, array $options = array()) {
|
||
/** @var WireFileTools $fileTools */
|
||
$fileTools = wire('files');
|
||
return $fileTools->zip($zipfile, $files, $options);
|
||
}
|
||
|
||
/**
|
||
* Send the contents of the given filename via http
|
||
*
|
||
* This function utilizes the `$config->fileContentTypes` to match file extension
|
||
* to content type headers and force-download state.
|
||
*
|
||
* This function throws a WireException if the file can’t be sent for some reason.
|
||
*
|
||
* This is procedural version of the `$files->send()` method. See that method for all options.
|
||
*
|
||
* #pw-group-files
|
||
*
|
||
* @param string $filename Full path and filename to send
|
||
* @param array $options Optional options that you may pass in (see `WireHttp::sendFile()` for details)
|
||
* @param array $headers Optional headers that are sent (see `WireHttp::sendFile()` for details)
|
||
* @throws WireException
|
||
* @see WireHttp::sendFile(), WireFileTools::send()
|
||
*
|
||
*/
|
||
function wireSendFile($filename, array $options = array(), array $headers = array()) {
|
||
/** @var WireFileTools $fileTools */
|
||
$files = wire('files');
|
||
$files->send($filename, $options, $headers);
|
||
}
|
||
|
||
/**
|
||
* Given a unix timestamp (or date string), returns a formatted string indicating the time relative to now
|
||
*
|
||
* Examples: “1 day ago”, “30 seconds ago”, “just now”, etc.
|
||
*
|
||
* This is the procedural version of `$datetime->relativeTimeStr()`.
|
||
*
|
||
* Based upon: http://www.php.net/manual/en/function.time.php#89415
|
||
*
|
||
* #pw-group-strings
|
||
*
|
||
* @param int|string $ts Unix timestamp or date string
|
||
* @param bool|int|array $abbreviate Whether to use abbreviations for shorter strings.
|
||
* - Specify boolean TRUE for abbreviations (abbreviated where common, not always different from non-abbreviated)
|
||
* - Specify integer 1 for extra short abbreviations (all terms abbreviated into shortest possible string)
|
||
* - Specify boolean FALSE or omit for no abbreviations.
|
||
* - Specify associative array of key=value pairs of terms to use for abbreviations. The possible keys are:
|
||
* just now, ago, from now, never, second, minute, hour, day, week, month, year, decade, seconds, minutes,
|
||
* hours, days, weeks, months, years, decades
|
||
* @param bool $useTense Whether to append a tense like “ago” or “from now”,
|
||
* May be ok to disable in situations where all times are assumed in future or past.
|
||
* In abbreviate=1 (shortest) mode, this removes the leading "+" or "-" from the string.
|
||
* @return string
|
||
* @see WireDateTime::relativeTimeStr()
|
||
*
|
||
*/
|
||
function wireRelativeTimeStr($ts, $abbreviate = false, $useTense = true) {
|
||
/** @var WireDateTime $datetime */
|
||
$datetime = wire('datetime');
|
||
return $datetime->relativeTimeStr($ts, $abbreviate, $useTense);
|
||
}
|
||
|
||
|
||
/**
|
||
* Send an email or get a WireMail object to populate before send
|
||
*
|
||
* - Please note that the order of arguments is different from PHP’s mail() function.
|
||
* - If no arguments are specified it simply returns a WireMail object (see #4 below).
|
||
* - This is a procedural version of functions provided by the `$mail` API variable (see that for more options).
|
||
* - This function will attempt to use an installed module that extends WireMail.
|
||
* - If no other WireMail module is installed, the default `WireMail` (which uses PHP mail) will be used instead.
|
||
*
|
||
* ~~~~~~
|
||
* // Default usage:
|
||
* wireMail($to, $from, $subject, $body, $options);
|
||
*
|
||
* // Specify body and/or bodyHTML in $options array (perhaps with other options):
|
||
* $options = [ 'body' => $body, 'bodyHTML' => $bodyHTML ];
|
||
* wireMail($to, $from, $subject, $options);
|
||
*
|
||
* // Specify both $body and $bodyHTML as arguments, but no $options:
|
||
* wireMail($to, $from, $subject, $body, $bodyHTML);
|
||
*
|
||
* // Specify a blank call to wireMail() to get the WireMail sending object. This can
|
||
* // be either WireMail() or a class descending from it. If a WireMail descending
|
||
* // module is installed, it will be returned rather than WireMail():
|
||
* $mail = wireMail();
|
||
* $mail->to('user@domain.com')->from('you@company.com');
|
||
* $mail->subject('Mail Subject')->body('Mail Body Text')->bodyHTML('Body HTML');
|
||
* $numSent = $mail->send();
|
||
*
|
||
* #pw-group-common
|
||
*
|
||
* @param string|array $to Email address TO. For multiple, specify CSV string or array.
|
||
* @param string $from Email address FROM. This may be an email address, or a combined name and email address.
|
||
* Example of combined name and email: `Karen Cramer <karen@processwire.com>`
|
||
* @param string $subject Email subject
|
||
* @param string|array $body Email body or omit to move straight to $options array
|
||
* @param array|string $options Array of options OR the $bodyHTML string. Array $options are:
|
||
* - `bodyHTML` (string): Email body as HTML.
|
||
* - `body` (string): Email body as plain text. This is created automatically if you only provide $bodyHTML.
|
||
* - `headers` (array): Associative array of ['header name' => 'header value']
|
||
* - Any additional options you provide will be sent along to the WireMail module or class, in tact.
|
||
* @return int|WireMail Returns number of messages sent or WireMail object if no arguments specified.
|
||
*
|
||
*/
|
||
function wireMail($to = '', $from = '', $subject = '', $body = '', $options = array()) {
|
||
/** @var WireMailTools $mail */
|
||
$mail = wire('mail');
|
||
return $mail->send($to, $from, $subject, $body, $options);
|
||
}
|
||
|
||
|
||
/**
|
||
* Given a string ($str) and values ($vars), replace “{tags}” in the string with the values
|
||
*
|
||
* - The `$vars` should be an associative array of `[ 'tag' => 'value' ]`.
|
||
* - The `$vars` may also be an object, in which case values will be pulled as properties of the object.
|
||
*
|
||
* By default, tags are specified in the format: {first_name} where first_name is the name of the
|
||
* variable to pull from $vars, `{` is the opening tag character, and `}` is the closing tag char.
|
||
*
|
||
* The tag parser can also handle subfields and OR tags, if `$vars` is an object that supports that.
|
||
* For instance `{products.title}` is a subfield, and `{first_name|title|name}` is an OR tag.
|
||
*
|
||
* ~~~~~
|
||
* $vars = [ 'foo' => 'FOO!', 'bar' => 'BAR!' ];
|
||
* $str = 'This is a test: {foo}, and this is another test: {bar}';
|
||
* echo wirePopulateStringTags($str, $vars);
|
||
* // outputs: This is a test: FOO!, and this is another test: BAR!
|
||
* ~~~~~
|
||
*
|
||
* #pw-group-strings
|
||
*
|
||
* @param string $str The string to operate on (where the {tags} might be found)
|
||
* @param WireData|object|array $vars Object or associative array to pull replacement values from.
|
||
* @param array $options Array of optional changes to default behavior, including:
|
||
* - `tagOpen` (string): The required opening tag character(s), default is '{'
|
||
* - `tagClose` (string): The optional closing tag character(s), default is '}'
|
||
* - `recursive` (bool): If replacement value contains tags, populate those too? (default=false)
|
||
* - `removeNullTags` (bool): If a tag resolves to a NULL, remove it? If false, tag will remain. (default=true)
|
||
* - `entityEncode` (bool): Entity encode the values pulled from $vars? (default=false)
|
||
* - `entityDecode` (bool): Entity decode the values pulled from $vars? (default=false)
|
||
* @return string String with tags populated.
|
||
*
|
||
*/
|
||
function wirePopulateStringTags($str, $vars, array $options = array()) {
|
||
return wire('sanitizer')->getTextTools()->populatePlaceholders($str, $vars, $options);
|
||
}
|
||
|
||
|
||
/**
|
||
* Return a new temporary directory/path ready to use for files
|
||
*
|
||
* - The directory will be automatically removed after a set period of time (default=120s).
|
||
* - This is a procedural version of the `$files->tempDir()` method.
|
||
*
|
||
* ~~~~~
|
||
* $td = wireTempDir('hello-world');
|
||
* $path = (string) $td; // or use $td->get();
|
||
* file_put_contents($path . 'some-file.txt', 'Hello world');
|
||
* ~~~~~
|
||
*
|
||
* #pw-group-files
|
||
*
|
||
* @param Object|string $name Provide the object that needs the temp dir, or name your own string
|
||
* @param array|int $options Options array to modify default behavior:
|
||
* - `maxAge` (integer): Maximum age of temp dir files in seconds (default=120)
|
||
* - `basePath` (string): Base path where temp dirs should be created. Omit to use default (recommended).
|
||
* - Note: if you specify an integer for $options, then 'maxAge' is assumed.
|
||
* @return WireTempDir If you typecast return value to a string, it is the temp dir path (with trailing slash).
|
||
* @see WireFileTools::tempDir(), WireTempDir
|
||
*
|
||
*/
|
||
function wireTempDir($name, $options = array()) {
|
||
/** @var WireFileTools $files */
|
||
$files = wire('files');
|
||
return $files->tempDir($name, $options);
|
||
}
|
||
|
||
/**
|
||
* Given a filename, render it as a ProcessWire template file
|
||
*
|
||
* This is a shortcut to using the `TemplateFile` class, as well as the procedural version of `$files->render()`.
|
||
*
|
||
* File is assumed relative to `/site/templates/` (or a directory within there) unless you specify a full path.
|
||
* If you specify a full path, it will accept files in or below any of the following:
|
||
*
|
||
* - /site/templates/
|
||
* - /site/modules/
|
||
* - /wire/modules/
|
||
*
|
||
* Note this function returns the output to you, so that you can send the output wherever you want (delayed output).
|
||
* For direct output, use the `wireIncludeFile()` or `$files->include()` function instead.
|
||
*
|
||
* #pw-group-files
|
||
*
|
||
* @param string $filename Assumed relative to /site/templates/ unless you provide a full path name with the filename.
|
||
* If you provide a path, it must resolve somewhere in site/templates/, site/modules/ or wire/modules/.
|
||
* @param array $vars Optional associative array of variables to send to template file.
|
||
* Please note that all template files automatically receive all API variables already (you don't have to provide them).
|
||
* @param array $options Associative array of options to modify behavior:
|
||
* - `defaultPath` (string): Path where files are assumed to be when only filename or relative filename is specified (default=/site/templates/)
|
||
* - `autoExtension` (string): Extension to assume when no ext in filename, make blank for no auto assumption (default=php)
|
||
* - `allowedPaths` (array): Array of paths that are allowed (default is templates, core modules and site modules)
|
||
* - `allowDotDot` (bool): Allow use of ".." in paths? (default=false)
|
||
* - `throwExceptions` (bool): Throw exceptions when fatal error occurs? (default=true)
|
||
* @return string|bool Rendered template file or boolean false on fatal error (and throwExceptions disabled)
|
||
* @throws WireException if template file doesn’t exist
|
||
* @see wireIncludeFile(), WireFileTools::render(), WireFileTools::include()
|
||
*
|
||
*/
|
||
function wireRenderFile($filename, array $vars = array(), array $options = array()) {
|
||
/** @var WireFileTools $files */
|
||
$files = wire('files');
|
||
return $files->render($filename, $vars, $options);
|
||
}
|
||
|
||
/**
|
||
* Include a PHP file passing it all API variables and optionally your own specified variables
|
||
|
||
* This is the procedural version of the `$files->include()` method.
|
||
*
|
||
* This is the same as PHP’s `include()` function except for the following:
|
||
*
|
||
* - It receives all API variables and optionally your custom variables
|
||
* - If your filename is not absolute, it doesn’t look in PHP’s include path, only in the current dir.
|
||
* - It only allows including files that are part of the PW installation: templates, core modules or site modules
|
||
* - It will assume a “.php” extension if filename has no extension.
|
||
*
|
||
* Note this function produces direct output. To retrieve output as a return value, use the
|
||
* `wireRenderFile()` or `$files->render()` function instead.
|
||
*
|
||
* #pw-group-files
|
||
*
|
||
* @param string $filename Filename to include
|
||
* @param array $vars Optional variables you want to hand to the include (associative array)
|
||
* @param array $options Array of options to modify behavior:
|
||
* - `func` (string): Function to use: include, include_once, require or require_once (default=include)
|
||
* - `autoExtension` (string): Extension to assume when no ext in filename, make blank for no auto assumption (default=php)
|
||
* - `allowedPaths` (array): Array of start paths include files are allowed from. Note current dir is always allowed.
|
||
* @return bool Always returns true
|
||
* @throws WireException if file doesn’t exist or is not allowed
|
||
* @see wireRenderFile(), WireFileTools::include(), WireFileTools::render()
|
||
*
|
||
*
|
||
*/
|
||
function wireIncludeFile($filename, array $vars = array(), array $options = array()) {
|
||
/** @var WireFileTools $files */
|
||
$files = wire('files');
|
||
return $files->___include($filename, $vars, $options);
|
||
}
|
||
|
||
/**
|
||
* Format a date, using PHP date(), strftime() or other special strings (see arguments).
|
||
*
|
||
* This is designed to work the same wa as PHP’s `date()` but be able to accept any common format
|
||
* used in ProcessWire. This is helpful in reducing code in places where you might have logic
|
||
* determining when to use `date()`, `strftime()`, or `wireRelativeTimeStr()`.
|
||
*
|
||
* This is the procedural version of the `$datetime->date()` method.
|
||
*
|
||
* ~~~~~
|
||
* echo wireDate('Y-m-d H:i:s'); // Outputs: 2019-01-20 06:48:11
|
||
* echo wireDate('relative', '2019-01-20 06:00'); // Outputs: 48 minutes ago
|
||
* ~~~~~
|
||
*
|
||
* #pw-group-strings
|
||
* #pw-group-common
|
||
*
|
||
* @param string|int $format Use any PHP date() or strftime() format, or one of the following:
|
||
* - `relative` for a relative date/time string.
|
||
* - `relative-` for a relative date/time string with no tense.
|
||
* - `rel` for an abbreviated relative date/time string.
|
||
* - `rel-` for an abbreviated relative date/time string with no tense.
|
||
* - `r` for an extra-abbreviated relative date/time string.
|
||
* - `r-` for an extra-abbreviated relative date/time string with no tense.
|
||
* - `ts` makes it return a unix timestamp.
|
||
* - Specify blank string to make it use the system date format ($config->dateFormat) .
|
||
* - If given an integer and no second argument specified, it is assumed to be the second ($ts) argument.
|
||
* @param int|string|null $ts Optionally specify the date/time stamp or strtotime() compatible string. If not specified, current time is used.
|
||
* @return string|bool Formatted date/time, or boolean false on failure
|
||
*
|
||
*/
|
||
function wireDate($format = '', $ts = null) {
|
||
/** @var WireDateTime $datetime */
|
||
$datetime = wire('datetime');
|
||
return $datetime->date($format, $ts);
|
||
}
|
||
|
||
|
||
/**
|
||
* Render markup for a system icon
|
||
*
|
||
* It is NOT necessary to specify an icon prefix like “fa-” with the icon name.
|
||
*
|
||
* ~~~~~
|
||
* // Outputs: "<i class='fa fa-home'></i>"
|
||
* echo wireIconMarkup('home');
|
||
* ~~~~~
|
||
*
|
||
* #pw-group-markup
|
||
*
|
||
* @param string $icon Icon name (currently a font-awesome icon name, but support for more in future)
|
||
* @param string $class Additional attributes for class (example: "fw" for fixed width)
|
||
* @return string
|
||
*
|
||
*/
|
||
function wireIconMarkup($icon, $class = '') {
|
||
if(empty($icon)) return '';
|
||
if(strpos($icon, 'icon-') === 0) $icon = str_replace('icon-', 'fa-', $icon);
|
||
if(strpos($icon, 'fa-') !== 0) $icon = "fa-$icon";
|
||
if($class) {
|
||
$modifiers = array(
|
||
'lg', 'fw', '2x', '3x', '4x', '5x', 'spin', 'spinner', 'li', 'border',
|
||
'rotate-90', 'rotate-180', 'rotate-270', 'flip-horizontal', 'flip-vertical',
|
||
'stack', 'stack-1x', 'stack-2x', 'inverse',
|
||
);
|
||
$classes = explode(' ', $class);
|
||
foreach($classes as $key => $modifier) {
|
||
if(in_array($modifier, $modifiers)) $classes[$key] = "fa-$modifier";
|
||
}
|
||
$class = implode(' ', $classes);
|
||
}
|
||
$class = trim("fa $icon $class");
|
||
return "<i class='$class'></i>";
|
||
}
|
||
|
||
/**
|
||
* Get the markup or class name for an icon that can represent the given filename
|
||
*
|
||
* ~~~~~
|
||
* // Outputs: "<i class='fa fa-pdf-o'></i>"
|
||
* echo wireIconMarkupFile('file.pdf');
|
||
* ~~~~~
|
||
*
|
||
* #pw-group-markup
|
||
* #pw-group-files
|
||
*
|
||
* @param string $filename Can be any type of filename (with or without path).
|
||
* @param string|bool $class Additional class attributes, i.e. "fw" for fixed-width (optional).
|
||
* Or specify boolean TRUE to get just the icon class name (no markup).
|
||
* @return string
|
||
*
|
||
*/
|
||
function wireIconMarkupFile($filename, $class = '') {
|
||
$icon = 'file-o';
|
||
$icons = array(
|
||
'pdf' => 'file-pdf-o',
|
||
'doc' => 'file-word-o',
|
||
'docx' => 'file-word-o',
|
||
'xls' => 'file-excel-o',
|
||
'xlsx' => 'file-excel-o',
|
||
'xlsb' => 'file-excel-o',
|
||
'csv' => 'file-excel-o',
|
||
'zip' => 'file-archive-o',
|
||
'txt' => 'file-text-o',
|
||
'rtf' => 'file-text-o',
|
||
'mp3' => 'file-sound-o',
|
||
'wav' => 'file-sound-o',
|
||
'ogg' => 'file-sound-o',
|
||
'jpg' => 'file-image-o',
|
||
'jpeg' => 'file-image-o',
|
||
'png' => 'file-image-o',
|
||
'gif' => 'file-image-o',
|
||
'svg' => 'file-image-o',
|
||
'ppt' => 'file-powerpoint-o',
|
||
'pptx' => 'file-powerpoint-o',
|
||
'mov' => 'file-video-o',
|
||
'mp4' => 'file-video-o',
|
||
'wmv' => 'file-video-o',
|
||
'js' => 'file-code-o',
|
||
'css' => 'file-code-o',
|
||
);
|
||
$pos = strrpos($filename, '.');
|
||
$ext = $pos !== false ? substr($filename, $pos+1) : '';
|
||
if($ext && isset($icons[$ext])) $icon = $icons[$ext];
|
||
return $class === true ? "fa-$icon" : wireIconMarkup($icon, $class);
|
||
}
|
||
|
||
/**
|
||
* Given a quantity of bytes (int), return readable string that refers to quantity in bytes, kB, MB, GB, etc.
|
||
*
|
||
* #pw-group-strings
|
||
*
|
||
* @param int $bytes Quantity in bytes
|
||
* @param bool|int|array $small Make returned string as small as possible? (default=false)
|
||
* - `true` (bool): Yes, make returned string as small as possible.
|
||
* - `1` (int): Same as `true` but with space between number and unit label.
|
||
* - Or optionally specify the $options argument here if you do not need the $small argument.
|
||
* @param array|int $options Options to modify default behavior, or if an integer then `decimals` option is assumed:
|
||
* - `decimals` (int): Number of decimals to use in returned value (default=0).
|
||
* - `decimal_point` (string|null): Decimal point character, or null to detect from locale (default=null).
|
||
* - `thousands_sep` (string|null): Thousands separator, or null to detect from locale (default=null).
|
||
* - `small` (bool): If no $small argument was specified, you can optionally specify it in this $options array.
|
||
* - `type` (string): To force return value as specific type, specify one of: bytes, kilobytes, megabytes, gigabytes; or just: b, k, m, g. (3.0.148+ only)
|
||
* @return string
|
||
*
|
||
*/
|
||
function wireBytesStr($bytes, $small = false, $options = array()) {
|
||
|
||
$defaults = array(
|
||
'type' => '',
|
||
'decimals' => 0,
|
||
'decimal_point' => null,
|
||
'thousands_sep' => null,
|
||
);
|
||
|
||
if(is_array($small)) {
|
||
$options = $small;
|
||
$small = isset($options['small']) ? $options['small'] : false;
|
||
}
|
||
if(!is_array($options)) $options = array('decimals' => (int) $options);
|
||
if(!is_int($bytes)) $bytes = (int) $bytes;
|
||
|
||
$options = array_merge($defaults, $options);
|
||
$locale = array();
|
||
$type = empty($options['type']) ? '' : strtolower(substr($options['type'], 0, 1));
|
||
|
||
// determine size value and units label
|
||
if($bytes < 1024 || $type === 'b') {
|
||
$val = $bytes;
|
||
if($small) {
|
||
$label = $val > 0 ? __('B', __FILE__) : ''; // bytes
|
||
} else if($val == 1) {
|
||
$label = __('byte', __FILE__); // singular 1-byte
|
||
} else {
|
||
$label = __('bytes', __FILE__); // plural 2+ bytes (or 0 bytes)
|
||
}
|
||
} else if($bytes < 1000000 || $type === 'k') {
|
||
$val = $bytes / 1024;
|
||
$label = __('kB', __FILE__); // kilobytes
|
||
} else if($bytes < 1073741824 || $type === 'm') {
|
||
$val = $bytes / 1024 / 1024;
|
||
$label = __('MB', __FILE__); // megabytes
|
||
} else {
|
||
$val = $bytes / 1024 / 1024 / 1024;
|
||
$label = __('GB', __FILE__); // gigabytes
|
||
}
|
||
|
||
// determine decimal point if not specified in $options
|
||
if($options['decimal_point'] === null) {
|
||
if($options['decimals'] > 0) {
|
||
// determine decimal point from locale
|
||
if(empty($locale)) $locale = localeconv();
|
||
$options['decimal_point'] = empty($locale['decimal_point']) ? '.' : $locale['decimal_point'];
|
||
} else {
|
||
// no decimal point needed (not used)
|
||
$options['decimal_point'] = '.';
|
||
}
|
||
}
|
||
|
||
// determine thousands separator if not specified in $options
|
||
if($options['thousands_sep'] === null) {
|
||
if($small || $val < 1000) {
|
||
// no thousands separator needed
|
||
$options['thousands_sep'] = '';
|
||
} else {
|
||
// get thousands separator from current locale
|
||
if(empty($locale)) $locale = localeconv();
|
||
$options['thousands_sep'] = empty($locale['thousands_sep']) ? '' : $locale['thousands_sep'];
|
||
}
|
||
}
|
||
|
||
// format number to string
|
||
$str = number_format($val, $options['decimals'], $options['decimal_point'], $options['thousands_sep']);
|
||
|
||
// in small mode remove numbers with decimals that consist only of zeros "0"
|
||
if($small && $options['decimals'] > 0) {
|
||
$test = substr($str, -1 * $options['decimals']);
|
||
if(((int) $test) === 0) {
|
||
$str = substr($str, 0, strlen($str) - ($options['decimals'] + 1)); // i.e. 123.00 => 123
|
||
} else {
|
||
$str = rtrim($str, '0'); // i.e. 123.10 => 123.1
|
||
}
|
||
}
|
||
|
||
// append units label to number
|
||
$str .= ($small === true ? '' : ' ') . $label;
|
||
|
||
return $str;
|
||
}
|
||
|
||
/**
|
||
* Normalize a class name with or without namespace, or get namespace of class
|
||
*
|
||
* Default behavior is to return class name without namespace.
|
||
*
|
||
* #pw-group-class-helpers
|
||
*
|
||
* @param string|object $className Class name or object instance
|
||
* @param bool|int|string $withNamespace Should return value include namespace? (default=false)
|
||
* - `false` (bool): Return only class name without namespace (default).
|
||
* - `true` (bool): Yes include namespace in returned value.
|
||
* - `1` (int): Return only namespace (i.e. “ProcessWire”, with no backslashes unless $verbose argument is true)
|
||
* @param bool $verbose When namespace argument is true or 1, use verbose return value (added 3.0.143). This does the following:
|
||
* - If returning class name with namespace, this makes it include a leading backslash, i.e. `\ProcessWire\Wire`
|
||
* - If returning namespace only, adds leading backslash, plus trailing backslash if namespace is not root, i.e. `\ProcessWire\`
|
||
* @return string|null Returns string or NULL if namespace-only requested and unable to determine
|
||
*
|
||
*/
|
||
function wireClassName($className, $withNamespace = false, $verbose = false) {
|
||
|
||
$bs = "\\"; // backslash
|
||
|
||
if(is_object($className)) {
|
||
$object = $className;
|
||
$className = get_class($className);
|
||
} else {
|
||
$object = null;
|
||
}
|
||
|
||
$pos = strrpos($className, $bs);
|
||
|
||
if($withNamespace === true) {
|
||
if($object) {
|
||
// result of get_class() is already what we want
|
||
} else if($pos === false && __NAMESPACE__) {
|
||
// return class with namespace, substituting ProcessWire namespace if none present
|
||
$className = __NAMESPACE__ . $bs . $className;
|
||
}
|
||
if($verbose) {
|
||
// add leading backslash
|
||
$className = $bs . ltrim($className, $bs);
|
||
}
|
||
|
||
} else if($withNamespace === 1) {
|
||
// return namespace only
|
||
if($pos !== false) {
|
||
// there is a namespace, extract it
|
||
$className = substr($className, 0, $pos);
|
||
} else if($object) {
|
||
// namespace is root
|
||
$className = $verbose ? $bs : '';
|
||
} else {
|
||
// there is no namespace in given className, attempt to detect in ProcessWire or root namespace
|
||
if(class_exists(__NAMESPACE__ . $bs . $className)) {
|
||
// class in ProcessWire namespace
|
||
$className = __NAMESPACE__;
|
||
} else if(class_exists($bs . $className)) {
|
||
// class in root namespace
|
||
$className = '';
|
||
} else {
|
||
// unable to determine
|
||
$className = null;
|
||
}
|
||
}
|
||
if($verbose && $className !== null) {
|
||
$className = $bs . trim($className, $bs); // leading
|
||
if(strlen($className) > 1) $className .= $bs; // trailing
|
||
}
|
||
|
||
} else {
|
||
// return className without namespace (default behavior)
|
||
if($pos !== false) $className = substr($className, $pos+1);
|
||
}
|
||
|
||
return $className;
|
||
}
|
||
|
||
/**
|
||
* Get namespace for given class
|
||
*
|
||
* ~~~~~
|
||
* echo wireClassNamespace('Page'); // returns: "\ProcessWire\"
|
||
* echo wireClassNamespace('DirectoryIterator'); // returns: "\"
|
||
* echo wireClassNamespace('UnknownClass'); // returns "" (blank)
|
||
*
|
||
* // Specify true for 2nd argument to make it include class name
|
||
* echo wireClassNamespace('Page', true); // outputs: \ProcessWire\Page
|
||
*
|
||
* // Specify true for 3rd argument to find all matching classes
|
||
* // and return array if more than 1 matches (or string if just 1):
|
||
* $val = wireClassNamespace('Foo', true, true);
|
||
* if(is_array($val)) {
|
||
* // 2+ classes found, so array value is returned
|
||
* // $val: [ '\Bar\Foo', '\Foo', '\Baz\Foo' ]
|
||
* } else {
|
||
* // string value is returned when only one class matches
|
||
* // $val: '\Bar\Foo'
|
||
* }
|
||
* ~~~~~
|
||
*
|
||
* #pw-group-class-helpers
|
||
*
|
||
* @param string|object $className
|
||
* @param bool $withClass Include class name in returned namespace? (default=false)
|
||
* @param bool $strict Return array of namespaces if multiple match? (default=false)
|
||
* @return string|array Returns one of the following:
|
||
* - String of `\Namespace\` (leading+trailing backslashes) if namespace found.
|
||
* - String of `\` if class in root namespace.
|
||
* - Blank string if unable to find namespace for class.
|
||
* - Array of namespaces only if $strict option is true AND multiple namespaces were found for class.
|
||
* - If the $withClass option is true, then return value(s) have class, i.e. `\Namespace\ClassName`.
|
||
* @since 3.0.150
|
||
*
|
||
*/
|
||
function wireClassNamespace($className, $withClass = false, $strict = false) {
|
||
|
||
$bs = "\\";
|
||
$ns = "";
|
||
|
||
if(is_object($className)) {
|
||
$className = get_class($className);
|
||
}
|
||
|
||
if(strpos($className, $bs) !== false) {
|
||
// namespace is already included in class name
|
||
$a = explode($bs, $className);
|
||
array_pop($a); // class
|
||
$ns = count($a) ? implode($bs, $a) : $bs;
|
||
if(empty($ns)) $ns = $bs;
|
||
$strict = false; // strict not necessary
|
||
|
||
} else if(class_exists(__NAMESPACE__ . "$bs$className")) {
|
||
// class in ProcessWire namespace
|
||
$ns = __NAMESPACE__;
|
||
|
||
} else if(class_exists("$bs$className")) {
|
||
// class in root namespace
|
||
$ns = $bs;
|
||
}
|
||
|
||
if(empty($ns) || $strict) {
|
||
// hunt down namespace from declared classes
|
||
$nsa = array();
|
||
$name = strtolower($className);
|
||
foreach(get_declared_classes() as $class) {
|
||
if(strpos($class, $bs) === false) {
|
||
// root namespace
|
||
if(!$strict) continue;
|
||
$class = "$bs$class";
|
||
}
|
||
if(stripos($class, "$bs$className") === false) continue;
|
||
if(strtolower(substr($class, -1 * strlen($className))) !== $name) continue;
|
||
$a = explode($bs, trim($class, $bs));
|
||
$cn = array_pop($a);
|
||
$ns = count($a) ? implode($bs, $a) : $bs;
|
||
if($ns && $ns !== $bs) $ns = $bs . trim($ns, $bs) . $bs;
|
||
if($withClass) $ns .= $cn;
|
||
$nsa[] = $ns;
|
||
if(!$strict) break;
|
||
}
|
||
$n = count($nsa);
|
||
// return array now for multi-match strict mode
|
||
if($strict && $n > 1) return $nsa;
|
||
$ns = $n ? reset($nsa) : '';
|
||
|
||
} else if($ns && $ns !== $bs) {
|
||
// format with leading/trailing backslashes, i.e. \Namespace\
|
||
$ns = $bs . trim($ns, $bs) . $bs;
|
||
if($withClass) $ns .= $className;
|
||
}
|
||
|
||
return $ns;
|
||
}
|
||
|
||
|
||
/**
|
||
* Does the given class name exist?
|
||
*
|
||
* ProcessWire namespace aware version of PHP’s class_exists() function
|
||
*
|
||
* If given a class name that does not include a namespace, the `\ProcessWire` namespace is assumed.
|
||
*
|
||
* #pw-group-class-helpers
|
||
*
|
||
* @param string $className
|
||
* @param bool $autoload
|
||
* @return bool
|
||
*
|
||
*/
|
||
function wireClassExists($className, $autoload = true) {
|
||
if(!is_object($className)) $className = wireClassName($className, true);
|
||
return class_exists($className, $autoload);
|
||
}
|
||
|
||
/**
|
||
* Does the given class have the given method?
|
||
*
|
||
* ProcessWire namespace aware version of PHP’s method_exists() function
|
||
*
|
||
* If given a class name that does not include a namespace, the `\ProcessWire` namespace is assumed.
|
||
*
|
||
* #pw-group-class-helpers
|
||
*
|
||
* @param string $className
|
||
* @param string $method
|
||
* @return bool
|
||
*
|
||
*/
|
||
function wireMethodExists($className, $method) {
|
||
if(!is_object($className)) $className = wireClassName($className, true);
|
||
return method_exists($className, $method);
|
||
}
|
||
|
||
/**
|
||
* Get an array of all the interfaces that the given class implements
|
||
*
|
||
* - ProcessWire namespace aware version of PHP’s class_implements() function.
|
||
* - Return value has array keys as class name with namespace and array values as class name without namespace.
|
||
*
|
||
* #pw-group-class-helpers
|
||
*
|
||
* @param string|object $className
|
||
* @param bool $autoload
|
||
* @return array
|
||
*
|
||
*/
|
||
function wireClassImplements($className, $autoload = true) {
|
||
if(is_object($className)) {
|
||
$implements = @class_implements($className, $autoload);
|
||
} else {
|
||
$className = wireClassName($className, true);
|
||
if(!class_exists($className, false)) {
|
||
$_className = wireClassName($className, false);
|
||
if(class_exists("\\$_className")) $className = $_className;
|
||
}
|
||
$implements = @class_implements(ltrim($className, "\\"), $autoload);
|
||
}
|
||
$a = array();
|
||
if(is_array($implements)) foreach($implements as $k => $v) {
|
||
$v = wireClassName($k, false);
|
||
$a[$k] = $v; // values have no namespace
|
||
}
|
||
return $a;
|
||
}
|
||
|
||
/**
|
||
* Return array of all parent classes for given class/object
|
||
*
|
||
* ProcessWire namespace aware version of PHP’s class_parents() function
|
||
*
|
||
* Returns associative array where array keys are full namespaced class name, and
|
||
* values are the non-namespaced classname.
|
||
*
|
||
* #pw-group-class-helpers
|
||
*
|
||
* @param string|object $className
|
||
* @param bool $autoload
|
||
* @return array
|
||
*
|
||
*/
|
||
function wireClassParents($className, $autoload = true) {
|
||
if(is_object($className)) {
|
||
$parents = class_parents($className, $autoload);
|
||
} else {
|
||
$className = wireClassName($className, true);
|
||
if(!class_exists($className, false)) {
|
||
$_className = wireClassName($className, false);
|
||
if(class_exists("\\$_className")) {
|
||
$className = $_className;
|
||
} else {
|
||
$ns = wireClassNamespace($_className);
|
||
if($ns) $className = $ns . $_className;
|
||
}
|
||
}
|
||
$parents = class_parents(ltrim($className, "\\"), $autoload);
|
||
}
|
||
$a = array();
|
||
if(is_array($parents)) foreach($parents as $k => $v) {
|
||
$v = wireClassName($k, false);
|
||
$a[$k] = $v; // values have no namespace
|
||
}
|
||
return $a;
|
||
}
|
||
|
||
/**
|
||
* Does given instance (or class) represent an instance of the given className (or class names)?
|
||
*
|
||
* Since version 3.0.108 the $className argument may also represent an interface,
|
||
* array of interfaces, or mixed array of interfaces and class names. Previous versions did
|
||
* not support interfaces unless the $instance argument was an object.
|
||
*
|
||
* #pw-group-class-helpers
|
||
*
|
||
* @param object|string $instance Object instance to test (or string of its class name).
|
||
* @param string|array $className Class/interface name or array of class/interface names to test against.
|
||
* @param bool $autoload Allow PHP to autoload the class? (default=true)
|
||
* @return bool|string Returns one of the following:
|
||
* - `false` (bool): if not an instance (whether $className argument is string or array).
|
||
* - `true` (bool): if given a single $className (string) and $instance is an instance of it.
|
||
* - `ClassName` (string): first matching class/interface name if $className was an array of classes to test.
|
||
*
|
||
*/
|
||
function wireInstanceOf($instance, $className, $autoload = true) {
|
||
|
||
if(is_array($className)) {
|
||
$returnClass = true;
|
||
$classNames = $className;
|
||
} else {
|
||
$returnClass = false;
|
||
$classNames = array($className);
|
||
}
|
||
|
||
$matchClass = null;
|
||
$instanceIsObject = is_object($instance);
|
||
$instanceParents = null;
|
||
$instanceInterfaces = null;
|
||
$instanceClass = null;
|
||
|
||
if($instanceIsObject) {
|
||
// instance is an object
|
||
} else if(is_string($instance)) {
|
||
// instance is a class name, make sure it has namespace
|
||
$instanceClass = wireClassName($instance, true);
|
||
if($instanceClass === null) $instanceClass = $instance; // if above failed
|
||
$instance = $instanceClass;
|
||
} else {
|
||
// unrecognized instance value
|
||
return false;
|
||
}
|
||
|
||
foreach($classNames as $className) {
|
||
$className = wireClassName($className, true); // with namespace
|
||
if($instanceIsObject && (class_exists($className, $autoload) || interface_exists($className, $autoload))) {
|
||
if($instance instanceof $className) {
|
||
$matchClass = $className;
|
||
}
|
||
} else {
|
||
if($instanceClass === null) {
|
||
$instanceClass = wireClassName($instance, true);
|
||
if($instanceClass === null) break;
|
||
}
|
||
if($instanceParents === null) {
|
||
$instanceParents = wireClassParents($instance, $autoload);
|
||
$instanceParents[$instanceClass] = 1;
|
||
}
|
||
if(isset($instanceParents[$className])) {
|
||
$matchClass = $className;
|
||
} else {
|
||
if($instanceInterfaces === null) {
|
||
$instanceInterfaces = wireClassImplements($instance, $autoload);
|
||
}
|
||
if(isset($instanceInterfaces[$className])) {
|
||
$matchClass = $className;
|
||
}
|
||
}
|
||
}
|
||
if($matchClass !== null) break;
|
||
}
|
||
|
||
return $returnClass ? $matchClass : ($matchClass !== null);
|
||
}
|
||
|
||
/**
|
||
* Is the given $var callable as a function?
|
||
*
|
||
* ProcessWire namespace aware version of PHP’s is_callable() function
|
||
*
|
||
* #pw-group-class-helpers
|
||
*
|
||
* @param string|callable $var
|
||
* @param bool $syntaxOnly
|
||
* @var string $callableName
|
||
* @return array
|
||
*
|
||
*/
|
||
function wireIsCallable($var, $syntaxOnly = false, &$callableName = '') {
|
||
if(is_string($var)) $var = wireClassName($var, true);
|
||
return is_callable($var, $syntaxOnly, $callableName);
|
||
}
|
||
|
||
/**
|
||
* Return the count of item(s) present in the given value
|
||
*
|
||
* Duplicates behavior of PHP count() function prior to PHP 7.2, which states:
|
||
*
|
||
* > Returns the number of elements in $value. When the parameter is neither an array nor an
|
||
* object with implemented Countable interface, 1 will be returned. There is one exception,
|
||
* if $value is NULL, 0 will be returned.
|
||
*
|
||
* #pw-group-common
|
||
*
|
||
* @param mixed $value
|
||
* @return int
|
||
*
|
||
*/
|
||
function wireCount($value) {
|
||
if($value === null) return 0;
|
||
if(is_array($value)) return count($value);
|
||
if(is_object($value) && $value instanceof \Countable) return count($value);
|
||
return 1;
|
||
}
|
||
|
||
/**
|
||
* Is the given value empty according to ProcessWire standards?
|
||
*
|
||
* This works the same as PHP’s empty() function except for the following:
|
||
*
|
||
* - It returns true for Countable objects that have 0 items.
|
||
* - It considers whitespace-only strings to be empty.
|
||
* - It considers WireNull objects (like NullPage or any others) to be empty (3.0.149+).
|
||
* - It uses the string value of objects that can be typecast strings (3.0.150+).
|
||
* - You cannot pass it an undefined variable without triggering a PHP warning.
|
||
*
|
||
* ~~~~~
|
||
* // behavior with Countable objects
|
||
* $a = new WireArray();
|
||
* empty($a); // PHP’s function returns false
|
||
* wireEmpty($a); // PW’s function returns true
|
||
* $a->add('item');
|
||
* wireEmpty($a); // returns false, since there is now an item
|
||
*
|
||
* // behavior with whitespace-only string
|
||
* $s = ' ';
|
||
* empty($s); // PHP’s function returns false
|
||
* wireEmpty($s); // PW’s function returns true
|
||
*
|
||
* // behavior with undefined variable $v
|
||
* isset($v); // returns false
|
||
* empty($v); // returns true
|
||
* wireEmpty($v); // returns true but with PHP’s warning triggered
|
||
* ~~~~~
|
||
*
|
||
* @param mixed $value Value to test if empty
|
||
* @return bool
|
||
* @since 3.0.143
|
||
*
|
||
*/
|
||
function wireEmpty($value) {
|
||
if(empty($value)) return true;
|
||
if(is_object($value)) {
|
||
if($value instanceof \Countable && !count($value)) return true;
|
||
if($value instanceof WireNull) return true; // 3.0.149+
|
||
if(method_exists($value, '__toString')) $value = (string) $value;
|
||
}
|
||
if(is_string($value)) {
|
||
$value = trim($value);
|
||
if(empty($value)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Get or set an output region (primarily for front-end output usage)
|
||
*
|
||
* This function is an convenience for storing markup that ultimately gets output in a _main.php file
|
||
* (or whatever file `$config->appendTemplateFile` is set to). It is an alternative to passing variables
|
||
* between included files and provides an interface for setting, appending, prepending and ultimately
|
||
* getting markup (or other strings) for output. It’s designed for use the the “Delayed Output” strategy,
|
||
* though does not necessarily require it.
|
||
*
|
||
* ~~~~~
|
||
* // define a region
|
||
* region('content', '<p>this is some content</p>');
|
||
*
|
||
* // prepend some text to region
|
||
* region('+content', '<h2>Good morning</h2>');
|
||
*
|
||
* // append some text to region
|
||
* region('content+', '<p><small>Good night</small></p>');
|
||
*
|
||
* // output a region
|
||
* echo region('content');
|
||
*
|
||
* // get all regions in an array
|
||
* $regions = region('*');
|
||
*
|
||
* // clear the 'content' region
|
||
* region('content', '');
|
||
*
|
||
* // clear all regions
|
||
* region('*', '');
|
||
* ~~~~~
|
||
*
|
||
* #pw-internal
|
||
*
|
||
* @param string $key Name of region to get or set.
|
||
* - Specify "*" to retrieve all defined regions in an array.
|
||
* - Prepend a "+" to the region name to have it prepend your given value to any existing value.
|
||
* - Append a "+" to the region name to have it append your given value to any existing value.
|
||
* - Prepend a "++" to region name to make future calls without "+" automatically prepend.
|
||
* - Append a "++" to region name to make future calls without "+" to automatically append.
|
||
* @param null|string $value If setting a region, the text that you want to set.
|
||
* @return string|null|bool|array Returns string of text when getting a region, NULL if region not set, or TRUE if setting region.
|
||
*
|
||
*/
|
||
function wireRegion($key, $value = null) {
|
||
|
||
static $regions = array();
|
||
static $locked = array();
|
||
|
||
if(empty($key) || $key === '*') {
|
||
// all regions
|
||
if($value === '') $regions = array(); // clear
|
||
return $regions;
|
||
}
|
||
|
||
if(is_null($value)) {
|
||
// get region
|
||
$result = isset($regions[$key]) ? $regions[$key] : null;
|
||
|
||
} else {
|
||
// set region
|
||
$pos = strpos($key, '+');
|
||
if($pos !== false) {
|
||
$lock = strpos($key, '++') !== false;
|
||
$key = trim($key, '+');
|
||
if($lock !== false && !isset($locked[$key])) {
|
||
$locked[$key] = $lock === 0 ? '^' : '$'; // prepend : append
|
||
}
|
||
}
|
||
$lock = isset($locked[$key]) ? $locked[$key] : '';
|
||
if(!isset($regions[$key])) $regions[$key] = '';
|
||
if($pos === 0 || ($pos === false && $lock == '^')) {
|
||
// prepend
|
||
$regions[$key] = $value . $regions[$key];
|
||
} else if($pos || ($pos === false && $lock == '$')) {
|
||
// append
|
||
$regions[$key] .= $value;
|
||
} else if($value === '') {
|
||
// clear region
|
||
if(!$lock) unset($regions[$key]);
|
||
} else {
|
||
// insert/replace
|
||
$regions[$key] = $value;
|
||
}
|
||
$result = true;
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* Stop execution with a 404 unless redirect URL available (for front-end use)
|
||
*
|
||
* This is an alternative to using a manual `throw new Wire404Exception()` and is recognized by
|
||
* PW as a front-end 404 where PagePathHistory (or potentially other modules) are still allowed
|
||
* to change the behavior of the request from a 404 to something else (like a 301 redirect).
|
||
*
|
||
* #pw-group-common
|
||
*
|
||
* @param string $message Optional message to send to Exception message argument (not used in output by default)
|
||
* @throws Wire404Exception
|
||
* @since 3.0.146
|
||
*
|
||
*/
|
||
function wire404($message = '') {
|
||
throw new Wire404Exception($message, Wire404Exception::codeFunction);
|
||
}
|
||
|
||
/**
|
||
* Create new WireArray, add given $items to it, and return it
|
||
*
|
||
* This is the same as creating a `new WireArray()` and then adding items to it with separate `add()` calls,
|
||
* except that this function enables you to do it all in one shot.
|
||
*
|
||
* ~~~~~~
|
||
* $a = WireArray(); // create empty WireArray
|
||
* $a = WireArray('foo'); // create WireArray with one "foo" string
|
||
* $a = WireArray(['foo', 'bar', 'baz']); // create WireArray with 3 strings
|
||
* ~~~~~~
|
||
*
|
||
* #pw-group-arrays
|
||
*
|
||
* @param array|WireArray|mixed $items
|
||
* @return WireArray
|
||
* @since 3.0.123
|
||
*
|
||
*/
|
||
function WireArray($items = array()) {
|
||
return WireArray::newInstance($items);
|
||
}
|
||
|
||
/**
|
||
* Create a new WireData instance and optionally add given associative array of data to it
|
||
*
|
||
* ~~~~~
|
||
* $data = WireData([ 'hello' => 'world', 'foo' => 'bar' ]);
|
||
* ~~~~~
|
||
*
|
||
* #pw-group-arrays
|
||
*
|
||
* @param array|\Traversable $data Can be an associative array or Traversable object of data to set, or omit if not needed
|
||
* @return WireData
|
||
* @since 3.0.126
|
||
*
|
||
*/
|
||
function WireData($data = array()) {
|
||
$wireData = new WireData();
|
||
if(is_array($data)) {
|
||
if(!empty($data)) $wireData->setArray($data);
|
||
} else if($data instanceof \Traversable) {
|
||
foreach($data as $k => $v) $wireData->set($k, $v);
|
||
}
|
||
$wireData->resetTrackChanges(true);
|
||
return $wireData;
|
||
}
|
||
|
||
/**
|
||
* Create new PageArray, add given $items (pages) to it, and return it
|
||
*
|
||
* This is the same as creating a `new PageArray()` and then adding items to it with separate `add()` calls,
|
||
* except that this function enables you to do it all in one shot.
|
||
*
|
||
* ~~~~~
|
||
* $a = PageArray(); // create empty PageArray
|
||
* $a = PageArray($page); // create PageArray with one page
|
||
* $a = PageArray([ $page1, $page2, $page3 ]); // create PageArray with multiple items
|
||
* ~~~~~
|
||
*
|
||
* #pw-group-arrays
|
||
*
|
||
* @param array|PageArray $items
|
||
* @return PageArray
|
||
* @since 3.0.123
|
||
*
|
||
*/
|
||
function PageArray($items = array()) {
|
||
/** @var PageArray $pa */
|
||
$pa = PageArray::newInstance($items);
|
||
return $pa;
|
||
}
|
||
|
||
|