diff --git a/Readme.md b/Readme.md index ce8e3e9..b874b71 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,8 @@ PHP Error | Improve Error Reporting for PHP =========================================== +**THIS IS NO LOBGER MAINTAINED, Please leave me alone.** + PHP errors are not good enough for development, it's as simple as that. This aims to solve this. ![Better Error Message](http://i.imgur.com/1G77I.png) @@ -15,7 +17,19 @@ If the server errors during an ajax request, then the request is paused, and the This requires no changes to your JavaScript, and works with existing JS libraries such as jQuery. -Check out the [project homepage](http://phperror.net) for a live demo. +Do not use on a live site! +-------------------------- + +To help make development easier, this __deliberately__ makes your code unsafe. +External requests are allowed to change your code, it shows more about your site, +gives you more info, and makes trivial errors fatal. +All of that is awesome if you want to fix bugs in less time, +but in production, it is totally unsafe. + +**seriously, only use this for development!** + +In case you forget, +you can disable this in production using the 'php_error.force_disabled' php.ini option (see below). Features -------- @@ -32,7 +46,7 @@ Features Getting Started --------------- - * [Download](http://phperror.net/download/php_error.php), it's just one file. + * Download, it's just one file. * Place it in your project. * import php_error.php * call \php_error\reportErrors() @@ -55,16 +69,6 @@ Documentation ### [php.ini settings](https://github.com/JosephLenton/PHP-Error/wiki/php.ini) -Do not use on a live site! --------------------------- - -This is intended for __development only__. It shows more about your site, gives you more info, and makes trivial errors fatal. -All of that is awesome if you want to debug quicker, and force high quality standards. - -On a production server, that sucks, and is potentially unsafe. - -In case you forget, you can disable this in production using the 'php_error.force_disabled' php.ini option (see below). - Advanced Features ----------------- diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..1e18964 --- /dev/null +++ b/composer.json @@ -0,0 +1,31 @@ +{ + "name": "joseph-lenton/php-error", + "description": "Better error reporting for PHP, and prettier too!", + "version": "1.0.0", + "homepage": "https://github.com/JosephLenton/PHP-Error", + "license": "BSD-3-Clause", + "require": { + "php": ">=5.3.0" + }, + "repositories": [ + { + "type": "git", + "url": "https://github.com/JosephLenton/PHP-Error" + } + ], + "keywords": [ + "php", + "error", + "reporting" + ], + "authors": [ + { + "name": "Joseph Lenton", + "homepage": "http://www.playmycode.com/", + "role": "Developer" + } + ], + "autoload": { + "files": ["src/php_error.php"] + } +} \ No newline at end of file diff --git a/src/php_error.php b/src/php_error.php index fa55cd5..1796913 100644 --- a/src/php_error.php +++ b/src/php_error.php @@ -1,12 +1,12 @@ nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -28,7 +28,7 @@ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + * * Uses: * JSMin-php https://github.com/rgrove/jsmin-php/ * jQuery http://jquery.com/ @@ -36,62 +36,61 @@ /** * PHP Error - * + * * --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- - * + * * WARNING! It is downright _DANGEROUS_ to use this in production, on * a live website. It should *ONLY* be used for development. - * + * * PHP Error will kill your environment at will, clear the output * buffers, and allows HTML injection from exceptions. - * + * * In future versions it plans to do far more then that. - * + * * If you use it in development, awesome! If you use it in production, * you're an idiot. - * + * * --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- - * + * * = Info - * + * * A small API for replacing the standard PHP errors, with prettier * error reporting. This will change the error reporting level, and this * is deliberate, as I believe in strict development errors. - * + * * simple usage: - * + * * \php_error\reportErrors(); - * + * * Advanced example: - * + * * There is more too it if you want more customized error handling. You * can pass in options, to customize the setup, and you get back a * handler you can alter at runtime. - * + * * $handler = new \php_error\ErrorHandler( $myOptions ); * $handler->turnOn(); - * + * * There should only ever be one handler! This is an (underdstandable) * limitation in PHP. It's because if an exception or error is raised, * then there is a single point of handling it. - * + * * = INI Options - * + * * - php_error.force_disabled When set to a true value (such as on), * this forces this to be off. * This is so you can disable this script * in your production servers ini file, * incase you accidentally upload this there. - * + * * --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- - * + * * @author Joseph Lenton | https://github.com/josephlenton */ namespace php_error; - use \php_error\ErrorException, - \php_error\FileLinesSet, + use \php_error\FileLinesSet, \php_error\ErrorHandler, \php_error\JSMin, @@ -99,6 +98,7 @@ use \Closure, \Exception, + \ErrorException, \InvalidArgumentException; use \ReflectionMethod, @@ -117,35 +117,32 @@ /* * These are used as token identifiers by PHP. - * + * * If they are missing, then they should never pop out of PHP, * so we just give them their future value. - * + * * They are primarily here so I don't have to alter the 5.3 * compliant code. Instead I can delete pre-5.3 code (this * code), in the future. - * + * * As long as the value is unique, and does not clash with PHP, * then any number could be used. That is why they start counting * at 100,000. */ - if ( ! defined('T_DIR') ) { - define( 'T_DIR', 100001 ); - } - if ( ! defined('T_GOTO') ) { - define( 'T_GOTO', 100002 ); - } - if ( ! defined('T_NAMESPACE') ) { - define( 'T_NAMESPACE', 100003 ); - } - if ( ! defined('T_NS_C') ) { - define( 'T_NS_C', 100004 ); - } - if ( ! defined('T_NS_SEPARATOR') ) { - define( 'T_NS_SEPARATOR', 100005 ); - } - if ( ! defined('T_USE') ) { - define( 'T_USE', 100006 ); + + $missingIdentifier = array( + 'T_INSTEADOF', + 'T_TRAIT', + 'T_TRAIT_C', + 'T_YIELD', + 'T_FINALLY' + ); + + $counter = 100001; + foreach ( $missingIdentifier as $id ) { + if ( ! defined($id) ) { + define( $id, $counter++ ); + } } /* @@ -161,7 +158,7 @@ * check that display errors is on * and ensure we are *not* a command line script. */ - $_php_error_is_ini_enabled = + $_php_error_is_ini_enabled = ! @get_cfg_var( 'php_error.force_disabled' ) && ! @get_cfg_var( 'php_error.force_disable' ) && @ini_get('display_errors') === '1' && @@ -172,10 +169,10 @@ /** * This is shorthand for turning off error handling, * calling a block of code, and then turning it on. - * + * * However if 'reportErrors' has not been called, * then this will silently do nothing. - * + * * @param callback A PHP function to call. * @return The result of calling the callback. */ @@ -191,19 +188,19 @@ function withoutErrors( $callback ) { /** * Turns on error reporting, and returns the handler. - * + * * If you just want error reporting on, then don't bother * catching the handler. If you're building something * clever, like a framework, then you might want to grab * and use it. - * + * * Note that calling this a second time will replace the * global error handling with a new error handler. * The existing one will be turned off, and the new one * turned on. - * + * * You can't use two at once! - * + * * @param options Optional, options declaring how PHP Error should be setup and used. * @return The ErrorHandler used for reporting errors. */ @@ -227,18 +224,18 @@ class ErrorHandler * {closure}() * blah::foo() * foo() - * + * * It is: * a closure * or a method or function * followed by parenthesis '()' - * + * * a function is 'namespace function' * a method is 'namespace class::function', or 'namespace class->function' * the whole namespace is optional * namespace is made up of an '\' and then repeating 'namespace\' * both the first slash, and the repeating 'namespace\', are optional - * + * * 'END' matches it at the end of a string, the other one does not. */ const REGEX_METHOD_OR_FUNCTION_END = '/(\\{closure\\})|(((\\\\)?(\b[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\\\\)*)?\b[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(::[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)?)\\(\\)$/'; @@ -253,7 +250,7 @@ class ErrorHandler * where the error is reported. This is the number * of lines around the line in question, * including that line. - * + * * So '9' will be the error line + 4 lines above + 4 lines below. */ const NUM_FILE_LINES = 13; @@ -262,16 +259,28 @@ class ErrorHandler const FILE_TYPE_IGNORE = 2; const FILE_TYPE_ROOT = 3; + /* + * These are the various magic identifiers, + * used for headers, post requests, and so on. + * + * Their main purpose is to be long and more or less unique, + * enough that a collision with user code is rare. + */ + const PHP_ERROR_MAGIC_HEADER_KEY = 'PHP_ERROR_MAGIC_HEADER'; const PHP_ERROR_MAGIC_HEADER_VALUE = 'php_stack_error'; const MAGIC_IS_PRETTY_ERRORS_MARKER = ''; + const HEADER_SAVE_FILE = 'PHP_ERROR_SAVE_FILES'; + + const POST_FILE_LOCATION = 'php_error_upload_file'; + const PHP_ERROR_INI_PREFIX = 'php_error'; /** * At the time of writing, scalar type hints are unsupported. * By scalar, I mean 'string' and 'integer'. - * + * * If they do get added, this is here as a trap to turn scalar * type hint warnings on and off. */ @@ -335,6 +344,7 @@ class ErrorHandler 'T_EXTENDS' => 'extends', 'T_FILE' => '__FILE__', 'T_FINAL' => 'final', + 'T_FINALLY' => 'finally', 'T_FOR' => 'for', 'T_FOREACH' => 'foreach', 'T_FUNCTION' => 'function', @@ -348,6 +358,7 @@ class ErrorHandler 'T_INCLUDE' => 'include', 'T_INCLUDE_ONCE' => 'include_once', 'T_INSTANCEOF' => 'instanceof', + 'T_INSTEADOF' => 'insteadof', 'T_INT_CAST' => 'int cast', 'T_INTERFACE' => 'interface', 'T_ISSET' => 'isset', @@ -398,6 +409,8 @@ class ErrorHandler 'T_SWITCH' => 'switch', 'T_THROW' => 'throw', 'T_TRY' => 'try', + 'T_TRAIT' => 'trait', + 'T_TRAIT_C' => '__trait__', 'T_UNSET' => 'unset', 'T_UNSET_CAST' => 'unset cast', 'T_USE' => 'use', @@ -405,7 +418,8 @@ class ErrorHandler 'T_VARIABLE' => 'variable', 'T_WHILE' => 'while', 'T_WHITESPACE' => 'whitespace', - 'T_XOR_EQUAL' => "'^='" + 'T_XOR_EQUAL' => "'^='", + 'T_YIELD' => 'yield' ); private static $syntaxMap = array( @@ -428,7 +442,7 @@ class ErrorHandler T_DECLARE => 'syntax-keyword', T_DEFAULT => 'syntax-keyword', T_DO => 'syntax-keyword', - + T_ELSE => 'syntax-keyword', T_ELSEIF => 'syntax-keyword', T_ENDDECLARE => 'syntax-keyword', @@ -440,15 +454,17 @@ class ErrorHandler T_EXTENDS => 'syntax-keyword', T_FINAL => 'syntax-keyword', + T_FINALLY => 'syntax-keyword', T_FOR => 'syntax-keyword', T_FOREACH => 'syntax-keyword', T_FUNCTION => 'syntax-keyword', T_GLOBAL => 'syntax-keyword', T_GOTO => 'syntax-keyword', - + T_IF => 'syntax-keyword', T_IMPLEMENTS => 'syntax-keyword', T_INSTANCEOF => 'syntax-keyword', + T_INSTEADOF => 'syntax-keyword', T_INTERFACE => 'syntax-keyword', T_LOGICAL_AND => 'syntax-keyword', @@ -463,10 +479,12 @@ class ErrorHandler T_STATIC => 'syntax-keyword', T_SWITCH => 'syntax-keyword', T_THROW => 'syntax-keyword', + T_TRAIT => 'syntax-keyword', T_TRY => 'syntax-keyword', T_USE => 'syntax-keyword', T_VAR => 'syntax-keyword', T_WHILE => 'syntax-keyword', + T_YIELD => 'syntax-keyword', // __VAR__ type magic constants T_CLASS_C => 'syntax-literal', @@ -476,6 +494,7 @@ class ErrorHandler T_LINE => 'syntax-literal', T_METHOD_C => 'syntax-literal', T_NS_C => 'syntax-literal', + T_TRAIT_C => 'syntax-literal', T_DNUMBER => 'syntax-literal', T_LNUMBER => 'syntax-literal', @@ -508,7 +527,7 @@ class ErrorHandler /** * A list of methods which are known to call the autoloader, * but should not error, if the class is not found. - * + * * They are allowed to fail, so we don't store a class not * found exception if they do. */ @@ -539,7 +558,17 @@ private static function isIIS() { strpos($_SERVER['_FCGI_X_PIPE_'], 'IISFCGI') !== false ); } - + + private static function isBinaryRequest() { + $response = ErrorHandler::getResponseHeaders(); + + foreach ( $response as $key => $value ) { + if ( strtolower($key) === 'content-transfer-encoding' ) { + return strtolower($value) === 'binary'; + } + } + } + /** * This attempts to state if this is *not* a PHP request, * but it cannot say if it *is* a PHP request. It achieves @@ -552,28 +581,24 @@ private static function isIIS() { private static function isNonPHPRequest() { /* * Check if we are a mime type that isn't allowed. + * + * If an allowed type is found, then we return false, + * as were are a PHP Request. * - * Anything other than 'text/html' or similar will cause - * this to turn off. + * Anything else found, returns true, as that means + * we are dealing with something unknown. */ $response = ErrorHandler::getResponseHeaders(); foreach ( $response as $key => $value ) { if ( strtolower($key) === 'content-type' ) { - $found = true; - foreach ( ErrorHandler::$ALLOWED_RETURN_MIME_TYPES as $type ) { if ( stripos($value, $type) !== false ) { - $found = true; - break; + return false; } } - if ( ! $found ) { - return true; - } - - break; + return true; } } @@ -583,7 +608,7 @@ private static function isNonPHPRequest() { /** * Looks up a description for the symbol given, * and if found, it is returned. - * + * * If it's not found, then the symbol given is returned. */ private static function phpSymbolToDescription( $symbol ) { @@ -596,9 +621,9 @@ private static function phpSymbolToDescription( $symbol ) { /** * Attempts to syntax highlight the code snippet done. - * + * * This is then returned as HTML, ready to be dumped to the screen. - * + * * @param code An array of code lines to syntax highlight. * @return HTML version of the code given, syntax highlighted. */ @@ -702,13 +727,13 @@ private static function syntaxHighlight( $code ) { /** * Splits a given function name into it's 'class, function' parts. * If there is no class, then null is returned. - * + * * It also returns these parts in an array of: array( $className, $functionName ); - * + * * Usage: - * + * * list( $class, $function ) = ErrorHandler::splitFunction( $name ); - * + * * @param name The function name to split. * @return An array containing class and function name. */ @@ -750,7 +775,7 @@ private static function newArgument( $name, $type=false, $isPassedByReference=fa ( $klass ? array( "\\$klass", $functionName ) : $functionName ), - $name->name, + $name->name, true ); @@ -840,12 +865,12 @@ private static function syntaxHighlightFunctionMatch( $match, &$stackTrace, $hig * Returns the values given, as HTML, syntax highlighted. * It's a shorter, slightly faster, more no-nonsense approach * then 'syntaxHighlight'. - * + * * This is for syntax highlighting: * - fun( [args] ) * - class->fun( [args] ) * - class::fun( [args] ) - * + * * Class and type can be null, to denote no class, but are not optional. */ private static function syntaxHighlightFunction( $class, $type, $fun, &$args=null ) { @@ -921,7 +946,7 @@ private static function syntaxHighlightFunction( $class, $type, $fun, &$args=nul /** * Checks if the item is in options, and if it is, then it is removed and returned. - * + * * If it is not found, or if options is not an array, then the alt is returned. */ private static function optionsPop( &$options, $key, $alt=null ) { @@ -1012,7 +1037,7 @@ private static function setFoldersInner( &$newFolders, &$newLongest, $folder ) { $count = count( $parts ); $newLongest = max( $newLongest, $count ); - + if ( isset($newFolders[$count]) ) { $folds = &$newFolders[$count]; $folds[]= $parts; @@ -1026,10 +1051,10 @@ private static function getRequestHeaders() { return getallheaders(); } else { $headers = array(); - + foreach ( $_SERVER as $key => $value ) { if ( strpos($key, 'HTTP_') === 0 ) { - $key = str_replace( " ", "-", ucwords(strtolower( str_replace("_", " ", substr($key, 5)) )) ); + $key = str_replace( " ", "-", ucwords(strtolower( str_replace("_", " ", substr($key, 5)) )) ); $headers[ $key ] = $value; } } @@ -1045,7 +1070,7 @@ private static function getResponseHeaders() { /* * Merge the headers_list into apache_response_headers. - * + * * This is because sometimes things are in one, which are * not present in the other. */ @@ -1099,6 +1124,9 @@ public static function identifyTypeHTML( $arg, $recurseLevels=1 ) { } } + private $saveUrl; + private $isSavingEnabled; + private $cachedFiles; private $isShutdownRegistered; @@ -1137,58 +1165,67 @@ public static function identifyTypeHTML( $arg, $recurseLevels=1 ) { /** * = Options = - * + * * All options are optional, and so is passing in an options item. * You don't have to supply any, it's up to you. - * + * * Note that if 'php_error.force_disable' is true, then this object * will try to look like it works, but won't actually do anything. - * + * * All options can also be passed in from 'php.ini'. You do this * by setting it with 'php_error.' prefix. For example: - * + * * php_error.catch_ajax_errors = On * php_error.error_reporting_on = E_ALL | E_STRICT - * + * * Includes: * = Types of errors this will catch = * - catch_ajax_errors When on, this will inject JS Ajax wrapping code, to allow this to catch any future JSON errors. Defaults to true. * - catch_supressed_errors The @ supresses errors. If set to true, then they are still reported anyway, but respected when false. Defaults to false. * - catch_class_not_found When true, loading a class that does not exist will be caught. This defaults to true. - * + * * = Error reporting level = * - error_reporting_on value for when errors are on, defaults to all errors * - error_reporting_off value for when errors are off, defaults to php.ini's error_reporting. - * + * * = Setup Details = * - application_root When it's working out hte stack trace, this is the root folder of the application, to use as it's base. * Defaults to the servers root directory. - * + * * A relative path can be given, but lets be honest, an explicit path is the way to guarantee that you * will get the path you want. My relative might not be the same as your relative. - * - * - snippet_num_lines The number of lines to display in the code snippet. + * + * - snippet_num_lines The number of lines to display in the code snippet. * That includes the line being reported. - * + * * - server_name The name for this server, defaults to "$_SERVER['SERVER_NAME']" - * + * * - ignore_folders This is allows you to highlight non-framework code in a stack trace. * An array of folders to ignore, when working out the stack trace. * This is folder prefixes in relation to the application_root, whatever that might be. * They are only ignored if there is a file found outside of them. * If you still don't get what this does, don't worry, it's here cos I use it. - * + * * - application_folders Just like ignore, but anything found in these folders takes precedence * over anything else. - * + * * - background_text The text that appeares in the background. By default this is blank. * Why? You can replace this with the name of your framework, for extra customization spice. - * + * * - html_only By default, PHP Error only runs on ajax and HTML pages. * If this is false, then it will also run when on non-HTML * pages too, such as replying with images of JavaScript * from your PHP. Defaults to true. * + * - file_link When true, files are linked to from the CSS Stack trace, allowing you to open them. + * Defaults to true. + * + * - save_url The url of where to send files, to be saved. + * Note that 'enable_saving' must be on for this to be used (which it is by default). + * + * - enable_saving Can be true or false. When true, saving files is enabled, and when false, it is disabled. + * Defaults to true! + * * @param options Optional, an array of values to customize this handler. * @throws Exception This is raised if given an options that does *not* exist (so you know that option is meaningless). */ @@ -1209,7 +1246,7 @@ public function __construct( $options=null ) { /* * Deal with the options. - * + * * They are removed one by one, and any left, will raise an error. */ @@ -1223,11 +1260,14 @@ public function __construct( $options=null ) { ErrorHandler::setFolders( $this->applicationFolders, $this->applicationFoldersLongest, $appFolders ); } - $this->defaultErrorReportingOn = ErrorHandler::optionsPop( $options, 'error_reporting_on' , -1 ); - $this->defaultErrorReportingOff = ErrorHandler::optionsPop( $options, 'error_reporting_off', error_reporting() ); + $this->saveUrl = ErrorHandler::optionsPop( $options, 'save_url', $_SERVER['REQUEST_URI'] ); + $this->isSavingEnabled = ErrorHandler::optionsPop( $options, 'enable_saving', true ); + + $this->defaultErrorReportingOn = ErrorHandler::optionsPop( $options, 'error_reporting_on' , -1 ); + $this->defaultErrorReportingOff = ErrorHandler::optionsPop( $options, 'error_reporting_off' , error_reporting() ); - $this->applicationRoot = ErrorHandler::optionsPop( $options, 'application_root' , $_SERVER['DOCUMENT_ROOT'] ); - $this->serverName = ErrorHandler::optionsPop( $options, 'error_reporting_off', $_SERVER['SERVER_NAME'] ); + $this->applicationRoot = ErrorHandler::optionsPop( $options, 'application_root' , $_SERVER['DOCUMENT_ROOT'] ); + $this->serverName = ErrorHandler::optionsPop( $options, 'server_name' , $_SERVER['SERVER_NAME'] ); /* * Relative paths might be given for document root, @@ -1246,7 +1286,7 @@ public function __construct( $options=null ) { $this->backgroundText = ErrorHandler::optionsPop( $options, 'background_text' , '' ); $this->numLines = ErrorHandler::optionsPop( $options, 'snippet_num_lines' , ErrorHandler::NUM_FILE_LINES ); - $this->displayLineNumber = ErrorHandler::optionsPop( $options, 'display_line_numbers' , false ); + $this->displayLineNumber = ErrorHandler::optionsPop( $options, 'display_line_numbers' , true ); $this->htmlOnly = !! ErrorHandler::optionsPop( $options, 'html_only', true ); @@ -1258,6 +1298,11 @@ public function __construct( $options=null ) { $this->defaultErrorReportingOn = E_ERROR | E_WARNING | E_PARSE | E_USER_DEPRECATED & ~E_DEPRECATED & ~E_STRICT; } + $concrete5 = ErrorHandler::optionsPop( $options, 'concrete5', false ); + if ( $concrete5 ) { + $this->defaultErrorReportingOn = E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED; + } + if ( $options ) { foreach ( $options as $key => $val ) { throw new InvalidArgumentException( "Unknown option given $key" ); @@ -1300,27 +1345,50 @@ public function isOff() { /** * Turns error reporting on. - * + * * This will use the strictest error reporting available, or the * level you pass in when creating this using the 'error_reporting_on' * option. - * + * * @return This error reporting handler, for method chaining. */ public function turnOn() { $this->propagateTurnOff(); $this->setEnabled( true ); + /* + * Check if file changes have been uploaded, + * and if so, save them. + */ + global $_php_error_is_ini_enabled; + if ( $_php_error_is_ini_enabled ) { + if ( $this->isSavingEnabled ) { + $headers = ErrorHandler::getRequestHeaders(); + + if ( isset($headers[ErrorHandler::HEADER_SAVE_FILE]) ) { + if ( isset($_POST) && isset($_POST[ErrorHandler::POST_FILE_LOCATION]) ) { + $files = $_POST[ErrorHandler::POST_FILE_LOCATION]; + + foreach ( $files as $file => $content ) { + @file_put_contents( $file, stripcslashes($content) ); + } + + exit(0); + } + } + } + } + return $this; } /** * Turns error reporting off. - * + * * This will use the 'php.ini' setting for the error_reporting level, * or one you have passed in if you used the 'error_reporting_off' * option when creating this. - * + * * @return This error reporting handler, for method chaining. */ public function turnOff() { @@ -1333,19 +1401,19 @@ public function turnOff() { * Allows you to run a callback with strict errors turned off. * Standard errors still apply, but this will use the default * error and exception handlers. - * + * * This is useful for when loading libraries which do not * adhere to strict errors, such as Wordpress. - * + * * To use: - * + * * withoutErrors( function() { * // unsafe code here * }); - * + * * This will use the error_reporting value for when this is * turned off. - * + * * @param callback A PHP function to call. * @return The result of calling the callback. */ @@ -1364,16 +1432,16 @@ public function withoutErrors( $callback ) { return $callback(); } } - + /** - * This is the shutdown function, which should *only* be called + * This is the shutdown function, which should *only* be called * via 'register_shutdown_function'. - * + * * It's exposed because it has to be exposed. */ public function __onShutdown() { global $_php_error_is_ini_enabled; - + if ( $_php_error_is_ini_enabled ) { if ( $this->isOn() ) { $error = error_get_last(); @@ -1409,21 +1477,21 @@ private function propagateTurnOff() { $this->lastGlobalErrorHandler = null; } } - + /** * This is intended to be used closely with 'onShutdown'. * It ensures that output buffering is turned on. - * + * * Why? The user may output content, and *then* hit an error. * We cannot replace the page if this happens, * because they have already outputted information. - * + * * So we buffer the page, and then output at the end of the page, * or when an error strikes. */ private function startBuffer() { global $_php_error_is_ini_enabled; - + if ( $_php_error_is_ini_enabled && !$this->isBufferSetup ) { $this->isBufferSetup = true; @@ -1461,7 +1529,7 @@ private function startBuffer() { /** * Turns off buffering, and discards anything buffered * so far. - * + * * This will return what has been buffered incase you * do want it. However otherwise, it will be lost. */ @@ -1477,7 +1545,7 @@ private function discardBuffer() { /** * Flushes the internal buffer, * outputting what is left. - * + * * @param append Optional, extra content to append onto the output buffer. */ private function flushBuffer() { @@ -1491,7 +1559,7 @@ private function flushBuffer() { * This will finish buffering, and output the page. * It also appends the magic JS onto the beginning of the page, * if enabled, to allow working with Ajax. - * + * * Note that if PHP Error has been disabled in the php.ini file, * or through some other option, such as running from the command line, * then this will do nothing (as no buffering will take place). @@ -1525,10 +1593,11 @@ public function endBuffer() { ob_start(); } - if ( - !$this->isAjax && - $this->catchAjaxErrors && - (!$this->htmlOnly || !ErrorHandler::isNonPHPRequest()) + if ( + !$this->isAjax && + $this->catchAjaxErrors && + (!$this->htmlOnly || !ErrorHandler::isNonPHPRequest()) && + !ErrorHandler::isBinaryRequest() ) { $js = $this->getContent( 'displayJSInjection' ); $js = JSMin::minify( $js ); @@ -1552,7 +1621,7 @@ public function endBuffer() { /** * Calls the given method on this object, * captures it's output, and then returns it. - * + * * @param method The name of the method to call. * @return All of the text outputted during the method call. */ @@ -1602,14 +1671,14 @@ private function getFolderType( $root, $file ) { /** * Finds the file named, and returns it's contents in an array. - * + * * It's essentially the same as 'file_get_contents'. However * this will add caching at this PHP layer, avoiding lots of * duplicate calls. - * + * * It also splits the file into an array of lines, and makes * it html safe. - * + * * @param path The file to get the contents of. * @return The file we are after, as an array of lines. */ @@ -1641,9 +1710,9 @@ private function getFileContents( $path ) { /** * Reads out the code from the section of the line, * which is at fault. - * + * * The array is in a mapping of: array( line-number => line ) - * + * * If something goes wrong, then null is returned. */ private function readCodeFile( $errFile, $errLine ) { @@ -1655,7 +1724,7 @@ private function readCodeFile( $errFile, $errLine ) { $searchUp = ceil( $numLines*0.75 ); $searchDown = $numLines - $searchUp; - + $countLines = count( $lines ); /* @@ -1738,14 +1807,14 @@ private function readCodeFile( $errFile, $errLine ) { /** * Attempts to remove the root path from the path given. * If the path can't be removed, then the original path is returned. - * + * * For example if root is 'C:/users/projects/my_site', * and the file is 'C:/users/projects/my_site/index.php', * then the root is removed, and we are left with just 'index.php'. - * + * * This is to remove line noise; you don't need to be told the * 'C:/whatever' bit 20 times. - * + * * @param root The root path to remove. * @param path The file we are removing the root section from. */ @@ -1764,7 +1833,7 @@ private function removeRootPath( $root, $path ) { /** * Parses, and alters, the errLine, errFile and message given. - * + * * This includes adding syntax highlighting, removing duplicate * information we already have, and making the error easier to * read. @@ -1794,7 +1863,7 @@ private function improveErrorMessage( $ex, $code, $message, $errLine, $errFile, /* * This is for calling a function that doesn't exist. - * + * * The message contains a long description of where this takes * place, even though we are already told this through line and * file info. So we cut it out. @@ -1915,7 +1984,7 @@ private function improveErrorMessage( $ex, $code, $message, $errLine, $errFile, /* * Unexpected symbol errors. * For example 'unexpected T_OBJECT_OPERATOR'. - * + * * This swaps the 'T_WHATEVER' for the symbolic representation. */ } else if ( $code === 4 ) { @@ -1925,7 +1994,7 @@ private function improveErrorMessage( $ex, $code, $message, $errLine, $errFile, $semiColonError = false; if ( strpos($message, 'syntax error,') === 0 && $errLine > 2 ) { $lines = ErrorHandler::getFileContents( $errFile ); - + $line = $lines[$errLine-1]; if ( preg_match( ErrorHandler::REGEX_MISSING_SEMI_COLON_FOLLOWING_LINE, $line ) !== 0 ) { $content = rtrim( join( "\n", array_slice($lines, 0, $errLine-1) ) ); @@ -2073,11 +2142,11 @@ private function improveErrorMessage( $ex, $code, $message, $errLine, $errFile, * The code above can prioritize a location in the stack trace, * this is 'stackSearchI'. So we should start our search from there, * and work down the stack. - * + * * This is built in a way so that when it reaches the end, it'll loop * back round to the beginning, and check the traces we didn't check * last time. - * + * * If stackSearchI was not altered, then it just searches from top * through to the bottom. */ @@ -2110,7 +2179,7 @@ private function improveErrorMessage( $ex, $code, $message, $errLine, $errFile, /** * Parses the stack trace, and makes it look pretty. - * + * * This includes adding in the syntax highlighting, * highlighting the colours for the files, * and padding with whitespace. @@ -2172,7 +2241,7 @@ private function parseStackTrace( $code, $message, $errLine, $errFile, &$stackTr trim( $contents[$trace['line']-1] ) ); } - } + } $trace['info'] = $info; @@ -2235,6 +2304,8 @@ private function parseStackTrace( $code, $message, $errLine, $errFile, &$stackTr } // line + file + info + $file = trim( $file ); + $stackStr = "$line" . "$file" . @@ -2269,8 +2340,9 @@ private function parseStackTrace( $code, $message, $errLine, $errFile, &$stackTr } $data = ''; - if ( isset($trace['file-lines-id']) ) { - $data = 'data-file-lines-id="' . $trace['file-lines-id'] . '"'; + if ( isset($trace['file-id']) ) { + $data = ' data-file-id="' . $trace['file-id'] . '"' . + ' data-line="' . $line . '"' ; } $stackTrace[$i] = "$exHtml$stackStr"; @@ -2302,12 +2374,12 @@ private function logError( $message, $file, $line, $ex=null ) { /** * Given a class name, which can include a namespace, * this will report that it is not found. - * + * * This will also report it as an exception, * so you will get a full stack trace. */ public function reportClassNotFound( $className ) { - throw new ErrorException( "Class '$className' not found", E_ERROR, E_ERROR, __FILE__, __LINE__ ); + throw new \ErrorException( "Class '$className' not found", E_ERROR, 0, __FILE__, __LINE__ ); } /** @@ -2325,11 +2397,11 @@ public function reportException( $ex ) { /** * The entry point for handling an error. - * + * * This is the lowest entry point for error reporting, * and for that reason it can either take just error info, * or a combination of error and exception information. - * + * * Note that this will still log errors in the error log * even when it's disabled with ini. It just does nothing * more than that. @@ -2397,9 +2469,11 @@ public function reportError( $code, $message, $errLine, $errFile, $ex=null ) { $stackTrace = $this->parseStackTrace( $code, $message, $errLine, $errFile, $stackTrace, $root, $altInfo ); $fileLines = $this->readCodeFile( $srcErrFile, $srcErrLine ); - // load the session, if it's there - - if ( isset($_COOKIE[session_name()]) && session_id() !== '' && !isset($_SESSION)) { + // load the session, if ... + // - there *is* a session cookie to load + // - the session has not yet been started + // Do not start the session without he cookie, because there may be no session ever. + if ( isset($_COOKIE[session_name()]) && session_id() === '' ) { session_start(); } @@ -2475,9 +2549,15 @@ private function getStackTrace( $ex, $code, $errFile, $errLine ) { $errFile = $ex->getFile(); $errLine = $ex->getLine(); - $code = method_exists($ex, 'getSeverity') ? - $ex->getSeverity() : - $ex->getCode() ; + $code = $ex->getCode(); + + if ( method_exists($ex, 'getSeverity') ) { + $severity = $ex->getSeverity(); + + if ( $code === 0 && $severity !== 0 && $severity !== null ) { + $code = $severity; + } + } } return array( $ex, $stackTrace, $code, $errFile, $errLine ); @@ -2527,9 +2607,13 @@ private function generateFileLineSets( $srcErrFile, $srcErrLine, &$stackTrace ) $srcErrID = "file-line-$fileLineID"; $fileLineID++; - $lines = $this->readCodeFile( $srcErrFile, $srcErrLine ); + + $lines = $this->getFileContents( $srcErrFile ); $minSize = count( $lines ); - $fileLinesSets = array( new FileLinesSet( $srcErrLine, $srcErrID, $lines, true ) ); + + $srcFileSet = new FileLinesSet( $srcErrFile, $srcErrID, $lines ); + + $seenFiles = array( $srcErrFile => $srcFileSet ); if ( $stackTrace ) { foreach ( $stackTrace as $i => &$trace ) { @@ -2537,29 +2621,32 @@ private function generateFileLineSets( $srcErrFile, $srcErrLine, &$stackTrace ) $file = $trace['file']; $line = $trace['line']; - if ( $file === $srcErrFile && $line === $srcErrLine ) { - $trace['file-lines-id'] = $srcErrID; + if ( isset($seenFiles[$file]) ) { + $fileSet = $seenFiles[$file]; } else { $traceFileID = "file-line-$fileLineID"; - $trace['file-lines-id'] = $traceFileID; - $lines = $this->readCodeFile( $file, $line ); + $lines = $this->getFileContents( $file ); $minSize = max( $minSize, count($lines) ); - $fileLinesSets[]= new FileLinesSet( $line, $traceFileID, $lines, false ); + $fileSet = new FileLinesSet( $file, $traceFileID, $lines ); + + $seenFiles[ $file ] = $fileSet; $fileLineID++; } + + $trace['file-id'] = $fileSet->getHTMLID(); } } } - return array( $fileLinesSets, $minSize ); + return array( array_values($seenFiles), $minSize ); } /* * Even if disabled, we still act like reporting is on, * if it's turned on. - * + * * We just don't do anything. */ private function setEnabled( $isOn ) { @@ -2570,7 +2657,7 @@ private function setEnabled( $isOn ) { if ( $_php_error_is_ini_enabled ) { /* * Only turn off, if we're moving from on to off. - * + * * This is so if it's turned off without turning on, * we don't change anything. */ @@ -2580,7 +2667,7 @@ private function setEnabled( $isOn ) { } /* * Always turn it on, even if already on. - * + * * This is incase it was messed up in some way * by the user. */ @@ -2597,7 +2684,7 @@ private function runDisableErrors() { error_reporting( $this->defaultErrorReportingOff ); @ini_restore( 'html_errors' ); - + if ( ErrorHandler::isIIS() ) { @ini_restore( 'log_errors' ); } @@ -2606,14 +2693,14 @@ private function runDisableErrors() { /* * Now the actual hooking into PHP's error reporting. - * + * * We enable _ALL_ errors, and make them all exceptions. * We also need to hook into the shutdown function so * we can catch fatal and compile time errors. */ private function runEnableErrors() { global $_php_error_is_ini_enabled; - + if ( $_php_error_is_ini_enabled ) { $catchSurpressedErrors = &$this->catchSurpressedErrors; $self = $this; @@ -2630,10 +2717,10 @@ private function runEnableErrors() { function( $code, $message, $file, $line, $context ) use ( $self, &$catchSurpressedErrors ) { /* * DO NOT! log the error. - * + * * Either it's thrown as an exception, and so logged by the exception handler, * or we return false, and it's logged by PHP. - * + * * Also DO NOT! throw an exception, instead report it. * This is because if an operation raises both a user AND * fatal error (such as require), then the exception is @@ -2644,7 +2731,7 @@ function( $code, $message, $file, $line, $context ) use ( $self, &$catchSurpress * When using an @, the error reporting drops to 0. */ if ( error_reporting() !== 0 || $catchSurpressedErrors ) { - $ex = new ErrorException( $message, $code, $code, $file, $line ); + $ex = new \ErrorException( $message, $code, 0, $file, $line ); $self->reportException( $ex ); } @@ -2652,7 +2739,7 @@ function( $code, $message, $file, $line, $context ) use ( $self, &$catchSurpress return false; } }, - $this->defaultErrorReportingOn + $this->defaultErrorReportingOn ); set_exception_handler( function($ex) use ( $self ) { @@ -2678,7 +2765,7 @@ function( $code, $message, $file, $line, $context ) use ( $self, &$catchSurpress * This is done for two reasons: * - functions like 'class_exists' will run the autoloader, and we shouldn't error on them * - on PHP 5.3.0, the class loader registered functions does *not* return closure objects, so we can't do anything clever. - * + * * So we watch, but don't touch. */ spl_autoload_register( function($className) use ( $self, &$classException, &$autoloaderFuns ) { @@ -2713,7 +2800,7 @@ function( $code, $message, $file, $line, $context ) use ( $self, &$catchSurpress } if ( $error ) { - $classException = new ErrorException( "Class '$className' not found", E_ERROR, E_ERROR, __FILE__, __LINE__ ); + $classException = new \ErrorException( "Class '$className' not found", E_ERROR, 0, __FILE__, __LINE__ ); } } } ); @@ -2730,12 +2817,12 @@ private function displayJSInjection() { (function( window ) { if ( window.XMLHttpRequest ) { - /** + /** * A method wrapping helper function. - * + * * Wraps the method given, from the old prototype to the new * XMLHttpRequest prototype. - * + * * This only happens if the old one actually has that prototype. * If the browser doesn't support a prototype, then it doesn't * get wrapped. @@ -2778,10 +2865,10 @@ private function displayJSInjection() { /* * Certain properties will error when read, * and which ones do vary from browser to browser. - * + * * I've found both Chrome and Firefox will error * on _different_ properties. - * + * * So every read needs to be wrapped in a try/catch, * and just hope it doesn't error. */ @@ -2863,7 +2950,7 @@ private function displayJSInjection() { } var iBody = iDoc.getElementsByTagName("body")[0]; - iBody.innerHTML = response; + iBody.innerHTML = response; var iHead = iDoc.getElementsByTagName("head")[0]; // re-run the script tags @@ -2897,13 +2984,13 @@ private function displayJSInjection() { /* * Retry Handler. - * + * * Clear this, make a new (real) XMLHttpRequest, * and then re-run everything. */ var retry = iDoc.getElementById('ajax-retry'); if ( retry ) { - retry.onclick = function() { + var retryFun = function() { var methodCalls = self.__.methodCalls; initializeXMLHttpRequest.call( self ); @@ -2916,10 +3003,13 @@ private function displayJSInjection() { return false; }; + retry.onclick = retryFun; + + iframe.__php_error_retry = retryFun; /* * The close handler. - * + * * When closed, the response is cleared, * and then the request finishes with null info. */ @@ -2950,7 +3040,7 @@ private function displayJSInjection() { /* * Placed inside a timeout, incase the document doesn't exist yet. - * + * * Can happen if the page ajax's straight away. */ setTimeout( function() { @@ -2963,7 +3053,7 @@ private function displayJSInjection() { /** * The middle man http request object. - * + * * Acts just like a normal one, but will show errors if they * occur instead of running the result. */ @@ -2978,7 +3068,7 @@ private function displayJSInjection() { /** * With a buggy XMLHttpRequest, it's possible to accidentally run the error handler * multiple times. - * + * * This is a flag to only do it once, to keep the code more defensive. */ var errorOnce = true, @@ -3012,7 +3102,7 @@ private function displayJSInjection() { /* * Success ! \o/ - * + * * Pass any state change on to the parent caller, * unless we hit an ajaxy error. */ @@ -3072,7 +3162,7 @@ private function displayJSInjection() { /* * Private fields are stored underneath the unhappy face, * to localize them. - * + * * Access becomes: * this.__.fieldName */ @@ -3139,6 +3229,8 @@ private function displayError( $message, $errLine, $errFile, $errFileType, $stac $serverName = $this->serverName; $backgroundText = $this->backgroundText; $displayLineNumber = $this->displayLineNumber; + $saveUrl = $this->saveUrl; + $isSavingEnabled = $this->isSavingEnabled; /* * When a query string is not provided, @@ -3148,7 +3240,7 @@ private function displayError( $message, $errLine, $errFile, $errFileType, $stac if ( isset($_SERVER['QUERY_STRING']) ) { $requestUrl = str_replace( $_SERVER['QUERY_STRING'], '', $_SERVER['REQUEST_URI'] ); $requestUrlLen = strlen( $requestUrl ); - + // remove the '?' if it's there (I suspect it isn't always, but don't take my word for it!) if ( $requestUrlLen > 0 && substr($requestUrl, $requestUrlLen-1) === '?' ) { $requestUrl = substr( $requestUrl, 0, $requestUrlLen-1 ); @@ -3157,6 +3249,7 @@ private function displayError( $message, $errLine, $errFile, $errFileType, $stac $requestUrl = $_SERVER['REQUEST_URI']; } + header_remove('Content-Transfer-Encoding'); $this->displayHTML( // pre, in the head function() use( $message, $errFile, $errLine ) { @@ -3173,7 +3266,8 @@ function() use ( $message, $errLine, $errFile, $errFileType, $stackTrace, &$fileLinesSets, $numFileLines, $displayLineNumber, - $dumpInfo + $dumpInfo, + $isSavingEnabled ) { if ( $backgroundText ) { ?>
@@ -3192,52 +3286,36 @@ function() use (

-

- -
- getHTMLID(); - $fileLines = $fileLinesSet->getLines(); - $show = $fileLinesSet->isShown(); - $highlightLine = $fileLinesSet->getLine(); - - // calculate last line number length - end($fileLines); - $maxLineNumber = key($fileLines); - $lineDecimals = strlen($maxLineNumber); - ?> -
- $origLine ) { - $line = ltrim($origLine, ' '); - $numSpaces = strlen($origLine) - strlen($line); - - $size = 8*$numSpaces + 64; - $style = "style='padding-left: " . $size . "px; text-indent: -" . $size . "px;'"; - - for ( $i = 0; $i < $numSpaces; $i++ ) { - $line = " $line"; - } - - if ($displayLineNumber) { - $lineNumLabel = str_replace(' ', ' ', sprintf("%{$lineDecimals}d", $lineNum)); - } else { - $lineNumLabel = ''; - } +
+

+ + save changes + +
+ +
+ +
+
+ $fileLinesSet ) { + $id = $fileLinesSet->getHTMLID(); + $fileLines = $fileLinesSet->getLines(); + + ?>
getContent() ) ?>
class="error-file-line "> - - -
- -
- -
- ",next:"htmltag"},{token:"meta.tag",regex:">"},{token:"text",regex:"(?:media|type|href)"},{token:"string",regex:'=".*?"'},{token:"paren.lparen",regex:"{",next:"cssdeclaration"},{token:"keyword",regex:"#[A-Za-z0-9-_.]+"},{token:"variable",regex:"\\.[A-Za-z0-9-_.]+"},{token:"constant",regex:"[A-Za-z0-9]+"}],cssdeclaration:[{token:"support.type",regex:"[-a-zA-Z]+",next:"cssvalue"},{token:"paren.rparen",regex:"}",next:"css"}],cssvalue:[{token:"text",regex:":"},{token:"constant",regex:"#[0-9a-zA-Z]+"},{token:"text",regex:"[-_0-9a-zA-Z\"' ,%]+"},{token:"text",regex:";",next:"cssdeclaration"}]},this.embedRules(f,"doc-",[f.getEndRule("start")])};d.inherits(h,g),b.PhpHighlightRules=h}),ace.define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(a,b,c){var d=a("../lib/oop"),e=a("./text_highlight_rules").TextHighlightRules,f=function(){this.$rules={start:[{token:"comment.doc.tag",regex:"@[\\w\\d_]+"},{token:"comment.doc",merge:!0,regex:"\\s+"},{token:"comment.doc",merge:!0,regex:"TODO"},{token:"comment.doc",merge:!0,regex:"[^@\\*]+"},{token:"comment.doc",merge:!0,regex:"."}]}};d.inherits(f,e),f.getStartRule=function(a){return{token:"comment.doc",merge:!0,regex:"\\/\\*(?=\\*)",next:a}},f.getEndRule=function(a){return{token:"comment.doc",merge:!0,regex:"\\*\\/",next:a}},b.DocCommentHighlightRules=f}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(a,b,c){var d=a("../range").Range,e=function(){};(function(){this.checkOutdent=function(a,b){return/^\s+$/.test(a)?/^\s*\}/.test(b):!1},this.autoOutdent=function(a,b){var c=a.getLine(b),e=c.match(/^(\s*\})/);if(!e)return 0;var f=e[1].length,g=a.findMatchingBracket({row:b,column:f});if(!g||g.row==b)return 0;var h=this.$getIndent(a.getLine(g.row));a.replace(new d(b,0,b,f-1),h)},this.$getIndent=function(a){var b=a.match(/^(\s+)/);return b?b[1]:""}}).call(e.prototype),b.MatchingBraceOutdent=e}),ace.define("ace/mode/behaviour/cstyle",["require","exports","module","ace/lib/oop","ace/mode/behaviour"],function(a,b,c){var d=a("../../lib/oop"),e=a("../behaviour").Behaviour,f=function(){this.add("braces","insertion",function(a,b,c,d,e){if(e=="{"){var f=c.getSelectionRange(),g=d.doc.getTextRange(f);return g!==""?{text:"{"+g+"}",selection:!1}:{text:"{}",selection:[1,1]}}if(e=="}"){var h=c.getCursorPosition(),i=d.doc.getLine(h.row),j=i.substring(h.column,h.column+1);if(j=="}"){var k=d.$findOpeningBracket("}",{column:h.column+1,row:h.row});if(k!==null)return{text:"",selection:[1,1]}}}else if(e=="\n"){var h=c.getCursorPosition(),i=d.doc.getLine(h.row),j=i.substring(h.column,h.column+1);if(j=="}"){var l=d.findMatchingBracket({row:h.row,column:h.column+1});if(!l)return null;var m=this.getNextLineIndent(a,i.substring(0,i.length-1),d.getTabString()),n=this.$getIndent(d.doc.getLine(l.row));return{text:"\n"+m+"\n"+n,selection:[1,m.length,1,m.length]}}}}),this.add("braces","deletion",function(a,b,c,d,e){var f=d.doc.getTextRange(e);if(!e.isMultiLine()&&f=="{"){var g=d.doc.getLine(e.start.row),h=g.substring(e.end.column,e.end.column+1);if(h=="}")return e.end.column++,e}}),this.add("parens","insertion",function(a,b,c,d,e){if(e=="("){var f=c.getSelectionRange(),g=d.doc.getTextRange(f);return g!==""?{text:"("+g+")",selection:!1}:{text:"()",selection:[1,1]}}if(e==")"){var h=c.getCursorPosition(),i=d.doc.getLine(h.row),j=i.substring(h.column,h.column+1);if(j==")"){var k=d.$findOpeningBracket(")",{column:h.column+1,row:h.row});if(k!==null)return{text:"",selection:[1,1]}}}}),this.add("parens","deletion",function(a,b,c,d,e){var f=d.doc.getTextRange(e);if(!e.isMultiLine()&&f=="("){var g=d.doc.getLine(e.start.row),h=g.substring(e.start.column+1,e.start.column+2);if(h==")")return e.end.column++,e}}),this.add("brackets","insertion",function(a,b,c,d,e){if(e=="["){var f=c.getSelectionRange(),g=d.doc.getTextRange(f);return g!==""?{text:"["+g+"]",selection:!1}:{text:"[]",selection:[1,1]}}if(e=="]"){var h=c.getCursorPosition(),i=d.doc.getLine(h.row),j=i.substring(h.column,h.column+1);if(j=="]"){var k=d.$findOpeningBracket("]",{column:h.column+1,row:h.row});if(k!==null)return{text:"",selection:[1,1]}}}}),this.add("brackets","deletion",function(a,b,c,d,e){var f=d.doc.getTextRange(e);if(!e.isMultiLine()&&f=="["){var g=d.doc.getLine(e.start.row),h=g.substring(e.start.column+1,e.start.column+2);if(h=="]")return e.end.column++,e}}),this.add("string_dquotes","insertion",function(a,b,c,d,e){if(e=='"'||e=="'"){var f=e,g=c.getSelectionRange(),h=d.doc.getTextRange(g);if(h!=="")return{text:f+h+f,selection:!1};var i=c.getCursorPosition(),j=d.doc.getLine(i.row),k=j.substring(i.column-1,i.column);if(k=="\\")return null;var l=d.getTokens(g.start.row),m=0,n,o=-1;for(var p=0;pg.start.column)break;m+=l[p].value.length}if(!n||o<0&&n.type!=="comment"&&(n.type!=="string"||g.start.column!==n.value.length+m-1&&n.value.lastIndexOf(f)===n.value.length-1))return{text:f+f,selection:[1,1]};if(n&&n.type==="string"){var q=j.substring(i.column,i.column+1);if(q==f)return{text:"",selection:[1,1]}}}}),this.add("string_dquotes","deletion",function(a,b,c,d,e){var f=d.doc.getTextRange(e);if(!e.isMultiLine()&&(f=='"'||f=="'")){var g=d.doc.getLine(e.start.row),h=g.substring(e.start.column+1,e.start.column+2);if(h=='"')return e.end.column++,e}})};d.inherits(f,e),b.CstyleBehaviour=f}),ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"],function(a,b,c){var d=a("../../lib/oop"),e=a("../../range").Range,f=a("./fold_mode").FoldMode,g=b.FoldMode=function(){};d.inherits(g,f),function(){this.foldingStartMarker=/(\{|\[)[^\}\]]*$|^\s*(\/\*)/,this.foldingStopMarker=/^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/,this.getFoldWidgetRange=function(a,b,c){var d=a.getLine(c),f=d.match(this.foldingStartMarker);if(f){var g=f.index;if(f[1])return this.openingBracketBlock(a,f[1],c,g);var h=a.getCommentFoldRange(c,g+f[0].length);return h.end.column-=2,h}if(b!=="markbeginend")return;var f=d.match(this.foldingStopMarker);if(f){var g=f.index+f[0].length;if(f[2]){var h=a.getCommentFoldRange(c,g);return h.end.column-=2,h}var i={row:c,column:g},j=a.$findOpeningBracket(f[1],i);if(!j)return;return j.column++,i.column--,e.fromPoints(j,i)}}}.call(g.prototype)}),ace.define("ace/mode/folding/fold_mode",["require","exports","module","ace/range"],function(a,b,c){var d=a("../../range").Range,e=b.FoldMode=function(){};(function(){this.foldingStartMarker=null,this.foldingStopMarker=null,this.getFoldWidget=function(a,b,c){var d=a.getLine(c);return this.foldingStartMarker.test(d)?"start":b=="markbeginend"&&this.foldingStopMarker&&this.foldingStopMarker.test(d)?"end":""},this.getFoldWidgetRange=function(a,b,c){return null},this.indentationBlock=function(a,b,c){var e=/\S/,f=a.getLine(b),g=f.search(e);if(g==-1)return;var h=c||f.length,i=a.getLength(),j=b,k=b;while(++bj){var m=a.getLine(k).length;return new d(j,h,k,m)}},this.openingBracketBlock=function(a,b,c,e,f){var g={row:c,column:e+1},h=a.$findClosingBracket(b,g,f);if(!h)return;var i=a.foldWidgets[h.row];return i==null&&(i=this.getFoldWidget(a,h.row)),i=="start"&&h.row>g.row&&(h.row--,h.column=a.getLine(h.row).length),d.fromPoints(g,h)}}).call(e.prototype)}) +