r was raised * @param string $errline Line in which error was raised * @param array $errcontext Error context symbols table copy * * @throws Ai1ec_Error_Exception If error originates from within Ai1EC * * @return boolean|void Nothing when error is ours, false when no * other handler exists */ public function handle_error( $errno, $errstr, $errfile, $errline, $errcontext = array() ) { // if the error is not in our plugin, let PHP handle things. $position = strpos( $errfile, AI1EC_PLUGIN_NAME ); if ( false === $position ) { if ( is_callable( $this->_prev_er_handler ) ) { return call_user_func_array( $this->_prev_er_handler, func_get_args() ); } return false; } // do not disable plugin in production if the error is rather low if ( isset( $this->_nonfatal_errors[$errno] ) && ( ! defined( 'AI1EC_DEBUG' ) || false === AI1EC_DEBUG ) ) { $message = sprintf( 'All-in-One Event Calendar: %s @ %s:%d #%d', $errstr, $errfile, $errline, $errno ); return error_log( $message, 0 ); } // let's get the plugin folder $tail = substr( $errfile, $position ); $exploded = explode( DIRECTORY_SEPARATOR, $tail ); $plugin_dir = $exploded[0]; // if the error doesn't belong to core, throw the plugin exception to trigger disabling // of the plugin in the exception handler if ( AI1EC_PLUGIN_NAME !== $plugin_dir ) { $exc = implode( array_map( array( $this, 'return_first_char' ), explode( '-', $plugin_dir ) ) ); // all plugins should implement an exception based on this convention // which is the same convention we use for constants, only with just first letter uppercase $exc = str_replace( 'aioec', 'Ai1ec', $exc ) . '_Exception'; if ( class_exists( $exc ) ) { $message = sprintf( 'All-in-One Event Calendar: %s @ %s:%d #%d', $errstr, $errfile, $errline, $errno ); throw new $exc( $message ); } } throw new Ai1ec_Error_Exception( $errstr, $errno, 0, $errfile, $errline ); } public function return_first_char( $name ) { return $name[0]; } /** * Perform what's needed to deactivate the plugin softly * * @param string $message Error message to be displayed to admin * * @return void Method does not return */ protected function soft_deactivate_plugin( $message ) { add_option( self::DB_DEACTIVATE_MESSAGE, $message ); $this->redirect(); } /** * Perform what's needed to reactivate the plugin * * @return boolean Success */ public function reactivate_plugin() { return delete_option( self::DB_DEACTIVATE_MESSAGE ); } /** * Get message to be displayed to admin if any * * @return string|boolean Error message or false if plugin is not disabled */ public function get_disabled_message() { global $wpdb; $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", self::DB_DEACTIVATE_MESSAGE ) ); if ( is_object( $row ) ) { return $row->option_value; } else { // option does not exist, so we must cache its non-existence return false; } } /** * Add an admin notice * * @param string $message Message to be displayed to admin * * @return void Method does not return */ public function show_notices( $message ) { // save the message to use it later $this->_message = $message; add_action( 'admin_notices', array( $this, 'render_admin_notice' ) ); } /** * Render HTML snipped to be displayed as a notice to admin * * @hook admin_notices When plugin is soft-disabled * * @return void Method does not return */ public function render_admin_notice() { $redirect_url = esc_url( add_query_arg( self::DB_REACTIVATE_PLUGIN, 'true', get_admin_url() ) ); $label = __( 'All-in-One Event Calendar has been disabled due to an error:', AI1EC_PLUGIN_NAME ); $message = '
'; $message .= '

' . $label . '

'; $message .= $this->_message; $message .= ' ' . __( 'Try reactivating plugin', AI1EC_PLUGIN_NAME ); $message .= ''; $message .= '

'; echo $message; } /** * Redirect the user either to the front page or the dashbord page * * @return void Method does not return */ protected function redirect( $suggested_url = null ) { $url = ai1ec_get_site_url(); if ( is_admin() ) { $url = null !== $suggested_url ? $suggested_url : ai1ec_get_admin_url(); } Ai1ec_Http_Response_Helper::redirect( $url ); } /** * Had to add it as var_dump was locking my browser. * * Taken from http://www.leaseweblabs.com/2013/10/smart-alternative-phps-var_dump-function/ * * @param mixed $variable * @param int $strlen * @param int $width * @param int $depth * @param int $i * @param array $objects * * @return string */ public function var_debug( $variable, $strlen = 400, $width = 25, $depth = 10, $i = 0, &$objects = array() ) { $search = array( "\0", "\a", "\b", "\f", "\n", "\r", "\t", "\v" ); $replace = array( '\0', '\a', '\b', '\f', '\n', '\r', '\t', '\v' ); $string = ''; switch ( gettype( $variable ) ) { case 'boolean' : $string .= $variable ? 'true' : 'false'; break; case 'integer' : $string .= $variable; break; case 'double' : $string .= $variable; break; case 'resource' : $string .= '[resource]'; break; case 'NULL' : $string .= "null"; break; case 'unknown type' : $string .= '???'; break; case 'string' : $len = strlen( $variable ); $variable = str_replace( $search, $replace, substr( $variable, 0, $strlen ), $count ); $variable = substr( $variable, 0, $strlen ); if ( $len < $strlen ) { $string .= '"' . $variable . '"'; } else { $string .= 'string(' . $len . '): "' . $variable . '"...'; } break; case 'array' : $len = count( $variable ); if ( $i == $depth ) { $string .= 'array(' . $len . ') {...}'; } elseif ( ! $len) { $string .= 'array(0) {}'; } else { $keys = array_keys( $variable ); $spaces = str_repeat( ' ', $i * 2 ); $string .= "array($len)\n" . $spaces . '{'; $count = 0; foreach ( $keys as $key ) { if ( $count == $width ) { $string .= "\n" . $spaces . " ..."; break; } $string .= "\n" . $spaces . " [$key] => "; $string .= $this->var_debug( $variable[$key], $strlen, $width, $depth, $i + 1, $objects ); $count ++; } $string .= "\n" . $spaces . '}'; } break; case 'object': $id = array_search( $variable, $objects, true ); if ( $id !== false ) { $string .= get_class( $variable ) . '#' . ( $id + 1 ) . ' {...}'; } else if ( $i == $depth ) { $string .= get_class( $variable ) . ' {...}'; } else { $id = array_push( $objects, $variable ); $array = ( array ) $variable; $spaces = str_repeat( ' ', $i * 2 ); $string .= get_class( $variable ) . "#$id\n" . $spaces . '{'; $properties = array_keys( $array ); foreach ( $properties as $property ) { $name = str_replace( "\0", ':', trim( $property ) ); $string .= "\n" . $spaces . " [$name] => "; $string .= $this->var_debug( $array[$property], $strlen, $width, $depth, $i + 1, $objects ); } $string .= "\n" . $spaces . '}'; } break; } if ( $i > 0 ) { return $string; } $backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); do { $caller = array_shift( $backtrace ); } while ( $caller && ! isset( $caller['file'] ) ); if ( $caller ) { $string = $caller['file'] . ':' . $caller['line'] . "\n" . $string; } echo nl2br( str_replace( ' ', ' ', htmlentities( $string ) ) ); } /** * Get HTML code with backtrace information for given exception. * * @param Exception $exception * * @return string HTML code. */ protected function _get_backtrace( $exception ) { $backtrace = ''; $trace = nl2br( $exception->getTraceAsString() ); $ident = sha1( $trace ); if ( ! empty( $trace ) ) { $request_uri = ''; if ( isset( $_SERVER['REQUEST_URI'] ) ) { // Remove all whitespaces $request_uri = preg_replace( '/\s+/', '', $_SERVER['REQUEST_URI'] ); // Convert request URI and strip tags $request_uri = strip_tags( htmlspecialchars_decode( $request_uri ) ); // Limit URL to 100 characters $request_uri = substr($request_uri, 0, 100); } $button_label = __( 'Toggle error details', AI1EC_PLUGIN_NAME ); $title = __( 'Error Details:', AI1EC_PLUGIN_NAME ); $backtrace = << jQuery( function($) { $( "a[data-rel='$ident']" ).click( function() { jQuery( "#ai1ec-error-$ident" ).slideToggle( "fast" ); return false; }); }); $button_label JAVASCRIPT; } return $backtrace; } }