1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10:
11: Yii::import('CHtml',true);
12:
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56:
57: class CErrorHandler extends CApplicationComponent
58: {
59: 60: 61:
62: public $maxSourceLines=25;
63:
64: 65: 66: 67:
68: public $maxTraceSourceLines = 10;
69:
70: 71: 72:
73: public $adminInfo='the webmaster';
74: 75: 76:
77: public $discardOutput=true;
78: 79: 80: 81: 82:
83: public $errorAction;
84:
85: private $_error;
86: private $_exception;
87:
88: 89: 90: 91: 92: 93:
94: public function handle($event)
95: {
96:
97: $event->handled=true;
98:
99: if($this->discardOutput)
100: {
101: $gzHandler=false;
102: foreach(ob_list_handlers() as $h)
103: {
104: if(strpos($h,'gzhandler')!==false)
105: $gzHandler=true;
106: }
107:
108:
109: for($level=ob_get_level();$level>0;--$level)
110: {
111: if(!@ob_end_clean())
112: ob_clean();
113: }
114:
115: if($gzHandler && !headers_sent() && ob_list_handlers()===array())
116: {
117: if(function_exists('header_remove'))
118: {
119: header_remove('Vary');
120: header_remove('Content-Encoding');
121: }
122: else
123: {
124: header('Vary:');
125: header('Content-Encoding:');
126: }
127: }
128: }
129:
130: if($event instanceof CExceptionEvent)
131: $this->handleException($event->exception);
132: else
133: $this->handleError($event);
134: }
135:
136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149:
150: public function getError()
151: {
152: return $this->_error;
153: }
154:
155: 156: 157: 158:
159: public function getException()
160: {
161: return $this->_exception;
162: }
163:
164: 165: 166: 167:
168: protected function handleException($exception)
169: {
170: $app=Yii::app();
171: if($app instanceof CWebApplication)
172: {
173: if(($trace=$this->getExactTrace($exception))===null)
174: {
175: $fileName=$exception->getFile();
176: $errorLine=$exception->getLine();
177: }
178: else
179: {
180: $fileName=$trace['file'];
181: $errorLine=$trace['line'];
182: }
183:
184: $trace = $exception->getTrace();
185:
186: foreach($trace as $i=>$t)
187: {
188: if(!isset($t['file']))
189: $trace[$i]['file']='unknown';
190:
191: if(!isset($t['line']))
192: $trace[$i]['line']=0;
193:
194: if(!isset($t['function']))
195: $trace[$i]['function']='unknown';
196:
197: unset($trace[$i]['object']);
198: }
199:
200: $this->_exception=$exception;
201: $this->_error=$data=array(
202: 'code'=>($exception instanceof CHttpException)?$exception->statusCode:500,
203: 'type'=>get_class($exception),
204: 'errorCode'=>$exception->getCode(),
205: 'message'=>$exception->getMessage(),
206: 'file'=>$fileName,
207: 'line'=>$errorLine,
208: 'trace'=>$exception->getTraceAsString(),
209: 'traces'=>$trace,
210: );
211:
212: if(!headers_sent())
213: {
214: $httpVersion=Yii::app()->request->getHttpVersion();
215: header("HTTP/$httpVersion {$data['code']} ".$this->getHttpHeader($data['code'], get_class($exception)));
216: }
217:
218: $this->renderException();
219: }
220: else
221: $app->displayException($exception);
222: }
223:
224: 225: 226: 227:
228: protected function handleError($event)
229: {
230: $trace=debug_backtrace();
231:
232: if(count($trace)>3)
233: $trace=array_slice($trace,3);
234: $traceString='';
235: foreach($trace as $i=>$t)
236: {
237: if(!isset($t['file']))
238: $trace[$i]['file']='unknown';
239:
240: if(!isset($t['line']))
241: $trace[$i]['line']=0;
242:
243: if(!isset($t['function']))
244: $trace[$i]['function']='unknown';
245:
246: $traceString.="#$i {$trace[$i]['file']}({$trace[$i]['line']}): ";
247: if(isset($t['object']) && is_object($t['object']))
248: $traceString.=get_class($t['object']).'->';
249: $traceString.="{$trace[$i]['function']}()\n";
250:
251: unset($trace[$i]['object']);
252: }
253:
254: $app=Yii::app();
255: if($app instanceof CWebApplication)
256: {
257: switch($event->code)
258: {
259: case E_WARNING:
260: $type = 'PHP warning';
261: break;
262: case E_NOTICE:
263: $type = 'PHP notice';
264: break;
265: case E_USER_ERROR:
266: $type = 'User error';
267: break;
268: case E_USER_WARNING:
269: $type = 'User warning';
270: break;
271: case E_USER_NOTICE:
272: $type = 'User notice';
273: break;
274: case E_RECOVERABLE_ERROR:
275: $type = 'Recoverable error';
276: break;
277: default:
278: $type = 'PHP error';
279: }
280: $this->_exception=null;
281: $this->_error=array(
282: 'code'=>500,
283: 'type'=>$type,
284: 'message'=>$event->message,
285: 'file'=>$event->file,
286: 'line'=>$event->line,
287: 'trace'=>$traceString,
288: 'traces'=>$trace,
289: );
290: if(!headers_sent())
291: {
292: $httpVersion=Yii::app()->request->getHttpVersion();
293: header("HTTP/$httpVersion 500 Internal Server Error");
294: }
295:
296: $this->renderError();
297: }
298: else
299: $app->displayError($event->code,$event->message,$event->file,$event->line);
300: }
301:
302: 303: 304: 305:
306: protected function isAjaxRequest()
307: {
308: return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest';
309: }
310:
311: 312: 313: 314: 315:
316: protected function getExactTrace($exception)
317: {
318: $traces=$exception->getTrace();
319:
320: foreach($traces as $trace)
321: {
322:
323: if(isset($trace['function']) && ($trace['function']==='__get' || $trace['function']==='__set'))
324: return $trace;
325: }
326: return null;
327: }
328:
329: 330: 331: 332: 333: 334:
335: protected function render($view,$data)
336: {
337: $data['version']=$this->getVersionInfo();
338: $data['time']=time();
339: $data['admin']=$this->adminInfo;
340: include($this->getViewFile($view,$data['code']));
341: }
342:
343: 344: 345: 346:
347: protected function renderException()
348: {
349: $exception=$this->getException();
350: if($exception instanceof CHttpException || !YII_DEBUG)
351: $this->renderError();
352: else
353: {
354: if($this->isAjaxRequest())
355: Yii::app()->displayException($exception);
356: else
357: $this->render('exception',$this->getError());
358: }
359: }
360:
361: 362: 363: 364:
365: protected function renderError()
366: {
367: if($this->errorAction!==null)
368: Yii::app()->runController($this->errorAction);
369: else
370: {
371: $data=$this->getError();
372: if($this->isAjaxRequest())
373: Yii::app()->displayError($data['code'],$data['message'],$data['file'],$data['line']);
374: elseif(YII_DEBUG)
375: $this->render('exception',$data);
376: else
377: $this->render('error',$data);
378: }
379: }
380:
381: 382: 383: 384: 385: 386:
387: protected function getViewFile($view,$code)
388: {
389: $viewPaths=array(
390: Yii::app()->getTheme()===null ? null : Yii::app()->getTheme()->getSystemViewPath(),
391: Yii::app() instanceof CWebApplication ? Yii::app()->getSystemViewPath() : null,
392: YII_PATH.DIRECTORY_SEPARATOR.'views',
393: );
394:
395: foreach($viewPaths as $i=>$viewPath)
396: {
397: if($viewPath!==null)
398: {
399: $viewFile=$this->getViewFileInternal($viewPath,$view,$code,$i===2?'en_us':null);
400: if(is_file($viewFile))
401: return $viewFile;
402: }
403: }
404: }
405:
406: 407: 408: 409: 410: 411: 412: 413:
414: protected function getViewFileInternal($viewPath,$view,$code,$srcLanguage=null)
415: {
416: $app=Yii::app();
417: if($view==='error')
418: {
419: $viewFile=$app->findLocalizedFile($viewPath.DIRECTORY_SEPARATOR."error{$code}.php",$srcLanguage);
420: if(!is_file($viewFile))
421: $viewFile=$app->findLocalizedFile($viewPath.DIRECTORY_SEPARATOR.'error.php',$srcLanguage);
422: }
423: else
424: $viewFile=$viewPath.DIRECTORY_SEPARATOR."exception.php";
425: return $viewFile;
426: }
427:
428: 429: 430: 431: 432:
433: protected function getVersionInfo()
434: {
435: if(YII_DEBUG)
436: {
437: $version='<a href="http://www.yiiframework.com/">Yii Framework</a>/'.Yii::getVersion();
438: if(isset($_SERVER['SERVER_SOFTWARE']))
439: $version=$_SERVER['SERVER_SOFTWARE'].' '.$version;
440: }
441: else
442: $version='';
443: return $version;
444: }
445:
446: 447: 448: 449: 450: 451:
452: protected function argumentsToString($args)
453: {
454: $count=0;
455:
456: $isAssoc=$args!==array_values($args);
457:
458: foreach($args as $key => $value)
459: {
460: $count++;
461: if($count>=5)
462: {
463: if($count>5)
464: unset($args[$key]);
465: else
466: $args[$key]='...';
467: continue;
468: }
469:
470: if(is_object($value))
471: $args[$key] = get_class($value);
472: elseif(is_bool($value))
473: $args[$key] = $value ? 'true' : 'false';
474: elseif(is_string($value))
475: {
476: if(strlen($value)>64)
477: $args[$key] = '"'.substr($value,0,64).'..."';
478: else
479: $args[$key] = '"'.$value.'"';
480: }
481: elseif(is_array($value))
482: $args[$key] = 'array('.$this->argumentsToString($value).')';
483: elseif($value===null)
484: $args[$key] = 'null';
485: elseif(is_resource($value))
486: $args[$key] = 'resource';
487:
488: if(is_string($key))
489: {
490: $args[$key] = '"'.$key.'" => '.$args[$key];
491: }
492: elseif($isAssoc)
493: {
494: $args[$key] = $key.' => '.$args[$key];
495: }
496: }
497: $out = implode(", ", $args);
498:
499: return $out;
500: }
501:
502: 503: 504: 505: 506:
507: protected function isCoreCode($trace)
508: {
509: if(isset($trace['file']))
510: {
511: $systemPath=realpath(dirname(__FILE__).'/..');
512: return $trace['file']==='unknown' || strpos(realpath($trace['file']),$systemPath.DIRECTORY_SEPARATOR)===0;
513: }
514: return false;
515: }
516:
517: 518: 519: 520: 521: 522: 523:
524: protected function renderSourceCode($file,$errorLine,$maxLines)
525: {
526: $errorLine--;
527: if($errorLine<0 || ($lines=@file($file))===false || ($lineCount=count($lines))<=$errorLine)
528: return '';
529:
530: $halfLines=(int)($maxLines/2);
531: $beginLine=$errorLine-$halfLines>0 ? $errorLine-$halfLines:0;
532: $endLine=$errorLine+$halfLines<$lineCount?$errorLine+$halfLines:$lineCount-1;
533: $lineNumberWidth=strlen($endLine+1);
534:
535: $output='';
536: for($i=$beginLine;$i<=$endLine;++$i)
537: {
538: $isErrorLine = $i===$errorLine;
539: $code=sprintf("<span class=\"ln".($isErrorLine?' error-ln':'')."\">%0{$lineNumberWidth}d</span> %s",$i+1,CHtml::encode(str_replace("\t",' ',$lines[$i])));
540: if(!$isErrorLine)
541: $output.=$code;
542: else
543: $output.='<span class="error">'.$code.'</span>';
544: }
545: return '<div class="code"><pre>'.$output.'</pre></div>';
546: }
547: 548: 549: 550: 551: 552:
553: protected function ($httpCode, $replacement='')
554: {
555: $httpCodes = array(
556: 100 => 'Continue',
557: 101 => 'Switching Protocols',
558: 102 => 'Processing',
559: 118 => 'Connection timed out',
560: 200 => 'OK',
561: 201 => 'Created',
562: 202 => 'Accepted',
563: 203 => 'Non-Authoritative',
564: 204 => 'No Content',
565: 205 => 'Reset Content',
566: 206 => 'Partial Content',
567: 207 => 'Multi-Status',
568: 210 => 'Content Different',
569: 300 => 'Multiple Choices',
570: 301 => 'Moved Permanently',
571: 302 => 'Found',
572: 303 => 'See Other',
573: 304 => 'Not Modified',
574: 305 => 'Use Proxy',
575: 307 => 'Temporary Redirect',
576: 310 => 'Too many Redirect',
577: 400 => 'Bad Request',
578: 401 => 'Unauthorized',
579: 402 => 'Payment Required',
580: 403 => 'Forbidden',
581: 404 => 'Not Found',
582: 405 => 'Method Not Allowed',
583: 406 => 'Not Acceptable',
584: 407 => 'Proxy Authentication Required',
585: 408 => 'Request Time-out',
586: 409 => 'Conflict',
587: 410 => 'Gone',
588: 411 => 'Length Required',
589: 412 => 'Precondition Failed',
590: 413 => 'Request Entity Too Large',
591: 414 => 'Request-URI Too Long',
592: 415 => 'Unsupported Media Type',
593: 416 => 'Requested range unsatisfiable',
594: 417 => 'Expectation failed',
595: 418 => 'I’m a teapot',
596: 422 => 'Unprocessable entity',
597: 423 => 'Locked',
598: 424 => 'Method failure',
599: 425 => 'Unordered Collection',
600: 426 => 'Upgrade Required',
601: 449 => 'Retry With',
602: 450 => 'Blocked by Windows Parental Controls',
603: 500 => 'Internal Server Error',
604: 501 => 'Not Implemented',
605: 502 => 'Bad Gateway ou Proxy Error',
606: 503 => 'Service Unavailable',
607: 504 => 'Gateway Time-out',
608: 505 => 'HTTP Version not supported',
609: 507 => 'Insufficient storage',
610: 509 => 'Bandwidth Limit Exceeded',
611: );
612: if(isset($httpCodes[$httpCode]))
613: return $httpCodes[$httpCode];
614: else
615: return $replacement;
616: }
617: }
618: