├── test.php ├── README.md ├── support ├── str_basics.php └── cli.php └── find.php /test.php: -------------------------------------------------------------------------------- 1 | <-- Should be ignored. 3 | */?> 4 | '' 7 | // This line should be ignored. 8 | // This line should be ignored too. 9 | // This line should also be ignored. 10 | 11 | 12 | now what? $val) 10 | { 11 | if (is_string($val)) $_REQUEST[$key] = trim($val); 12 | else if (is_array($val)) 13 | { 14 | $_REQUEST[$key] = array(); 15 | foreach ($val as $key2 => $val2) $_REQUEST[$key][$key2] = (is_string($val2) ? trim($val2) : $val2); 16 | } 17 | else $_REQUEST[$key] = $val; 18 | } 19 | } 20 | 21 | // Cleans up all PHP input issues so that $_REQUEST may be used as expected. 22 | public static function ProcessAllInput() 23 | { 24 | self::ProcessSingleInput($_COOKIE); 25 | self::ProcessSingleInput($_GET); 26 | self::ProcessSingleInput($_POST); 27 | } 28 | 29 | public static function ExtractPathname($dirfile) 30 | { 31 | $dirfile = str_replace("\\", "/", $dirfile); 32 | $pos = strrpos($dirfile, "/"); 33 | if ($pos === false) $dirfile = ""; 34 | else $dirfile = substr($dirfile, 0, $pos + 1); 35 | 36 | return $dirfile; 37 | } 38 | 39 | public static function ExtractFilename($dirfile) 40 | { 41 | $dirfile = str_replace("\\", "/", $dirfile); 42 | $pos = strrpos($dirfile, "/"); 43 | if ($pos !== false) $dirfile = substr($dirfile, $pos + 1); 44 | 45 | return $dirfile; 46 | } 47 | 48 | public static function ExtractFileExtension($dirfile) 49 | { 50 | $dirfile = self::ExtractFilename($dirfile); 51 | $pos = strrpos($dirfile, "."); 52 | if ($pos !== false) $dirfile = substr($dirfile, $pos + 1); 53 | else $dirfile = ""; 54 | 55 | return $dirfile; 56 | } 57 | 58 | public static function ExtractFilenameNoExtension($dirfile) 59 | { 60 | $dirfile = self::ExtractFilename($dirfile); 61 | $pos = strrpos($dirfile, "."); 62 | if ($pos !== false) $dirfile = substr($dirfile, 0, $pos); 63 | 64 | return $dirfile; 65 | } 66 | 67 | // Makes an input filename safe for use. 68 | // Allows a very limited number of characters through. 69 | public static function FilenameSafe($filename) 70 | { 71 | return preg_replace('/\s+/', "-", trim(trim(preg_replace('/[^A-Za-z0-9_.\-]/', " ", $filename), "."))); 72 | } 73 | 74 | public static function ReplaceNewlines($replacewith, $data) 75 | { 76 | $data = str_replace("\r\n", "\n", $data); 77 | $data = str_replace("\r", "\n", $data); 78 | $data = str_replace("\n", $replacewith, $data); 79 | 80 | return $data; 81 | } 82 | 83 | public static function LineInput($data, &$pos) 84 | { 85 | $CR = ord("\r"); 86 | $LF = ord("\n"); 87 | 88 | $result = ""; 89 | $y = strlen($data); 90 | if ($pos > $y) $pos = $y; 91 | while ($pos < $y && ord($data[$pos]) != $CR && ord($data[$pos]) != $LF) 92 | { 93 | $result .= $data[$pos]; 94 | $pos++; 95 | } 96 | if ($pos + 1 < $y && ord($data[$pos]) == $CR && ord($data[$pos + 1]) == $LF) $pos++; 97 | if ($pos < $y) $pos++; 98 | 99 | return $result; 100 | } 101 | 102 | // Constant-time string comparison. Ported from CubicleSoft C++ code. 103 | public static function CTstrcmp($secret, $userinput) 104 | { 105 | $sx = 0; 106 | $sy = strlen($secret); 107 | $uy = strlen($userinput); 108 | $result = $sy - $uy; 109 | for ($ux = 0; $ux < $uy; $ux++) 110 | { 111 | $result |= ord($userinput[$ux]) ^ ord($secret[$sx]); 112 | $sx = ($sx + 1) % $sy; 113 | } 114 | 115 | return $result; 116 | } 117 | 118 | public static function ConvertUserStrToBytes($str) 119 | { 120 | $str = trim($str); 121 | $num = (double)$str; 122 | if (strtoupper(substr($str, -1)) == "B") $str = substr($str, 0, -1); 123 | switch (strtoupper(substr($str, -1))) 124 | { 125 | case "P": $num *= 1024; 126 | case "T": $num *= 1024; 127 | case "G": $num *= 1024; 128 | case "M": $num *= 1024; 129 | case "K": $num *= 1024; 130 | } 131 | 132 | return $num; 133 | } 134 | 135 | public static function ConvertBytesToUserStr($num) 136 | { 137 | $num = (double)$num; 138 | 139 | if ($num < 0) return "0 B"; 140 | if ($num < 1024) return number_format($num, 0) . " B"; 141 | if ($num < 1048576) return str_replace(".0 ", "", number_format($num / 1024, 1)) . " KB"; 142 | if ($num < 1073741824) return str_replace(".0 ", "", number_format($num / 1048576, 1)) . " MB"; 143 | if ($num < 1099511627776.0) return str_replace(".0 ", "", number_format($num / 1073741824.0, 1)) . " GB"; 144 | if ($num < 1125899906842624.0) return str_replace(".0 ", "", number_format($num / 1099511627776.0, 1)) . " TB"; 145 | 146 | return str_replace(".0 ", "", number_format($num / 1125899906842624.0, 1)) . " PB"; 147 | } 148 | 149 | public static function JSSafe($data) 150 | { 151 | return str_replace(array("'", "\r", "\n"), array("\\'", "\\r", "\\n"), $data); 152 | } 153 | } 154 | ?> -------------------------------------------------------------------------------- /find.php: -------------------------------------------------------------------------------- 1 | array( 21 | "?" => "help", 22 | ), 23 | "rules" => array( 24 | "ask" => array("arg" => false), 25 | "ext" => array("arg" => true, "multiple" => true), 26 | "help" => array("arg" => false) 27 | ) 28 | ); 29 | $args = CLI::ParseCommandLine($options); 30 | 31 | if (isset($args["opts"]["help"]) || count($args["params"]) != 1) 32 | { 33 | echo "Short open tag finder for PHP\n"; 34 | echo "Purpose: Finds short open tag references in PHP files.\n"; 35 | echo " Works under PHP 8 w/ pull request #3975 applied (https://github.com/php/php-src/pull/3975).\n"; 36 | echo "\n"; 37 | echo "Syntax: " . $args["file"] . " [options] directory\n"; 38 | echo "Options:\n"; 39 | echo "\t-ask Ask about the references that are found and modify the file only if the suggested changes are accepted.\n"; 40 | echo "\t-ext Additional case-insensitive file extension to look for. Default is 'php', 'php3', 'php4', 'php5', 'php7', and 'phtml'.\n"; 41 | echo "\n"; 42 | echo "Examples:\n"; 43 | echo "\tphp " . $args["file"] . " /var/www\n"; 44 | echo "\tphp " . $args["file"] . " -ext phpapp -ext html /var/www\n"; 45 | echo "\tphp " . $args["file"] . " -ask /var/www\n"; 46 | 47 | exit(); 48 | } 49 | 50 | // Very large PHP files will run the tokenizer out of RAM. 51 | ini_set("memory_limit", "-1"); 52 | 53 | $lastfile = ""; 54 | function OutputSplitter($currfile) 55 | { 56 | global $lastfile; 57 | 58 | if ($lastfile !== $currfile) 59 | { 60 | echo "--------------------------------------------------\n"; 61 | 62 | $lastfile = $currfile; 63 | } 64 | } 65 | 66 | // This function does the heavy lifting. 67 | function ScanFile($path, $file) 68 | { 69 | global $args, $exts, $filesscanned, $linesscanned, $numrefs, $numreffiles, $filesmodified, $linesmodified; 70 | 71 | $ext = strtolower(Str::ExtractFileExtension($file)); 72 | if (isset($exts[$ext])) 73 | { 74 | $exts[$ext]++; 75 | 76 | // Read in the file. 77 | $data = file_get_contents($path . "/" . $file); 78 | if (strlen($data) > 1000000) 79 | { 80 | OutputSplitter($path . "/" . $file); 81 | echo "File size warning while loading " . realpath($path . "/" . $file) . ": " . number_format(strlen($data), 0) . " bytes\n\n"; 82 | } 83 | $filesscanned++; 84 | 85 | // Modify the data for comparison later. 86 | $data2 = $data; 87 | do 88 | { 89 | $modified = false; 90 | 91 | // Be overly aggressive on RAM cleanup. 92 | if (function_exists("gc_mem_caches")) gc_mem_caches(); 93 | 94 | // Use the built-in PHP token parser. 95 | $tokens = token_get_all($data2); 96 | 97 | // On very large PHP files (> 1MB in size such as those generated by PHP Decomposer), RAM usage will skyrocket temporarily. 98 | if (memory_get_usage(true) > 100000000) 99 | { 100 | OutputSplitter($path . "/" . $file); 101 | echo "RAM usage warning while parsing " . realpath($path . "/" . $file) . ": " . number_format(memory_get_usage(true), 0) . " bytes\n\n"; 102 | } 103 | 104 | $data2 = ""; 105 | foreach ($tokens as $tnum => $token) 106 | { 107 | // If short open tags are enabled, this re-parses the content as if they were disabled. 108 | if (!$modified && is_array($token) && $token[0] === T_OPEN_TAG && $token[1] === "<" . "?") 109 | { 110 | if (!isset($tokens[$tnum + 1]) || (is_array($tokens[$tnum + 1]) && strtolower(trim(substr($tokens[$tnum + 1][1], 0, 3))) !== "xml" && strtolower(trim(substr($tokens[$tnum + 1][1], 0, 3))) !== "mso")) 111 | { 112 | $token[1] .= "php"; 113 | if (isset($tokens[$tnum + 1]) && ltrim($tokens[$tnum + 1][1]) === $tokens[$tnum + 1][1]) $token[1] .= " "; 114 | 115 | $numrefs++; 116 | $modified = true; 117 | } 118 | } 119 | 120 | // Standardizes on all lowercase 'php'. 121 | if (!$modified && is_array($token) && $token[0] === T_OPEN_TAG && rtrim(strtolower($token[1])) === "<" . "?php" && rtrim($token[1]) !== "<" . "?php") 122 | { 123 | $token[1] = "<" . "?php" . (string)substr($token[1], 5); 124 | 125 | $numrefs++; 126 | $modified = true; 127 | } 128 | 129 | // If short open tags are disabled, this finds the first entry and tries to parse it as an open tag. 130 | if (!$modified && is_array($token) && $token[0] === T_INLINE_HTML) 131 | { 132 | $tags = explode("<" . "?", $token[1]); 133 | if (count($tags) > 1) 134 | { 135 | $y = count($tags); 136 | for ($x = 1; $x < $y; $x++) 137 | { 138 | if (strtolower(trim(substr($tags[$x], 0, 3))) !== "xml" && strtolower(trim(substr($tags[$x], 0, 4))) !== "mso-") 139 | { 140 | $tags[$x] = "php" . ($tags[$x][0] !== "\t" && $tags[$x][0] !== " " && $tags[$x][0] !== "\r" && $tags[$x][0] !== "\n" ? " " : "") . $tags[$x]; 141 | 142 | $token[1] = implode("<" . "?", $tags); 143 | 144 | $numrefs++; 145 | $modified = true; 146 | 147 | break; 148 | } 149 | } 150 | } 151 | } 152 | 153 | $data2 .= (is_array($token) ? $token[1] : $token); 154 | } 155 | 156 | $tokens = false; 157 | } while ($modified); 158 | 159 | // Compare the two data blobs. 160 | $lines = explode("\n", $data); 161 | $linesscanned += count($lines); 162 | $lines2 = explode("\n", $data2); 163 | $numproposed = 0; 164 | foreach ($lines as $num => $line) 165 | { 166 | if ($lines[$num] !== $lines2[$num]) 167 | { 168 | OutputSplitter($path . "/" . $file); 169 | echo "Line " . ($num + 1) . " in " . realpath($path . "/" . $file) . ":\n"; 170 | echo " " . trim($line) . "\n"; 171 | $numproposed++; 172 | 173 | if (isset($args["opts"]["ask"])) 174 | { 175 | echo "=>\n"; 176 | echo " " . ltrim($lines2[$num]) . "\n\n"; 177 | } 178 | } 179 | } 180 | 181 | if ($numproposed) $numreffiles++; 182 | 183 | // Ask the user if they want to replace the file with the proposed changes applied (if the option has been enabled). 184 | if (isset($args["opts"]["ask"]) && $numproposed) 185 | { 186 | $args2 = array("opts" => array(), "params" => array()); 187 | 188 | $change = CLI::GetYesNoUserInputWithArgs($args2, false, ($numproposed == 1 ? "Accept change" : "Accept " . $numproposed . " changes"), "N"); 189 | 190 | // Apply all of the proposed changes for the file. 191 | if ($change) 192 | { 193 | file_put_contents($path . "/" . $file, $data2); 194 | 195 | $filesmodified++; 196 | $linesmodified += $numproposed; 197 | } 198 | } 199 | } 200 | } 201 | 202 | // This function scans a path recursively. 203 | function ScanPath($path) 204 | { 205 | global $exts; 206 | 207 | // Some OSes read directories in non-alphabetic order. Reorder the list of files. 208 | $files = array(); 209 | $dir = opendir($path); 210 | if ($dir) 211 | { 212 | while (($file = readdir($dir)) !== false) 213 | { 214 | if ($file !== "." && $file !== "..") 215 | { 216 | if (is_dir($path . "/" . $file) || isset($exts[strtolower(Str::ExtractFileExtension($file))])) $files[] = $file; 217 | } 218 | } 219 | 220 | closedir($dir); 221 | } 222 | 223 | sort($files, SORT_NATURAL | SORT_FLAG_CASE); 224 | 225 | // Now process the files. 226 | foreach ($files as $file) 227 | { 228 | if (is_dir($path . "/" . $file)) ScanPath($path . "/" . $file); 229 | else ScanFile($path, $file); 230 | } 231 | } 232 | 233 | // Path to scan. 234 | $path = realpath($args["params"][0]); 235 | if ($path === false) CLI::DisplayError("The supplied parameter '" . $args["params"][0] . "' does not exist."); 236 | 237 | // What file extensions to look at (and keep tabs on the number of). 238 | $exts = array( 239 | "php" => 0, 240 | "php3" => 0, 241 | "php4" => 0, 242 | "php5" => 0, 243 | "php7" => 0, 244 | "phtml" => 0 245 | ); 246 | 247 | if (!isset($args["opts"]["ext"])) $args["opts"]["ext"] = array(); 248 | foreach ($args["opts"]["ext"] as $ext) $exts[strtolower(ltrim($ext, "."))] = 0; 249 | 250 | // Other stats. 251 | $filesscanned = 0; 252 | $linesscanned = 0; 253 | $numrefs = 0; 254 | $numreffiles = 0; 255 | $filesmodified = 0; 256 | $linesmodified = 0; 257 | $extsfound = array(); 258 | 259 | // Start the scan. 260 | if (is_dir($path)) ScanPath($path); 261 | else 262 | { 263 | $file = Str::ExtractFilename($path); 264 | $path = substr($path, 0, -(strlen($file) + 1)); 265 | 266 | ScanFile($path, $file); 267 | } 268 | 269 | // Dump the statistics from the run. 270 | OutputSplitter(""); 271 | echo "\n"; 272 | echo "File extensions found in path (extension => # of instances):\n"; 273 | arsort($exts); 274 | foreach ($exts as $ext => $num) 275 | { 276 | if ($num) echo " ." . $ext . " => " . $num . "\n"; 277 | } 278 | 279 | echo "\n"; 280 | echo "Total files scanned: " . number_format($filesscanned, 0) . "\n"; 281 | echo "Total lines scanned: " . number_format($linesscanned, 0) . "\n"; 282 | echo "Total short open tag references: " . number_format($numrefs, 0) . "\n"; 283 | echo "Total files w/ short open tag references: " . number_format($numreffiles, 0) . "\n"; 284 | if (isset($args["opts"]["ask"])) 285 | { 286 | echo "Modified files: " . number_format($filesmodified, 0) . "\n"; 287 | echo "Modified lines: " . number_format($linesmodified, 0) . "\n"; 288 | } 289 | ?> -------------------------------------------------------------------------------- /support/cli.php: -------------------------------------------------------------------------------- 1 | $val) 15 | { 16 | if (!isset($options["rules"][$val])) unset($options["shortmap"][$key]); 17 | } 18 | foreach ($options["rules"] as $key => $val) 19 | { 20 | if (!isset($val["arg"])) $options["rules"][$key]["arg"] = false; 21 | if (!isset($val["multiple"])) $options["rules"][$key]["multiple"] = false; 22 | } 23 | 24 | if ($args === false) $args = $_SERVER["argv"]; 25 | else if (is_string($args)) 26 | { 27 | $args2 = $args; 28 | $args = array(); 29 | $inside = false; 30 | $currarg = ""; 31 | $y = strlen($args2); 32 | for ($x = 0; $x < $y; $x++) 33 | { 34 | $currchr = substr($args2, $x, 1); 35 | 36 | if ($inside === false && $currchr == " " && $currarg != "") 37 | { 38 | $args[] = $currarg; 39 | $currarg = ""; 40 | } 41 | else if ($currchr == "\"" || $currchr == "'") 42 | { 43 | if ($inside === false) $inside = $currchr; 44 | else if ($inside === $currchr) $inside = false; 45 | else $currarg .= $currchr; 46 | } 47 | else if ($currchr == "\\" && $x < $y - 1) 48 | { 49 | $x++; 50 | $currarg .= substr($args2, $x, 1); 51 | } 52 | else if ($inside !== false || $currchr != " ") 53 | { 54 | $currarg .= $currchr; 55 | } 56 | } 57 | 58 | if ($currarg != "") $args[] = $currarg; 59 | } 60 | 61 | $result = array("success" => true, "file" => array_shift($args), "opts" => array(), "params" => array()); 62 | 63 | // Look over shortmap to determine if options exist that are one byte (flags) and don't have arguments. 64 | $chrs = array(); 65 | foreach ($options["shortmap"] as $key => $val) 66 | { 67 | if (isset($options["rules"][$val]) && !$options["rules"][$val]["arg"]) $chrs[$key] = true; 68 | } 69 | 70 | $allowopt = true; 71 | $y = count($args); 72 | for ($x = 0; $x < $y; $x++) 73 | { 74 | $arg = $args[$x]; 75 | 76 | // Attempt to process an option. 77 | $opt = false; 78 | $optval = false; 79 | if ($allowopt && substr($arg, 0, 1) == "-") 80 | { 81 | $pos = strpos($arg, "="); 82 | if ($pos === false) $pos = strlen($arg); 83 | else $optval = substr($arg, $pos + 1); 84 | $arg2 = substr($arg, 1, $pos - 1); 85 | 86 | if (isset($options["rules"][$arg2])) $opt = $arg2; 87 | else if (isset($options["shortmap"][$arg2])) $opt = $options["shortmap"][$arg2]; 88 | else if ($x == 0) 89 | { 90 | // Attempt to process as a set of flags. 91 | $y2 = strlen($arg2); 92 | if ($y2 > 0) 93 | { 94 | for ($x2 = 0; $x2 < $y2; $x2++) 95 | { 96 | $currchr = substr($arg2, $x2, 1); 97 | 98 | if (!isset($chrs[$currchr])) break; 99 | } 100 | 101 | if ($x2 == $y2) 102 | { 103 | for ($x2 = 0; $x2 < $y2; $x2++) 104 | { 105 | $opt = $options["shortmap"][substr($arg2, $x2, 1)]; 106 | 107 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = true; 108 | else 109 | { 110 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = 0; 111 | $result["opts"][$opt]++; 112 | } 113 | } 114 | 115 | continue; 116 | } 117 | } 118 | } 119 | } 120 | 121 | if ($opt === false) 122 | { 123 | // Is a parameter. 124 | if (substr($arg, 0, 1) === "\"" || substr($arg, 0, 1) === "'") $arg = substr($arg, 1); 125 | if (substr($arg, -1) === "\"" || substr($arg, -1) === "'") $arg = substr($arg, 0, -1); 126 | 127 | $result["params"][] = $arg; 128 | 129 | if (!$options["allow_opts_after_param"]) $allowopt = false; 130 | } 131 | else if (!$options["rules"][$opt]["arg"]) 132 | { 133 | // Is a flag by itself. 134 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = true; 135 | else 136 | { 137 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = 0; 138 | $result["opts"][$opt]++; 139 | } 140 | } 141 | else 142 | { 143 | // Is an option. 144 | if ($optval === false) 145 | { 146 | $x++; 147 | if ($x == $y) break; 148 | $optval = $args[$x]; 149 | } 150 | 151 | if (substr($optval, 0, 1) === "\"" || substr($optval, 0, 1) === "'") $optval = substr($optval, 1); 152 | if (substr($optval, -1) === "\"" || substr($optval, -1) === "'") $optval = substr($optval, 0, -1); 153 | 154 | if (!$options["rules"][$opt]["multiple"]) $result["opts"][$opt] = $optval; 155 | else 156 | { 157 | if (!isset($result["opts"][$opt])) $result["opts"][$opt] = array(); 158 | $result["opts"][$opt][] = $optval; 159 | } 160 | } 161 | } 162 | 163 | return $result; 164 | } 165 | 166 | public static function CanGetUserInputWithArgs(&$args, $prefix) 167 | { 168 | return (($prefix !== false && isset($args["opts"][$prefix]) && is_array($args["opts"][$prefix]) && count($args["opts"][$prefix])) || count($args["params"])); 169 | } 170 | 171 | // Gets a line of input from the user. If the user supplies all information via the command-line, this could be entirely automated. 172 | public static function GetUserInputWithArgs(&$args, $prefix, $question, $default, $noparamsoutput = "", $suppressoutput = false, $callback = false, $callbackopts = false) 173 | { 174 | if (!self::CanGetUserInputWithArgs($args, $prefix) && $noparamsoutput != "") 175 | { 176 | echo "\n" . rtrim($noparamsoutput) . "\n\n"; 177 | 178 | $suppressoutput = false; 179 | $noparamsoutput = ""; 180 | } 181 | 182 | do 183 | { 184 | $prompt = ($suppressoutput ? "" : $question . ($default !== false ? " [" . $default . "]" : "") . ": "); 185 | 186 | if ($prefix !== false && isset($args["opts"][$prefix]) && is_array($args["opts"][$prefix]) && count($args["opts"][$prefix])) 187 | { 188 | $line = array_shift($args["opts"][$prefix]); 189 | if ($line === "") $line = $default; 190 | if (!$suppressoutput) echo $prompt . $line . "\n"; 191 | } 192 | else if (count($args["params"])) 193 | { 194 | $line = array_shift($args["params"]); 195 | if ($line === "") $line = $default; 196 | if (!$suppressoutput) echo $prompt . $line . "\n"; 197 | } 198 | else if (strtoupper(substr(php_uname("s"), 0, 3)) != "WIN" && function_exists("readline") && function_exists("readline_add_history")) 199 | { 200 | $line = readline($prompt); 201 | if ($line === false) exit(); 202 | 203 | $line = trim($line); 204 | if ($line === "") $line = $default; 205 | if ($line !== false && $line !== "") readline_add_history($line); 206 | } 207 | else 208 | { 209 | echo $prompt; 210 | fflush(STDOUT); 211 | $line = fgets(STDIN); 212 | if ($line === false || ($line === "" && feof(STDIN))) exit(); 213 | 214 | $line = trim($line); 215 | if ($line === "") $line = $default; 216 | } 217 | 218 | if ($line === false || (is_callable($callback) && !call_user_func_array($callback, array($line, &$callbackopts)))) 219 | { 220 | if ($line !== false) $line = false; 221 | else echo "Please enter a value.\n"; 222 | 223 | if (!self::CanGetUserInputWithArgs($args, $prefix) && $noparamsoutput != "") 224 | { 225 | echo "\n" . $noparamsoutput . "\n"; 226 | 227 | $noparamsoutput = ""; 228 | } 229 | 230 | $suppressoutput = false; 231 | } 232 | } while ($line === false); 233 | 234 | return $line; 235 | } 236 | 237 | // Obtains a valid line of input. 238 | public static function GetLimitedUserInputWithArgs(&$args, $prefix, $question, $default, $allowedoptionsprefix, $allowedoptions, $loop = true, $suppressoutput = false, $multipleuntil = false) 239 | { 240 | $noparamsoutput = $allowedoptionsprefix . "\n\n"; 241 | $size = 0; 242 | foreach ($allowedoptions as $key => $val) 243 | { 244 | if ($size < strlen($key)) $size = strlen($key); 245 | } 246 | 247 | foreach ($allowedoptions as $key => $val) 248 | { 249 | $newtab = str_repeat(" ", 2 + $size + 3); 250 | $noparamsoutput .= " " . $key . ":" . str_repeat(" ", $size - strlen($key)) . " " . str_replace("\n\t", "\n" . $newtab, $val) . "\n"; 251 | } 252 | 253 | $noparamsoutput .= "\n"; 254 | 255 | if ($default === false && count($allowedoptions) == 1) 256 | { 257 | reset($allowedoptions); 258 | $default = key($allowedoptions); 259 | } 260 | 261 | $results = array(); 262 | do 263 | { 264 | $displayed = (!count($args["params"])); 265 | $result = self::GetUserInputWithArgs($args, $prefix, $question, $default, $noparamsoutput, $suppressoutput); 266 | if (is_array($multipleuntil) && $multipleuntil["exit"] === $result) break; 267 | $result2 = false; 268 | if (!count($allowedoptions)) break; 269 | foreach ($allowedoptions as $key => $val) 270 | { 271 | if (!strcasecmp($key, $result) || !strcasecmp($val, $result)) $result2 = $key; 272 | } 273 | if ($loop) 274 | { 275 | if ($result2 === false) 276 | { 277 | echo "Please select an option from the list.\n"; 278 | 279 | $suppressoutput = false; 280 | } 281 | else if (is_array($multipleuntil)) 282 | { 283 | $results[$result2] = $result2; 284 | 285 | $question = $multipleuntil["nextquestion"]; 286 | $default = $multipleuntil["nextdefault"]; 287 | } 288 | } 289 | 290 | if ($displayed) $noparamsoutput = ""; 291 | } while ($loop && ($result2 === false || is_array($multipleuntil))); 292 | 293 | return (is_array($multipleuntil) ? $results : $result2); 294 | } 295 | 296 | // Obtains Yes/No style input. 297 | public static function GetYesNoUserInputWithArgs(&$args, $prefix, $question, $default, $noparamsoutput = "", $suppressoutput = false) 298 | { 299 | $default = (substr(strtoupper(trim($default)), 0, 1) === "Y" ? "Y" : "N"); 300 | 301 | $result = self::GetUserInputWithArgs($args, $prefix, $question, $default, $noparamsoutput, $suppressoutput); 302 | $result = (substr(strtoupper(trim($result)), 0, 1) === "Y"); 303 | 304 | return $result; 305 | } 306 | 307 | public static function GetHexDump($data) 308 | { 309 | $result = ""; 310 | 311 | $x = 0; 312 | $y = strlen($data); 313 | if ($y <= 256) $padwidth = 2; 314 | else if ($y <= 65536) $padwidth = 4; 315 | else if ($y <= 16777216) $padwidth = 6; 316 | else $padwidth = 8; 317 | 318 | $pad = str_repeat(" ", $padwidth); 319 | 320 | $data2 = str_split(strtoupper(bin2hex($data)), 32); 321 | foreach ($data2 as $line) 322 | { 323 | $result .= sprintf("%0" . $padwidth . "X", $x) . " | "; 324 | 325 | $line = str_split($line, 2); 326 | array_splice($line, 8, 0, ""); 327 | $result .= implode(" ", $line) . "\n"; 328 | 329 | $result .= $pad . " |"; 330 | $y2 = $x + 16; 331 | for ($x2 = 0; $x2 < 16 && $x < $y; $x2++) 332 | { 333 | $result .= " "; 334 | if ($x2 === 8) $result .= " "; 335 | 336 | $tempchr = ord($data[$x]); 337 | if ($tempchr === 0x09) $result .= "\\t"; 338 | else if ($tempchr === 0x0D) $result .= "\\r"; 339 | else if ($tempchr === 0x0A) $result .= "\\n"; 340 | else if ($tempchr === 0x00) $result .= "\\0"; 341 | else if ($tempchr < 32 || $tempchr > 126) $result .= " "; 342 | else $result .= " " . $data[$x]; 343 | 344 | $x++; 345 | } 346 | 347 | $result .= "\n"; 348 | } 349 | 350 | return $result; 351 | } 352 | 353 | // Outputs a JSON array (useful for captured output). 354 | public static function DisplayResult($result, $exit = true) 355 | { 356 | if (is_array($result)) echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"; 357 | else echo $result . "\n"; 358 | 359 | if ($exit) exit(); 360 | } 361 | 362 | // Useful for reparsing remaining parameters as new arguments. 363 | public static function ReinitArgs(&$args, $newargs) 364 | { 365 | // Process the parameters. 366 | $options = array( 367 | "shortmap" => array( 368 | "?" => "help" 369 | ), 370 | "rules" => array( 371 | ) 372 | ); 373 | 374 | foreach ($newargs as $arg) $options["rules"][$arg] = array("arg" => true, "multiple" => true); 375 | $options["rules"]["help"] = array("arg" => false); 376 | 377 | $args = self::ParseCommandLine($options, array_merge(array(""), $args["params"])); 378 | 379 | if (isset($args["opts"]["help"])) self::DisplayResult(array("success" => true, "options" => array_keys($options["rules"]))); 380 | } 381 | 382 | // Tracks messages for a command-line interface app. 383 | private static $messages = array(); 384 | 385 | public static function LogMessage($msg, $data = null) 386 | { 387 | if (isset($data)) $msg .= "\n\t" . trim(str_replace("\n", "\n\t", json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))); 388 | 389 | self::$messages[] = $msg; 390 | 391 | fwrite(STDERR, $msg . "\n"); 392 | } 393 | 394 | public static function DisplayError($msg, $result = false, $exit = true) 395 | { 396 | self::LogMessage(($exit ? "[Error] " : "") . $msg); 397 | 398 | if ($result !== false && is_array($result) && isset($result["error"]) && isset($result["errorcode"])) self::LogMessage("[Error] " . $result["error"] . " (" . $result["errorcode"] . ")", (isset($result["info"]) ? $result["info"] : null)); 399 | 400 | if ($exit) exit(); 401 | } 402 | 403 | public static function GetLogMessages($filters = array()) 404 | { 405 | if (is_string($filters)) $filters = array($filters); 406 | 407 | $result = array(); 408 | foreach (self::$messages as $message) 409 | { 410 | $found = (!count($filters)); 411 | foreach ($filters as $filter) 412 | { 413 | if (preg_match($filter, $message)) $found = true; 414 | } 415 | 416 | if ($found) $result[] = $message; 417 | } 418 | 419 | return $result; 420 | } 421 | 422 | public static function ResetLogMessages() 423 | { 424 | self::$messages = array(); 425 | } 426 | 427 | 428 | private static $timerinfo = array(); 429 | 430 | public static function StartTimer() 431 | { 432 | $ts = microtime(true); 433 | 434 | self::$timerinfo = array( 435 | "start" => $ts, 436 | "diff" => $ts 437 | ); 438 | } 439 | 440 | public static function UpdateTimer() 441 | { 442 | $ts = microtime(true); 443 | $diff = $ts - self::$timerinfo["diff"]; 444 | self::$timerinfo["diff"] = $ts; 445 | 446 | $result = array( 447 | "success" => true, 448 | "diff" => sprintf("%.2f", $diff), 449 | "total" => sprintf("%.2f", $ts - self::$timerinfo["start"]) 450 | ); 451 | 452 | return $result; 453 | } 454 | } 455 | ?> --------------------------------------------------------------------------------