1<?php 2/** 3 * 4 * General utilities. 5 * 6 */ 7namespace CssCrush; 8 9class Util 10{ 11 public static function htmlAttributes(array $attributes, array $sort_order = null) 12 { 13 // Optionally sort attributes (for better readability). 14 if ($sort_order) { 15 uksort($attributes, function ($a, $b) use ($sort_order) { 16 $a_index = array_search($a, $sort_order); 17 $b_index = array_search($b, $sort_order); 18 $a_found = is_int($a_index); 19 $b_found = is_int($b_index); 20 21 if ($a_found && $b_found) { 22 if ($a_index == $b_index) { 23 return 0; 24 } 25 return $a_index > $b_index ? 1 : -1; 26 } 27 elseif ($a_found && ! $b_found) { 28 return -1; 29 } 30 elseif ($b_found && ! $a_found) { 31 return 1; 32 } 33 34 return strcmp($a, $b); 35 }); 36 } 37 38 $str = ''; 39 foreach ($attributes as $name => $value) { 40 $value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8', false); 41 $str .= " $name=\"$value\""; 42 } 43 return $str; 44 } 45 46 public static function normalizePath($path, $strip_drive_letter = false) 47 { 48 if (! $path) { 49 return ''; 50 } 51 52 if ($strip_drive_letter) { 53 $path = preg_replace('~^[a-z]\:~i', '', $path); 54 } 55 56 // Backslashes and repeat slashes to a single forward slash. 57 $path = rtrim(preg_replace('~[\\\\/]+~', '/', $path), '/'); 58 59 // Removing redundant './'. 60 $path = str_replace('/./', '/', $path); 61 if (strpos($path, './') === 0) { 62 $path = substr($path, 2); 63 } 64 65 return Util::simplifyPath($path); 66 } 67 68 public static function simplifyPath($path) 69 { 70 // Reduce redundant path segments. e.g 'foo/../bar' => 'bar' 71 $patt = '~[^/.]+/\.\./~S'; 72 while (preg_match($patt, $path)) { 73 $path = preg_replace($patt, '', $path); 74 } 75 return $path; 76 } 77 78 public static function resolveUserPath($path, $recovery = null, $docRoot = null) 79 { 80 // System path. 81 if ($realpath = realpath($path)) { 82 $path = $realpath; 83 } 84 else { 85 if (! $docRoot) { 86 $docRoot = isset(Crush::$process->docRoot) ? Crush::$process->docRoot : Crush::$config->docRoot; 87 } 88 89 // Absolute path. 90 if (strpos($path, '/') === 0) { 91 // If $path is not doc_root based assume it's doc_root relative and prepend doc_root. 92 if (strpos($path, $docRoot) !== 0) { 93 $path = $docRoot . $path; 94 } 95 } 96 // Relative path. Try resolving based on the directory of the executing script. 97 else { 98 $path = Crush::$config->scriptDir . '/' . $path; 99 } 100 101 if (! file_exists($path) && is_callable($recovery)) { 102 $path = $recovery($path); 103 } 104 $path = realpath($path); 105 } 106 107 return $path ? Util::normalizePath($path) : false; 108 } 109 110 public static function stripCommentTokens($str) 111 { 112 return preg_replace(Regex::$patt->c_token, '', $str); 113 } 114 115 public static function normalizeWhiteSpace($str) 116 { 117 static $find, $replace; 118 if (! $find) { 119 $replacements = array( 120 // Convert all whitespace sequences to a single space. 121 '~\s+~S' => ' ', 122 // Trim bracket whitespace where it's safe to do it. 123 '~([\[(]) | ([\])])| ?([{}]) ?~S' => '${1}${2}${3}', 124 // Trim whitespace around delimiters and special characters. 125 '~ ?([;,]) ?~S' => '$1', 126 ); 127 $find = array_keys($replacements); 128 $replace = array_values($replacements); 129 } 130 131 return preg_replace($find, $replace, $str); 132 } 133 134 public static function splitDelimList($str, $options = array()) 135 { 136 extract($options + array( 137 'delim' => ',', 138 'regex' => false, 139 'allow_empty_strings' => false, 140 )); 141 142 $str = trim($str); 143 144 if (! $regex && strpos($str, $delim) === false) { 145 return ! $allow_empty_strings && ! strlen($str) ? array() : array($str); 146 } 147 148 if ($match_count = preg_match_all(Regex::$patt->parens, $str, $matches)) { 149 $keys = array(); 150 foreach ($matches[0] as $index => &$value) { 151 $keys[] = "?$index?"; 152 } 153 $str = str_replace($matches[0], $keys, $str); 154 } 155 156 $list = $regex ? preg_split($regex, $str) : explode($delim, $str); 157 158 if ($match_count) { 159 foreach ($list as &$value) { 160 $value = str_replace($keys, $matches[0], $value); 161 } 162 } 163 164 $list = array_map('trim', $list); 165 166 return ! $allow_empty_strings ? array_filter($list, 'strlen') : $list; 167 } 168 169 public static function getLinkBetweenPaths($path1, $path2, $directories = true) 170 { 171 $path1 = trim(Util::normalizePath($path1, true), '/'); 172 $path2 = trim(Util::normalizePath($path2, true), '/'); 173 174 $link = ''; 175 176 if ($path1 != $path2) { 177 178 // Split the directory paths into arrays so we can compare segment by segment. 179 $path1_segs = explode('/', $path1); 180 $path2_segs = explode('/', $path2); 181 182 // Shift the segments until they are on different branches. 183 while (isset($path1_segs[0]) && isset($path2_segs[0]) && ($path1_segs[0] === $path2_segs[0])) { 184 array_shift($path1_segs); 185 array_shift($path2_segs); 186 } 187 188 $link = str_repeat('../', count($path1_segs)) . implode('/', $path2_segs); 189 } 190 191 $link = $link !== '' ? rtrim($link, '/') : ''; 192 193 // Append end slash if getting a link between directories. 194 if ($link && $directories) { 195 $link .= '/'; 196 } 197 198 return $link; 199 } 200 201 public static function filePutContents($file, $str) 202 { 203 if ($stream = fopen($file, 'w')) { 204 fwrite($stream, $str); 205 fclose($stream); 206 207 return true; 208 } 209 210 warning("Could not write file '$file'."); 211 212 return false; 213 } 214 215 public static function parseIni($path, $sections = false) 216 { 217 if (! ($result = @parse_ini_file($path, $sections))) { 218 notice("Ini file '$path' could not be parsed."); 219 220 return false; 221 } 222 return $result; 223 } 224 225 public static function readConfigFile($path) 226 { 227 require_once $path; 228 return Options::filter(get_defined_vars()); 229 } 230 231 /* 232 * Get raw value (useful if testing values that may or may not be a token). 233 */ 234 public static function rawValue($value) 235 { 236 if ($tokenType = Tokens::test($value)) { 237 if ($tokenType == 'u') { 238 $value = Crush::$process->tokens->get($value)->value; 239 } 240 elseif ($tokenType == 's') { 241 $value = Crush::$process->tokens->get($value); 242 } 243 } 244 245 return $value; 246 } 247 248 /* 249 * Encode integer to Base64 VLQ. 250 */ 251 public static function vlqEncode($value) 252 { 253 static $VLQ_BASE_SHIFT, $VLQ_BASE, $VLQ_BASE_MASK, $VLQ_CONTINUATION_BIT, $BASE64_MAP; 254 if (! $VLQ_BASE_SHIFT) { 255 $VLQ_BASE_SHIFT = 5; 256 $VLQ_BASE = 1 << $VLQ_BASE_SHIFT; 257 $VLQ_BASE_MASK = $VLQ_BASE - 1; 258 $VLQ_CONTINUATION_BIT = $VLQ_BASE; 259 $BASE64_MAP = str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'); 260 } 261 262 $vlq = $value < 0 ? ((-$value) << 1) + 1 : ($value << 1) + 0; 263 264 $encoded = ""; 265 do { 266 $digit = $vlq & $VLQ_BASE_MASK; 267 $vlq >>= $VLQ_BASE_SHIFT; 268 if ($vlq > 0) { 269 $digit |= $VLQ_CONTINUATION_BIT; 270 } 271 $encoded .= $BASE64_MAP[$digit]; 272 273 } while ($vlq > 0); 274 275 return $encoded; 276 } 277} 278