diff --git a/wire/core/Functions.php b/wire/core/Functions.php index 32dada8f..44b60671 100644 --- a/wire/core/Functions.php +++ b/wire/core/Functions.php @@ -680,7 +680,7 @@ function wireIconMarkupFile($filename, $class = '') { } /** - * Given a quantity of bytes (int), return readable string that refers to quantity in bytes, kB, MB, GB, etc. + * Given a quantity of bytes (int), return readable string that refers to quantity in bytes, kB, MB, GB and TB * * #pw-group-strings * @@ -690,96 +690,31 @@ function wireIconMarkupFile($filename, $class = '') { * - `1` (int): Same as `true` but with space between number and unit label. * - Or optionally specify the $options argument here if you do not need the $small argument. * @param array|int $options Options to modify default behavior, or if an integer then `decimals` option is assumed: - * - `decimals` (int): Number of decimals to use in returned value (default=0). + * - `decimals` (int|null): Number of decimals to use in returned value or NULL for auto (default=null). + * When null (auto) a decimal value of 1 is used when appropriate, for megabytes and higher (3.0.214+). * - `decimal_point` (string|null): Decimal point character, or null to detect from locale (default=null). * - `thousands_sep` (string|null): Thousands separator, or null to detect from locale (default=null). * - `small` (bool): If no $small argument was specified, you can optionally specify it in this $options array. - * - `type` (string): To force return value as specific type, specify one of: bytes, kilobytes, megabytes, gigabytes; or just: b, k, m, g. (3.0.148+ only) + * - `type` (string): To force return value as specific type, specify one of: bytes, kilobytes, megabytes, + * gigabytes, terabytes; or just: b, k, m, g, t. (3.0.148+ only, terabytes 3.0.214+). * @return string * */ function wireBytesStr($bytes, $small = false, $options = array()) { - - $defaults = array( - 'type' => '', - 'decimals' => 0, - 'decimal_point' => null, - 'thousands_sep' => null, - ); - - if(is_array($small)) { - $options = $small; - $small = isset($options['small']) ? $options['small'] : false; - } - if(!is_array($options)) $options = array('decimals' => (int) $options); - if(!is_int($bytes)) $bytes = (int) $bytes; - - $options = array_merge($defaults, $options); - $locale = array(); - $type = empty($options['type']) ? '' : strtolower(substr($options['type'], 0, 1)); - - // determine size value and units label - if($bytes < 1024 || $type === 'b') { - $val = $bytes; - if($small) { - $label = $val > 0 ? __('B', __FILE__) : ''; // bytes - } else if($val == 1) { - $label = __('byte', __FILE__); // singular 1-byte + if(is_array($small)) $options = $small; + if(!is_array($options)) { + if(ctype_digit("$options")) { + $options = array('decimals' => (int) $options); } else { - $label = __('bytes', __FILE__); // plural 2+ bytes (or 0 bytes) - } - } else if($bytes < 1000000 || $type === 'k') { - $val = $bytes / 1024; - $label = __('kB', __FILE__); // kilobytes - } else if($bytes < 1073741824 || $type === 'm') { - $val = $bytes / 1024 / 1024; - $label = __('MB', __FILE__); // megabytes - } else { - $val = $bytes / 1024 / 1024 / 1024; - $label = __('GB', __FILE__); // gigabytes - } - - // determine decimal point if not specified in $options - if($options['decimal_point'] === null) { - if($options['decimals'] > 0) { - // determine decimal point from locale - if(empty($locale)) $locale = localeconv(); - $options['decimal_point'] = empty($locale['decimal_point']) ? '.' : $locale['decimal_point']; - } else { - // no decimal point needed (not used) - $options['decimal_point'] = '.'; + $options = array(); } } - - // determine thousands separator if not specified in $options - if($options['thousands_sep'] === null) { - if($small || $val < 1000) { - // no thousands separator needed - $options['thousands_sep'] = ''; - } else { - // get thousands separator from current locale - if(empty($locale)) $locale = localeconv(); - $options['thousands_sep'] = empty($locale['thousands_sep']) ? '' : $locale['thousands_sep']; - } + if(is_int($small) && !isset($options['decimals'])) { + $options['decimals'] = $small; + } else if(is_bool($small)) { + $options['small'] = $small; } - - // format number to string - $str = number_format($val, $options['decimals'], $options['decimal_point'], $options['thousands_sep']); - - // in small mode remove numbers with decimals that consist only of zeros "0" - if($small && $options['decimals'] > 0) { - $test = substr($str, -1 * $options['decimals']); - if(((int) $test) === 0) { - $str = substr($str, 0, strlen($str) - ($options['decimals'] + 1)); // i.e. 123.00 => 123 - } else { - $str = rtrim($str, '0'); // i.e. 123.10 => 123.1 - } - } - - // append units label to number - $str .= ($small === true ? '' : ' ') . $label; - - return $str; + return wire()->sanitizer->getNumberTools()->bytesToStr($bytes, $options); } /** @@ -1158,7 +1093,7 @@ function wireInstanceOf($instance, $className, $autoload = true) { * @param string|callable $var * @param bool $syntaxOnly * @var string $callableName - * @return array + * @return bool * */ function wireIsCallable($var, $syntaxOnly = false, &$callableName = '') { @@ -1184,7 +1119,7 @@ function wireIsCallable($var, $syntaxOnly = false, &$callableName = '') { function wireCount($value) { if($value === null) return 0; if(is_array($value)) return count($value); - if(is_object($value) && $value instanceof \Countable) return count($value); + if($value instanceof \Countable) return count($value); return 1; } diff --git a/wire/core/Sanitizer.php b/wire/core/Sanitizer.php index 0c9b06bd..c37fdcaa 100644 --- a/wire/core/Sanitizer.php +++ b/wire/core/Sanitizer.php @@ -164,6 +164,12 @@ class Sanitizer extends Wire { * */ protected $textTools = null; + + /** + * @var null|WireNumberTools + * + */ + protected $numberTools = null; /** * Runtime caches @@ -5337,6 +5343,24 @@ class Sanitizer extends Wire { } return $this->textTools; } + + /** + * Get instance of WireNumberTools + * + * #pw-group-numbers + * #pw-group-other + * + * @return WireNumberTools + * @since 3.0.214 + * + */ + public function getNumberTools() { + if(!$this->numberTools) { + $this->numberTools = new WireNumberTools(); + $this->wire($this->numberTools); + } + return $this->numberTools; + } /********************************************************************************************************************** * FILE VALIDATORS diff --git a/wire/core/WireNumberTools.php b/wire/core/WireNumberTools.php index 8016b00d..f02d3ea3 100644 --- a/wire/core/WireNumberTools.php +++ b/wire/core/WireNumberTools.php @@ -11,6 +11,14 @@ */ class WireNumberTools extends Wire { + /** + * Caches for methods in this class + * + * @var array + * + */ + protected $caches = array(); + /** * Generate and return an installation unique number/ID (integer) * @@ -90,4 +98,208 @@ class WireNumberTools extends Wire { return $uniqueNum; } + /** + * Return a random integer (cryptographically secure when available) + * + * @param int $min Minimum value (default=0) + * @param int $max Maximum value (default=PHP_INT_MAX) + * @param bool $throw Throw WireException if we cannot achieve a cryptographically secure random number? (default=false) + * @return int + * @since 3.0.214 + * + */ + public function randomInteger($min, $max, $throw = false) { + $rand = new WireRandom(); + return $rand->integer($min, $max, array('cryptoSecure' => $throw)); + } + + /** + * Given a value like "1M", "2MB", "3 kB", "4 GB", "5tb" etc. return quantity of bytes + * + * Spaces, commas and case in given value do not matter. Only the first character of the unit is + * taken into account, whether it appears in the given value, or is given in the $unit argument. + * Meaning a unit like megabytes (for example) can be specified as 'm', 'mb', 'megabytes', etc. + * + * @param string|int|float $value + * @param string|null $unit Optional unit that given value is in (b, kb, mb, gb, tb), or omit to auto-detect + * @return int + * @since 3.0.214 + * + */ + public function strToBytes($value, $unit = null) { + + if(is_int($value) && $unit === null) return $value; + + $value = str_replace(array(' ', ','), '', "$value"); + + if(ctype_digit("$value")) { + $value = (int) $value; + } else { + $value = trim("$value"); + $negative = strpos($value, '-') === 0; + if($negative) $value = ltrim($value, '-'); + if(preg_match('/^([\d.]+)([bkmgt])/i', $value, $matches)) { + $value = strpos($matches[1], '.') !== false ? (float) $matches[1] : (int) $matches[1]; + if($unit === null) $unit = $matches[2]; + } + if($negative) $value *= -1; + } + + if(is_string($unit)) switch(substr(strtolower($unit), 0, 1)) { + case 'b': $value *= 1; break; // bytes + case 'k': $value *= 1024; break; // kilobytes + case 'm': $value *= (1024 * 1024); break; // megabytes + case 'g': $value *= (1024 * 1024 * 1024); break; // gigabytes + case 't': $value *= (1024 * 1024 * 1024 * 1024); break; // terabytes + } + + if(is_float($value)) $value = (int) round($value); + + return (int) $value; + } + + /** + * Given a quantity of bytes (int), return readable string that refers to quantity in bytes, kB, MB, GB and TB + * + * @param int|string $bytes Quantity in bytes (int) or any string accepted by strToBytes method. + * @param array|int $options Options to modify default behavior, or if an integer then `decimals` option is assumed: + * - `decimals` (int|null): Number of decimals to use in returned value or NULL for auto (default=null). + * When null (auto) a decimal value of 1 is used when appropriate, for megabytes and higher (3.0.214+). + * - `decimal_point` (string|null): Decimal point character, or null to detect from locale (default=null). + * - `thousands_sep` (string|null): Thousands separator, or null to detect from locale (default=null). + * - `small` (bool|int): Make returned string as small as possible? false=no, true=yes, 1=yes with space (default=false) + * - `labels` (array): Labels to use for units, indexed by: b, byte, bytes, k, m, g, t + * - `type` (string): To force return value as specific type, specify one of: bytes, kilobytes, megabytes, + * gigabytes, terabytes; or just: b, k, m, g, t. (3.0.148+ only, terabytes 3.0.214+). + * @return string + * @since 3.0.214 All versions can also use the wireBytesStr() function + * + */ + public function bytesToStr($bytes, $options = array()) { + + $defaults = array( + 'type' => '', + 'small' => false, + 'decimals' => null, + 'decimal_point' => null, + 'thousands_sep' => null, + 'labels' => array(), + ); + + if(is_string($bytes) && !ctype_digit($bytes)) { + $bytes = $this->strToBytes($bytes); + } + + $bytes = (int) $bytes; + $options = array_merge($defaults, $options); + $type = empty($options['type']) ? '' : strtolower(substr($options['type'], 0, 1)); + $small = isset($options['small']) ? $options['small'] : false; + $labels = $options['labels']; + + if($options['decimals'] === null) { + if($bytes > 1024 && empty($options['type'])) { + // auto decimals (use 1 decimal for megabytes and higher) + $options['decimals'] = 1; + } else { + $options['decimals'] = 0; + } + } + + // determine size value and units label + if($bytes < 1024 || $type === 'b') { + // bytes + $val = $bytes; + if($small) { + $label = $val > 0 ? (isset($labels['b']) ? $labels['b'] : $this->_('B')) : ''; // bytes + } else if($val == 1) { + $label = isset($labels['byte']) ? $labels['byte'] : $this->_('byte'); // singular 1-byte + } else { + $label = isset($labels['bytes']) ? $labels['bytes'] : $this->_('bytes'); // plural 2+ bytes (or 0 bytes) + } + } else if($bytes < 1000000 || $type === 'k') { + // kilobytes + $val = $bytes / 1024; + $label = isset($labels['k']) ? $labels['k'] : $this->_('kB'); + } else if($bytes < 1073741824 || $type === 'm') { + // megabytes + $val = $bytes / 1024 / 1024; + $label = isset($labels['m']) ? $labels['m'] : $this->_('MB'); + } else if($bytes < 1099511627776 || $type === 'g') { + // gigabytes + $val = $bytes / 1024 / 1024 / 1024; + $label = isset($labels['g']) ? $labels['g'] : $this->_('GB'); + } else { + // terabytes + $val = $bytes / 1024 / 1024 / 1024 / 1024; + $label = isset($labels['t']) ? $labels['t'] : $this->_('TB'); + } + + // determine decimal point if not specified in $options + if($options['decimal_point'] === null) { + if($options['decimals'] > 0) { + $options['decimal_point'] = $this->locale('decimal_point'); + } else { + // no decimal point needed (not used) + $options['decimal_point'] = '.'; + } + } + + // determine thousands separator if not specified in $options + if($options['thousands_sep'] === null) { + if($small || $val < 1000) { + // no thousands separator needed + $options['thousands_sep'] = ''; + } else { + $options['thousands_sep'] = $this->locale('thousands_sep'); + } + } + + // format number to string + $str = number_format($val, $options['decimals'], $options['decimal_point'], $options['thousands_sep']); + + // in small mode remove numbers with decimals that consist only of zeros "0" + if($small && $options['decimals'] > 0) { + $test = substr($str, -1 * $options['decimals']); + if(((int) $test) === 0) { + $str = substr($str, 0, strlen($str) - ($options['decimals'] + 1)); // i.e. 123.00 => 123 + } else { + $str = rtrim($str, '0'); // i.e. 123.10 => 123.1 + } + } + + // append units label to number + $str .= ($small === true ? '' : ' ') . $label; + + return $str; + } + + /** + * Get a number formatting property from current locale + * + * In multi-language environments, this method’s return values are affected by the + * current language locale. + * + * @param string $key Property to get or omit to get all properties. Properties include: + * - `decimal_point`: Decimal point character + * - `thousands_sep`: Thousands separator + * - `currency_symbol`: Local currency symbol (i.e. $) + * - `int_curr_symbol`: International currency symbol (i.e. USD) + * - `mon_decimal_point`: Monetary decimal point character + * - `mon_thousands_sep`: Monetary thousands separator + * - `positive_sign`: Sign for positive values + * - `negative_sign`: Sign for negative values + * - `clear`: Clear any cached values for current language/locale. + * - See for more. + * @return array|string|int|null + * + */ + public function locale($key = '') { + $lang = $this->wire()->languages ? $this->wire()->user->language->id : ''; + $locale = "locale$lang"; + if($key === 'clear') unset($this->caches[$locale]); + if(empty($this->caches[$locale])) $this->caches[$locale] = localeconv(); + if($key === '') return $this->caches[$locale]; + return isset($this->caches[$locale][$key]) ? $this->caches[$locale][$key] : null; + } + } diff --git a/wire/core/WireRandom.php b/wire/core/WireRandom.php index 97402dfd..d25b2275 100644 --- a/wire/core/WireRandom.php +++ b/wire/core/WireRandom.php @@ -398,7 +398,6 @@ class WireRandom extends Wire { $count = count($a); $keys = array_keys($a); $items = array(); - $item = null; $keepKeys = true; // if getArray option not specified, auto determine from qty @@ -475,7 +474,6 @@ class WireRandom extends Wire { * */ public function arrayKey(array $a) { - $options['getKey'] = true; return $this->arrayItem($a, array('getKey' => true)); } @@ -941,4 +939,4 @@ class WireRandom extends Wire { protected function _strlen($s) { return function_exists('mb_strlen') ? mb_strlen($s, '8bit') : strlen($s); } -} \ No newline at end of file +}