FlashComponent.php 7.1 KB

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