PK ^GM.<
license.mdnu W+A Licenses
========
Good news! You may use Nette Framework under the terms of either
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Nette Framework in commercial projects as long as the copyright header
remains intact.
Please be advised that the name "Nette Framework" is a protected trademark and its
usage has some limitations. So please do not use word "Nette" in the name of your
project or top-level domain, and choose a name that stands on its own merits.
If your stuff is good, it will not take long to establish a reputation for yourselves.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of "Nette Framework" nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. In no event shall the copyright owner or contributors be liable for
any direct, indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on
any theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
PK ^GMhrD D contributing.mdnu W+A How to contribute & use the issue tracker
=========================================
Nette welcomes your contributions. There are several ways to help out:
* Create an issue on GitHub, if you have found a bug
* Write test cases for open bug issues
* Write fixes for open bug/feature issues, preferably with test cases included
* Contribute to the [documentation](https://nette.org/en/writing)
Issues
------
Please **do not use the issue tracker to ask questions**. We will be happy to help you
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report.
**Feature requests** are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature.
Contributing
------------
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
Please do not fix whitespace, format code, or make a purely cosmetic patch.
Thanks! :heart:
PK ^GMr src/Utils/IHtmlString.phpnu W+A = 70000) {
for ($i = 0; $i < $length; $i++) {
$res .= $charlist[random_int(0, $chLen - 1)];
}
return $res;
}
$bytes = '';
if (function_exists('openssl_random_pseudo_bytes')) {
$bytes = (string) openssl_random_pseudo_bytes($length, $secure);
if (!$secure) {
$bytes = '';
}
}
if (strlen($bytes) < $length && function_exists('mcrypt_create_iv')) {
$bytes = (string) mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
}
if (strlen($bytes) < $length && !defined('PHP_WINDOWS_VERSION_BUILD') && is_readable('/dev/urandom')) {
$bytes = (string) file_get_contents('/dev/urandom', FALSE, NULL, -1, $length);
}
if (strlen($bytes) < $length) {
$rand3 = md5(serialize($_SERVER), TRUE);
$charlist = str_shuffle($charlist);
for ($i = 0; $i < $length; $i++) {
if ($i % 5 === 0) {
list($rand1, $rand2) = explode(' ', microtime());
$rand1 += lcg_value();
}
$rand1 *= $chLen;
$res .= $charlist[($rand1 + $rand2 + ord($rand3[$i % strlen($rand3)])) % $chLen];
$rand1 -= (int) $rand1;
}
return $res;
}
for ($i = 0; $i < $length; $i++) {
$res .= $charlist[($i + ord($bytes[$i])) % $chLen];
}
return $res;
}
}
PK ^GM+Z src/Utils/Paginator.phpnu W+A page = (int) $page;
return $this;
}
/**
* Returns current page number.
* @return int
*/
public function getPage()
{
return $this->base + $this->getPageIndex();
}
/**
* Returns first page number.
* @return int
*/
public function getFirstPage()
{
return $this->base;
}
/**
* Returns last page number.
* @return int|NULL
*/
public function getLastPage()
{
return $this->itemCount === NULL ? NULL : $this->base + max(0, $this->getPageCount() - 1);
}
/**
* Sets first page (base) number.
* @param int
* @return static
*/
public function setBase($base)
{
$this->base = (int) $base;
return $this;
}
/**
* Returns first page (base) number.
* @return int
*/
public function getBase()
{
return $this->base;
}
/**
* Returns zero-based page number.
* @return int
*/
protected function getPageIndex()
{
$index = max(0, $this->page - $this->base);
return $this->itemCount === NULL ? $index : min($index, max(0, $this->getPageCount() - 1));
}
/**
* Is the current page the first one?
* @return bool
*/
public function isFirst()
{
return $this->getPageIndex() === 0;
}
/**
* Is the current page the last one?
* @return bool
*/
public function isLast()
{
return $this->itemCount === NULL ? FALSE : $this->getPageIndex() >= $this->getPageCount() - 1;
}
/**
* Returns the total number of pages.
* @return int|NULL
*/
public function getPageCount()
{
return $this->itemCount === NULL ? NULL : (int) ceil($this->itemCount / $this->itemsPerPage);
}
/**
* Sets the number of items to display on a single page.
* @param int
* @return static
*/
public function setItemsPerPage($itemsPerPage)
{
$this->itemsPerPage = max(1, (int) $itemsPerPage);
return $this;
}
/**
* Returns the number of items to display on a single page.
* @return int
*/
public function getItemsPerPage()
{
return $this->itemsPerPage;
}
/**
* Sets the total number of items.
* @param int (or NULL as infinity)
* @return static
*/
public function setItemCount($itemCount)
{
$this->itemCount = ($itemCount === FALSE || $itemCount === NULL) ? NULL : max(0, (int) $itemCount);
return $this;
}
/**
* Returns the total number of items.
* @return int|NULL
*/
public function getItemCount()
{
return $this->itemCount;
}
/**
* Returns the absolute index of the first item on current page.
* @return int
*/
public function getOffset()
{
return $this->getPageIndex() * $this->itemsPerPage;
}
/**
* Returns the absolute index of the first item on current page in countdown paging.
* @return int|NULL
*/
public function getCountdownOffset()
{
return $this->itemCount === NULL
? NULL
: max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage);
}
/**
* Returns the number of items on current page.
* @return int|NULL
*/
public function getLength()
{
return $this->itemCount === NULL
? $this->itemsPerPage
: min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage);
}
}
PK ^GM= = src/Utils/Strings.phpnu W+A = 0xD800 && $code <= 0xDFFF) || $code > 0x10FFFF) {
throw new Nette\InvalidArgumentException('Code point must be in range 0x0 to 0xD7FF or 0xE000 to 0x10FFFF.');
}
return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code));
}
/**
* Starts the $haystack string with the prefix $needle?
* @param string
* @param string
* @return bool
*/
public static function startsWith($haystack, $needle)
{
return strncmp($haystack, $needle, strlen($needle)) === 0;
}
/**
* Ends the $haystack string with the suffix $needle?
* @param string
* @param string
* @return bool
*/
public static function endsWith($haystack, $needle)
{
return strlen($needle) === 0 || substr($haystack, -strlen($needle)) === $needle;
}
/**
* Does $haystack contain $needle?
* @param string
* @param string
* @return bool
*/
public static function contains($haystack, $needle)
{
return strpos($haystack, $needle) !== FALSE;
}
/**
* Returns a part of UTF-8 string.
* @param string
* @param int in characters (code points)
* @param int in characters (code points)
* @return string
*/
public static function substring($s, $start, $length = NULL)
{
if (function_exists('mb_substr')) {
return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster
} elseif ($length === NULL) {
$length = self::length($s);
} elseif ($start < 0 && $length < 0) {
$start += self::length($s); // unifies iconv_substr behavior with mb_substr
}
return iconv_substr($s, $start, $length, 'UTF-8');
}
/**
* Removes special controls characters and normalizes line endings and spaces.
* @param string UTF-8 encoding
* @return string
*/
public static function normalize($s)
{
$s = self::normalizeNewLines($s);
// remove control characters; leave \t + \n
$s = preg_replace('#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s);
// right trim
$s = preg_replace('#[\t ]+$#m', '', $s);
// leading and trailing blank lines
$s = trim($s, "\n");
return $s;
}
/**
* Standardize line endings to unix-like.
* @param string UTF-8 encoding or 8-bit
* @return string
*/
public static function normalizeNewLines($s)
{
return str_replace(["\r\n", "\r"], "\n", $s);
}
/**
* Converts to ASCII.
* @param string UTF-8 encoding
* @return string ASCII
*/
public static function toAscii($s)
{
static $transliterator = NULL;
if ($transliterator === NULL && class_exists('Transliterator', FALSE)) {
$transliterator = \Transliterator::create('Any-Latin; Latin-ASCII');
}
$s = preg_replace('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s);
$s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06");
$s = str_replace(
["\xE2\x80\x9E", "\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2\x80\x9A", "\xE2\x80\x98", "\xE2\x80\x99", "\xC2\xB0"],
["\x03", "\x03", "\x03", "\x02", "\x02", "\x02", "\x04"], $s
);
if ($transliterator !== NULL) {
$s = $transliterator->transliterate($s);
}
if (ICONV_IMPL === 'glibc') {
$s = str_replace(
["\xC2\xBB", "\xC2\xAB", "\xE2\x80\xA6", "\xE2\x84\xA2", "\xC2\xA9", "\xC2\xAE"],
['>>', '<<', '...', 'TM', '(c)', '(R)'], $s
);
$s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s);
$s = strtr($s, "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e"
. "\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3"
. "\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8"
. "\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe"
. "\x96\xa0\x8b\x97\x9b\xa6\xad\xb7",
'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.');
$s = preg_replace('#[^\x00-\x7F]++#', '', $s);
} else {
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
}
$s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s);
return strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?');
}
/**
* Converts to web safe characters [a-z0-9-] text.
* @param string UTF-8 encoding
* @param string allowed characters
* @param bool
* @return string
*/
public static function webalize($s, $charlist = NULL, $lower = TRUE)
{
$s = self::toAscii($s);
if ($lower) {
$s = strtolower($s);
}
$s = preg_replace('#[^a-z0-9' . ($charlist !== NULL ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s);
$s = trim($s, '-');
return $s;
}
/**
* Truncates string to maximal length.
* @param string UTF-8 encoding
* @param int
* @param string UTF-8 encoding
* @return string
*/
public static function truncate($s, $maxLen, $append = "\xE2\x80\xA6")
{
if (self::length($s) > $maxLen) {
$maxLen = $maxLen - self::length($append);
if ($maxLen < 1) {
return $append;
} elseif ($matches = self::match($s, '#^.{1,'.$maxLen.'}(?=[\s\x00-/:-@\[-`{-~])#us')) {
return $matches[0] . $append;
} else {
return self::substring($s, 0, $maxLen) . $append;
}
}
return $s;
}
/**
* Indents the content from the left.
* @param string UTF-8 encoding or 8-bit
* @param int
* @param string
* @return string
*/
public static function indent($s, $level = 1, $chars = "\t")
{
if ($level > 0) {
$s = self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level));
}
return $s;
}
/**
* Convert to lower case.
* @param string UTF-8 encoding
* @return string
*/
public static function lower($s)
{
return mb_strtolower($s, 'UTF-8');
}
/**
* Convert first character to lower case.
* @param string UTF-8 encoding
* @return string
*/
public static function firstLower($s)
{
return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1);
}
/**
* Convert to upper case.
* @param string UTF-8 encoding
* @return string
*/
public static function upper($s)
{
return mb_strtoupper($s, 'UTF-8');
}
/**
* Convert first character to upper case.
* @param string UTF-8 encoding
* @return string
*/
public static function firstUpper($s)
{
return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1);
}
/**
* Capitalize string.
* @param string UTF-8 encoding
* @return string
*/
public static function capitalize($s)
{
return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8');
}
/**
* Case-insensitive compares UTF-8 strings.
* @param string
* @param string
* @param int
* @return bool
*/
public static function compare($left, $right, $len = NULL)
{
if ($len < 0) {
$left = self::substring($left, $len, -$len);
$right = self::substring($right, $len, -$len);
} elseif ($len !== NULL) {
$left = self::substring($left, 0, $len);
$right = self::substring($right, 0, $len);
}
return self::lower($left) === self::lower($right);
}
/**
* Finds the length of common prefix of strings.
* @param string|array
* @return string
*/
public static function findPrefix(...$strings)
{
if (is_array($strings[0])) {
$strings = $strings[0];
}
$first = array_shift($strings);
for ($i = 0; $i < strlen($first); $i++) {
foreach ($strings as $s) {
if (!isset($s[$i]) || $first[$i] !== $s[$i]) {
while ($i && $first[$i - 1] >= "\x80" && $first[$i] >= "\x80" && $first[$i] < "\xC0") {
$i--;
}
return substr($first, 0, $i);
}
}
}
return $first;
}
/**
* Returns number of characters (not bytes) in UTF-8 string.
* That is the number of Unicode code points which may differ from the number of graphemes.
* @param string
* @return int
*/
public static function length($s)
{
return function_exists('mb_strlen') ? mb_strlen($s, 'UTF-8') : strlen(utf8_decode($s));
}
/**
* Strips whitespace.
* @param string UTF-8 encoding
* @param string
* @return string
*/
public static function trim($s, $charlist = self::TRIM_CHARACTERS)
{
$charlist = preg_quote($charlist, '#');
return self::replace($s, '#^['.$charlist.']+|['.$charlist.']+\z#u', '');
}
/**
* Pad a string to a certain length with another string.
* @param string UTF-8 encoding
* @param int
* @param string
* @return string
*/
public static function padLeft($s, $length, $pad = ' ')
{
$length = max(0, $length - self::length($s));
$padLen = self::length($pad);
return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s;
}
/**
* Pad a string to a certain length with another string.
* @param string UTF-8 encoding
* @param int
* @param string
* @return string
*/
public static function padRight($s, $length, $pad = ' ')
{
$length = max(0, $length - self::length($s));
$padLen = self::length($pad);
return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen);
}
/**
* Reverse string.
* @param string UTF-8 encoding
* @return string
*/
public static function reverse($s)
{
return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s)));
}
/**
* Use Nette\Utils\Random::generate
* @deprecated
*/
public static function random($length = 10, $charlist = '0-9a-z')
{
trigger_error(__METHOD__ . '() is deprecated, use Nette\Utils\Random::generate()', E_USER_DEPRECATED);
return Random::generate($length, $charlist);
}
/**
* Returns part of $haystack before $nth occurence of $needle.
* @param string
* @param string
* @param int negative value means searching from the end
* @return string|FALSE returns FALSE if the needle was not found
*/
public static function before($haystack, $needle, $nth = 1)
{
$pos = self::pos($haystack, $needle, $nth);
return $pos === FALSE
? FALSE
: substr($haystack, 0, $pos);
}
/**
* Returns part of $haystack after $nth occurence of $needle.
* @param string
* @param string
* @param int negative value means searching from the end
* @return string|FALSE returns FALSE if the needle was not found
*/
public static function after($haystack, $needle, $nth = 1)
{
$pos = self::pos($haystack, $needle, $nth);
return $pos === FALSE
? FALSE
: (string) substr($haystack, $pos + strlen($needle));
}
/**
* Returns position of $nth occurence of $needle in $haystack.
* @param string
* @param string
* @param int negative value means searching from the end
* @return int|FALSE offset in characters or FALSE if the needle was not found
*/
public static function indexOf($haystack, $needle, $nth = 1)
{
$pos = self::pos($haystack, $needle, $nth);
return $pos === FALSE
? FALSE
: self::length(substr($haystack, 0, $pos));
}
/**
* Returns position of $nth occurence of $needle in $haystack.
* @return int|FALSE offset in bytes or FALSE if the needle was not found
*/
private static function pos($haystack, $needle, $nth = 1)
{
if (!$nth) {
return FALSE;
} elseif ($nth > 0) {
if (strlen($needle) === 0) {
return 0;
}
$pos = 0;
while (FALSE !== ($pos = strpos($haystack, $needle, $pos)) && --$nth) {
$pos++;
}
} else {
$len = strlen($haystack);
if (strlen($needle) === 0) {
return $len;
}
$pos = $len - 1;
while (FALSE !== ($pos = strrpos($haystack, $needle, $pos - $len)) && ++$nth) {
$pos--;
}
}
return $pos;
}
/**
* Splits string by a regular expression.
* @param string
* @param string
* @param int
* @return array
*/
public static function split($subject, $pattern, $flags = 0)
{
return self::pcre('preg_split', [$pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE]);
}
/**
* Performs a regular expression match.
* @param string
* @param string
* @param int can be PREG_OFFSET_CAPTURE (returned in bytes)
* @param int offset in bytes
* @return mixed
*/
public static function match($subject, $pattern, $flags = 0, $offset = 0)
{
if ($offset > strlen($subject)) {
return NULL;
}
return self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])
? $m
: NULL;
}
/**
* Performs a global regular expression match.
* @param string
* @param string
* @param int can be PREG_OFFSET_CAPTURE (returned in bytes); PREG_SET_ORDER is default
* @param int offset in bytes
* @return array
*/
public static function matchAll($subject, $pattern, $flags = 0, $offset = 0)
{
if ($offset > strlen($subject)) {
return [];
}
self::pcre('preg_match_all', [
$pattern, $subject, &$m,
($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER),
$offset,
]);
return $m;
}
/**
* Perform a regular expression search and replace.
* @param string
* @param string|array
* @param string|callable
* @param int
* @return string
*/
public static function replace($subject, $pattern, $replacement = NULL, $limit = -1)
{
if (is_object($replacement) || is_array($replacement)) {
if ($replacement instanceof Nette\Callback) {
trigger_error('Nette\Callback is deprecated, use PHP callback.', E_USER_DEPRECATED);
$replacement = $replacement->getNative();
}
if (!is_callable($replacement, FALSE, $textual)) {
throw new Nette\InvalidStateException("Callback '$textual' is not callable.");
}
return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit]);
} elseif ($replacement === NULL && is_array($pattern)) {
$replacement = array_values($pattern);
$pattern = array_keys($pattern);
}
return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]);
}
/** @internal */
public static function pcre($func, $args)
{
static $messages = [
PREG_INTERNAL_ERROR => 'Internal error',
PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted',
PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted',
PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data',
PREG_BAD_UTF8_OFFSET_ERROR => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point',
6 => 'Failed due to limited JIT stack space', // PREG_JIT_STACKLIMIT_ERROR
];
$res = Callback::invokeSafe($func, $args, function ($message) use ($args) {
// compile-time error, not detectable by preg_last_error
throw new RegexpException($message . ' in pattern: ' . implode(' or ', (array) $args[0]));
});
if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars
&& ($res === NULL || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace']))
) {
throw new RegexpException((isset($messages[$code]) ? $messages[$code] : 'Unknown error')
. ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code);
}
return $res;
}
}
PK ^GMս src/Utils/Arrays.phpnu W+A $v) {
if (is_array($v) && is_array($arr2[$k])) {
$res[$k] = self::mergeTree($v, $arr2[$k]);
}
}
return $res;
}
/**
* Searches the array for a given key and returns the offset if successful.
* @return int|FALSE offset if it is found, FALSE otherwise
*/
public static function searchKey(array $arr, $key)
{
$foo = [$key => NULL];
return array_search(key($foo), array_keys($arr), TRUE);
}
/**
* Inserts new array before item specified by key.
* @return void
*/
public static function insertBefore(array &$arr, $key, array $inserted)
{
$offset = (int) self::searchKey($arr, $key);
$arr = array_slice($arr, 0, $offset, TRUE) + $inserted + array_slice($arr, $offset, count($arr), TRUE);
}
/**
* Inserts new array after item specified by key.
* @return void
*/
public static function insertAfter(array &$arr, $key, array $inserted)
{
$offset = self::searchKey($arr, $key);
$offset = $offset === FALSE ? count($arr) : $offset + 1;
$arr = array_slice($arr, 0, $offset, TRUE) + $inserted + array_slice($arr, $offset, count($arr), TRUE);
}
/**
* Renames key in array.
* @return void
*/
public static function renameKey(array &$arr, $oldKey, $newKey)
{
$offset = self::searchKey($arr, $oldKey);
if ($offset !== FALSE) {
$keys = array_keys($arr);
$keys[$offset] = $newKey;
$arr = array_combine($keys, $arr);
}
}
/**
* Returns array entries that match the pattern.
* @return array
*/
public static function grep(array $arr, $pattern, $flags = 0)
{
return Strings::pcre('preg_grep', [$pattern, $arr, $flags]);
}
/**
* Returns flattened array.
* @return array
*/
public static function flatten(array $arr, $preserveKeys = FALSE)
{
$res = [];
$cb = $preserveKeys
? function ($v, $k) use (&$res) { $res[$k] = $v; }
: function ($v) use (&$res) { $res[] = $v; };
array_walk_recursive($arr, $cb);
return $res;
}
/**
* Finds whether a variable is a zero-based integer indexed array.
* @return bool
*/
public static function isList($value)
{
return is_array($value) && (!$value || array_keys($value) === range(0, count($value) - 1));
}
/**
* Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
* @return array|\stdClass
*/
public static function associate(array $arr, $path)
{
$parts = is_array($path)
? $path
: preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
if (!$parts || $parts[0] === '=' || $parts[0] === '|' || $parts === ['->']) {
throw new Nette\InvalidArgumentException("Invalid path '$path'.");
}
$res = $parts[0] === '->' ? new \stdClass : [];
foreach ($arr as $rowOrig) {
$row = (array) $rowOrig;
$x = &$res;
for ($i = 0; $i < count($parts); $i++) {
$part = $parts[$i];
if ($part === '[]') {
$x = &$x[];
} elseif ($part === '=') {
if (isset($parts[++$i])) {
$x = $row[$parts[$i]];
$row = NULL;
}
} elseif ($part === '->') {
if (isset($parts[++$i])) {
$x = &$x->{$row[$parts[$i]]};
} else {
$row = is_object($rowOrig) ? $rowOrig : (object) $row;
}
} elseif ($part !== '|') {
$x = &$x[(string) $row[$part]];
}
}
if ($x === NULL) {
$x = $row;
}
}
return $res;
}
/**
* Normalizes to associative array.
* @return array
*/
public static function normalize(array $arr, $filling = NULL)
{
$res = [];
foreach ($arr as $k => $v) {
$res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v;
}
return $res;
}
/**
* Picks element from the array by key and return its value.
* @param array
* @param string|int array key
* @param mixed
* @return mixed
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
*/
public static function pick(array &$arr, $key, $default = NULL)
{
if (array_key_exists($key, $arr)) {
$value = $arr[$key];
unset($arr[$key]);
return $value;
} elseif (func_num_args() < 3) {
throw new Nette\InvalidArgumentException("Missing item '$key'.");
} else {
return $default;
}
}
/**
* Tests whether some element in the array passes the callback test.
* @return bool
*/
public static function some(array $arr, callable $callback)
{
foreach ($arr as $k => $v) {
if ($callback($v, $k, $arr)) {
return TRUE;
}
}
return FALSE;
}
/**
* Tests whether all elements in the array pass the callback test.
* @return bool
*/
public static function every(array $arr, callable $callback)
{
foreach ($arr as $k => $v) {
if (!$callback($v, $k, $arr)) {
return FALSE;
}
}
return TRUE;
}
/**
* Applies the callback to the elements of the array.
* @return array
*/
public static function map(array $arr, callable $callback)
{
$res = [];
foreach ($arr as $k => $v) {
$res[$k] = $callback($v, $k, $arr);
}
return $res;
}
}
PK ^GM[R= = src/Utils/ObjectMixin.phpnu W+A [type => callback]] used by extension methods */
private static $extMethods = [];
/********************* strictness ****************d*g**/
/**
* @throws MemberAccessException
*/
public static function strictGet($class, $name)
{
$rc = new \ReflectionClass($class);
$hint = self::getSuggestion(array_merge(
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
), $name);
throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
}
/**
* @throws MemberAccessException
*/
public static function strictSet($class, $name)
{
$rc = new \ReflectionClass($class);
$hint = self::getSuggestion(array_merge(
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
), $name);
throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
}
/**
* @throws MemberAccessException
*/
public static function strictCall($class, $method, $additionalMethods = [])
{
$hint = self::getSuggestion(array_merge(
get_class_methods($class),
self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
$additionalMethods
), $method);
if (method_exists($class, $method)) { // called parent::$method()
$class = 'parent';
}
throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
}
/**
* @throws MemberAccessException
*/
public static function strictStaticCall($class, $method)
{
$hint = self::getSuggestion(
array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
$method
);
throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
}
/********************* Nette\Object ****************d*g**/
/**
* __call() implementation.
* @param object
* @param string
* @param array
* @return mixed
* @throws MemberAccessException
*/
public static function call($_this, $name, $args)
{
$class = get_class($_this);
$isProp = self::hasProperty($class, $name);
if ($name === '') {
throw new MemberAccessException("Call to class '$class' method without name.");
} elseif ($isProp === 'event') { // calling event handlers
if (is_array($_this->$name) || $_this->$name instanceof \Traversable) {
foreach ($_this->$name as $handler) {
Callback::invokeArgs($handler, $args);
}
} elseif ($_this->$name !== NULL) {
throw new Nette\UnexpectedValueException("Property $class::$$name must be array or NULL, " . gettype($_this->$name) . ' given.');
}
} elseif ($isProp && $_this->$name instanceof \Closure) { // closure in property
return call_user_func_array($_this->$name, $args);
} elseif (($methods = &self::getMethods($class)) && isset($methods[$name]) && is_array($methods[$name])) { // magic @methods
list($op, $rp, $type) = $methods[$name];
if (count($args) !== ($op === 'get' ? 0 : 1)) {
throw new Nette\InvalidArgumentException("$class::$name() expects " . ($op === 'get' ? 'no' : '1') . ' argument, ' . count($args) . ' given.');
} elseif ($type && $args && !self::checkType($args[0], $type)) {
throw new Nette\InvalidArgumentException("Argument passed to $class::$name() must be $type, " . gettype($args[0]) . ' given.');
}
if ($op === 'get') {
return $rp->getValue($_this);
} elseif ($op === 'set') {
$rp->setValue($_this, $args[0]);
} elseif ($op === 'add') {
$val = $rp->getValue($_this);
$val[] = $args[0];
$rp->setValue($_this, $val);
}
return $_this;
} elseif ($cb = self::getExtensionMethod($class, $name)) { // extension methods
return Callback::invoke($cb, $_this, ...$args);
} else {
self::strictCall($class, $name, array_keys(self::getExtensionMethods($class)));
}
}
/**
* __callStatic() implementation.
* @param string
* @param string
* @param array
* @return void
* @throws MemberAccessException
*/
public static function callStatic($class, $method, $args)
{
self::strictStaticCall($class, $method);
}
/**
* __get() implementation.
* @param object
* @param string property name
* @return mixed property value
* @throws MemberAccessException if the property is not defined.
*/
public static function &get($_this, $name)
{
$class = get_class($_this);
$uname = ucfirst($name);
$methods = &self::getMethods($class);
if ($name === '') {
throw new MemberAccessException("Cannot read a class '$class' property without name.");
} elseif (isset($methods[$m = 'get' . $uname]) || isset($methods[$m = 'is' . $uname])) { // property getter
if ($methods[$m] === 0) {
$methods[$m] = (new \ReflectionMethod($class, $m))->returnsReference();
}
if ($methods[$m] === TRUE) {
return $_this->$m();
} else {
$val = $_this->$m();
return $val;
}
} elseif (isset($methods[$name])) { // public method as closure getter
if (preg_match('#^(is|get|has)([A-Z]|$)#', $name) && !(new \ReflectionMethod($class, $name))->getNumberOfRequiredParameters()) {
trigger_error("Did you forget parentheses after $name" . self::getSource() . '?', E_USER_WARNING);
}
$val = Callback::closure($_this, $name);
return $val;
} elseif (isset($methods['set' . $uname])) { // property getter
throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
} else {
self::strictGet($class, $name);
}
}
/**
* __set() implementation.
* @param object
* @param string property name
* @param mixed property value
* @return void
* @throws MemberAccessException if the property is not defined or is read-only
*/
public static function set($_this, $name, $value)
{
$class = get_class($_this);
$uname = ucfirst($name);
$methods = &self::getMethods($class);
if ($name === '') {
throw new MemberAccessException("Cannot write to a class '$class' property without name.");
} elseif (self::hasProperty($class, $name)) { // unsetted property
$_this->$name = $value;
} elseif (isset($methods[$m = 'set' . $uname])) { // property setter
$_this->$m($value);
} elseif (isset($methods['get' . $uname]) || isset($methods['is' . $uname])) { // property setter
throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
} else {
self::strictSet($class, $name);
}
}
/**
* __unset() implementation.
* @param object
* @param string property name
* @return void
* @throws MemberAccessException
*/
public static function remove($_this, $name)
{
$class = get_class($_this);
if (!self::hasProperty($class, $name)) {
throw new MemberAccessException("Cannot unset the property $class::\$$name.");
}
}
/**
* __isset() implementation.
* @param object
* @param string property name
* @return bool
*/
public static function has($_this, $name)
{
$name = ucfirst($name);
$methods = &self::getMethods(get_class($_this));
return $name !== '' && (isset($methods['get' . $name]) || isset($methods['is' . $name]));
}
/********************* magic @properties ****************d*g**/
/**
* Returns array of magic properties defined by annotation @property.
* @return array of [name => bit mask]
*/
public static function getMagicProperties($class)
{
static $cache;
$props = &$cache[$class];
if ($props !== NULL) {
return $props;
}
$rc = new \ReflectionClass($class);
preg_match_all(
'~^ [ \t*]* @property(|-read|-write) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx',
(string) $rc->getDocComment(), $matches, PREG_SET_ORDER
);
$props = [];
foreach ($matches as list(, $type, $name)) {
$uname = ucfirst($name);
$write = $type !== '-read'
&& $rc->hasMethod($nm = 'set' . $uname)
&& ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic();
$read = $type !== '-write'
&& ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname))
&& ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic();
if ($read || $write) {
$props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3;
}
}
foreach ($rc->getTraits() as $trait) {
$props += self::getMagicProperties($trait->getName());
}
if ($parent = get_parent_class($class)) {
$props += self::getMagicProperties($parent);
}
return $props;
}
/** @internal */
public static function getMagicProperty($class, $name)
{
$props = self::getMagicProperties($class);
return isset($props[$name]) ? $props[$name] : NULL;
}
/********************* magic @methods ****************d*g**/
/**
* Returns array of magic methods defined by annotation @method.
* @return array
*/
public static function getMagicMethods($class)
{
$rc = new \ReflectionClass($class);
preg_match_all('~^
[ \t*]* @method [ \t]+
(?: [^\s(]+ [ \t]+ )?
(set|get|is|add) ([A-Z]\w*)
(?: ([ \t]* \() [ \t]* ([^)$\s]*) )?
()~mx', (string) $rc->getDocComment(), $matches, PREG_SET_ORDER);
$methods = [];
foreach ($matches as list(, $op, $prop, $bracket, $type)) {
if ($bracket !== '(') {
trigger_error("Bracket must be immediately after @method $op$prop() in class $class.", E_USER_WARNING);
}
$name = $op . $prop;
$prop = strtolower($prop[0]) . substr($prop, 1) . ($op === 'add' ? 's' : '');
if ($rc->hasProperty($prop) && ($rp = $rc->getProperty($prop)) && !$rp->isStatic()) {
$rp->setAccessible(TRUE);
if ($op === 'get' || $op === 'is') {
$type = NULL;
$op = 'get';
} elseif (!$type && preg_match('#@var[ \t]+(\S+)' . ($op === 'add' ? '\[\]#' : '#'), (string) $rp->getDocComment(), $m)) {
$type = $m[1];
}
if ($rc->inNamespace() && preg_match('#^[A-Z]\w+(\[|\||\z)#', (string) $type)) {
$type = $rc->getNamespaceName() . '\\' . $type;
}
$methods[$name] = [$op, $rp, $type];
}
}
return $methods;
}
/**
* Finds whether a variable is of expected type and do non-data-loss conversion.
* @return bool
* @internal
*/
public static function checkType(&$val, $type)
{
if (strpos($type, '|') !== FALSE) {
$found = NULL;
foreach (explode('|', $type) as $type) {
$tmp = $val;
if (self::checkType($tmp, $type)) {
if ($val === $tmp) {
return TRUE;
}
$found[] = $tmp;
}
}
if ($found) {
$val = $found[0];
return TRUE;
}
return FALSE;
} elseif (substr($type, -2) === '[]') {
if (!is_array($val)) {
return FALSE;
}
$type = substr($type, 0, -2);
$res = [];
foreach ($val as $k => $v) {
if (!self::checkType($v, $type)) {
return FALSE;
}
$res[$k] = $v;
}
$val = $res;
return TRUE;
}
switch (strtolower($type)) {
case NULL:
case 'mixed':
return TRUE;
case 'bool':
case 'boolean':
return ($val === NULL || is_scalar($val)) && settype($val, 'bool');
case 'string':
return ($val === NULL || is_scalar($val) || (is_object($val) && method_exists($val, '__toString'))) && settype($val, 'string');
case 'int':
case 'integer':
return ($val === NULL || is_bool($val) || is_numeric($val)) && ((float) (int) $val === (float) $val) && settype($val, 'int');
case 'float':
return ($val === NULL || is_bool($val) || is_numeric($val)) && settype($val, 'float');
case 'scalar':
case 'array':
case 'object':
case 'callable':
case 'resource':
case 'null':
return call_user_func("is_$type", $val);
default:
return $val instanceof $type;
}
}
/********************* extension methods ****************d*g**/
/**
* Adds a method to class.
* @param string
* @param string
* @param mixed callable
* @return void
*/
public static function setExtensionMethod($class, $name, $callback)
{
$name = strtolower($name);
self::$extMethods[$name][$class] = Callback::check($callback);
self::$extMethods[$name][''] = NULL;
}
/**
* Returns extension method.
* @param string
* @param string
* @return mixed
*/
public static function getExtensionMethod($class, $name)
{
$list = &self::$extMethods[strtolower($name)];
$cache = &$list[''][$class];
if (isset($cache)) {
return $cache;
}
foreach ([$class] + class_parents($class) + class_implements($class) as $cl) {
if (isset($list[$cl])) {
return $cache = $list[$cl];
}
}
return $cache = FALSE;
}
/**
* Returns extension methods.
* @param string
* @return array
*/
public static function getExtensionMethods($class)
{
$res = [];
foreach (array_keys(self::$extMethods) as $name) {
if ($cb = self::getExtensionMethod($class, $name)) {
$res[$name] = $cb;
}
}
return $res;
}
/********************* utilities ****************d*g**/
/**
* Finds the best suggestion (for 8-bit encoding).
* @return string|NULL
* @internal
*/
public static function getSuggestion(array $possibilities, $value)
{
$norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '', $value);
$best = NULL;
$min = (strlen($value) / 4 + 1) * 10 + .1;
foreach (array_unique($possibilities, SORT_REGULAR) as $item) {
$item = $item instanceof \Reflector ? $item->getName() : $item;
if ($item !== $value && (
($len = levenshtein($item, $value, 10, 11, 10)) < $min
|| ($len = levenshtein(preg_replace($re, '', $item), $norm, 10, 11, 10) + 20) < $min
)) {
$min = $len;
$best = $item;
}
}
return $best;
}
private static function parseFullDoc(\ReflectionClass $rc, $pattern)
{
do {
$doc[] = $rc->getDocComment();
$traits = $rc->getTraits();
while ($trait = array_pop($traits)) {
$doc[] = $trait->getDocComment();
$traits += $trait->getTraits();
}
} while ($rc = $rc->getParentClass());
return preg_match_all($pattern, implode($doc), $m) ? $m[1] : [];
}
/**
* Checks if the public non-static property exists.
* @return bool|'event'
* @internal
*/
public static function hasProperty($class, $name)
{
static $cache;
$prop = &$cache[$class][$name];
if ($prop === NULL) {
$prop = FALSE;
try {
$rp = new \ReflectionProperty($class, $name);
if ($rp->isPublic() && !$rp->isStatic()) {
$prop = $name >= 'onA' && $name < 'on_' ? 'event' : TRUE;
}
} catch (\ReflectionException $e) {
}
}
return $prop;
}
/**
* Returns array of public (static, non-static and magic) methods.
* @return array
* @internal
*/
public static function &getMethods($class)
{
static $cache;
if (!isset($cache[$class])) {
$cache[$class] = array_fill_keys(get_class_methods($class), 0) + self::getMagicMethods($class);
if ($parent = get_parent_class($class)) {
$cache[$class] += self::getMethods($parent);
}
}
return $cache[$class];
}
/** @internal */
public static function getSource()
{
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) {
if (isset($item['file']) && dirname($item['file']) !== __DIR__) {
return " in $item[file]:$item[line]";
}
}
}
}
PK ^GM6k k src/Utils/Callback.phpnu W+A getClosure();
} elseif (is_array($callable) && method_exists($callable[0], $callable[1])) {
return (new \ReflectionMethod($callable[0], $callable[1]))->getClosure($callable[0]);
}
self::check($callable);
$_callable_ = $callable;
return function (...$args) use ($_callable_) {
return $_callable_(...$args);
};
}
/**
* Invokes callback.
* @return mixed
*/
public static function invoke($callable, ...$args)
{
self::check($callable);
return call_user_func_array($callable, $args);
}
/**
* Invokes callback with an array of parameters.
* @return mixed
*/
public static function invokeArgs($callable, array $args = [])
{
self::check($callable);
return call_user_func_array($callable, $args);
}
/**
* Invokes internal PHP function with own error handler.
* @param string
* @return mixed
*/
public static function invokeSafe($function, array $args, $onError)
{
$prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function) {
if ($file === '' && defined('HHVM_VERSION')) { // https://github.com/facebook/hhvm/issues/4625
$file = func_get_arg(5)[1]['file'];
}
if ($file === __FILE__) {
$msg = preg_replace("#^$function\(.*?\): #", '', $message);
if ($onError($msg, $severity) !== FALSE) {
return;
}
}
return $prev ? $prev(...func_get_args()) : FALSE;
});
try {
return $function(...$args);
} finally {
restore_error_handler();
}
}
/**
* @return callable
*/
public static function check($callable, $syntax = FALSE)
{
if (!is_callable($callable, $syntax)) {
throw new Nette\InvalidArgumentException($syntax
? 'Given value is not a callable type.'
: sprintf("Callback '%s' is not callable.", self::toString($callable))
);
}
return $callable;
}
/**
* @return string
*/
public static function toString($callable)
{
if ($callable instanceof \Closure) {
$inner = self::unwrap($callable);
return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}');
} elseif (is_string($callable) && $callable[0] === "\0") {
return '{lambda}';
} else {
is_callable($callable, TRUE, $textual);
return $textual;
}
}
/**
* @return \ReflectionMethod|\ReflectionFunction
*/
public static function toReflection($callable)
{
if ($callable instanceof \Closure) {
$callable = self::unwrap($callable);
} elseif ($callable instanceof Nette\Callback) {
trigger_error('Nette\Callback is deprecated.', E_USER_DEPRECATED);
$callable = $callable->getNative();
}
$class = class_exists(Nette\Reflection\Method::class) ? Nette\Reflection\Method::class : 'ReflectionMethod';
if (is_string($callable) && strpos($callable, '::')) {
return new $class($callable);
} elseif (is_array($callable)) {
return new $class($callable[0], $callable[1]);
} elseif (is_object($callable) && !$callable instanceof \Closure) {
return new $class($callable, '__invoke');
} else {
$class = class_exists(Nette\Reflection\GlobalFunction::class) ? Nette\Reflection\GlobalFunction::class : 'ReflectionFunction';
return new $class($callable);
}
}
/**
* @return bool
*/
public static function isStatic($callable)
{
return is_array($callable) ? is_string($callable[0]) : is_string($callable);
}
/**
* Unwraps closure created by self::closure()
* @internal
* @return callable
*/
public static function unwrap(\Closure $closure)
{
$r = new \ReflectionFunction($closure);
if (substr($r->getName(), -1) === '}') {
$vars = $r->getStaticVariables();
return isset($vars['_callable_']) ? $vars['_callable_'] : $closure;
} elseif ($obj = $r->getClosureThis()) {
return [$obj, $r->getName()];
} elseif ($class = $r->getClosureScopeClass()) {
return [$class->getName(), $r->getName()];
} else {
return $r->getName();
}
}
}
PK ^GM src/Utils/SmartObject.phpnu W+A $name) || $this->$name instanceof \Traversable) {
foreach ($this->$name as $handler) {
Callback::invokeArgs($handler, $args);
}
} elseif ($this->$name !== NULL) {
throw new UnexpectedValueException("Property $class::$$name must be array or NULL, " . gettype($this->$name) . ' given.');
}
} elseif ($isProp && $this->$name instanceof \Closure) { // closure in property
trigger_error("Invoking closure in property via \$obj->$name() is deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED);
return call_user_func_array($this->$name, $args);
} elseif (($methods = &ObjectMixin::getMethods($class)) && isset($methods[$name]) && is_array($methods[$name])) { // magic @methods
trigger_error("Magic methods such as $class::$name() are deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED);
list($op, $rp, $type) = $methods[$name];
if (count($args) !== ($op === 'get' ? 0 : 1)) {
throw new InvalidArgumentException("$class::$name() expects " . ($op === 'get' ? 'no' : '1') . ' argument, ' . count($args) . ' given.');
} elseif ($type && $args && !ObjectMixin::checkType($args[0], $type)) {
throw new InvalidArgumentException("Argument passed to $class::$name() must be $type, " . gettype($args[0]) . ' given.');
}
if ($op === 'get') {
return $rp->getValue($this);
} elseif ($op === 'set') {
$rp->setValue($this, $args[0]);
} elseif ($op === 'add') {
$val = $rp->getValue($this);
$val[] = $args[0];
$rp->setValue($this, $val);
}
return $this;
} elseif ($cb = ObjectMixin::getExtensionMethod($class, $name)) { // extension methods
trigger_error("Extension methods such as $class::$name() are deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED);
return Callback::invoke($cb, $this, ...$args);
} else {
ObjectMixin::strictCall($class, $name);
}
}
/**
* @return void
* @throws MemberAccessException
*/
public static function __callStatic($name, $args)
{
ObjectMixin::strictStaticCall(get_called_class(), $name);
}
/**
* @return mixed property value
* @throws MemberAccessException if the property is not defined.
*/
public function &__get($name)
{
$class = get_class($this);
$uname = ucfirst($name);
if ($prop = ObjectMixin::getMagicProperty($class, $name)) { // property getter
if (!($prop & 0b0001)) {
throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
}
$m = ($prop & 0b0010 ? 'get' : 'is') . $uname;
if ($prop & 0b0100) { // return by reference
return $this->$m();
} else {
$val = $this->$m();
return $val;
}
} elseif ($name === '') {
throw new MemberAccessException("Cannot read a class '$class' property without name.");
} elseif (($methods = &ObjectMixin::getMethods($class)) && isset($methods[$m = 'get' . $uname]) || isset($methods[$m = 'is' . $uname])) { // old property getter
trigger_error("Add annotation @property for $class::\$$name or use $m()" . ObjectMixin::getSource(), E_USER_DEPRECATED);
if ($methods[$m] === 0) {
$methods[$m] = (new \ReflectionMethod($class, $m))->returnsReference();
}
if ($methods[$m] === TRUE) {
return $this->$m();
} else {
$val = $this->$m();
return $val;
}
} elseif (isset($methods[$name])) { // public method as closure getter
trigger_error("Accessing methods as properties via \$obj->$name is deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED);
$val = Callback::closure($this, $name);
return $val;
} elseif (isset($methods['set' . $uname])) { // property getter
throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
} else {
ObjectMixin::strictGet($class, $name);
}
}
/**
* @return void
* @throws MemberAccessException if the property is not defined or is read-only
*/
public function __set($name, $value)
{
$class = get_class($this);
$uname = ucfirst($name);
if (ObjectMixin::hasProperty($class, $name)) { // unsetted property
$this->$name = $value;
} elseif ($prop = ObjectMixin::getMagicProperty($class, $name)) { // property setter
if (!($prop & 0b1000)) {
throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
}
$this->{'set' . $name}($value);
} elseif ($name === '') {
throw new MemberAccessException("Cannot write to a class '$class' property without name.");
} elseif (($methods = &ObjectMixin::getMethods($class)) && isset($methods[$m = 'set' . $uname])) { // old property setter
trigger_error("Add annotation @property for $class::\$$name or use $m()" . ObjectMixin::getSource(), E_USER_DEPRECATED);
$this->$m($value);
} elseif (isset($methods['get' . $uname]) || isset($methods['is' . $uname])) { // property setter
throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
} else {
ObjectMixin::strictSet($class, $name);
}
}
/**
* @return void
* @throws MemberAccessException
*/
public function __unset($name)
{
$class = get_class($this);
if (!ObjectMixin::hasProperty($class, $name)) {
throw new MemberAccessException("Cannot unset the property $class::\$$name.");
}
}
/**
* @return bool
*/
public function __isset($name)
{
$uname = ucfirst($name);
return ObjectMixin::getMagicProperty(get_class($this), $name)
|| ($name !== '' && ($methods = ObjectMixin::getMethods(get_class($this))) && (isset($methods['get' . $uname]) || isset($methods['is' . $uname])));
}
/**
* @return Reflection\ClassType|\ReflectionClass
* @deprecated
*/
public static function getReflection()
{
trigger_error(get_called_class() . '::getReflection() is deprecated' . ObjectMixin::getSource(), E_USER_DEPRECATED);
$class = class_exists(Reflection\ClassType::class) ? Reflection\ClassType::class : \ReflectionClass::class;
return new $class(get_called_class());
}
/**
* @return mixed
* @deprecated use Nette\Utils\ObjectMixin::setExtensionMethod()
*/
public static function extensionMethod($name, $callback = NULL)
{
if (strpos($name, '::') === FALSE) {
$class = get_called_class();
} else {
list($class, $name) = explode('::', $name);
$class = (new \ReflectionClass($class))->getName();
}
trigger_error("Extension methods such as $class::$name() are deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED);
if ($callback === NULL) {
return ObjectMixin::getExtensionMethod($class, $name);
} else {
ObjectMixin::setExtensionMethod($class, $name, $callback);
}
}
}
PK ^GMF src/Utils/Validators.phpnu W+A 'is_bool',
'boolean' => 'is_bool',
'int' => 'is_int',
'integer' => 'is_int',
'float' => 'is_float',
'number' => [__CLASS__, 'isNumber'],
'numeric' => [__CLASS__, 'isNumeric'],
'numericint' => [__CLASS__, 'isNumericInt'],
'string' => 'is_string',
'unicode' => [__CLASS__, 'isUnicode'],
'array' => 'is_array',
'list' => [Arrays::class, 'isList'],
'object' => 'is_object',
'resource' => 'is_resource',
'scalar' => 'is_scalar',
'callable' => [__CLASS__, 'isCallable'],
'null' => 'is_null',
'email' => [__CLASS__, 'isEmail'],
'url' => [__CLASS__, 'isUrl'],
'uri' => [__CLASS__, 'isUri'],
'none' => [__CLASS__, 'isNone'],
'type' => [__CLASS__, 'isType'],
'identifier' => [__CLASS__, 'isPhpIdentifier'],
'pattern' => NULL,
'alnum' => 'ctype_alnum',
'alpha' => 'ctype_alpha',
'digit' => 'ctype_digit',
'lower' => 'ctype_lower',
'upper' => 'ctype_upper',
'space' => 'ctype_space',
'xdigit' => 'ctype_xdigit',
'iterable' => [__CLASS__, 'isIterable'],
];
protected static $counters = [
'string' => 'strlen',
'unicode' => [Strings::class, 'length'],
'array' => 'count',
'list' => 'count',
'alnum' => 'strlen',
'alpha' => 'strlen',
'digit' => 'strlen',
'lower' => 'strlen',
'space' => 'strlen',
'upper' => 'strlen',
'xdigit' => 'strlen',
];
/**
* Throws exception if a variable is of unexpected type.
* @param mixed
* @param string expected types separated by pipe
* @param string label
* @return void
*/
public static function assert($value, $expected, $label = 'variable')
{
if (!static::is($value, $expected)) {
$expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
if (is_array($value)) {
$type = 'array(' . count($value) . ')';
} elseif (is_object($value)) {
$type = 'object ' . get_class($value);
} elseif (is_string($value) && strlen($value) < 40) {
$type = "string '$value'";
} else {
$type = gettype($value);
}
throw new AssertionException("The $label expects to be $expected, $type given.");
}
}
/**
* Throws exception if an array field is missing or of unexpected type.
* @param array
* @param string item
* @param string expected types separated by pipe
* @param string
* @return void
*/
public static function assertField($arr, $field, $expected = NULL, $label = "item '%' in array")
{
self::assert($arr, 'array', 'first argument');
if (!array_key_exists($field, $arr)) {
throw new AssertionException('Missing ' . str_replace('%', $field, $label) . '.');
} elseif ($expected) {
static::assert($arr[$field], $expected, str_replace('%', $field, $label));
}
}
/**
* Finds whether a variable is of expected type.
* @param mixed
* @param string expected types separated by pipe with optional ranges
* @return bool
*/
public static function is($value, $expected)
{
foreach (explode('|', $expected) as $item) {
if (substr($item, -2) === '[]') {
if (self::everyIs($value, substr($item, 0, -2))) {
return TRUE;
}
continue;
}
list($type) = $item = explode(':', $item, 2);
if (isset(static::$validators[$type])) {
if (!call_user_func(static::$validators[$type], $value)) {
continue;
}
} elseif ($type === 'pattern') {
if (preg_match('|^' . (isset($item[1]) ? $item[1] : '') . '\z|', $value)) {
return TRUE;
}
continue;
} elseif (!$value instanceof $type) {
continue;
}
if (isset($item[1])) {
$length = $value;
if (isset(static::$counters[$type])) {
$length = call_user_func(static::$counters[$type], $value);
}
$range = explode('..', $item[1]);
if (!isset($range[1])) {
$range[1] = $range[0];
}
if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) {
continue;
}
}
return TRUE;
}
return FALSE;
}
/**
* Finds whether all values are of expected type.
* @param array|\Traversable
* @param string expected types separated by pipe with optional ranges
* @return bool
*/
public static function everyIs($values, $expected)
{
if (!self::isIterable($values)) {
return FALSE;
}
foreach ($values as $value) {
if (!static::is($value, $expected)) {
return FALSE;
}
}
return TRUE;
}
/**
* Finds whether a value is an integer or a float.
* @return bool
*/
public static function isNumber($value)
{
return is_int($value) || is_float($value);
}
/**
* Finds whether a value is an integer.
* @return bool
*/
public static function isNumericInt($value)
{
return is_int($value) || is_string($value) && preg_match('#^-?[0-9]+\z#', $value);
}
/**
* Finds whether a string is a floating point number in decimal base.
* @return bool
*/
public static function isNumeric($value)
{
return is_float($value) || is_int($value) || is_string($value) && preg_match('#^-?[0-9]*[.]?[0-9]+\z#', $value);
}
/**
* Finds whether a value is a syntactically correct callback.
* @return bool
*/
public static function isCallable($value)
{
return $value && is_callable($value, TRUE);
}
/**
* Finds whether a value is an UTF-8 encoded string.
* @param string
* @return bool
*/
public static function isUnicode($value)
{
return is_string($value) && preg_match('##u', $value);
}
/**
* Finds whether a value is "falsy".
* @return bool
*/
public static function isNone($value)
{
return $value == NULL; // intentionally ==
}
/**
* Finds whether a variable is a zero-based integer indexed array.
* @param array
* @return bool
*/
public static function isList($value)
{
return Arrays::isList($value);
}
/**
* Is a value in specified range?
* @param mixed
* @param array min and max value pair
* @return bool
*/
public static function isInRange($value, $range)
{
return (!isset($range[0]) || $range[0] === '' || $value >= $range[0])
&& (!isset($range[1]) || $range[1] === '' || $value <= $range[1]);
}
/**
* Finds whether a string is a valid email address.
* @param string
* @return bool
*/
public static function isEmail($value)
{
$atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part
$alpha = "a-z\x80-\xFF"; // superset of IDN
return (bool) preg_match("(^
(\"([ !#-[\\]-~]*|\\\\[ -~])+\"|$atom+(\\.$atom+)*) # quoted or unquoted
@
([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
\\z)ix", $value);
}
/**
* Finds whether a string is a valid http(s) URL.
* @param string
* @return bool
*/
public static function isUrl($value)
{
$alpha = "a-z\x80-\xFF";
return (bool) preg_match("(^
https?://(
(([-_0-9$alpha]+\\.)* # subdomain
[0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)? # domain
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4
|\[[0-9a-f:]{3,39}\] # IPv6
)(:\\d{1,5})? # port
(/\\S*)? # path
\\z)ix", $value);
}
/**
* Finds whether a string is a valid URI according to RFC 1738.
* @param string
* @return bool
*/
public static function isUri($value)
{
return (bool) preg_match('#^[a-z\d+\.-]+:\S+\z#i', $value);
}
/**
* Checks whether the input is a class, interface or trait.
* @param string
* @return bool
*/
public static function isType($type)
{
return class_exists($type) || interface_exists($type) || trait_exists($type);
}
/**
* Checks whether the input is a valid PHP identifier.
* @return bool
*/
public static function isPhpIdentifier($value)
{
return is_string($value) && preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\z#', $value);
}
/**
* Returns true if value is iterable (array or instance of Traversable).
* @return bool
*/
private static function isIterable($value)
{
return is_array($value) || $value instanceof \Traversable;
}
}
PK ^GM6(2qv v src/Utils/ArrayList.phpnu W+A list);
}
/**
* Returns items count.
* @return int
*/
public function count()
{
return count($this->list);
}
/**
* Replaces or appends a item.
* @param int|NULL
* @param mixed
* @return void
* @throws Nette\OutOfRangeException
*/
public function offsetSet($index, $value)
{
if ($index === NULL) {
$this->list[] = $value;
} elseif ($index < 0 || $index >= count($this->list)) {
throw new Nette\OutOfRangeException('Offset invalid or out of range');
} else {
$this->list[(int) $index] = $value;
}
}
/**
* Returns a item.
* @param int
* @return mixed
* @throws Nette\OutOfRangeException
*/
public function offsetGet($index)
{
if ($index < 0 || $index >= count($this->list)) {
throw new Nette\OutOfRangeException('Offset invalid or out of range');
}
return $this->list[(int) $index];
}
/**
* Determines whether a item exists.
* @param int
* @return bool
*/
public function offsetExists($index)
{
return $index >= 0 && $index < count($this->list);
}
/**
* Removes the element at the specified position in this list.
* @param int
* @return void
* @throws Nette\OutOfRangeException
*/
public function offsetUnset($index)
{
if ($index < 0 || $index >= count($this->list)) {
throw new Nette\OutOfRangeException('Offset invalid or out of range');
}
array_splice($this->list, (int) $index, 1);
}
/**
* Prepends a item.
* @param mixed
* @return void
*/
public function prepend($value)
{
$first = array_slice($this->list, 0, 1);
$this->offsetSet(0, $value);
array_splice($this->list, 1, 0, $first);
}
}
PK ^GMY{ { src/Utils/FileSystem.phpnu W+A getPathname());
}
foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
if ($item->isDir()) {
static::createDir($dest . '/' . $iterator->getSubPathName());
} else {
static::copy($item->getPathname(), $dest . '/' . $iterator->getSubPathName());
}
}
} else {
static::createDir(dirname($dest));
if (@stream_copy_to_stream(fopen($source, 'r'), fopen($dest, 'w')) === FALSE) { // @ is escalated to exception
throw new Nette\IOException("Unable to copy file '$source' to '$dest'.");
}
}
}
/**
* Deletes a file or directory.
* @return void
* @throws Nette\IOException
*/
public static function delete($path)
{
if (is_file($path) || is_link($path)) {
$func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink';
if (!@$func($path)) { // @ is escalated to exception
throw new Nette\IOException("Unable to delete '$path'.");
}
} elseif (is_dir($path)) {
foreach (new \FilesystemIterator($path) as $item) {
static::delete($item->getPathname());
}
if (!@rmdir($path)) { // @ is escalated to exception
throw new Nette\IOException("Unable to delete directory '$path'.");
}
}
}
/**
* Renames a file or directory.
* @return void
* @throws Nette\IOException
* @throws Nette\InvalidStateException if the target file or directory already exist
*/
public static function rename($name, $newName, $overwrite = TRUE)
{
if (!$overwrite && file_exists($newName)) {
throw new Nette\InvalidStateException("File or directory '$newName' already exists.");
} elseif (!file_exists($name)) {
throw new Nette\IOException("File or directory '$name' not found.");
} else {
static::createDir(dirname($newName));
static::delete($newName);
if (!@rename($name, $newName)) { // @ is escalated to exception
throw new Nette\IOException("Unable to rename file or directory '$name' to '$newName'.");
}
}
}
/**
* Reads file content.
* @return string
* @throws Nette\IOException
*/
public static function read($file)
{
$content = @file_get_contents($file); // @ is escalated to exception
if ($content === FALSE) {
throw new Nette\IOException("Unable to read file '$file'.");
}
return $content;
}
/**
* Writes a string to a file.
* @return void
* @throws Nette\IOException
*/
public static function write($file, $content, $mode = 0666)
{
static::createDir(dirname($file));
if (@file_put_contents($file, $content) === FALSE) { // @ is escalated to exception
throw new Nette\IOException("Unable to write file '$file'.");
}
if ($mode !== NULL && !@chmod($file, $mode)) { // @ is escalated to exception
throw new Nette\IOException("Unable to chmod file '$file'.");
}
}
/**
* Is path absolute?
* @return bool
*/
public static function isAbsolute($path)
{
return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path);
}
}
PK ^GM src/Utils/exceptions.phpnu W+A
* $el = Html::el('a')->href($link)->setText('Nette');
* $el->class = 'myclass';
* echo $el;
*
* echo $el->startTag(), $el->endTag();
*
*/
class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
{
use Nette\SmartObject;
/** @var string element's name */
private $name;
/** @var bool is element empty? */
private $isEmpty;
/** @var array element's attributes */
public $attrs = [];
/** @var array of Html | string nodes */
protected $children = [];
/** @var bool use XHTML syntax? */
public static $xhtml = FALSE;
/** @var array empty (void) elements */
public static $emptyElements = [
'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1,
'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1,
'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1,
];
/**
* Static factory.
* @param string element name (or NULL)
* @param array|string element's attributes or plain text content
* @return static
*/
public static function el($name = NULL, $attrs = NULL)
{
$el = new static;
$parts = explode(' ', (string) $name, 2);
$el->setName($parts[0]);
if (is_array($attrs)) {
$el->attrs = $attrs;
} elseif ($attrs !== NULL) {
$el->setText($attrs);
}
if (isset($parts[1])) {
foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\\2|\s))?#i') as $m) {
$el->attrs[$m[1]] = isset($m[3]) ? $m[3] : TRUE;
}
}
return $el;
}
/**
* Changes element's name.
* @param string
* @param bool Is element empty?
* @return static
* @throws Nette\InvalidArgumentException
*/
public function setName($name, $isEmpty = NULL)
{
if ($name !== NULL && !is_string($name)) {
throw new Nette\InvalidArgumentException(sprintf('Name must be string or NULL, %s given.', gettype($name)));
}
$this->name = $name;
$this->isEmpty = $isEmpty === NULL ? isset(static::$emptyElements[$name]) : (bool) $isEmpty;
return $this;
}
/**
* Returns element's name.
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Is element empty?
* @return bool
*/
public function isEmpty()
{
return $this->isEmpty;
}
/**
* Sets multiple attributes.
* @param array
* @return static
*/
public function addAttributes(array $attrs)
{
$this->attrs = array_merge($this->attrs, $attrs);
return $this;
}
/**
* Appends value to element's attribute.
* @param string
* @param string|array value to append
* @param string|bool value option
* @return static
*/
public function appendAttribute($name, $value, $option = TRUE)
{
if (is_array($value)) {
$prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : [];
$this->attrs[$name] = $value + $prev;
} elseif ((string) $value === '') {
$tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists
} elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array
$this->attrs[$name][$value] = $option;
} else {
$this->attrs[$name] = [$this->attrs[$name] => TRUE, $value => $option];
}
return $this;
}
/**
* Sets element's attribute.
* @param string
* @param mixed
* @return static
*/
public function setAttribute($name, $value)
{
$this->attrs[$name] = $value;
return $this;
}
/**
* Returns element's attribute.
* @param string
* @return mixed
*/
public function getAttribute($name)
{
return isset($this->attrs[$name]) ? $this->attrs[$name] : NULL;
}
/**
* Unsets element's attribute.
* @param string
* @return static
*/
public function removeAttribute($name)
{
unset($this->attrs[$name]);
return $this;
}
/**
* Overloaded setter for element's attribute.
* @param string HTML attribute name
* @param mixed HTML attribute value
* @return void
*/
public function __set($name, $value)
{
$this->attrs[$name] = $value;
}
/**
* Overloaded getter for element's attribute.
* @param string HTML attribute name
* @return mixed HTML attribute value
*/
public function &__get($name)
{
return $this->attrs[$name];
}
/**
* Overloaded tester for element's attribute.
* @param string HTML attribute name
* @return bool
*/
public function __isset($name)
{
return isset($this->attrs[$name]);
}
/**
* Overloaded unsetter for element's attribute.
* @param string HTML attribute name
* @return void
*/
public function __unset($name)
{
unset($this->attrs[$name]);
}
/**
* Overloaded setter for element's attribute.
* @param string HTML attribute name
* @param array (string) HTML attribute value or pair?
* @return mixed
*/
public function __call($m, $args)
{
$p = substr($m, 0, 3);
if ($p === 'get' || $p === 'set' || $p === 'add') {
$m = substr($m, 3);
$m[0] = $m[0] | "\x20";
if ($p === 'get') {
return isset($this->attrs[$m]) ? $this->attrs[$m] : NULL;
} elseif ($p === 'add') {
$args[] = TRUE;
}
}
if (count($args) === 0) { // invalid
} elseif (count($args) === 1) { // set
$this->attrs[$m] = $args[0];
} else { // add
$this->appendAttribute($m, $args[0], $args[1]);
}
return $this;
}
/**
* Special setter for element's attribute.
* @param string path
* @param array query
* @return static
*/
public function href($path, $query = NULL)
{
if ($query) {
$query = http_build_query($query, '', '&');
if ($query !== '') {
$path .= '?' . $query;
}
}
$this->attrs['href'] = $path;
return $this;
}
/**
* Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'.
* @return static
*/
public function data($name, $value = NULL)
{
if (func_num_args() === 1) {
$this->attrs['data'] = $name;
} else {
$this->attrs["data-$name"] = is_bool($value) ? json_encode($value) : $value;
}
return $this;
}
/**
* Sets element's HTML content.
* @param string raw HTML string
* @return static
* @throws Nette\InvalidArgumentException
*/
public function setHtml($html)
{
if (is_array($html)) {
throw new Nette\InvalidArgumentException(sprintf('Textual content must be a scalar, %s given.', gettype($html)));
}
$this->removeChildren();
$this->children[] = (string) $html;
return $this;
}
/**
* Returns element's HTML content.
* @return string
*/
public function getHtml()
{
$s = '';
foreach ($this->children as $child) {
if (is_object($child)) {
$s .= $child->render();
} else {
$s .= $child;
}
}
return $s;
}
/**
* Sets element's textual content.
* @param string
* @return static
* @throws Nette\InvalidArgumentException
*/
public function setText($text)
{
if (!is_array($text) && !$text instanceof self) {
$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
}
return $this->setHtml($text);
}
/**
* Returns element's textual content.
* @return string
*/
public function getText()
{
return html_entity_decode(strip_tags($this->getHtml()), ENT_QUOTES, 'UTF-8');
}
/**
* @deprecated
*/
public function add($child)
{
trigger_error(__METHOD__ . '() is deprecated, use addHtml() or addText() instead.', E_USER_DEPRECATED);
return $this->addHtml($child);
}
/**
* Adds new element's child.
* @param Html|string Html node or raw HTML string
* @return static
*/
public function addHtml($child)
{
return $this->insert(NULL, $child);
}
/**
* Appends plain-text string to element content.
* @param string plain-text string
* @return static
*/
public function addText($text)
{
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
return $this->insert(NULL, $text);
}
/**
* Creates and adds a new Html child.
* @param string elements's name
* @param array|string element's attributes or raw HTML string
* @return static created element
*/
public function create($name, $attrs = NULL)
{
$this->insert(NULL, $child = static::el($name, $attrs));
return $child;
}
/**
* Inserts child node.
* @param int|NULL position or NULL for appending
* @param Html|string Html node or raw HTML string
* @param bool
* @return static
* @throws Nette\InvalidArgumentException
*/
public function insert($index, $child, $replace = FALSE)
{
if ($child instanceof self || is_scalar($child)) {
if ($index === NULL) { // append
$this->children[] = $child;
} else { // insert or replace
array_splice($this->children, (int) $index, $replace ? 1 : 0, [$child]);
}
} else {
throw new Nette\InvalidArgumentException(sprintf('Child node must be scalar or Html object, %s given.', is_object($child) ? get_class($child) : gettype($child)));
}
return $this;
}
/**
* Inserts (replaces) child node (\ArrayAccess implementation).
* @param int|NULL position or NULL for appending
* @param Html|string Html node or raw HTML string
* @return void
*/
public function offsetSet($index, $child)
{
$this->insert($index, $child, TRUE);
}
/**
* Returns child node (\ArrayAccess implementation).
* @param int
* @return static|string
*/
public function offsetGet($index)
{
return $this->children[$index];
}
/**
* Exists child node? (\ArrayAccess implementation).
* @param int
* @return bool
*/
public function offsetExists($index)
{
return isset($this->children[$index]);
}
/**
* Removes child node (\ArrayAccess implementation).
* @param int
* @return void
*/
public function offsetUnset($index)
{
if (isset($this->children[$index])) {
array_splice($this->children, (int) $index, 1);
}
}
/**
* Returns children count.
* @return int
*/
public function count()
{
return count($this->children);
}
/**
* Removes all children.
* @return void
*/
public function removeChildren()
{
$this->children = [];
}
/**
* Iterates over elements.
* @return \ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator($this->children);
}
/**
* Returns all children.
* @return array
*/
public function getChildren()
{
return $this->children;
}
/**
* Renders element's start tag, content and end tag.
* @param int
* @return string
*/
public function render($indent = NULL)
{
$s = $this->startTag();
if (!$this->isEmpty) {
// add content
if ($indent !== NULL) {
$indent++;
}
foreach ($this->children as $child) {
if (is_object($child)) {
$s .= $child->render($indent);
} else {
$s .= $child;
}
}
// add end tag
$s .= $this->endTag();
}
if ($indent !== NULL) {
return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
}
return $s;
}
public function __toString()
{
try {
return $this->render();
} catch (\Throwable $e) {
} catch (\Exception $e) {
}
trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
/**
* Returns element's start tag.
* @return string
*/
public function startTag()
{
if ($this->name) {
return '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>');
} else {
return '';
}
}
/**
* Returns element's end tag.
* @return string
*/
public function endTag()
{
return $this->name && !$this->isEmpty ? '' . $this->name . '>' : '';
}
/**
* Returns element's attributes.
* @return string
* @internal
*/
public function attributes()
{
if (!is_array($this->attrs)) {
return '';
}
$s = '';
$attrs = $this->attrs;
if (isset($attrs['data']) && is_array($attrs['data'])) { // deprecated
trigger_error('Expanded attribute "data" is deprecated.', E_USER_DEPRECATED);
foreach ($attrs['data'] as $key => $value) {
$attrs['data-' . $key] = $value;
}
unset($attrs['data']);
}
foreach ($attrs as $key => $value) {
if ($value === NULL || $value === FALSE) {
continue;
} elseif ($value === TRUE) {
if (static::$xhtml) {
$s .= ' ' . $key . '="' . $key . '"';
} else {
$s .= ' ' . $key;
}
continue;
} elseif (is_array($value)) {
if (strncmp($key, 'data-', 5) === 0) {
$value = Json::encode($value);
} else {
$tmp = NULL;
foreach ($value as $k => $v) {
if ($v != NULL) { // intentionally ==, skip NULLs & empty string
// composite 'style' vs. 'others'
$tmp[] = $v === TRUE ? $k : (is_string($k) ? $k . ':' . $v : $v);
}
}
if ($tmp === NULL) {
continue;
}
$value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
}
} elseif (is_float($value)) {
$value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
} else {
$value = (string) $value;
}
$q = strpos($value, '"') === FALSE ? '"' : "'";
$s .= ' ' . $key . '=' . $q
. str_replace(
['&', $q, '<'],
['&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'],
$value
)
. (strpos($value, '`') !== FALSE && strpbrk($value, ' <>"\'') === FALSE ? ' ' : '')
. $q;
}
$s = str_replace('@', '@', $s);
return $s;
}
/**
* Clones all children too.
*/
public function __clone()
{
foreach ($this->children as $key => $value) {
if (is_object($value)) {
$this->children[$key] = clone $value;
}
}
}
}
PK ^GM>^s src/Utils/Reflection.phpnu W+A 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1,
'callable' => 1, 'iterable' => 1, 'void' => 1
];
/**
* @param string
* @return bool
*/
public static function isBuiltinType($type)
{
return isset(self::$builtinTypes[strtolower($type)]);
}
/**
* @return string|NULL
*/
public static function getReturnType(\ReflectionFunctionAbstract $func)
{
if (PHP_VERSION_ID >= 70000 && $func->hasReturnType()) {
$type = (string) $func->getReturnType();
return strtolower($type) === 'self' ? $func->getDeclaringClass()->getName() : $type;
}
}
/**
* @return string|NULL
*/
public static function getParameterType(\ReflectionParameter $param)
{
if (PHP_VERSION_ID >= 70000) {
$type = $param->hasType() ? (string) $param->getType() : NULL;
return strtolower($type) === 'self' ? $param->getDeclaringClass()->getName() : $type;
} elseif ($param->isArray() || $param->isCallable()) {
return $param->isArray() ? 'array' : 'callable';
} else {
try {
return ($ref = $param->getClass()) ? $ref->getName() : NULL;
} catch (\ReflectionException $e) {
if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) {
return $m[1];
}
throw $e;
}
}
}
/**
* @return mixed
* @throws \ReflectionException when default value is not available or resolvable
*/
public static function getParameterDefaultValue(\ReflectionParameter $param)
{
if ($param->isDefaultValueConstant()) {
$const = $param->getDefaultValueConstantName();
$pair = explode('::', $const);
if (isset($pair[1]) && strtolower($pair[0]) === 'self') {
$const = $param->getDeclaringClass()->getName() . '::' . $pair[1];
}
if (!defined($const)) {
$name = self::toString($param);
throw new \ReflectionException("Unable to resolve constant $const used as default value of $name.");
}
return constant($const);
}
return $param->getDefaultValue();
}
/**
* Returns declaring class or trait.
* @return \ReflectionClass
*/
public static function getPropertyDeclaringClass(\ReflectionProperty $prop)
{
foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
if ($trait->hasProperty($prop->getName())) {
return self::getPropertyDeclaringClass($trait->getProperty($prop->getName()));
}
}
return $prop->getDeclaringClass();
}
/**
* Are documentation comments available?
* @return bool
*/
public static function areCommentsAvailable()
{
static $res;
return $res === NULL
? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment()
: $res;
}
/**
* @return string
*/
public static function toString(\Reflector $ref)
{
if ($ref instanceof \ReflectionClass) {
return $ref->getName();
} elseif ($ref instanceof \ReflectionMethod) {
return $ref->getDeclaringClass()->getName() . '::' . $ref->getName();
} elseif ($ref instanceof \ReflectionFunction) {
return $ref->getName();
} elseif ($ref instanceof \ReflectionProperty) {
return self::getPropertyDeclaringClass($ref)->getName() . '::$' . $ref->getName();
} elseif ($ref instanceof \ReflectionParameter) {
return '$' . $ref->getName() . ' in ' . self::toString($ref->getDeclaringFunction()) . '()';
} else {
throw new Nette\InvalidArgumentException;
}
}
/**
* Expands class name into full name.
* @param string
* @return string full name
* @throws Nette\InvalidArgumentException
*/
public static function expandClassName($name, \ReflectionClass $rc)
{
$lower = strtolower($name);
if (empty($name)) {
throw new Nette\InvalidArgumentException('Class name must not be empty.');
} elseif (isset(self::$builtinTypes[$lower])) {
return $lower;
} elseif ($lower === 'self') {
return $rc->getName();
} elseif ($name[0] === '\\') { // fully qualified name
return ltrim($name, '\\');
}
$uses = self::getUseStatements($rc);
$parts = explode('\\', $name, 2);
if (isset($uses[$parts[0]])) {
$parts[0] = $uses[$parts[0]];
return implode('\\', $parts);
} elseif ($rc->inNamespace()) {
return $rc->getNamespaceName() . '\\' . $name;
} else {
return $name;
}
}
/**
* @return array of [alias => class]
*/
public static function getUseStatements(\ReflectionClass $class)
{
static $cache = [];
if (!isset($cache[$name = $class->getName()])) {
if ($class->isInternal()) {
$cache[$name] = [];
} else {
$code = file_get_contents($class->getFileName());
$cache = self::parseUseStatements($code, $name) + $cache;
}
}
return $cache[$name];
}
/**
* Parses PHP code.
* @param string
* @return array of [class => [alias => class, ...]]
*/
private static function parseUseStatements($code, $forClass = NULL)
{
$tokens = token_get_all($code);
$namespace = $class = $classLevel = $level = NULL;
$res = $uses = [];
while (list(, $token) = each($tokens)) {
switch (is_array($token) ? $token[0] : $token) {
case T_NAMESPACE:
$namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\');
$uses = [];
break;
case T_CLASS:
case T_INTERFACE:
case T_TRAIT:
if ($name = self::fetch($tokens, T_STRING)) {
$class = $namespace . $name;
$classLevel = $level + 1;
$res[$class] = $uses;
if ($class === $forClass) {
return $res;
}
}
break;
case T_USE:
while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) {
$name = ltrim($name, '\\');
if (self::fetch($tokens, '{')) {
while ($suffix = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR])) {
if (self::fetch($tokens, T_AS)) {
$uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
} else {
$tmp = explode('\\', $suffix);
$uses[end($tmp)] = $name . $suffix;
}
if (!self::fetch($tokens, ',')) {
break;
}
}
} elseif (self::fetch($tokens, T_AS)) {
$uses[self::fetch($tokens, T_STRING)] = $name;
} else {
$tmp = explode('\\', $name);
$uses[end($tmp)] = $name;
}
if (!self::fetch($tokens, ',')) {
break;
}
}
break;
case T_CURLY_OPEN:
case T_DOLLAR_OPEN_CURLY_BRACES:
case '{':
$level++;
break;
case '}':
if ($level === $classLevel) {
$class = $classLevel = NULL;
}
$level--;
}
}
return $res;
}
private static function fetch(&$tokens, $take)
{
$res = NULL;
while ($token = current($tokens)) {
list($token, $s) = is_array($token) ? $token : [$token, $token];
if (in_array($token, (array) $take, TRUE)) {
$res .= $s;
} elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], TRUE)) {
break;
}
next($tokens);
}
return $res;
}
}
PK ^GMʙs src/Utils/Object.phpnu W+A
* $val = $obj->label; // equivalent to $val = $obj->getLabel();
* $obj->label = 'Nette'; // equivalent to $obj->setLabel('Nette');
*
* Property names are case-sensitive, and they are written in the camelCaps
* or PascalCaps.
*
* Event functionality is provided by declaration of property named 'on{Something}'
* Multiple handlers are allowed.
*
* public $onClick; // declaration in class
* $this->onClick[] = 'callback'; // attaching event handler
* if (!empty($this->onClick)) ... // are there any handlers?
* $this->onClick($sender, $arg); // raises the event with arguments
*
*
* Adding method to class (i.e. to all instances) works similar to JavaScript
* prototype property. The syntax for adding a new method is:
*
* MyClass::extensionMethod('newMethod', function (MyClass $obj, $arg, ...) { ... });
* $obj = new MyClass;
* $obj->newMethod($x);
*
*
* @property-read Nette\Reflection\ClassType|\ReflectionClass $reflection
*/
abstract class Object
{
/**
* Access to reflection.
* @return Nette\Reflection\ClassType|\ReflectionClass
*/
public static function getReflection()
{
$class = class_exists(Nette\Reflection\ClassType::class) ? Nette\Reflection\ClassType::class : 'ReflectionClass';
return new $class(get_called_class());
}
/**
* Call to undefined method.
* @param string method name
* @param array arguments
* @return mixed
* @throws MemberAccessException
*/
public function __call($name, $args)
{
return Nette\Utils\ObjectMixin::call($this, $name, $args);
}
/**
* Call to undefined static method.
* @param string method name (in lower case!)
* @param array arguments
* @return mixed
* @throws MemberAccessException
*/
public static function __callStatic($name, $args)
{
return Nette\Utils\ObjectMixin::callStatic(get_called_class(), $name, $args);
}
/**
* Adding method to class.
* @param string method name
* @param callable
* @return mixed
*/
public static function extensionMethod($name, $callback = NULL)
{
if (strpos($name, '::') === FALSE) {
$class = get_called_class();
} else {
list($class, $name) = explode('::', $name);
$class = (new \ReflectionClass($class))->getName();
}
if ($callback === NULL) {
return Nette\Utils\ObjectMixin::getExtensionMethod($class, $name);
} else {
Nette\Utils\ObjectMixin::setExtensionMethod($class, $name, $callback);
}
}
/**
* Returns property value. Do not call directly.
* @param string property name
* @return mixed property value
* @throws MemberAccessException if the property is not defined.
*/
public function &__get($name)
{
return Nette\Utils\ObjectMixin::get($this, $name);
}
/**
* Sets value of a property. Do not call directly.
* @param string property name
* @param mixed property value
* @return void
* @throws MemberAccessException if the property is not defined or is read-only
*/
public function __set($name, $value)
{
Nette\Utils\ObjectMixin::set($this, $name, $value);
}
/**
* Is property defined?
* @param string property name
* @return bool
*/
public function __isset($name)
{
return Nette\Utils\ObjectMixin::has($this, $name);
}
/**
* Access to undeclared property.
* @param string property name
* @return void
* @throws MemberAccessException
*/
public function __unset($name)
{
Nette\Utils\ObjectMixin::remove($this, $name);
}
}
PK ^GMd}9 9 src/Utils/Json.phpnu W+A $value) {
if ($recursive && is_array($value)) {
$obj->$key = static::from($value, TRUE);
} else {
$obj->$key = $value;
}
}
return $obj;
}
/**
* Returns an iterator over all items.
* @return \RecursiveArrayIterator
*/
public function getIterator()
{
return new \RecursiveArrayIterator((array) $this);
}
/**
* Returns items count.
* @return int
*/
public function count()
{
return count((array) $this);
}
/**
* Replaces or appends a item.
* @return void
*/
public function offsetSet($key, $value)
{
if (!is_scalar($key)) { // prevents NULL
throw new Nette\InvalidArgumentException(sprintf('Key must be either a string or an integer, %s given.', gettype($key)));
}
$this->$key = $value;
}
/**
* Returns a item.
* @return mixed
*/
public function offsetGet($key)
{
return $this->$key;
}
/**
* Determines whether a item exists.
* @return bool
*/
public function offsetExists($key)
{
return isset($this->$key);
}
/**
* Removes the element from this list.
* @return void
*/
public function offsetUnset($key)
{
unset($this->$key);
}
}
PK ^GMmN7J J src/Utils/Image.phpnu W+A
* $image = Image::fromFile('nette.jpg');
* $image->resize(150, 100);
* $image->sharpen();
* $image->send();
*
*
* @method void alphaBlending(bool $on)
* @method void antialias(bool $on)
* @method void arc($x, $y, $w, $h, $start, $end, $color)
* @method void char(int $font, $x, $y, string $char, $color)
* @method void charUp(int $font, $x, $y, string $char, $color)
* @method int colorAllocate($red, $green, $blue)
* @method int colorAllocateAlpha($red, $green, $blue, $alpha)
* @method int colorAt($x, $y)
* @method int colorClosest($red, $green, $blue)
* @method int colorClosestAlpha($red, $green, $blue, $alpha)
* @method int colorClosestHWB($red, $green, $blue)
* @method void colorDeallocate($color)
* @method int colorExact($red, $green, $blue)
* @method int colorExactAlpha($red, $green, $blue, $alpha)
* @method void colorMatch(Image $image2)
* @method int colorResolve($red, $green, $blue)
* @method int colorResolveAlpha($red, $green, $blue, $alpha)
* @method void colorSet($index, $red, $green, $blue)
* @method array colorsForIndex($index)
* @method int colorsTotal()
* @method int colorTransparent($color = NULL)
* @method void convolution(array $matrix, float $div, float $offset)
* @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH)
* @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
* @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
* @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
* @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
* @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1)
* @method void dashedLine($x1, $y1, $x2, $y2, $color)
* @method void ellipse($cx, $cy, $w, $h, $color)
* @method void fill($x, $y, $color)
* @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style)
* @method void filledEllipse($cx, $cy, $w, $h, $color)
* @method void filledPolygon(array $points, $numPoints, $color)
* @method void filledRectangle($x1, $y1, $x2, $y2, $color)
* @method void fillToBorder($x, $y, $border, $color)
* @method void filter($filtertype)
* @method void flip(int $mode)
* @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = NULL)
* @method void gammaCorrect(float $inputgamma, float $outputgamma)
* @method int interlace($interlace = NULL)
* @method bool isTrueColor()
* @method void layerEffect($effect)
* @method void line($x1, $y1, $x2, $y2, $color)
* @method void paletteCopy(Image $source)
* @method void paletteToTrueColor()
* @method void polygon(array $points, $numPoints, $color)
* @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = NULL, $tightness = NULL, float $angle = NULL, $antialiasSteps = NULL)
* @method void rectangle($x1, $y1, $x2, $y2, $col)
* @method Image rotate(float $angle, $backgroundColor)
* @method void saveAlpha(bool $saveflag)
* @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED)
* @method void setBrush(Image $brush)
* @method void setPixel($x, $y, $color)
* @method void setStyle(array $style)
* @method void setThickness($thickness)
* @method void setTile(Image $tile)
* @method void string($font, $x, $y, string $s, $col)
* @method void stringUp($font, $x, $y, string $s, $col)
* @method void trueColorToPalette(bool $dither, $ncolors)
* @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
* @property-read int $width
* @property-read int $height
* @property-read resource $imageResource
*/
class Image
{
use Nette\SmartObject;
/** {@link resize()} only shrinks images */
const SHRINK_ONLY = 0b0001;
/** {@link resize()} will ignore aspect ratio */
const STRETCH = 0b0010;
/** {@link resize()} fits in given area so its dimensions are less than or equal to the required dimensions */
const FIT = 0b0000;
/** {@link resize()} fills given area so its dimensions are greater than or equal to the required dimensions */
const FILL = 0b0100;
/** {@link resize()} fills given area exactly */
const EXACT = 0b1000;
/** image types */
const
JPEG = IMAGETYPE_JPEG,
PNG = IMAGETYPE_PNG,
GIF = IMAGETYPE_GIF,
WEBP = 18; // IMAGETYPE_WEBP is available as of PHP 7.1
const EMPTY_GIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
/** @deprecated */
const ENLARGE = 0;
static private $formats = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp'];
/** @var resource */
private $image;
/**
* Returns RGB color.
* @param int red 0..255
* @param int green 0..255
* @param int blue 0..255
* @param int transparency 0..127
* @return array
*/
public static function rgb($red, $green, $blue, $transparency = 0)
{
return [
'red' => max(0, min(255, (int) $red)),
'green' => max(0, min(255, (int) $green)),
'blue' => max(0, min(255, (int) $blue)),
'alpha' => max(0, min(127, (int) $transparency)),
];
}
/**
* Opens image from file.
* @param string
* @param mixed detected image format
* @throws Nette\NotSupportedException if gd extension is not loaded
* @throws UnknownImageFileException if file not found or file type is not known
* @return static
*/
public static function fromFile($file, &$format = NULL)
{
if (!extension_loaded('gd')) {
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
}
$format = @getimagesize($file)[2]; // @ - files smaller than 12 bytes causes read error
if (!$format && PHP_VERSION_ID < 70100 && @file_get_contents($file, FALSE, NULL, 8, 4) === 'WEBP') { // @ - may not exists
$format = self::WEBP;
}
if (!isset(self::$formats[$format])) {
$format = NULL;
throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found.");
}
return new static(Callback::invokeSafe('imagecreatefrom' . self::$formats[$format], [$file], function ($message) {
throw new ImageException($message);
}));
}
/**
* Create a new image from the image stream in the string.
* @param string
* @param mixed detected image format
* @return static
* @throws ImageException
*/
public static function fromString($s, &$format = NULL)
{
if (!extension_loaded('gd')) {
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
}
if (func_num_args() > 1) {
$tmp = @getimagesizefromstring($s)[2]; // @ - strings smaller than 12 bytes causes read error
$format = isset(self::$formats[$tmp]) ? $tmp : NULL;
}
return new static(Callback::invokeSafe('imagecreatefromstring', [$s], function ($message) {
throw new ImageException($message);
}));
}
/**
* Creates blank image.
* @param int
* @param int
* @param array
* @return static
*/
public static function fromBlank($width, $height, $color = NULL)
{
if (!extension_loaded('gd')) {
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
}
$width = (int) $width;
$height = (int) $height;
if ($width < 1 || $height < 1) {
throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.');
}
$image = imagecreatetruecolor($width, $height);
if (is_array($color)) {
$color += ['alpha' => 0];
$color = imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']);
imagealphablending($image, FALSE);
imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color);
imagealphablending($image, TRUE);
}
return new static($image);
}
/**
* Wraps GD image.
* @param resource
*/
public function __construct($image)
{
$this->setImageResource($image);
imagesavealpha($image, TRUE);
}
/**
* Returns image width.
* @return int
*/
public function getWidth()
{
return imagesx($this->image);
}
/**
* Returns image height.
* @return int
*/
public function getHeight()
{
return imagesy($this->image);
}
/**
* Sets image resource.
* @param resource
* @return static
*/
protected function setImageResource($image)
{
if (!is_resource($image) || get_resource_type($image) !== 'gd') {
throw new Nette\InvalidArgumentException('Image is not valid.');
}
$this->image = $image;
return $this;
}
/**
* Returns image GD resource.
* @return resource
*/
public function getImageResource()
{
return $this->image;
}
/**
* Resizes image.
* @param mixed width in pixels or percent
* @param mixed height in pixels or percent
* @param int flags
* @return static
*/
public function resize($width, $height, $flags = self::FIT)
{
if ($flags & self::EXACT) {
return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height);
}
list($newWidth, $newHeight) = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags);
if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize
$newImage = static::fromBlank($newWidth, $newHeight, self::RGB(0, 0, 0, 127))->getImageResource();
imagecopyresampled(
$newImage, $this->image,
0, 0, 0, 0,
$newWidth, $newHeight, $this->getWidth(), $this->getHeight()
);
$this->image = $newImage;
}
if ($width < 0 || $height < 0) {
imageflip($this->image, $width < 0 ? ($height < 0 ? IMG_FLIP_BOTH : IMG_FLIP_HORIZONTAL) : IMG_FLIP_VERTICAL);
}
return $this;
}
/**
* Calculates dimensions of resized image.
* @param mixed source width
* @param mixed source height
* @param mixed width in pixels or percent
* @param mixed height in pixels or percent
* @param int flags
* @return array
*/
public static function calculateSize($srcWidth, $srcHeight, $newWidth, $newHeight, $flags = self::FIT)
{
if (is_string($newWidth) && substr($newWidth, -1) === '%') {
$newWidth = (int) round($srcWidth / 100 * abs(substr($newWidth, 0, -1)));
$percents = TRUE;
} else {
$newWidth = (int) abs($newWidth);
}
if (is_string($newHeight) && substr($newHeight, -1) === '%') {
$newHeight = (int) round($srcHeight / 100 * abs(substr($newHeight, 0, -1)));
$flags |= empty($percents) ? 0 : self::STRETCH;
} else {
$newHeight = (int) abs($newHeight);
}
if ($flags & self::STRETCH) { // non-proportional
if (empty($newWidth) || empty($newHeight)) {
throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.');
}
if ($flags & self::SHRINK_ONLY) {
$newWidth = (int) round($srcWidth * min(1, $newWidth / $srcWidth));
$newHeight = (int) round($srcHeight * min(1, $newHeight / $srcHeight));
}
} else { // proportional
if (empty($newWidth) && empty($newHeight)) {
throw new Nette\InvalidArgumentException('At least width or height must be specified.');
}
$scale = [];
if ($newWidth > 0) { // fit width
$scale[] = $newWidth / $srcWidth;
}
if ($newHeight > 0) { // fit height
$scale[] = $newHeight / $srcHeight;
}
if ($flags & self::FILL) {
$scale = [max($scale)];
}
if ($flags & self::SHRINK_ONLY) {
$scale[] = 1;
}
$scale = min($scale);
$newWidth = (int) round($srcWidth * $scale);
$newHeight = (int) round($srcHeight * $scale);
}
return [max($newWidth, 1), max($newHeight, 1)];
}
/**
* Crops image.
* @param mixed x-offset in pixels or percent
* @param mixed y-offset in pixels or percent
* @param mixed width in pixels or percent
* @param mixed height in pixels or percent
* @return static
*/
public function crop($left, $top, $width, $height)
{
list($r['x'], $r['y'], $r['width'], $r['height'])
= static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height);
if (PHP_VERSION_ID > 50611) { // PHP bug #67447
$this->image = imagecrop($this->image, $r);
} else {
$newImage = static::fromBlank($r['width'], $r['height'], self::RGB(0, 0, 0, 127))->getImageResource();
imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']);
$this->image = $newImage;
}
return $this;
}
/**
* Calculates dimensions of cutout in image.
* @param mixed source width
* @param mixed source height
* @param mixed x-offset in pixels or percent
* @param mixed y-offset in pixels or percent
* @param mixed width in pixels or percent
* @param mixed height in pixels or percent
* @return array
*/
public static function calculateCutout($srcWidth, $srcHeight, $left, $top, $newWidth, $newHeight)
{
if (is_string($newWidth) && substr($newWidth, -1) === '%') {
$newWidth = (int) round($srcWidth / 100 * substr($newWidth, 0, -1));
}
if (is_string($newHeight) && substr($newHeight, -1) === '%') {
$newHeight = (int) round($srcHeight / 100 * substr($newHeight, 0, -1));
}
if (is_string($left) && substr($left, -1) === '%') {
$left = (int) round(($srcWidth - $newWidth) / 100 * substr($left, 0, -1));
}
if (is_string($top) && substr($top, -1) === '%') {
$top = (int) round(($srcHeight - $newHeight) / 100 * substr($top, 0, -1));
}
if ($left < 0) {
$newWidth += $left;
$left = 0;
}
if ($top < 0) {
$newHeight += $top;
$top = 0;
}
$newWidth = min($newWidth, $srcWidth - $left);
$newHeight = min($newHeight, $srcHeight - $top);
return [$left, $top, $newWidth, $newHeight];
}
/**
* Sharpen image.
* @return static
*/
public function sharpen()
{
imageconvolution($this->image, [ // my magic numbers ;)
[-1, -1, -1],
[-1, 24, -1],
[-1, -1, -1],
], 16, 0);
return $this;
}
/**
* Puts another image into this image.
* @param Image
* @param mixed x-coordinate in pixels or percent
* @param mixed y-coordinate in pixels or percent
* @param int opacity 0..100
* @return static
*/
public function place(Image $image, $left = 0, $top = 0, $opacity = 100)
{
$opacity = max(0, min(100, (int) $opacity));
if ($opacity === 0) {
return $this;
}
$width = $image->getWidth();
$height = $image->getHeight();
if (is_string($left) && substr($left, -1) === '%') {
$left = (int) round(($this->getWidth() - $width) / 100 * substr($left, 0, -1));
}
if (is_string($top) && substr($top, -1) === '%') {
$top = (int) round(($this->getHeight() - $height) / 100 * substr($top, 0, -1));
}
$output = $input = $image->image;
if ($opacity < 100) {
for ($i = 0; $i < 128; $i++) {
$tbl[$i] = round(127 - (127 - $i) * $opacity / 100);
}
$output = imagecreatetruecolor($width, $height);
imagealphablending($output, FALSE);
if (!$image->isTrueColor()) {
$input = $output;
imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127));
imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height);
}
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$c = \imagecolorat($input, $x, $y);
$c = ($c & 0xFFFFFF) + ($tbl[$c >> 24] << 24);
\imagesetpixel($output, $x, $y, $c);
}
}
imagealphablending($output, TRUE);
}
imagecopy(
$this->image, $output,
$left, $top, 0, 0, $width, $height
);
return $this;
}
/**
* Saves image to the file.
* @param string filename
* @param int quality (0..100 for JPEG and WEBP, 0..9 for PNG)
* @param int optional image type
* @return bool TRUE on success or FALSE on failure.
*/
public function save($file = NULL, $quality = NULL, $type = NULL)
{
if ($type === NULL) {
$extensions = array_flip(self::$formats) + ['jpg' => self::JPEG];
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (!isset($extensions[$ext])) {
throw new Nette\InvalidArgumentException("Unsupported file extension '$ext'.");
}
$type = $extensions[$ext];
}
switch ($type) {
case self::JPEG:
$quality = $quality === NULL ? 85 : max(0, min(100, (int) $quality));
return imagejpeg($this->image, $file, $quality);
case self::PNG:
$quality = $quality === NULL ? 9 : max(0, min(9, (int) $quality));
return imagepng($this->image, $file, $quality);
case self::GIF:
return imagegif($this->image, $file);
case self::WEBP:
$quality = $quality === NULL ? 80 : max(0, min(100, (int) $quality));
return imagewebp($this->image, $file, $quality);
default:
throw new Nette\InvalidArgumentException("Unsupported image type '$type'.");
}
}
/**
* Outputs image to string.
* @param int image type
* @param int quality (0..100 for JPEG and WEBP, 0..9 for PNG)
* @return string
*/
public function toString($type = self::JPEG, $quality = NULL)
{
ob_start(function () {});
$this->save(NULL, $quality, $type);
return ob_get_clean();
}
/**
* Outputs image to string.
* @return string
*/
public function __toString()
{
try {
return $this->toString();
} catch (\Throwable $e) {
} catch (\Exception $e) {
}
if (isset($e)) {
if (func_num_args()) {
throw $e;
}
trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
/**
* Outputs image to browser.
* @param int image type
* @param int quality (0..100 for JPEG and WEBP, 0..9 for PNG)
* @return bool TRUE on success or FALSE on failure.
*/
public function send($type = self::JPEG, $quality = NULL)
{
if (!isset(self::$formats[$type])) {
throw new Nette\InvalidArgumentException("Unsupported image type '$type'.");
}
header('Content-Type: image/' . self::$formats[$type]);
return $this->save(NULL, $quality, $type);
}
/**
* Call to undefined method.
*
* @param string method name
* @param array arguments
* @return mixed
* @throws Nette\MemberAccessException
*/
public function __call($name, $args)
{
$function = 'image' . $name;
if (!function_exists($function)) {
ObjectMixin::strictCall(get_class($this), $name);
}
foreach ($args as $key => $value) {
if ($value instanceof self) {
$args[$key] = $value->getImageResource();
} elseif (is_array($value) && isset($value['red'])) { // rgb
$args[$key] = imagecolorallocatealpha(
$this->image,
$value['red'], $value['green'], $value['blue'], $value['alpha']
) ?: imagecolorresolvealpha(
$this->image,
$value['red'], $value['green'], $value['blue'], $value['alpha']
);
}
}
$res = $function($this->image, ...$args);
return is_resource($res) && get_resource_type($res) === 'gd' ? $this->setImageResource($res) : $res;
}
public function __clone()
{
ob_start(function () {});
imagegd2($this->image);
$this->setImageResource(imagecreatefromstring(ob_get_clean()));
}
}
PK ^GMЕ src/Utils/DateTime.phpnu W+A format('Y-m-d H:i:s.u'), $time->getTimezone());
} elseif (is_numeric($time)) {
if ($time <= self::YEAR) {
$time += time();
}
return (new static('@' . $time))->setTimeZone(new \DateTimeZone(date_default_timezone_get()));
} else { // textual or NULL
return new static($time);
}
}
/**
* Creates DateTime object.
* @return static
*/
public static function fromParts($year, $month, $day, $hour = 0, $minute = 0, $second = 0)
{
$s = sprintf("%04d-%02d-%02d %02d:%02d:%02.5f", $year, $month, $day, $hour, $minute, $second);
if (!checkdate($month, $day, $year) || $hour < 0 || $hour > 23 || $minute < 0 || $minute > 59 || $second < 0 || $second >= 60) {
throw new Nette\InvalidArgumentException("Invalid date '$s'");
}
return new static($s);
}
/**
* @return string
*/
public function __toString()
{
return $this->format('Y-m-d H:i:s');
}
/**
* @param string
* @return static
*/
public function modifyClone($modify = '')
{
$dolly = clone $this;
return $modify ? $dolly->modify($modify) : $dolly;
}
/**
* @param int
* @return static
*/
public function setTimestamp($timestamp)
{
$zone = $this->getTimezone();
$this->__construct('@' . $timestamp);
return $this->setTimeZone($zone);
}
/**
* @return int|string
*/
public function getTimestamp()
{
$ts = $this->format('U');
return is_float($tmp = $ts * 1) ? $ts : $tmp;
}
/**
* Returns new DateTime object formatted according to the specified format.
* @param string The format the $time parameter should be in
* @param string String representing the time
* @param string|\DateTimeZone desired timezone (default timezone is used if NULL is passed)
* @return static|FALSE
*/
public static function createFromFormat($format, $time, $timezone = NULL)
{
if ($timezone === NULL) {
$timezone = new \DateTimeZone(date_default_timezone_get());
} elseif (is_string($timezone)) {
$timezone = new \DateTimeZone($timezone);
} elseif (!$timezone instanceof \DateTimeZone) {
throw new Nette\InvalidArgumentException('Invalid timezone given');
}
$date = parent::createFromFormat($format, $time, $timezone);
return $date ? static::from($date) : FALSE;
}
/**
* Returns JSON representation in ISO 8601 (used by JavaScript).
* @return string
*/
public function jsonSerialize()
{
return $this->format('c');
}
}
PK ^GM]t
! src/Iterators/RecursiveFilter.phpnu W+A getInnerIterator()->hasChildren();
}
public function getChildren()
{
return new static($this->getInnerIterator()->getChildren(), $this->callback);
}
}
PK ^GMHcu src/Iterators/Filter.phpnu W+A callback = Nette\Utils\Callback::check($callback);
}
public function accept()
{
return call_user_func($this->callback, $this->current(), $this->key(), $this);
}
}
PK ^GMM M ! src/Iterators/CachingIterator.phpnu W+A getIterator();
} while ($iterator instanceof \IteratorAggregate);
} elseif ($iterator instanceof \Traversable) {
if (!$iterator instanceof \Iterator) {
$iterator = new \IteratorIterator($iterator);
}
} else {
throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', __CLASS__, is_object($iterator) ? get_class($iterator) : gettype($iterator)));
}
parent::__construct($iterator, 0);
}
/**
* Is the current element the first one?
* @param int grid width
* @return bool
*/
public function isFirst($width = NULL)
{
return $this->counter === 1 || ($width && $this->counter !== 0 && (($this->counter - 1) % $width) === 0);
}
/**
* Is the current element the last one?
* @param int grid width
* @return bool
*/
public function isLast($width = NULL)
{
return !$this->hasNext() || ($width && ($this->counter % $width) === 0);
}
/**
* Is the iterator empty?
* @return bool
*/
public function isEmpty()
{
return $this->counter === 0;
}
/**
* Is the counter odd?
* @return bool
*/
public function isOdd()
{
return $this->counter % 2 === 1;
}
/**
* Is the counter even?
* @return bool
*/
public function isEven()
{
return $this->counter % 2 === 0;
}
/**
* Returns the counter.
* @return int
*/
public function getCounter()
{
return $this->counter;
}
/**
* Returns the count of elements.
* @return int
*/
public function count()
{
$inner = $this->getInnerIterator();
if ($inner instanceof \Countable) {
return $inner->count();
} else {
throw new Nette\NotSupportedException('Iterator is not countable.');
}
}
/**
* Forwards to the next element.
* @return void
*/
public function next()
{
parent::next();
if (parent::valid()) {
$this->counter++;
}
}
/**
* Rewinds the Iterator.
* @return void
*/
public function rewind()
{
parent::rewind();
$this->counter = parent::valid() ? 1 : 0;
}
/**
* Returns the next key.
* @return mixed
*/
public function getNextKey()
{
return $this->getInnerIterator()->key();
}
/**
* Returns the next element.
* @return mixed
*/
public function getNextValue()
{
return $this->getInnerIterator()->current();
}
}
PK ^GMJR R src/Iterators/Mapper.phpnu W+A callback = $callback;
}
public function current()
{
return call_user_func($this->callback, parent::current(), parent::key());
}
}
PK ^GM"A@. readme.mdnu W+A Nette Utility Classes
=====================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/utils.svg)](https://packagist.org/packages/nette/utils)
[![Build Status](https://travis-ci.org/nette/utils.svg?branch=master)](https://travis-ci.org/nette/utils)
[![Coverage Status](https://coveralls.io/repos/github/nette/utils/badge.svg?branch=master)](https://coveralls.io/github/nette/utils?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/utils/v/stable)](https://github.com/nette/utils/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/utils/blob/master/license.md)
Nette\SmartObject: Strict classes
---------------------------------
PHP gives a huge freedom to developers, which makes it a perfect language for making mistakes. But you can stop this bad behavior and start writing applications without hardly discoverable mistakes. Do you wonder how? It's really simple -- you just need to have stricter rules.
Can you find an error in this example?
```php
class Circle
{
public $radius;
public function getArea()
{
return $this->radius * $this->radius * M_PI;
}
}
$circle = new Circle;
$circle->raduis = 10;
echo $circle->getArea(); // 10² * π ≈ 314
```
On the first look it seems that code will print out 314; but it returns 0. How is this even possible? Accidentaly, `$circle->radius` was mistyped to `raduis`. Just a small typo, which will give you a hard time correcting it, because PHP does not say a thing when something is wrong. Not even a Warning or Notice error message. Because PHP does not think it is an error.
The mentioned mistake could be corrected immediately, if class `Circle` would use trait Nette\SmartObject:
```php
class Circle
{
use Nette\SmartObject;
...
```
Whereas the former code executed successfully (although it contained an error), the latter did not:
![](https://files.nette.org/git/doc-2.1/debugger-circle.png)
Trait `Nette\SmartObject` made `Circle` more strict and threw an exception when you tried to access an undeclared property. And `Tracy\Debugger` displayed error message about it. Line of code with fatal typo is now highlighted and error message has meaningful description: *Cannot write to an undeclared property Circle::$raduis*. Programmer can now fix the mistake he might have otherwise missed and which could be a real pain to find later.
One of many remarkable abilities of `Nette\SmartObject` is throwing exceptions when accessing undeclared members.
```php
$circle = new Circle;
echo $circle->undeclared; // throws Nette\MemberAccessException
$circle->undeclared = 1; // throws Nette\MemberAccessException
$circle->unknownMethod(); // throws Nette\MemberAccessException
```
But it has much more to offer!
Properties, getters a setters
-----------------------------
In modern object oriented languages *property* describes members of class, which look like variables but are represented by methods. When reading or assigning values to those "variables", methods are called instead (so-called getters and setters). It is really useful feature, which allows us to control the access to these variables. Using this we can validate inputs or postpone the computation of values of these variables to the time when it is actually accessed.
Any class that uses `Nette\SmartObject` acquires the ability to imitate properties. Only thing you need to do is to keep simple convention:
- Add annotation `@property type $xyz`
- Getter's name is `getXyz()` or `isXyz()`, setter's is `setXyz()`
- It is possible to have `@property-read` only and `@property-write` only properties
- Names of properties are case-sensitive (first letter being an exception)
We will make use of properties in the class Circle to make sure variable `$radius` contains only non-negative numbers:
```php
/**
* @property float $radius
* @property-read float $area
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private $radius; // not public anymore!
protected function getRadius()
{
return $this->radius;
}
protected function setRadius($radius)
{
// sanitizing value before saving it
$this->radius = max(0.0, (float) $radius);
}
protected function getArea()
{
return $this->radius * $this->radius * M_PI;
}
protected function isVisible()
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // calls setRadius()
echo $circle->area; // calls getArea()
echo $circle->visible; // calls $circle->isVisible()
```
Properties are mostly a syntactic sugar to beautify the code and make programmer's life easier. You do not have to use them, if you don't want to.
Events
------
Now we are going to create functions, which will be called when border radius changes. Let's call it `change` event and those functions event handlers:
```php
class Circle
{
use Nette\SmartObject;
/** @var array */
public $onChange;
public function setRadius($radius)
{
// call all handlers in array $onChange
$this->onChange($this, $this->radius, $radius);
$this->radius = max(0.0, (float) $radius);
}
}
$circle = new Circle;
// adding an event handler
$circle->onChange[] = function ($circle, $oldValue, $newValue) {
echo 'there was a change!';
};
$circle->setRadius(10);
```
There is another syntactic sugar in `setRadius`'s code. Instead of iteration on `$onChange` array and calling each method one by one with unreliable (does not report if callback has any errors) function call_user_func, you just have to write simple `onChange(...)` and given parameters will be handed over to the handlers.
PK ^GMF
composer.jsonnu W+A {
"name": "nette/utils",
"description": "Nette Utility Classes",
"homepage": "https://nette.org",
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=5.6.0"
},
"require-dev": {
"nette/tester": "~2.0",
"tracy/tracy": "^2.3"
},
"suggest": {
"ext-iconv": "to use Strings::webalize() and toAscii()",
"ext-json": "to use Nette\\Utils\\Json",
"ext-intl": "for script transliteration in Strings::webalize() and toAscii()",
"ext-mbstring": "to use Strings::lower() etc...",
"ext-xml": "to use Strings::length() etc. when mbstring is not available",
"ext-gd": "to use Image"
},
"conflict": {
"nette/nette": "<2.2"
},
"autoload": {
"classmap": ["src/"]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
}
}
PK ^GM.<
license.mdnu W+A PK ^GMhrD D
contributing.mdnu W+A PK ^GMr a src/Utils/IHtmlString.phpnu W+A PK ^GM[yS S src/Utils/StaticClass.phpnu W+A PK ^GM&. J src/Utils/ITranslator.phpnu W+A PK ^GM"$4 $ src/Utils/Random.phpnu W+A PK ^GM+Z v src/Utils/Paginator.phpnu W+A PK ^GM= = . src/Utils/Strings.phpnu W+A PK ^GMս l src/Utils/Arrays.phpnu W+A PK ^GM[R= = src/Utils/ObjectMixin.phpnu W+A PK ^GM6k k src/Utils/Callback.phpnu W+A PK ^GM src/Utils/SmartObject.phpnu W+A PK ^GMF src/Utils/Validators.phpnu W+A PK ^GM6(2qv v $ src/Utils/ArrayList.phpnu W+A PK ^GMY{ { src/Utils/FileSystem.phpnu W+A PK ^GM / src/Utils/exceptions.phpnu W+A PK ^GMh揧5 5 ; src/Utils/Html.phpnu W+A PK ^GM>^s sq src/Utils/Reflection.phpnu W+A PK ^GMʙs y src/Utils/Object.phpnu W+A PK ^GMd}9 9 n src/Utils/Json.phpnu W+A PK ^GM(h6a a src/Utils/ArrayHash.phpnu W+A PK ^GMmN7J J src/Utils/Image.phpnu W+A PK ^GMЕ src/Utils/DateTime.phpnu W+A PK ^GM]t
! src/Iterators/RecursiveFilter.phpnu W+A PK ^GMHcu src/Iterators/Filter.phpnu W+A PK ^GMM M ! src/Iterators/CachingIterator.phpnu W+A PK ^GMJR R src/Iterators/Mapper.phpnu W+A PK ^GM"A@. J readme.mdnu W+A PK ^GMF
w1 composer.jsonnu W+A PK 5