/modules/cms/classes/CmsException.php

https://gitlab.com/gideonmarked/yovelife · PHP · 236 lines · 118 code · 33 blank · 85 comment · 21 complexity · 4a48c0c26c574d19abaa5f0db836c2a9 MD5 · raw file

  1. <?php namespace Cms\Classes;
  2. use File;
  3. use Twig_Error;
  4. use October\Rain\Exception\ApplicationException;
  5. use October\Rain\Halcyon\Processors\SectionParser;
  6. use Exception;
  7. /**
  8. * The CMS exception class.
  9. * The exception class handles CMS related errors. Allows the masking of other exception types which
  10. * uses actual source CMS files -- instead of cached files -- for their error content.
  11. *
  12. * @package october\cms
  13. * @author Alexey Bobkov, Samuel Georges
  14. */
  15. class CmsException extends ApplicationException
  16. {
  17. /**
  18. * @var \Cms\Classes\CmsCompoundObject A reference to a CMS object used for masking errors.
  19. */
  20. protected $compoundObject;
  21. /**
  22. * @var array Collection of error codes for each error distinction.
  23. */
  24. protected static $errorCodes = [
  25. 100 => 'General',
  26. 200 => 'INI Settings',
  27. 300 => 'PHP Content',
  28. 400 => 'Twig Template'
  29. ];
  30. /**
  31. * Creates the CMS exception object.
  32. * @param mixed $message The message to display as a string, or a CmsCompoundObject that is used
  33. * for using this exception as a mask for another exception type.
  34. * @param int $code Error code to specify the exception type:
  35. * Error 100: A general exception.
  36. * Error 200: Mask the exception as INI content.
  37. * Error 300: Mask the exception as PHP content.
  38. * Error 400: Mask the exception as Twig content.
  39. * @param \Exception $previous Previous exception.
  40. */
  41. public function __construct($message = null, $code = 100, Exception $previous = null)
  42. {
  43. if ($message instanceof CmsCompoundObject || $message instanceof ComponentPartial) {
  44. $this->compoundObject = $message;
  45. $message = '';
  46. }
  47. if (isset(static::$errorCodes[$code])) {
  48. $this->errorType = static::$errorCodes[$code];
  49. }
  50. parent::__construct($message, $code, $previous);
  51. }
  52. /**
  53. * Checks some conditions to confirm error has actually occurred
  54. * due to the CMS template code, not some external code. If the error
  55. * has occurred in external code, the function will return false. Otherwise return
  56. * true and modify the exception by overriding it's content, line and message values
  57. * to be accurate against a CMS object properties.
  58. * @param \Exception $exception The exception to modify.
  59. * @return bool
  60. */
  61. public function processCompoundObject(Exception $exception)
  62. {
  63. switch ($this->code) {
  64. case 200:
  65. $result = $this->processIni($exception);
  66. break;
  67. case 300:
  68. $result = $this->processPhp($exception);
  69. break;
  70. case 400:
  71. $result = $this->processTwig($exception);
  72. break;
  73. }
  74. if ($result !== false) {
  75. $this->file = $this->compoundObject->getFilePath();
  76. if (File::isFile($this->file) && is_readable($this->file)) {
  77. $this->fileContent = @file($this->file);
  78. }
  79. }
  80. return $result;
  81. }
  82. /**
  83. * Override properties of an exception specific to the INI section
  84. * of a CMS object.
  85. * @param \Exception $exception The exception to modify.
  86. * @return bool
  87. */
  88. protected function processIni(Exception $exception)
  89. {
  90. $message = $exception->getMessage();
  91. /*
  92. * Expecting: syntax error, unexpected '!' in Unknown on line 4
  93. */
  94. if (!starts_with($message, 'syntax error')) {
  95. return false;
  96. }
  97. if (strpos($message, 'Unknown') === false) {
  98. return false;
  99. }
  100. if (strpos($exception->getFile(), 'Ini.php') === false) {
  101. return false;
  102. }
  103. /*
  104. * Line number from parse_ini_string() error.
  105. * The last word should contain the line number.
  106. */
  107. $parts = explode(' ', $message);
  108. $line = array_pop($parts);
  109. $this->line = (int)$line;
  110. // Find where the ini settings section begins
  111. $offsetArray = SectionParser::parseOffset($this->compoundObject->getContent());
  112. $this->line += $offsetArray['settings'];
  113. $this->message = $message;
  114. // Account for line 0
  115. $this->line--;
  116. return true;
  117. }
  118. /**
  119. * Override properties of an exception specific to the PHP section
  120. * of a CMS object.
  121. * @param \Exception $exception The exception to modify.
  122. * @return bool
  123. */
  124. protected function processPhp(Exception $exception)
  125. {
  126. /*
  127. * Fatal Error
  128. */
  129. if ($exception instanceof \Symfony\Component\Debug\Exception\FatalErrorException) {
  130. $check = false;
  131. // Expected: */modules/cms/classes/CodeParser.php(165) : eval()'d code line 7
  132. if (strpos($exception->getFile(), 'CodeParser.php')) {
  133. $check = true;
  134. }
  135. // Expected: */storage/cms/cache/39/05/home.htm.php
  136. if (strpos($exception->getFile(), $this->compoundObject->getFileName() . '.php')) {
  137. $check = true;
  138. }
  139. if (!$check) {
  140. return false;
  141. }
  142. /*
  143. * Errors occurring the PHP code base class (Cms\Classes\CodeBase)
  144. */
  145. }
  146. else {
  147. $trace = $exception->getTrace();
  148. if (isset($trace[1]['class'])) {
  149. $class = $trace[1]['class'];
  150. if (!is_subclass_of($class, 'Cms\Classes\CodeBase')) {
  151. return false;
  152. }
  153. }
  154. }
  155. $this->message = $exception->getMessage();
  156. // Offset the php, namespace and bracket tags from the generated class.
  157. $this->line = $exception->getLine() - 3;
  158. // Find where the php code section begins
  159. $offsetArray = SectionParser::parseOffset($this->compoundObject->getContent());
  160. $this->line += $offsetArray['code'];
  161. // Account for line 0
  162. $this->line--;
  163. return true;
  164. }
  165. /**
  166. * Override properties of an exception specific to the Twig section
  167. * of a CMS object.
  168. * @param \Exception $exception The exception to modify.
  169. * @return bool
  170. */
  171. protected function processTwig(Exception $exception)
  172. {
  173. // Must be a Twig related exception
  174. if (!$exception instanceof Twig_Error) {
  175. return false;
  176. }
  177. $this->message = $exception->getRawMessage();
  178. $this->line = $exception->getTemplateLine();
  179. // Find where the twig markup section begins
  180. $offsetArray = SectionParser::parseOffset($this->compoundObject->getContent());
  181. $this->line += $offsetArray['markup'];
  182. // Account for line 0
  183. $this->line--;
  184. return true;
  185. }
  186. /**
  187. * Masks this exception with the details of the supplied. The error code for
  188. * this exception object will determine how the supplied exception is used.
  189. * Error 100: A general exception. Inherits \System\Classes\ExceptionBase::applyMask()
  190. * Error 200: Mask the exception as INI content.
  191. * Error 300: Mask the exception as PHP content.
  192. * Error 400: Mask the exception as Twig content.
  193. * @param \Exception $exception The exception to modify.
  194. * @return void
  195. */
  196. public function applyMask(Exception $exception)
  197. {
  198. if ($this->code == 100 || $this->processCompoundObject($exception) === false) {
  199. parent::applyMask($exception);
  200. return;
  201. }
  202. }
  203. }