crayon-syntax-highlighter/util/crayon_util.class.php

929 lines
29 KiB
PHP

<?php
/* Common utility functions mainly for formatting, parsing etc. */
class CrayonUtil
{
// Used to detect touchscreen devices
private static $touchscreen = NULL;
/* Return the lines inside a file as an array, options:
l - lowercase
w - remove whitespace
r - escape regex chars
c - remove comments
s - return as string */
public static function lines($path, $opts = NULL)
{
$path = self::pathf($path);
if (($str = self::file($path)) === FALSE) {
// Log failure, n = no log
if (strpos($opts, 'n') === FALSE) {
CrayonLog::syslog("Cannot read lines at '$path'.", "CrayonUtil::lines()");
}
return FALSE;
}
// Read the options
if (is_string($opts)) {
$lowercase = strpos($opts, 'l') !== FALSE;
$whitespace = strpos($opts, 'w') !== FALSE;
$escape_regex = strpos($opts, 'r') !== FALSE;
$clean_commments = strpos($opts, 'c') !== FALSE;
$return_string = strpos($opts, 's') !== FALSE;
// $escape_hash = strpos($opts, 'h') !== FALSE;
} else {
$lowercase = $whitespace = $escape_regex = $clean_commments = $return_string = /*$escape_hash =*/
FALSE;
}
// Remove comments
if ($clean_commments) {
$str = self::clean_comments($str);
}
// Convert to lowercase if needed
if ($lowercase) {
$str = strtolower($str);
}
/* Match all the content on non-empty lines, also remove any whitespace to the left and
right if needed */
if ($whitespace) {
$pattern = '[^\s]+(?:.*[^\s])?';
} else {
$pattern = '^(?:.*)?';
}
preg_match_all('|' . $pattern . '|m', $str, $matches);
$lines = $matches[0];
// Remove regex syntax and assume all characters are literal
if ($escape_regex) {
for ($i = 0; $i < count($lines); $i++) {
$lines[$i] = self::esc_regex($lines[$i]);
// if ($escape_hash || true) {
// If we have used \#, then we don't want it to become \\#
$lines[$i] = preg_replace('|\\\\\\\\#|', '\#', $lines[$i]);
// }
}
}
// Return as string if needed
if ($return_string) {
// Add line breaks if they were stripped
$delimiter = '';
if ($whitespace) {
$delimiter = CRAYON_NL;
}
$lines = implode($delimiter, $lines);
}
return $lines;
}
// Returns the contents of a file
public static function file($path)
{
if (($str = @file_get_contents($path)) === FALSE) {
return FALSE;
} else {
return $str;
}
}
/**
* Zips a source file or directory.
*
* @param $src A directory or file
* @param $dest A directory or zip file. If a zip file is provided it must exist beforehand.
*/
public static function createZip($src, $dest, $removeExistingZip = FALSE)
{
if ($src == $dest) {
throw new InvalidArgumentException("Source '$src' and '$dest' cannot be the same");
}
if (is_dir($src)) {
$src = CrayonUtil::path_slash($src);
$base = $src;
// Make sure the destination isn't in the files
$files = self::getFiles($src, array('recursive' => TRUE, 'ignore' => array($dest)));
} else if (is_file($src)) {
$files = array($src);
$base = dirname($src);
} else {
throw new InvalidArgumentException("Source '$src' is not a directory or file");
}
if (is_dir($dest)) {
$dest = CrayonUtil::path_slash($dest);
$zipFile = $dest . basename($src) . '.zip';
} else if (is_file($dest)) {
$zipFile = $dest;
} else {
throw new InvalidArgumentException("Destination '$dest' is not a directory or file");
}
if ($removeExistingZip) {
@unlink($zipFile);
}
$zip = new ZipArchive;
if ($zip->open($zipFile, ZIPARCHIVE::CREATE) === TRUE) {
foreach ($files as $file) {
$relFile = str_replace($base, '', $file);
$zip->addFile($file, $relFile);
}
$zip->close();
} else {
throw new Exception("Could not create zip file at '$zipFile'");
}
return $zipFile;
}
/**
* Sends an email in html and plain encodings with a file attachment.
*
* @param array $args Arguments associative array
* 'to' (string)
* 'from' (string)
* 'subject' (optional string)
* 'message' (HTML string)
* 'plain' (optional plain string)
* 'file' (optional file path of the attachment)
* @see http://webcheatsheet.com/php/send_email_text_html_attachment.php
*/
public static function emailFile($args)
{
$to = self::set_default($args['to']);
$from = self::set_default($args['from']);
$subject = self::set_default($args['subject'], '');
$message = self::set_default($args['message'], '');
$plain = self::set_default($args['plain'], '');
$file = self::set_default($args['file']);
// MIME
$random_hash = md5(date('r', time()));
$boundaryMixed = 'PHP-mixed-' . $random_hash;
$boundaryAlt = 'PHP-alt-' . $random_hash;
$charset = 'UTF-8';
$bits = '8bit';
// Headers
$headers = "MIME-Version: 1.0";
$headers .= "Reply-To: $to\r\n";
if ($from !== NULL) {
$headers .= "From: $from\r\n";
}
$headers .= "Content-Type: multipart/mixed; boundary=$boundaryMixed";
if ($file !== NULL) {
$info = pathinfo($file);
$filename = $info['filename'];
$extension = $info['extension'];
$contents = @file_get_contents($file);
if ($contents === FALSE) {
throw new Exception("File contents of '$file' could not be read");
}
$chunks = chunk_split(base64_encode($contents));
$attachment = <<<EOT
--$boundaryMixed
Content-Type: application/$extension; name=$filename.$extension
Content-Transfer-Encoding: base64
Content-Disposition: attachment
$chunks
EOT;
} else {
$attachment = '';
}
$body = <<<EOT
--$boundaryMixed
Content-Type: multipart/alternative; boundary=$boundaryAlt
--$boundaryAlt
Content-Type: text/plain; charset="$charset"
Content-Transfer-Encoding: $bits
$plain
--$boundaryAlt
Content-Type: text/html; charset="$charset"
Content-Transfer-Encoding: $bits
$message
--$boundaryAlt--
$attachment
--$boundaryMixed--
EOT;
$result = @mail($to, $subject, $body, $headers);
return $result;
}
/**
* @param $path A directory
* @param array $args Argument array:
* hidden: If true, hidden files beginning with a dot will be included
* ignoreRef: If true, . and .. are ignored
* recursive: If true, this function is recursive
* ignore: An array of paths to ignore
* @return array Files in the directory
*/
public static function getFiles($path, $args = array())
{
$hidden = self::set_default($args['hidden'], TRUE);
$ignoreRef = self::set_default($args['ignoreRef'], TRUE);
$recursive = self::set_default($args['recursive'], FALSE);
$ignore = self::set_default($args['ignore'], NULL);
$ignore_map = array();
if ($ignore) {
foreach ($ignore as $i) {
if (is_dir($i)) {
$i = CrayonUtil::path_slash($i);
}
$ignore_map[$i] = TRUE;
}
}
$files = glob($path . '*', GLOB_MARK);
if ($hidden) {
$files = array_merge($files, glob($path . '.*', GLOB_MARK));
}
if ($ignoreRef || $ignore) {
$result = array();
for ($i = 0; $i < count($files); $i++) {
$file = $files[$i];
if (!isset($ignore_map[$file]) && (!$ignoreRef || (basename($file) != '.' && basename($file) != '..'))) {
$result[] = $file;
if ($recursive && is_dir($file)) {
$result = array_merge($result, self::getFiles($file, $args));
}
}
}
} else {
$result = $files;
}
return $result;
}
public static function deleteDir($path)
{
if (!is_dir($path)) {
throw new InvalidArgumentException("deleteDir: $path is not a directory");
}
if (substr($path, strlen($path) - 1, 1) != '/') {
$path .= '/';
}
$files = self::getFiles($path);
foreach ($files as $file) {
if (is_dir($file)) {
self::deleteDir($file);
} else {
unlink($file);
}
}
rmdir($path);
}
public static function copyDir($src, $dst, $mkdir = NULL)
{
// http://stackoverflow.com/questions/2050859
if (!is_dir($src)) {
throw new InvalidArgumentException("copyDir: $src is not a directory");
}
$dir = opendir($src);
if ($mkdir !== NULL) {
call_user_func($mkdir, $dst);
} else {
@mkdir($dst, 0777, TRUE);
}
while (false !== ($file = readdir($dir))) {
if (($file != '.') && ($file != '..')) {
if (is_dir($src . '/' . $file)) {
self::copyDir($src . '/' . $file, $dst . '/' . $file);
} else {
copy($src . '/' . $file, $dst . '/' . $file);
}
}
}
closedir($dir);
}
// Supports arrays in the values
public static function array_flip($array)
{
$result = array();
foreach ($array as $k => $v) {
if (is_array($v)) {
foreach ($v as $u) {
self::_array_flip($result, $k, $u);
}
} else {
self::_array_flip($result, $k, $v);
}
}
return $result;
}
private static function _array_flip(&$array, $k, $v)
{
if (is_string($v) || is_int($v)) {
$array[$v] = $k;
} else {
trigger_error("Values must be STRING or INTEGER", E_USER_WARNING);
}
}
// Detects if device is touchscreen or mobile
public static function is_touch()
{
// Only detect once
if (self::$touchscreen !== NULL) {
return self::$touchscreen;
}
if (($devices = self::lines(CRAYON_TOUCH_FILE, 'lw')) !== FALSE) {
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
return false;
}
// Create array of device strings from file
$user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
self::$touchscreen = (self::strposa($user_agent, $devices) !== FALSE);
return self::$touchscreen;
} else {
CrayonLog::syslog('Error occurred when trying to identify touchscreen devices');
}
}
// Removes duplicates in array, ensures they are all strings
public static function array_unique_str($array)
{
if (!is_array($array) || empty($array)) {
return array();
}
for ($i = 0; $i < count($array); $i++) {
$array[$i] = strval($array[$i]);
}
return array_unique($array);
}
// Same as array_key_exists, but returns the key when exists, else FALSE;
public static function array_key_exists($key, $array)
{
if (!is_array($array) || empty($array) || !is_string($key) || empty($key)) {
FALSE;
}
if (array_key_exists($key, $array)) {
return $array[$key];
}
}
// Performs explode() on a string with the given delimiter and trims all whitespace
public static function trim_e($str, $delimiter = ',')
{
if (is_string($delimiter)) {
$str = trim(preg_replace('|\s*(?:' . preg_quote($delimiter) . ')\s*|', $delimiter, $str));
return explode($delimiter, $str);
}
return $str;
}
/* Creates an array of integers based on a given range string of format "int - int"
Eg. range_str('2 - 5'); */
public static function range_str($str)
{
preg_match('#(\d+)\s*-\s*(\d+)#', $str, $matches);
if (count($matches) == 3) {
return range($matches[1], $matches[2]);
}
return FALSE;
}
// Creates an array out of a single range string (e.i "x-y")
public static function range_str_single($str)
{
$match = preg_match('#(\d+)(?:\s*-\s*(\d+))?#', $str, $matches);
if ($match > 0) {
if (empty($matches[2])) {
$matches[2] = $matches[1];
}
if ($matches[1] <= $matches[2]) {
return array($matches[1], $matches[2]);
}
}
return FALSE;
}
// Sets a variable to a string if valid
public static function str(&$var, $str, $escape = TRUE)
{
if (is_string($str)) {
$var = ($escape == TRUE ? self::htmlentities($str) : $str);
return TRUE;
}
return FALSE;
}
// Converts all special characters to entities
public static function htmlentities($str)
{
return htmlentities($str, ENT_COMPAT, 'UTF-8');
}
public static function html_entity_decode($str)
{
return html_entity_decode($str, ENT_QUOTES, 'UTF-8');
}
// Converts <, >, & into entities
public static function htmlspecialchars($str)
{
return htmlspecialchars($str, ENT_NOQUOTES, 'UTF-8');
}
// Sets a variable to an int if valid
public static function num(&$var, $num)
{
if (is_numeric($num)) {
$var = intval($num);
return TRUE;
}
return FALSE;
}
// Sets a variable to an array if valid
public static function arr(&$var, $array)
{
if (is_array($array)) {
$var = $array;
return TRUE;
}
return FALSE;
}
// Sets a variable to an array if valid
public static function set_array($var, $array, $false = FALSE)
{
return isset($array[$var]) ? $array[$var] : $false;
}
// Sets a variable to null if not set
public static function set_var(&$var, $false = NULL)
{
$var = isset($var) ? $var : $false;
}
// Sets a variable to null if not set
public static function set_default(&$var, $default = NULL)
{
return isset($var) ? $var : $default;
}
public static function set_default_null($var, $default = NULL)
{
return $var !== NULL ? $var : $default;
}
// Thanks, http://www.php.net/manual/en/function.str-replace.php#102186
function str_replace_once($str_pattern, $str_replacement, $string)
{
if (strpos($string, $str_pattern) !== FALSE) {
$occurrence = strpos($string, $str_pattern);
return substr_replace($string, $str_replacement, strpos($string, $str_pattern), strlen($str_pattern));
}
return $string;
}
// Removes non-numeric chars in string
public static function clean_int($str, $return_zero = TRUE)
{
$str = preg_replace('#[^\d]#', '', $str);
if ($return_zero) {
// If '', then returns 0
return strval(intval($str));
} else {
// Might be ''
return $str;
}
}
// Replaces whitespace with hypthens
public static function space_to_hyphen($str)
{
return preg_replace('#\s+#', '-', $str);
}
// Replaces hypthens with spaces
public static function hyphen_to_space($str)
{
return preg_replace('#-#', ' ', $str);
}
// Remove comments with /* */, // or #, if they occur before any other char on a line
public static function clean_comments($str)
{
$comment_pattern = '#(?:^\s*/\*.*?^\s*\*/)|(?:^(?!\s*$)[\s]*(?://|\#)[^\r\n]*)#ms';
$str = preg_replace($comment_pattern, '', $str);
return $str;
}
// Convert to title case and replace underscores with spaces
public static function ucwords($str)
{
$str = strval($str);
$str = str_replace('_', ' ', $str);
return ucwords($str);
}
// Escapes regex characters as literals
public static function esc_regex($regex)
{
return /*htmlspecialchars(*/
preg_quote($regex) /* , ENT_NOQUOTES)*/
;
}
// Escapes hash character as literals
public static function esc_hash($regex)
{
if (is_string($regex)) {
return preg_replace('|(?<!\\\\)#|', '\#', $regex);
} else {
return FALSE;
}
}
// Ensure all parenthesis are atomic to avoid conflicting with element matches
public static function esc_atomic($regex)
{
return preg_replace('#(?<!\\\\)\((?!\?)#', '(?:', $regex);
}
// Returns the current HTTP URL
public static function current_url()
{
$p = self::isSecure() ? "https://" : "http://";
return $p . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
}
// Removes crayon plugin path from absolute path
public static function path_rel($url)
{
if (is_string($url)) {
return str_replace(CRAYON_ROOT_PATH, '/', $url);
}
return $url;
}
// Returns path according to detected use of forwardslash/backslash
// Deprecated from regular use after v.1.1.1
public static function path($path, $detect)
{
$slash = self::detect_slash($detect);
return str_replace(array('\\', '/'), $slash, $path);
}
// Detect which kind of slash is being used in a path
public static function detect_slash($path)
{
if (strpos($path, '\\')) {
// Windows
return $slash = '\\';
} else {
// UNIX
return $slash = '/';
}
}
// Returns path using forward slashes
public static function pathf($url)
{
return str_replace('\\', '/', trim(strval($url)));
}
// Returns path using back slashes
public static function pathb($url)
{
return str_replace('/', '\\', trim(strval($url)));
}
// returns 'true' or 'false' depending on whether this PHP file was served over HTTPS
public static function isSecure()
{
// From https://core.trac.wordpress.org/browser/tags/4.0.1/src/wp-includes/functions.php
if (isset($_SERVER['HTTPS'])) {
if ('on' == strtolower($_SERVER['HTTPS']))
return true;
if ('1' == $_SERVER['HTTPS'])
return true;
} elseif (isset($_SERVER['SERVER_PORT']) && ('443' == $_SERVER['SERVER_PORT'])) {
return true;
}
return false;
}
public static function startsWith($haystack, $needle)
{
return substr($haystack, 0, strlen($needle)) === $needle;
}
// Append either forward slash or backslash based on environment to paths
public static function path_slash($path)
{
$path = self::pathf($path);
if (!empty($path) && !preg_match('#\/$#', $path)) {
$path .= '/';
}
if (self::startsWith($path, 'http://') && self::isSecure()) {
$path = str_replace('http://', 'https://', $path);
}
return $path;
}
public static function path_slash_remove($path)
{
return preg_replace('#\/+$#', '', $path);
}
// Append a forward slash to a path if needed
public static function url_slash($url)
{
$url = self::pathf($url);
if (!empty($url) && !preg_match('#\/$#', $url)) {
$url .= '/';
}
if (self::startsWith($url, 'http://') && self::isSecure()) {
$url = str_replace('http://', 'https://', $url);
}
return $url;
}
// Removes extension from file path
public static function path_rem_ext($path)
{
$path = self::pathf($path);
return preg_replace('#\.\w+$#m', '', $path);
}
// Shorten a URL into a string of given length, used to identify a URL uniquely
public static function shorten_url_to_length($url, $length)
{
if ($length < 1) {
return '';
}
$url = preg_replace('#(^\w+://)|([/\.])#si', '', $url);
if (strlen($url) > $length) {
$diff = strlen($url) - $length;
$rem = floor(strlen($url) / $diff);
$rem_count = 0;
for ($i = $rem - 1; $i < strlen($url) && $rem_count < $diff; $i = $i + $rem) {
$url[$i] = '.';
$rem_count++;
}
$url = preg_replace('#\.#s', '', $url);
}
return $url;
}
// Creates a unique ID from a string
public static function get_var_str()
{
$get_vars = array();
foreach ($_GET as $get => $val) {
$get_vars[] = $get . '=' . $val;
}
return implode('&', $get_vars);
}
// Creates a unique ID from a string
public static function str_uid($str)
{
$uid = 0;
for ($i = 1; $i < strlen($str); $i++) {
$uid += round(ord($str[$i]) * ($i / strlen($str)), 2) * 100;
}
return strval(dechex(strlen($str))) . strval(dechex($uid));
}
// Breaks up a version string into parts
public static function version_parts($version)
{
preg_match('#[\d+\.]+#msi', $version, $match);
if (count($match[0])) {
return explode('.', $match[0]);
} else {
return array();
}
}
// Compares two version strings lexicographically
public static function version_compare($a, $b)
{
$a_parts = self::version_parts($a);
$b_parts = self::version_parts($b);
return self::array_compare_lexi($a_parts, $b_parts);
}
// Compares two arrays lexicographically
// This could be extended with a compare function argument
public static function array_compare_lexi($a, $b)
{
$short = count($a) < count($b) ? $a : $b;
for ($i = 0; $i < count($short); $i++) {
if ($a[$i] > $b[$i]) {
return 1;
} else if ($a[$i] < $b[$i]) {
return -1;
}
}
return 0;
}
// strpos with an array of $needles
public static function strposa($haystack, $needles, $insensitive = FALSE)
{
if (is_array($needles)) {
foreach ($needles as $str) {
if (is_array($str)) {
$pos = self::strposa($haystack, $str, $insensitive);
} else {
$pos = $insensitive ? stripos($haystack, $str) : strpos($haystack, $str);
}
if ($pos !== FALSE) {
return $pos;
}
}
return FALSE;
} else {
return strpos($haystack, $needles);
}
}
// tests if $needle is equal to any strings in $haystack
public static function str_equal_array($needle, $haystack, $case_insensitive = TRUE)
{
if (!is_string($needle) || !is_array($haystack)) {
return FALSE;
}
if ($case_insensitive) {
$needle = strtolower($needle);
}
foreach ($haystack as $hay) {
if (!is_string($hay)) {
continue;
}
if ($case_insensitive) {
$hay = strtolower($hay);
}
if ($needle == $hay) {
return TRUE;
}
}
return FALSE;
}
// Support for singular and plural string variations
public static function spnum($int, $singular, $plural = NULL)
{
if (!is_int($int) || !is_string($singular)) {
$int = intval($int);
$singular = strval($singular);
}
if ($plural == NULL || !is_string($plural)) {
$plural = $singular . 's';
}
return $int . ' ' . (($int == 1) ? $singular : $plural);
}
// Turn boolean into Yes/No
public static function bool_yn($bool)
{
return $bool ? 'Yes' : 'No';
}
// String to boolean, default decides what boolean value to return when not found
public static function str_to_bool($str, $default = TRUE)
{
$str = self::tlower($str);
if ($default === FALSE) {
if ($str == 'true' || $str == 'yes' || $str == '1') {
return TRUE;
} else {
return FALSE;
}
} else {
if ($str == 'false' || $str == 'no' || $str == '0') {
return FALSE;
} else {
return TRUE;
}
}
}
public static function bool_to_str($bool, $strict = FALSE)
{
if ($strict) {
return $bool === TRUE ? 'true' : 'false';
} else {
return $bool ? 'true' : 'false';
}
}
public static function tlower($str)
{
return trim(strtolower($str));
}
// Escapes $ and \ from the replacement to avoid becoming a backreference
public static function preg_replace_escape_back($pattern, $replacement, $subject, $limit = -1, &$count = 0)
{
return preg_replace($pattern, self::preg_escape_back($replacement), $subject, $limit, $count);
}
// Escape backreferences from string for use with regex
public static function preg_escape_back($string)
{
// Replace $ with \$ and \ with \\
$string = preg_replace('#(\\$|\\\\)#', '\\\\$1', $string);
return $string;
}
// Detect if on a Mac or PC
public static function is_mac($default = FALSE)
{
$user = $_SERVER['HTTP_USER_AGENT'];
if (stripos($user, 'macintosh') !== FALSE) {
return TRUE;
} else if (stripos($user, 'windows') !== FALSE || stripos($user, 'linux') !== FALSE) {
return FALSE;
} else {
return $default === TRUE;
}
}
// Decodes WP html entities
public static function html_entity_decode_wp($str)
{
if (!is_string($str) || empty($str)) {
return $str;
}
// http://www.ascii.cl/htmlcodes.htm
$wp_entities = array('&#8216;', '&#8217;', '&#8218;', '&#8220;', '&#8221;');
$wp_replace = array('\'', '\'', ',', '"', '"');
$str = str_replace($wp_entities, $wp_replace, $str);
return $str;
}
// Constructs an html element
// If $content = FALSE, then element is closed
public static function html_element($name, $content = NULL, $attributes = array())
{
$atts = self::html_attributes($attributes);
$tag = "<$name $atts";
$tag .= $content === FALSE ? '/>' : ">$content</$name>";
return $tag;
}
public static function html_attributes($attributes, $assign = '=', $quote = '"', $glue = ' ')
{
$atts = '';
foreach ($attributes as $k => $v) {
$atts .= $k . $assign . $quote . $v . $quote . $glue;
}
return $atts;
}
// Strips only the given tags in the given HTML string.
public static function strip_tags_blacklist($html, $tags)
{
foreach ($tags as $tag) {
$regex = '#<\s*\b' . $tag . '\b[^>]*>.*?<\s*/\s*' . $tag . '\b[^>]*>?#msi';
$html = preg_replace($regex, '', $html);
}
return $html;
}
// Strips the given attributes found in the given HTML string.
public static function strip_attributes($html, $atts)
{
foreach ($atts as $att) {
$regex = '#\b' . $att . '\b(\s*=\s*[\'"][^\'"]*[\'"])?(?=[^<]*>)#msi';
$html = preg_replace($regex, '', $html);
}
return $html;
}
// Strips all event attributes on DOM elements (prefixe with "on").
public static function strip_event_attributes($html)
{
$regex = '#\bon\w+\b(\s*=\s*[\'"][^\'"]*[\'"])?(?=[^<]*>)#msi';
return preg_replace($regex, '', $html);
}
}
?>