PageRenderTime 74ms CodeModel.GetById 53ms app.highlight 16ms RepoModel.GetById 1ms app.codeStats 1ms

/wp-content/plugins/shopp/core/model/Error.php

https://bitbucket.org/sanders_nick/legacy-media
PHP | 624 lines | 289 code | 75 blank | 260 comment | 60 complexity | 3b16afcc60bc7e71238eab3d25b376f0 MD5 | raw file
  1<?php
  2/**
  3 * Error.php
  4 * Error management system for Shopp
  5 *
  6 * @author Jonathan Davis
  7 * @version 1.1
  8 * @copyright Ingenesis Limited, 28 March, 2008
  9 * @license GNU GPL version 3 (or later) {@see license.txt}
 10 * @package shopp
 11 * @subpackage errors
 12 **/
 13
 14define('SHOPP_ERR',1);			// Shopper visible general Shopp/shopping errors
 15define('SHOPP_TRXN_ERR',2);		// Transaction errors (third-party service errors)
 16define('SHOPP_AUTH_ERR',4);		// Authorization errors (login, credential problems)
 17define('SHOPP_COMM_ERR',8);		// Communication errors (connectivity)
 18define('SHOPP_STOCK_ERR',16);	// Inventory-related warnings (low stock, out-of-stock)
 19define('SHOPP_ADDON_ERR',32);	// Shopp module errors (bad descriptors, core version requriements)
 20define('SHOPP_ADMIN_ERR',64);	// Admin errors (for logging)
 21define('SHOPP_DB_ERR',128);		// DB errors (for logging)
 22define('SHOPP_PHP_ERR',256);	// PHP errors (for logging)
 23define('SHOPP_ALL_ERR',1024);	// All errors (for logging)
 24define('SHOPP_DEBUG_ERR',2048);	// Debug-only (for logging)
 25
 26/**
 27 * ShoppErrors class
 28 *
 29 * The error message management class that allows other
 30 * systems to subscribe to notifications when errors are
 31 * triggered.
 32 *
 33 * @author Jonathan Davis
 34 * @since 1.0
 35 * @package shopp
 36 * @subpackage errors
 37 **/
 38class ShoppErrors {
 39	private static $instance;
 40
 41	var $errors = array();				// Error message registry
 42	var $notifications;					// Notification subscription registry
 43	var $reporting = SHOPP_ALL_ERR;		// level of reporting
 44
 45	/**
 46	 * Setup error system and PHP error capture
 47	 *
 48	 * @author Jonathan Davis
 49	 * @since 1.0
 50	 *
 51	 * @return void
 52	 **/
 53	function __construct ($level = SHOPP_ALL_ERR) {
 54		$error_logging = shopp_setting('error_logging');
 55		if ( $error_logging ) $level = $error_logging;
 56
 57		if (defined('WP_DEBUG') && WP_DEBUG) $this->reporting = SHOPP_DEBUG_ERR;
 58		if ($level > $this->reporting) $this->reporting = $level;
 59
 60		$this->notifications = new CallbackSubscription();
 61
 62		$types = E_ALL;// ^ E_NOTICE;
 63		if (defined('WP_DEBUG') && WP_DEBUG) $types = E_ALL;
 64		// Handle PHP errors
 65		if ($this->reporting >= SHOPP_PHP_ERR)
 66			set_error_handler(array($this,'php'),$types);
 67
 68		add_action('init', array(&$this, 'init'), 5);
 69	}
 70
 71	function init () {
 72		ShoppingObject::store('errors', $this->errors);
 73		foreach( $this->errors as $index => $error ) {
 74			if ( $error->remove ) unset($this->errors[$index]);
 75		}
 76	}
 77
 78	public static function instance () {
 79		if ( ! self::$instance instanceof self )
 80			self::$instance = new self();
 81		return self::$instance;
 82	}
 83
 84	function set_loglevel () {
 85		$this->reporting = shopp_setting('error_logging');
 86	}
 87
 88	/**
 89	 * Adds a ShoppError to the registry
 90	 *
 91	 * @author Jonathan Davis
 92	 * @since 1.1
 93	 *
 94	 * @param ShoppError $ShoppError The ShoppError object to add
 95	 * @return void
 96	 **/
 97	function add ($ShoppError) {
 98		if (isset($ShoppError->code)) $this->errors[$ShoppError->code] = $ShoppError;
 99		else $this->errors[] = $ShoppError;
100		$this->notifications->send($ShoppError);
101	}
102
103	/**
104	 * Gets all errors up to a specified error level
105	 *
106	 * @author Jonathan Davis
107	 * @since 1.1
108	 *
109	 * @param int $level The maximum error level
110	 * @return array A list of errors
111	 **/
112	function get ($level=SHOPP_DEBUG_ERR) {
113		$errors = array();
114		foreach ($this->errors as &$error)
115			if ($error->level <= $level) $errors[] = &$error;
116		return $errors;
117	}
118
119	/**
120	 * Gets all errors of a specific error level
121	 *
122	 * @author Jonathan Davis
123	 * @since 1.1
124	 *
125	 * @param int $level The level of errors to retrieve
126	 * @return array A list of errors
127	 **/
128	function level ($level=SHOPP_ALL_ERR) {
129		$errors = array();
130		foreach ($this->errors as &$error)
131			if ($error->level == $level) $errors[] = &$error;
132		return $errors;
133	}
134
135	/**
136	 * Gets an error message with a specific error code
137	 *
138	 * @author Jonathan Davis
139	 * @since 1.1
140	 *
141	 * @param string $code The name of the error code
142	 * @return ShoppError The error object
143	 **/
144	function code ($code) {
145		if (!empty($code) && isset($this->errors[$code]))
146			return $this->errors[$code];
147	}
148
149	/**
150	 * Gets all errors from a specified source (object)
151	 *
152	 * @author Jonathan Davis
153	 * @since 1.1
154	 *
155	 * @param string $source The source object of errors
156	 * @return array A list of errors
157	 **/
158	function source ($source) {
159		if (empty($source)) return array();
160		$errors = array();
161		foreach ($this->errors as &$error)
162			if ($error->source == $source) $errors[] = &$error;
163		return $errors;
164	}
165
166	/**
167	 * Determines if any errors exist up to the specified error level
168	 *
169	 * @author Jonathan Davis
170	 * @since 1.0
171	 *
172	 * @param int $level The maximum level to look for errors in
173	 * @return void Description...
174	 **/
175	function exist ($level=SHOPP_DEBUG_ERR) {
176		$errors = array();
177		foreach ($this->errors as &$error)
178			if ($error->level <= $level) $errors[] = &$error;
179		return (count($errors) > 0);
180	}
181
182	/**
183	 * Removes an error from the registry
184	 *
185	 * @author Jonathan Davis
186	 * @since 1.1
187	 *
188	 * @param ShoppError $error The ShoppError object to remove
189	 * @return boolean True when removed, false if removal failed
190	 **/
191	function remove ($error) {
192		if (!isset($this->errors[$error->code])) return false;
193		$this->errors[$error->code]->remove = true;
194		return true;
195	}
196
197	/**
198	 * Removes all errors from the error registry
199	 *
200	 * @author Jonathan Davis
201	 * @since 1.0
202	 *
203	 * @return void
204	 **/
205	function reset () {
206		$this->errors = array();
207	}
208
209	/**
210	 * Reports PHP generated errors to the Shopp error system
211	 *
212	 * @author Jonathan Davis
213	 * @since 1.0
214	 *
215	 * @param int $number The error type
216	 * @param string $message The PHP error message
217	 * @param string $file The file the error occurred in
218	 * @param int $line The line number the error occurred at in the file
219	 * @return boolean
220	 **/
221	function php ($number, $message, $file, $line) {
222		if (strpos($file,SHOPP_PATH) === false) return true;
223		$debug = '';
224		if (SHOPP_DEBUG) $debug = sprintf(" [%s, line %d]", basename($file),$line);
225		new ShoppError($message.$debug,'php_error',SHOPP_PHP_ERR,
226			array('file'=>$file,'line'=>$line,'phperror'=>$number));
227		if ($number == E_USER_ERROR) return false; // Always show fatal errors
228		return true;
229	}
230
231} //end ShoppErrors
232
233
234/**
235 * ShoppError class
236 *
237 * Triggers an error that is handled by the Shopp error system.
238 *
239 * @author Jonathan Davis
240 * @since 1.0
241 * @package shopp
242 * @subpackage errors
243 **/
244class ShoppError {
245
246	var $code;
247	var $source;
248	var $messages;
249	var $level;
250	var $data = array();
251	var $remove = false;
252
253	/**
254	 * Creates and registers a new error
255	 *
256	 * @author Jonathan Davis
257	 * @since 1.1
258	 *
259	 * @return void Description...
260	 **/
261	function __construct ($message='',$code='',$level=SHOPP_ERR,$data='') {
262		$Errors = ShoppErrors();
263
264		if (!is_a($Errors,'ShoppErrors')) return;
265		if ($level > $Errors->reporting) return;
266		if (empty($message)) return;
267
268		$php = array(
269			1		=> 'ERROR',
270			2		=> 'WARNING',
271			4		=> 'PARSE ERROR',
272			8		=> 'NOTICE',
273			16		=> 'CORE ERROR',
274			32		=> 'CORE WARNING',
275			64		=> 'COMPILE ERROR',
276			128		=> 'COMPILE WARNING',
277			256		=> 'Fatal error',
278			512		=> 'Warning',
279			1024	=> 'Notice',
280			2048	=> 'STRICT NOTICE',
281			4096 	=> 'RECOVERABLE ERROR'
282		);
283		$debug = debug_backtrace();
284
285		$this->code = $code;
286		$this->messages[] = $message;
287		$this->level = $level;
288		$this->data = $data;
289		$this->debug = $debug[1];
290
291		// Handle template errors
292		if (isset($this->debug['class']) && $this->debug['class'] == "ShoppErrors")
293			$this->debug = $debug[2];
294
295		if (isset($data['file'])) $this->debug['file'] = $data['file'];
296		if (isset($data['line'])) $this->debug['line'] = $data['line'];
297		unset($this->debug['object'],$this->debug['args']);
298
299		$this->source = "Shopp";
300		if (isset($this->debug['class'])) $this->source = $this->debug['class'];
301		if (isset($this->data['phperror']) && isset($php[$this->data['phperror']]))
302			$this->source = "PHP ".$php[$this->data['phperror']];
303
304		$Errors = ShoppErrors();
305		if (!empty($Errors)) $Errors->add($this);
306	}
307
308	/**
309	 * Prevent excess data from being stored in the session
310	 *
311	 * @author Jonathan Davis
312	 * @since 1.1
313	 *
314	 * @return void
315	 **/
316	function __sleep () {
317		return array('remove','code','source','messages','level');
318	}
319
320	/**
321	 * Tests if the error message is blank
322	 *
323	 * @author Jonathan Davis
324	 * @since 1.1
325	 *
326	 * @return boolean True for blank error messages
327	 **/
328	function blank () {
329		return (join('',$this->messages) == "");
330	}
331
332	/**
333	 * Displays messages registered to a specific error code
334	 *
335	 * @author Jonathan Davis
336	 * @since 1.1
337	 *
338	 * @param boolean $remove (optional) (Default true) Removes the error after retrieving it
339	 * @param boolean $source Prefix the error with the source object where the error was triggered
340	 * @param string $delimiter The delimeter used to join multiple error messages
341	 * @return string A collection of concatenated error messages
342	 **/
343	function message ($remove=false,$source=false,$delimiter="\n") {
344		$string = "";
345		// Show source if debug is on, or not a general error message
346		if (((defined('WP_DEBUG') && WP_DEBUG) || $this->level > SHOPP_ERR) &&
347			!empty($this->source) && $source) $string .= "$this->source: ";
348		$string .= join($delimiter,$this->messages);
349		if ($remove) {
350			$Errors = ShoppErrors();
351			if (!empty($Errors->errors)) $Errors->remove($this);
352		}
353		return $string;
354	}
355
356}
357
358/**
359 * ShoppErrorLogging class
360 *
361 * Subscribes to error notifications in order to log any errors
362 * generated.
363 *
364 * @author Jonathan Davis
365 * @since 1.0
366 * @package shopp
367 * @subpackage errors
368 **/
369class ShoppErrorLogging {
370
371	private static $instance;
372
373	var $dir;
374	var $file = "shopp_debug.log";
375	var $logfile;
376	var $log;
377	var $loglevel = 0;
378
379	/**
380	 * Setup for error logging
381	 *
382	 * @author Jonathan Davis
383	 * @since 1.0
384	 *
385	 * @return void
386	 **/
387	function __construct ($loglevel=0) {
388		$loglevelsetting = shopp_setting('error_logging');
389		$this->loglevel = $loglevelsetting ? $loglevelsetting : $loglevel;
390
391		$this->dir = defined('SHOPP_LOG_PATH') ? SHOPP_LOG_PATH : sys_get_temp_dir();
392		$this->dir = sanitize_path($this->dir); // Windows path sanitiation
393
394		$siteurl = parse_url(get_bloginfo('siteurl'));
395		$sub = (!empty($path)?"_".sanitize_title_with_dashes($path):'');
396		$this->logfile = trailingslashit($this->dir).$siteurl['host'].$sub."_".$this->file;
397
398		$Errors = &ShoppErrors();
399		$Errors->notifications->subscribe($this,'log');
400	}
401
402	public static function instance() {
403		if ( ! self::$instance )
404			self::$instance = new self();
405		return self::$instance;
406	}
407
408	function set_loglevel() {
409		$this->loglevel = shopp_setting('error_logging');
410	}
411
412	/**
413	 * Logs an error to the error log file
414	 *
415	 * @author Jonathan Davis
416	 * @since 1.0
417	 *
418	 * @param ShoppError $error The error object to log
419	 * @return void
420	 **/
421	function log ($error) {
422		if ($error->level > $this->loglevel) return;
423		$debug = "";
424		if (isset($error->debug['file'])) {
425			$debug = sprintf("[%s, line %d]", basename($error->debug['file']),$error->debug['line']);
426			$debug = " ".apply_filters('shopp_error_message_debugdata',$debug,$error->debug);
427		}
428		$message = date("Y-m-d H:i:s",time())." - ".$error->message(false,true)."$debug\n";
429		error_log($message,3,$this->logfile);
430		error_log($error->message(false,true));
431	}
432
433	/**
434	 * Empties the error log file
435	 *
436	 * @author Jonathan Davis
437	 * @since 1.0
438	 *
439	 * @return void
440	 **/
441	function reset () {
442		$this->log = fopen($this->logfile,'w');
443		fwrite($this->log,'');
444		fclose($this->log);
445	}
446
447	/**
448	 * Gets the end of the log file
449	 *
450	 * @author Jonathan Davis
451	 * @since 1.0
452	 * @version 1.2
453	 *
454	 * @param int $lines The number of lines to get from the end of the log file
455	 * @param int $buffer Buffer size in bytes to handle each iteration
456	 * @return array List of log file lines
457	 **/
458	function tail($lines=100,$buffer=4096) {
459		if (!file_exists($this->logfile)) return;
460		$f = fopen($this->logfile, 'rb');
461
462		// Start at the end
463		fseek($f, -1, SEEK_END);
464
465		// Take into acount files that don't end with a blank line
466		if("\n" != fread($f, 1)) $lines--;
467
468		// Start reading
469		$output = $chunk = '';
470		while(ftell($f) > 0 && $lines >= 0) {
471			$seek = min(ftell($f), $buffer);				// Figure out how far to go back
472			fseek($f, -$seek, SEEK_CUR);					// Jump back from the current position
473			$output = ($chunk = fread($f, $seek)).$output;	// Read a buffer chunk and prepend it to our output
474			fseek($f, -strlen($chunk), SEEK_CUR);// Jump back to where we started reading
475			$lines -= substr_count($chunk, "\n");			// Decrease our line counter
476		}
477		fclose($f);
478
479		// Handle over-reading because of buffer size
480		while ($lines++ < 0)
481			$output = substr($output, strpos($output, "\n") + 1);
482
483		return explode("\n",trim($output));
484
485	}
486
487}
488
489/**
490 * ShoppErrorNotification class
491 *
492 * Sends error notification emails when errors are triggered
493 * to specified recipients
494 *
495 * @author Jonathan Davis
496 * @since 1.0
497 * @package shopp
498 * @subpackage errors
499 **/
500class ShoppErrorNotification {
501	private static $instance;
502	var $recipients;	// Recipient addresses to send to
503	var $types=0;		// Error types to send
504
505	/**
506	 * Relays triggered errors to email messages
507	 *
508	 * @author Jonathan Davis
509	 * @since 1.0
510	 *
511	 * @param string $recipients List of email addresses
512	 * @param array $types The types of errors to report
513	 * @return void
514	 **/
515	function __construct ($recipients='',$types=array()) {
516		$recipients = shopp_setting('merchant_email');
517		$types = shopp_setting('error_notifications');
518
519		if (empty($recipients)) return;
520		$this->recipients = $recipients;
521		foreach ((array)$types as $type) $this->types += $type;
522		$Errors = &ShoppErrors();
523		$Errors->notifications->subscribe($this,'notify');
524	}
525
526	public static function instance() {
527		if ( ! self::$instance )
528			self::$instance = new self();
529		return self::$instance;
530	}
531
532	function set_notifications () {
533		$recipients = shopp_setting('merchant_email');
534		$types = shopp_setting('error_notifications');
535
536		if (empty($recipients)) return;
537		$this->recipients = $recipients;
538		foreach ((array)$types as $type) $this->types += $type;
539
540		$Errors = ShoppErrors();
541		$Errors->notifications->subscribe($this,'notify');
542	}
543
544	/**
545	 * Generates and sends an email of an error to the recipient list
546	 *
547	 * @author Jonathan Davis
548	 * @since 1.0
549	 *
550	 * @param ShoppError $error The error object
551	 * @return void
552	 **/
553	function notify ($error) {
554		if (!($error->level & $this->types)) return;
555		$url = parse_url(get_bloginfo('url'));
556		$_ = array();
557		$_[] = 'From: "'.get_bloginfo('sitename').'" <shopp@'.$url['host'].'>';
558		$_[] = 'To: '.$this->recipients;
559		$_[] = 'Subject: '.__('Shopp Notification','Shopp');
560		$_[] = '';
561		$_[] = __('This is an automated notification message generated by the Shopp installation at '.get_bloginfo('url').'.','Shopp');
562		$_[] = '';
563		$_[] = $error->message();
564		$_[] = '';
565		if (isset($error->debug['file']) && defined('WP_DEBUG'))
566			$_[] = 'DEBUG: '.basename($error->debug['file']).', line '.$error->debug['line'].'';
567
568		shopp_email(join("\n",$_));
569	}
570
571}
572
573class CallbackSubscription {
574
575	var $subscribers = array();
576
577	function subscribe ($target,$method) {
578		if (!isset($this->subscribers[get_class($target)]))
579			$this->subscribers[get_class($target)] = array(&$target,$method);
580	}
581
582	function send () {
583		$args = func_get_args();
584		foreach ($this->subscribers as $callback) {
585			call_user_func_array($callback,$args);
586		}
587	}
588
589}
590
591/**
592 * Helper to access the error system
593 *
594 * @author Jonathan Davis
595 * @since 1.0
596 *
597 * @return void Description...
598 **/
599function ShoppErrors () {
600	return ShoppErrors::instance();
601}
602
603function ShoppErrorLogging () {
604	return ShoppErrorLogging::instance();
605}
606
607function ShoppErrorNotification () {
608	return ShoppErrorNotification::instance();
609}
610
611/**
612 * Detects ShoppError objects
613 *
614 * @author Jonathan Davis
615 * @since 1.0
616 *
617 * @param object $e The object to test
618 * @return boolean True if the object is a ShoppError
619 **/
620function is_shopperror ($e) {
621	return (get_class($e) == "ShoppError");
622}
623
624?>