'subl://open?url=file://%file&line=%line', 'textmate' => 'txmt://open?url=file://%file&line=%line', 'emacs' => 'emacs://open?url=file://%file&line=%line', 'macvim' => 'mvim://open/?url=file://%file&line=%line', 'phpstorm' => 'phpstorm://open?file=%file&line=%line', 'phpstorm-remote' => 'http://localhost:63342/api/file/%file:%line', 'idea' => 'idea://open?file=%file&line=%line', 'vscode' => 'vscode://file/%file:%line', 'vscode-insiders' => 'vscode-insiders://file/%file:%line', 'vscode-remote' => 'vscode://vscode-remote/%file:%line', 'vscode-insiders-remote' => 'vscode-insiders://vscode-remote/%file:%line', 'vscodium' => 'vscodium://file/%file:%line', 'atom' => 'atom://core/open/file?filename=%file&line=%line', 'nova' => 'nova://core/open/file?filename=%file&line=%line', 'netbeans' => 'netbeans://open/?f=%file:%line', 'xdebug' => 'xdebug://%file@%line' ); private static $aliasesRaw; private static $projectRootDir; public static function php53orLater() { if (! isset(self::$_php53)) { self::$_php53 = version_compare(PHP_VERSION, '5.3.0') > 0; } return self::$_php53; } public static function isRichMode() { return Sage::enabled() === Sage::MODE_RICH; } public static function isHtmlMode() { $enabledMode = Sage::enabled(); return $enabledMode === Sage::MODE_RICH || $enabledMode === Sage::MODE_PLAIN; } /** * generic path display callback, can be configured in the settings; purpose is to show relevant path info and hide * as much of the path as possible. * * @param string $file * * @return string */ public static function shortenPath($file) { $file = str_replace('\\', '/', $file); if (self::$projectRootDir && strpos($file, self::$projectRootDir) === 0) { return substr($file, strlen(self::$projectRootDir)); } return $file; } public static function buildAliases() { self::$aliasesRaw = array( 'methods' => array( array('sage', 'dump'), array('sage', 'doDump'), array('sage', 'trace') ), 'functions' => array() ); foreach (Sage::$aliases as $alias) { $alias = strtolower($alias); if (strpos($alias, '::') !== false) { self::$aliasesRaw['methods'][] = explode('::', $alias); } else { self::$aliasesRaw['functions'][] = $alias; } } } public static function detectProjectRoot($calledFromFile) { // Find common path with Sage dir self::$projectRootDir = ''; $sagePathParts = explode('/', str_replace('\\', '/', SAGE_DIR)); $filePathParts = explode('/', $calledFromFile); foreach ($filePathParts as $i => $filePart) { if (! isset($sagePathParts[$i]) || $sagePathParts[$i] !== $filePart) { break; } self::$projectRootDir .= $filePart . '/'; } } /** * returns whether current trace step belongs to Sage or its wrappers * * @param $step * * @return bool */ public static function stepIsInternal($step) { if (isset($step['class'])) { foreach (self::$aliasesRaw['methods'] as $alias) { if ($alias[0] === strtolower($step['class']) && $alias[1] === strtolower($step['function'])) { return true; } } return false; } return in_array(strtolower($step['function']), self::$aliasesRaw['functions'], true); } public static function isKeyBlacklisted($key) { return in_array(preg_replace('/\W/', '', $key), Sage::$keysBlacklist, true); } public static function substr($string, $start, $end, $encoding = null) { if (! isset($string)) { return ''; } if (function_exists('mb_substr')) { $encoding or $encoding = self::detectEncoding($string); return mb_substr($string, $start, $end, $encoding); } return substr($string, $start, $end); } /** * returns whether the array: * 1) is numeric and * 2) in sequence starting from zero * * @param array $array * * @return bool */ public static function isArraySequential(array $array) { $keys = array_keys($array); return array_keys($keys) === $keys; } public static function detectEncoding($value) { if (function_exists('mb_detect_encoding')) { $mbDetected = mb_detect_encoding($value); if ($mbDetected === 'ASCII') { return 'UTF-8'; } } if (! function_exists('iconv')) { return ! empty($mbDetected) ? $mbDetected : 'UTF-8'; } $md5 = md5($value); foreach (Sage::$charEncodings as $encoding) { // f*#! knows why, //IGNORE and //TRANSLIT still throw notice if (md5(@iconv($encoding, $encoding, $value)) === $md5) { return $encoding; } } return 'UTF-8'; } public static function strlen($string, $encoding = null) { if (function_exists('mb_strlen')) { $encoding or $encoding = self::detectEncoding($string); return mb_strlen($string, $encoding); } return strlen($string); } public static function ideLink($file, $line, $linkText = null) { $enabledMode = Sage::enabled(); $file = self::shortenPath($file); $fileLine = $file; // in some cases (like called from inside template) we don't know the $line // it's then passed here as null, in that case don't display it in the link text, but keep :0 in the // url so that the IDE protocols don't break. if ($line) { $fileLine .= ':' . $line; } else { $line = 0; } if (! self::isHtmlMode()) { return $fileLine; } $linkText = $linkText ? $linkText : $fileLine; $linkText = self::esc($linkText); if (! Sage::$editor) { return $linkText; } $ideLink = str_replace( array('%file', '%line', Sage::$fileLinkServerPath), array($file, $line, Sage::$fileLinkLocalPath), isset(self::$editors[Sage::$editor]) ? self::$editors[Sage::$editor] : Sage::$editor ); if ($enabledMode === Sage::MODE_RICH) { $class = (strpos($ideLink, 'http://') === 0) ? ' class="_sage-ide-link" ' : ' '; return "{$linkText}"; } // MODE_PLAIN if (strpos($ideLink, 'http://') === 0) { return <<{$linkText} HTML; } return "{$linkText}"; } public static function esc($value, $decode = true) { $value = self::isHtmlMode() ? htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8') : $value; if ($decode) { $value = self::decodeStr($value); } return $value; } /** * Make all invisible characters visible. HTML-escape if needed. */ private static function decodeStr($value) { if (is_int($value)) { return (string)$value; } if ($value === '') { return ''; } if (self::isHtmlMode()) { if (htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8') === '') { return '‹binary data›'; } $controlCharsMap = array( "\v" => '\v', "\f" => '\f', "\033" => '\e', "\t" => "\t\\t", "\r\n" => "\\r\\n\n", "\n" => "\\n\n", "\r" => "\\r" ); $replaceTemplate = '‹0x%d›'; } else { $controlCharsMap = array( "\v" => '\v', "\f" => '\f', "\033" => '\e', ); $replaceTemplate = '\x%02X'; } $out = ''; $i = 0; do { $character = $value[$i]; $ord = ord($character); // escape all invisible characters except \t, \n and \r - ORD 9, 10 and 13 respectively if ($ord < 32 && $ord !== 9 && $ord !== 10 && $ord !== 13) { if (isset($controlCharsMap[$character])) { $out .= $controlCharsMap[$character]; } else { $out .= sprintf($replaceTemplate, $ord); } } else { $out .= $character; } } while (isset($value[++$i])); return $out; } }