<?php
namespace XF\Util;
use function defined, get_class, intval, is_array, is_integer, is_object, is_string, ord, strlen, strval;
class Php
{
/**
* Validates a callback more strictly and with more detailed errors.
*
* @param string|object|array $class A class name, object, function name, or array containing class/object and method
* @param null|string $method If first param is class or object, the method name
* @param string $error Error key returned by reference
* @param bool $forceMethod If true, if no method is provided, never treat the class as a function
*
* @return bool
*
* @throws \InvalidArgumentException
*/
public static function validateCallback($class, $method = null, &$error = null, $forceMethod = true)
{
if (is_array($class))
{
if ($method)
{
throw new \InvalidArgumentException('Method cannot be provided with class as array');
}
$method = $class[1];
$class = $class[0];
}
if ($forceMethod)
{
$method = strval($method);
}
else
{
if (!$method)
{
if (is_object($class))
{
throw new \InvalidArgumentException('Object given with no method');
}
if (!function_exists($class))
{
$error = 'invalid_function';
return false;
}
else
{
return true;
}
}
}
if (!is_string($method))
{
throw new \InvalidArgumentException('Method to check is not a string');
}
if (!is_object($class))
{
if (!$class || preg_match('[\\\\\\\\]', $class) || !class_exists($class))
{
$error = 'invalid_class';
return false;
}
}
$reflectionClass = new \ReflectionClass($class);
$isObject = is_object($class);
if (
($isObject && $reflectionClass->hasMethod('__call'))
|| (!$isObject && $reflectionClass->hasMethod('__callStatic'))
)
{
// magic method will always be called if a method can't be
return true;
}
if (!$method || !$reflectionClass->hasMethod($method))
{
$error = 'invalid_method';
return false;
}
$reflectionMethod = $reflectionClass->getMethod($method);
if ($reflectionMethod->isAbstract() || !$reflectionMethod->isPublic())
{
$error = 'invalid_method_configuration';
return false;
}
$isStatic = $reflectionMethod->isStatic();
if ($isStatic && $isObject)
{
$error = 'method_static';
return false;
}
else if (!$isStatic && !$isObject)
{
$error = 'method_not_static';
return false;
}
return true;
}
/**
* Does a detailed validation of a callback and returns the error
* in a ready to print phrase
*
* @param string|object|array $class A class name, object, function name, or array containing class/object and method
* @param null|string $method If first param is class or object, the method name
* @param null|\XF\Phrase $errorPhrase If an error occurs, outputs the phrase
* @param bool $forceMethod If true, if no method is provided, never treat the class as a function
*
* @return bool
*/
public static function validateCallbackPhrased($class, $method = null, &$errorPhrase = null, $forceMethod = true)
{
$success = self::validateCallback($class, $method, $error, $forceMethod);
if ($success)
{
return true;
}
$printableCallback = self::getPrintableCallback($class, $method);
$innerErrorPhrase = \XF::phrase('error_' . $error);
$errorPhrase = \XF::phrase('callback_x_invalid_y', [
'callback' => $printableCallback,
'error' => $innerErrorPhrase
]);
return false;
}
/**
* Returns a callback in a simple printable form
*
* @param string|object|array $class A class name, object, function name, or array containing class/object and method
* @param null|string $method If first param is class or object, the method name
*
* @return string
*
* @throws \InvalidArgumentException
*/
public static function getPrintableCallback($class, $method = null)
{
if (is_array($class))
{
if ($method)
{
throw new \InvalidArgumentException('Method cannot be provided with class as array');
}
$method = $class[1];
$class = $class[0];
}
if (!$method)
{
if (is_object($class))
{
throw new \InvalidArgumentException('Object given with no method');
}
return strval($class);
}
if (!is_string($method))
{
throw new \InvalidArgumentException('Method must be a string when given an object');
}
if (is_object($class))
{
return get_class($class) . '->' . $method;
}
else
{
return $class . '::' . $method;
}
}
public static function nameIndicatesReadOnly($name)
{
$matches = [
'first',
'last'
];
if (preg_match('/^(' . implode('|', $matches) . ')$/i', $name))
{
return true;
}
$prefixes = [
'are',
'can',
'count',
'data',
'display',
'does',
'exists',
'fetch',
'filter',
'find',
'get',
'has',
'is',
'pluck',
'print',
'render',
'return',
'show',
'total',
'validate',
'verify',
'view',
];
if (preg_match('/^(' . implode('|', $prefixes) . ')/i', $name))
{
return true;
}
return false;
}
/**
* @param $known
* @param $user
*
* @return bool
* @deprecated PHP 5.6 polyfill no longer required, use hash_equals directly.
*/
public static function hashEquals($known, $user)
{
if (function_exists('hash_equals'))
{
return hash_equals($known, $user);
}
if (!is_string($known))
{
trigger_error('Expected known_string to be a string', E_USER_WARNING);
return false;
}
if (!is_string($user))
{
trigger_error('Expected user_string to be a string', E_USER_WARNING);
return false;
}
$knownLen = strlen($known);
$userLen = strlen($user);
if ($knownLen !== $userLen)
{
return false;
}
$result = 0;
for ($i = 0; $i < $knownLen; $i++)
{
$result |= ord($known[$i]) ^ ord($user[$i]);
}
return ($result === 0);
}
/**
* Unserializes a string, avoiding unserializing potentially dangerous objects.
*
* If an object is present, unserialization will fail and false will be returned.
*
* See serializedContainsObject for comments on false positives.
*
* @param string $serialized
*
* @return bool|mixed
*/
public static function safeUnserialize($serialized)
{
if (self::serializedContainsObject($serialized))
{
return false;
}
return @unserialize($serialized, ['allowed_classes' => false]);
}
/**
* Serializes a string only if it doesn't contain object constructs. This can be paired with safeUnserialize
* to block the serialization if unserialization will fail anyway. (Serialization itself is safe, but if it's going
* to fail to unserialize, it likely shouldn't be allowed.)
*
* See serializedContainsObject for comments on false positives.
*
* @param string $toSerialize
*
* @return string
*/
public static function safeSerialize($toSerialize)
{
$serialized = serialize($toSerialize);
if (self::serializedContainsObject($serialized))
{
throw new \InvalidArgumentException("Serialized value contains an object and this is not allowed");
}
return $serialized;
}
/**
* This detects if a serialized string may contain an object definition.
* This can trigger a false positive if a string matches the format but it should be unlikely.
*
* This function could be implemented with a single, one-line regex but it has been optimized, particularly
* for the case that no object or object-like construct is present.
*
* @param string $serialized
*
* @return bool
*/
public static function serializedContainsObject($serialized)
{
if (strpos($serialized, 'O:') !== false && preg_match('#(?<=^|[;{}])O:[+-]?[0-9]+:"#', $serialized))
{
return true;
}
if (strpos($serialized, 'C:') !== false && preg_match('#(?<=^|[;{}])C:[+-]?[0-9]+:"#', $serialized))
{
return true;
}
if (strpos($serialized, 'o:') !== false && preg_match('#(?<=^|[;{}])o:[+-]?[0-9]+:"#', $serialized))
{
return true;
}
return false;
}
public static function camelCase($string, $glue = '_')
{
return str_replace(' ', '', utf8_ucwords(str_replace($glue, ' ', $string)));
}
public static function fromCamelCase($string, $glue = '_')
{
return preg_replace_callback('/([a-z])([A-Z])/', function($match) use ($glue)
{
return $match[1] . $glue . utf8_strtolower($match[2]);
}, $string);
}
public static function isValidRegex($regex, $addDelimiter = null)
{
if ($addDelimiter !== null)
{
$regex = $addDelimiter . $regex . $addDelimiter;
}
set_error_handler(function() {}, E_WARNING);
$isValidRegex = preg_match($regex, '') !== false;
restore_error_handler();
return $isValidRegex;
}
public static function invalidateOpcodeCache($file)
{
if (!file_exists($file) && substr($file, -4) == '.php')
{
return;
}
if (function_exists('opcache_invalidate'))
{
try
{
@opcache_invalidate($file);
}
catch (\Exception $e) {}
}
if (function_exists('apc_delete_file'))
{
try
{
@apc_delete_file($file);
}
catch (\Exception $e) {}
}
if (function_exists('accelerator_reset'))
{
// Zend Optimizer (probably shouldn't be used much, but no harm)
try
{
@accelerator_reset();
}
catch (\Exception $e) {}
}
}
public static function resetOpcache()
{
if (function_exists('opcache_reset'))
{
try
{
@opcache_reset();
}
catch (\Exception $e) {}
}
if (function_exists('apc_clear_cache'))
{
try
{
@apc_clear_cache();
}
catch (\Exception $e) {}
}
if (function_exists('accelerator_reset'))
{
// Zend Optimizer (probably shouldn't be used much, but no harm)
try
{
@accelerator_reset();
}
catch (\Exception $e) {}
}
}
public static function getUploadMaxFilesize()
{
return min(
self::getBytesFromPhpConfigValue('upload_max_filesize'),
self::getBytesFromPhpConfigValue('post_max_size')
);
}
protected static function getBytesFromPhpConfigValue($configParam)
{
$configValue = ini_get($configParam);
if (is_integer($configValue))
{
return $configValue;
}
else
{
$units = strtoupper(substr($configValue, -1));
$value = intval($configValue);
// note that KB, MB and GB are not actually valid in PHP config, but are frequently encountered
switch ($units)
{
case 'K':
case 'KB':
return $value * 1024;
case 'M':
case 'MB':
return $value * 1048576;
case 'G':
case 'GB':
return $value * 1073741824;
default:
return $value;
}
}
}
public static function convertErrorCodeToString($code)
{
switch ($code)
{
case E_ERROR: return 'E_ERROR';
case E_WARNING: return 'E_WARNING';
case E_PARSE: return 'E_PARSE';
case E_NOTICE: return 'E_NOTICE';
case E_CORE_ERROR: return 'E_CORE_ERROR';
case E_CORE_WARNING: return 'E_CORE_WARNING';
case E_COMPILE_ERROR: return 'E_COMPILE_ERROR';
case E_COMPILE_WARNING: return 'E_COMPILE_WARNING';
case E_USER_ERROR: return 'E_USER_ERROR';
case E_USER_WARNING: return 'E_USER_WARNING';
case E_USER_NOTICE: return 'E_USER_NOTICE';
case E_STRICT: return 'E_STRICT';
case E_RECOVERABLE_ERROR: return 'E_RECOVERABLE_ERROR';
case E_DEPRECATED: return 'E_DEPRECATED';
case E_USER_DEPRECATED: return 'E_USER_DEPRECATED';
default: return "$code";
}
}
public static function getEnvironmentReport()
{
$env = [
'curl_version' => function_exists('curl_version')
? curl_version()['version'] : false,
'ssl_version' => function_exists('curl_version')
? curl_version()['ssl_version'] : false,
'openssl_version' => defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : null,
'suhosin' => extension_loaded('suhosin') || defined('SUHOSIN_PATCH'),
'gzip' => function_exists('gzopen'),
'mbstring' => function_exists('mb_get_info'),
'exif' => function_exists('exif_read_data'),
'imagick' => class_exists('Imagick'),
'gmp' => function_exists('gmp_init'),
'zip' => class_exists('ZipArchive')
];
if (isset($_SERVER['SERVER_SOFTWARE']) && preg_match('/^(.+\/[\d\.]+)/', $_SERVER['SERVER_SOFTWARE'], $match))
{
$env['server_software'] = $match[1];
}
else
{
$env['server_software'] = false;
}
$env['phpVersion'] = phpversion();
if (version_compare($env['phpVersion'], '7.2.0', '>='))
{
$env['phpVersionState'] = 'recommended';
}
else if (version_compare($env['phpVersion'], '7.0.0', '>='))
{
$env['phpVersionState'] = 'not_newest';
}
else
{
$env['phpVersionState'] = 'minimum';
}
$db = \XF::db();
$env['mysqlVersion'] = $db->getServerVersion();
try
{
$mysqlVersionVar = $db->fetchOne('
SELECT @@version
');
if ($env['mysqlVersion'] != $mysqlVersionVar)
{
$env['mysqlVersion'] = "$env[mysqlVersion] ($mysqlVersionVar)";
}
}
catch (\XF\Db\Exception $e) {}
$iniVars = [
'memory_limit',
'post_max_size',
'upload_max_filesize',
'max_input_vars',
'max_execution_time'
];
$env['ini'] = [];
foreach ($iniVars AS $iniVar)
{
$env['ini'][$iniVar] = ini_get($iniVar);
}
return $env;
}
}