FlashComponent.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <?php
  2. App::uses('Component', 'Controller');
  3. App::uses('Configure', 'Core');
  4. App::uses('Inflector', 'Utility');
  5. /**
  6. * A flash component to enhance flash message support with stackable messages, both
  7. * persistent and transient.
  8. *
  9. * @author Mark Scherer
  10. * @copyright 2012 Mark Scherer
  11. * @license http://opensource.org/licenses/mit-license.php MIT
  12. */
  13. class FlashComponent extends Component {
  14. public $components = ['Session'];
  15. protected $_defaultConfig = [
  16. 'headerOnAjax' => true,
  17. 'transformCore' => true,
  18. 'useElements' => false, // Set to true to use 3.x flash message rendering via Elements
  19. 'type' => 'info',
  20. 'typeToElement' => false, // Set to true to have a single type to Element matching
  21. 'plugin' => null, // Only for typeToElement
  22. 'element' => 'Tools.default',
  23. 'params' => [],
  24. 'escape' => false
  25. ];
  26. /**
  27. * Constructor.
  28. *
  29. * @param ComponentCollection $collection
  30. * @param array $config
  31. */
  32. public function __construct(ComponentCollection $collection, $config = []) {
  33. $defaults = (array)Configure::read('Flash') + $this->_defaultConfig;
  34. //BC
  35. if (Configure::read('Common.messages') !== null) {
  36. $defaults['transformCore'] = Configure::read('Common.messages');
  37. }
  38. $config += $defaults;
  39. parent::__construct($collection, $config);
  40. }
  41. /**
  42. * For automatic startup
  43. * for this helper the controller has to be passed as reference
  44. *
  45. * @return void
  46. */
  47. public function initialize(Controller $Controller) {
  48. parent::initialize($Controller);
  49. $this->Controller = $Controller;
  50. }
  51. /**
  52. * Called after the Controller::beforeRender(), after the view class is loaded, and before the
  53. * Controller::render()
  54. *
  55. * Unless Configure::read('Ajax.transformCore') is false, it will also transform any core ones to this plugin.
  56. * Unless Configure::read('Ajax.headerOnAjax') is false, it will pass the messages as header to AJAX requests.
  57. * Set it to false if other components are handling the message return in AJAX use cases already.
  58. *
  59. * @param object $Controller Controller with components to beforeRender
  60. * @return void
  61. */
  62. public function beforeRender(Controller $Controller) {
  63. if ($this->settings['transformCore'] && $messages = $this->Session->read('Message')) {
  64. foreach ($messages as $message) {
  65. $this->message($message['message'], 'error');
  66. }
  67. $this->Session->delete('Message');
  68. }
  69. if ($this->settings['headerOnAjax'] && isset($this->Controller->request) && $this->Controller->request->is('ajax')) {
  70. $ajaxMessages = array_merge(
  71. (array)$this->Session->read('messages'),
  72. (array)Configure::read('messages')
  73. );
  74. // The header can be read with JavaScript and a custom Message can be displayed
  75. $this->Controller->response->header('X-Ajax-Flashmessage', json_encode($ajaxMessages));
  76. $this->Session->delete('messages');
  77. }
  78. }
  79. /**
  80. * Adds a flash message.
  81. * Updates "messages" session content (to enable multiple messages of one type).
  82. *
  83. * ### Options:
  84. *
  85. * - `element` The element used to render the flash message. Default to 'default'.
  86. * - `params` An array of variables to make available when using an element.
  87. * - `escape` If content should be escaped or not in the element itself or if elements are not used in the component.
  88. * - `typeToElement`
  89. * - `plugin`
  90. *
  91. * @param string $message Message to output.
  92. * @param array|string $options Options or Type ('error', 'warning', 'success', 'info' or custom class).
  93. * @return void
  94. */
  95. public function message($message, $options = []) {
  96. $message = $this->_prepMessage($message, $options, $this->settings);
  97. $old = (array)$this->Session->read('messages');
  98. $type = $options['type'];
  99. if (isset($old[$type]) && count($old[$type]) > 99) {
  100. array_shift($old[$type]);
  101. }
  102. $old[$type][] = $message;
  103. $this->Session->write('messages', $old);
  104. }
  105. /**
  106. * Adds a transient flash message.
  107. * These flash messages that are not saved (only available for current view),
  108. * will be merged into the session flash ones prior to output.
  109. *
  110. * Since this method can be accessed statically, it only works with Configure configuration,
  111. * not with runtime config as the normal message() method.
  112. *
  113. * ### Options:
  114. *
  115. * - `element` The element used to render the flash message. Default to 'default'.
  116. * - `params` An array of variables to make available when using an element.
  117. * - `escape` If content should be escaped or not in the element itself or if elements are not used in the component.
  118. * - `typeToElement`
  119. * - `plugin`
  120. * - `useElements`
  121. *
  122. * @param string $message Message to output.
  123. * @param array|string $options Options or Type ('error', 'warning', 'success', 'info' or custom class).
  124. * @return void
  125. */
  126. public static function transientMessage($message, $options = []) {
  127. $defaults = (array)Configure::read('Flash') + [
  128. 'type' => 'info',
  129. 'escape' => false,
  130. 'params' => [],
  131. 'element' => 'Tools.default',
  132. 'typeToElement' => false,
  133. 'useElements' => false,
  134. 'plugin' => null,
  135. ];
  136. $message = static::_prepMessage($message, $options, $defaults);
  137. $old = (array)Configure::read('messages');
  138. $type = $options['type'];
  139. if (isset($old[$type]) && count($old[$type]) > 99) {
  140. array_shift($old[$type]);
  141. }
  142. $old[$type][] = $message;
  143. Configure::write('messages', $old);
  144. }
  145. /**
  146. * FlashComponent::_prepMessage()
  147. *
  148. * @param string $message
  149. * @param array $options
  150. * @return array
  151. */
  152. protected static function _prepMessage($message, &$options, $defaults) {
  153. if (!is_array($options)) {
  154. $type = $options ?: $defaults['type'];
  155. $options = ['type' => $type];
  156. }
  157. $options += $defaults;
  158. $message = [
  159. 'message' => $message,
  160. 'params' => $options['params'],
  161. 'escape' => $options['escape']
  162. ];
  163. if ($options['useElements']) {
  164. if ($options['typeToElement'] && $options['element'] === $defaults['element']) {
  165. $options['element'] = ($options['plugin'] ? $options['plugin'] . '.' : '') . $options['type'];
  166. }
  167. list($plugin, $element) = pluginSplit($options['element']);
  168. if ($plugin) {
  169. $message['element'] = $plugin . '.Flash/' . $element;
  170. } else {
  171. $message['element'] = 'Flash/' . $element;
  172. }
  173. } else {
  174. // Simplify?
  175. if (!$message['escape'] && !$message['params']) {
  176. $message = $message['message'];
  177. }
  178. }
  179. return $message;
  180. }
  181. /**
  182. * Magic method for verbose flash methods based on types.
  183. *
  184. * For example: $this->Flash->success('My message')
  185. *
  186. * @param string $name Element name to use.
  187. * @param array $args Parameters to pass when calling `FlashComponent::set()`.
  188. * @return void
  189. * @throws InternalErrorException If missing the flash message.
  190. */
  191. public function __call($name, $args) {
  192. if (count($args) < 1) {
  193. throw new InternalErrorException('Flash message missing.');
  194. }
  195. $options = ['type' => Inflector::underscore($name)];
  196. if (!empty($args[1])) {
  197. $options += (array)$args[1];
  198. }
  199. $this->message($args[0], $options);
  200. }
  201. }