607 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			607 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| /**
 | |
|  * Handles exception and errors
 | |
|  *
 | |
|  * @author     Time.ly Network Inc.
 | |
|  * @since      2.0
 | |
|  *
 | |
|  * @package    AI1EC
 | |
|  * @subpackage AI1EC.Exception
 | |
|  */
 | |
| class Ai1ec_Exception_Handler {
 | |
| 
 | |
|     /**
 | |
|      * @var string The option for the messgae in the db
 | |
|      */
 | |
|     const DB_DEACTIVATE_MESSAGE = 'ai1ec_deactivate_message';
 | |
| 
 | |
|     /**
 | |
|      * @var string The GET parameter to reactivate the plugin
 | |
|      */
 | |
|     const DB_REACTIVATE_PLUGIN  = 'ai1ec_reactivate_plugin';
 | |
| 
 | |
|     /**
 | |
|      * @var callable|null Previously set exception handler if any
 | |
|      */
 | |
|     protected $_prev_ex_handler;
 | |
| 
 | |
|     /**
 | |
|      * @var callable|null Previously set error handler if any
 | |
|      */
 | |
|     protected $_prev_er_handler;
 | |
| 
 | |
|     /**
 | |
|      * @var string The name of the Exception class to handle
 | |
|      */
 | |
|     protected $_exception_class;
 | |
| 
 | |
|     /**
 | |
|      * @var string The name of the ErrorException class to handle
 | |
|      */
 | |
|     protected $_error_exception_class;
 | |
| 
 | |
|     /**
 | |
|      * @var string The message to display in the admin notice
 | |
|      */
 | |
|     protected $_message;
 | |
| 
 | |
|     /**
 | |
|      * @var array Mapped list of errors that are non-fatal, to be ignored
 | |
|      *            in production.
 | |
|      */
 | |
|     protected $_nonfatal_errors = null;
 | |
| 
 | |
|     /**
 | |
|      * Store exception handler that was previously set
 | |
|      *
 | |
|      * @param callable|null $_prev_ex_handler
 | |
|      *
 | |
|      * @return void Method does not return
 | |
|      */
 | |
|     public function set_prev_ex_handler( $prev_ex_handler ) {
 | |
|         $this->_prev_ex_handler = $prev_ex_handler;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Store error handler that was previously set
 | |
|      *
 | |
|      * @param callable|null $_prev_er_handler
 | |
|      *
 | |
|      * @return void Method does not return
 | |
|      */
 | |
|     public function set_prev_er_handler( $prev_er_handler ) {
 | |
|         $this->_prev_er_handler = $prev_er_handler;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Constructor accepts names of classes to be handled
 | |
|      *
 | |
|      * @param string $exception_class Name of exceptions base class to handle
 | |
|      * @param string $error_class     Name of errors base class to handle
 | |
|      *
 | |
|      * @return void Constructor newer returns
 | |
|      */
 | |
|     public function __construct( $exception_class, $error_class ) {
 | |
|         $this->_exception_class       = $exception_class;
 | |
|         $this->_error_exception_class = $error_class;
 | |
|         $this->_nonfatal_errors       = array(
 | |
|             E_USER_WARNING => true,
 | |
|             E_WARNING      => true,
 | |
|             E_USER_NOTICE  => true,
 | |
|             E_NOTICE       => true,
 | |
|             E_STRICT       => true,
 | |
|         );
 | |
|         if ( version_compare( PHP_VERSION, '5.3.0' ) >= 0 ) {
 | |
|             // wrapper `constant( 'XXX' )` is used to avoid compile notices
 | |
|             // on earlier PHP versions.
 | |
|             $this->_nonfatal_errors[constant( 'E_DEPRECATED' )]      = true;
 | |
|             $this->_nonfatal_errors[constant( 'E_USER_DEPRECATED') ] = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Return add-on, which caused the exception or null if it was Core.
 | |
|      *
 | |
|      * Relies on `plugin_to_disable` method which may be implemented by
 | |
|      * an exception. If it returns non empty value - it is returned.
 | |
|      *
 | |
|      * @param Exception $exception Actual exception which was thrown.
 | |
|      *
 | |
|      * @return string|null Add-on identifier (plugin url), or null.
 | |
|      */
 | |
|     public function is_caused_by_addon( $exception ) {
 | |
|         $addon = null;
 | |
|         if ( method_exists( $exception, 'plugin_to_disable' ) ) {
 | |
|             $addon = $exception->plugin_to_disable();
 | |
|             if ( empty( $addon ) ) {
 | |
|                 $addon = null;
 | |
|             }
 | |
|         }
 | |
|         if ( null === $addon ) {
 | |
|             $position   = strlen( dirname( AI1EC_PATH ) ) + 1;
 | |
|             $length     = strlen( AI1EC_PLUGIN_NAME );
 | |
|             $trace_list = $exception->getTrace();
 | |
|             array_unshift(
 | |
|                 $trace_list,
 | |
|                 array( 'file' => $exception->getFile() )
 | |
|             );
 | |
|             foreach ( $trace_list as $trace ) {
 | |
|                 if (
 | |
|                     ! isset( $trace['file'] ) ||
 | |
|                     ! isset( $trace['file'][$position] )
 | |
|                 ) {
 | |
|                     continue;
 | |
|                 }
 | |
|                 $file = substr(
 | |
|                     $trace['file'],
 | |
|                     $position,
 | |
|                     strpos( $trace['file'], '/', $position ) - $position
 | |
|                 );
 | |
|                 if ( 0 === strncmp( AI1EC_PLUGIN_NAME, $file, $length ) ) {
 | |
|                     if ( AI1EC_PLUGIN_NAME !== $file ) {
 | |
|                         $addon = $file . '/' . $file . '.php';
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         if ( 'core' === strtolower( $addon ) ) {
 | |
|             return null;
 | |
|         }
 | |
|         return $addon;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get tag-line for disabling.
 | |
|      *
 | |
|      * Extracts plugin name from file.
 | |
|      *
 | |
|      * @param string $addon Name of disabled add-on.
 | |
|      *
 | |
|      * @return string Message to display before full trace.
 | |
|      */
 | |
|     public function get_disabled_line( $addon ) {
 | |
|         $file = dirname( AI1EC_PATH ) . DIRECTORY_SEPARATOR . $addon;
 | |
|         $line = '';
 | |
|         if (
 | |
|             is_file( $file ) &&
 | |
|             preg_match(
 | |
|                 '|Plugin Name:\s*(.+)|',
 | |
|                 file_get_contents( $file ),
 | |
|                 $matches
 | |
|             )
 | |
|         ) {
 | |
|             $line = '<p><strong>' .
 | |
|                 sprintf(
 | |
|                     __( 'The add-on "%s" has been disabled due to an error:' ),
 | |
|                     __( trim( $matches[1] ), dirname( $addon ) )
 | |
|                 ) .
 | |
|                 '</strong></p>';
 | |
|         }
 | |
|         return $line;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Global exceptions handling method
 | |
|      *
 | |
|      * @param Exception $exception Previously thrown exception to handle
 | |
|      *
 | |
|      * @return void Exception handler is not expected to return
 | |
|      */
 | |
|     public function handle_exception( $exception ) {
 | |
|         if ( defined( 'AI1EC_DEBUG' ) && true === AI1EC_DEBUG ) {
 | |
|             echo '<pre>';
 | |
|             $this->var_debug( $exception );
 | |
|             echo '</pre>';
 | |
|             die();
 | |
|         }
 | |
|         // if it's something we handle, handle it
 | |
|         $backtrace = $this->_get_backtrace( $exception );
 | |
|         if ( $exception instanceof $this->_exception_class ) {
 | |
|             // check if it's a plugin instead of core
 | |
|             $disable_addon = $this->is_caused_by_addon( $exception );
 | |
|             $message       = method_exists( $exception, 'get_html_message' )
 | |
|                 ? $exception->get_html_message()
 | |
|                 : $exception->getMessage();
 | |
|             $message = '<p>' . $message . '</p>';
 | |
|             if ( $exception->display_backtrace() ) {
 | |
|                 $message .= $backtrace;
 | |
|             }
 | |
|             if ( null !== $disable_addon ) {
 | |
|                 include_once ABSPATH . 'wp-admin/includes/plugin.php';
 | |
|                 // deactivate the plugin. Fire handlers to hide options.
 | |
|                 deactivate_plugins( $disable_addon );
 | |
|                 global $ai1ec_registry;
 | |
|                 $ai1ec_registry->get( 'notification.admin' )
 | |
|                     ->store(
 | |
|                         $this->get_disabled_line( $disable_addon ) . $message,
 | |
|                         'error',
 | |
|                         2,
 | |
|                         array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
 | |
|                         true
 | |
|                     );
 | |
|                 $this->redirect( $exception->get_redirect_url() );
 | |
|             } else {
 | |
|                 // check if it has a methof for deatiled html
 | |
|                 $this->soft_deactivate_plugin( $message );
 | |
|             }
 | |
| 
 | |
|         }
 | |
|         // if it's a PHP error in our plugin files, deactivate and redirect
 | |
|         else if ( $exception instanceof $this->_error_exception_class ) {
 | |
|             $this->soft_deactivate_plugin(
 | |
|                 $exception->getMessage() . $backtrace
 | |
|             );
 | |
|         }
 | |
|         // if another handler was set, let it handle the exception
 | |
|         if ( is_callable( $this->_prev_ex_handler ) ) {
 | |
|             call_user_func( $this->_prev_ex_handler, $exception );
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Throws an Ai1ec_Error_Exception if the error comes from our plugin
 | |
|      *
 | |
|      * @param int    $errno      Error level as integer
 | |
|      * @param string $errstr     Error message raised
 | |
|      * @param string $errfile    File in which error 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  = '<div class="message error">';
 | |
|         $message .= '<p><strong>' . $label . '</strong></p>';
 | |
|         $message .= $this->_message;
 | |
|         $message .= ' <a href="' . $redirect_url .
 | |
|             '" class="button button-primary ai1ec-dismissable">' .
 | |
|             __(
 | |
|                 'Try reactivating plugin',
 | |
|                 AI1EC_PLUGIN_NAME
 | |
|             );
 | |
|         $message .= '</a>';
 | |
|         $message .= '<p></p></div>';
 | |
|         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    = <<<JAVASCRIPT
 | |
|             <script type="text/javascript">
 | |
|             jQuery( function($) {
 | |
|                 $( "a[data-rel='$ident']" ).click( function() {
 | |
|                     jQuery( "#ai1ec-error-$ident" ).slideToggle( "fast" );
 | |
|                     return false;
 | |
|                 });
 | |
|             });
 | |
|             </script>
 | |
|             <blockquote id="ai1ec-error-$ident" style="display: none;">
 | |
|                 <strong>$title</strong>
 | |
|                 <p>$trace</p>
 | |
|                 <p>Request Uri: $request_uri</p>
 | |
|             </blockquote>
 | |
|             <a href="#" data-rel="$ident" class="button">$button_label</a>
 | |
| JAVASCRIPT;
 | |
|         }
 | |
|         return $backtrace;
 | |
|     }
 | |
| 
 | |
| }
 |