Merge pull request #14133 from civicrm/5.13
[civicrm-core.git] / CRM / Core / Region.php
CommitLineData
6a488035
TO
1<?php
2
3/**
4 * Maintain a set of markup/templates to inject inside various regions
5 */
6class CRM_Core_Region {
7 static private $_instances = NULL;
8
9 /**
d09edf64 10 * Obtain the content for a given region.
6a488035
TO
11 *
12 * @param string $name
6a0b768e
TO
13 * @param bool $autocreate
14 * Whether to automatically create an empty region.
6a488035
TO
15 * @return CRM_Core_Region
16 */
00be9182 17 public static function &instance($name, $autocreate = TRUE) {
353ffa53 18 if ($autocreate && !isset(self::$_instances[$name])) {
6a488035
TO
19 self::$_instances[$name] = new CRM_Core_Region($name);
20 }
21 return self::$_instances[$name];
22 }
23
24 /**
25 * Symbolic name of this region
26 *
27 * @var string
28 */
518fa0ee 29 public $_name;
6a488035
TO
30
31 /**
32 * List of snippets to inject within region
33 *
34 * @var array; e.g. $this->_snippets[3]['type'] = 'template';
35 */
518fa0ee 36 public $_snippets;
6a488035
TO
37
38 /**
39 * Whether the snippets array has been sorted
40 *
41 * @var boolean
42 */
518fa0ee 43 public $_isSorted;
6a488035 44
a0ee3941 45 /**
100fef9d 46 * @param string $name
a0ee3941 47 */
6a488035
TO
48 public function __construct($name) {
49 // Templates injected into regions should normally be file names, but sometimes inline notation is handy.
50 require_once 'CRM/Core/Smarty/resources/String.php';
481a74f4 51 civicrm_smarty_register_string_resource();
6a488035
TO
52
53 $this->_name = $name;
be2fb01f 54 $this->_snippets = [];
6a488035
TO
55
56 // Placeholder which represents any of the default content generated by the main Smarty template
be2fb01f 57 $this->add([
6a488035
TO
58 'name' => 'default',
59 'type' => 'markup',
60 'markup' => '',
61 'weight' => 0,
be2fb01f 62 ]);
6a488035
TO
63 $this->_isSorted = TRUE;
64 }
65
66 /**
d09edf64 67 * Add a snippet of content to a region.
6a488035
TO
68 *
69 * @code
70 * CRM_Core_Region::instance('page-header')->add(array(
71 * 'markup' => '<div style="color:red">Hello!</div>',
72 * ));
73 * CRM_Core_Region::instance('page-header')->add(array(
74 * 'script' => 'alert("Hello");',
75 * ));
76 * CRM_Core_Region::instance('page-header')->add(array(
77 * 'template' => 'CRM/Myextension/Extra.tpl',
78 * ));
79 * CRM_Core_Region::instance('page-header')->add(array(
80 * 'callback' => 'myextension_callback_function',
81 * ));
82 * @endcode
83 *
84 * Note: This function does not perform any extra encoding of markup, script code, or etc. If
85 * you're passing in user-data, you must clean it yourself.
86 *
5a4f6742 87 * @param array $snippet
6a0b768e 88 * Array; keys:.
6a488035
TO
89 * - type: string (auto-detected for markup, template, callback, script, scriptUrl, jquery, style, styleUrl)
90 * - name: string, optional
91 * - weight: int, optional; default=1
92 * - disabled: int, optional; default=0
93 * - markup: string, HTML; required (for type==markup)
94 * - template: string, path; required (for type==template)
95 * - callback: mixed; required (for type==callback)
96 * - arguments: array, optional (for type==callback)
97 * - script: string, Javascript code
98 * - scriptUrl: string, URL of a Javascript file
99 * - jquery: string, Javascript code which runs inside a jQuery(function($){...}); block
100 * - style: string, CSS code
101 * - styleUrl: string, URL of a CSS file
77b97be7
EM
102 *
103 * @return array
6a488035
TO
104 */
105 public function add($snippet) {
be2fb01f
CW
106 static $types = ['markup', 'template', 'callback', 'scriptUrl', 'script', 'jquery', 'style', 'styleUrl'];
107 $defaults = [
6a488035
TO
108 'region' => $this->_name,
109 'weight' => 1,
110 'disabled' => FALSE,
be2fb01f 111 ];
6a488035
TO
112 $snippet += $defaults;
113 if (!isset($snippet['type'])) {
114 foreach ($types as $type) {
115 // auto-detect
116 if (isset($snippet[$type])) {
117 $snippet['type'] = $type;
118 break;
119 }
120 }
121 }
122 if (!isset($snippet['name'])) {
123 $snippet['name'] = count($this->_snippets);
124 }
aefd7f6b 125
2aa397bc 126 $this->_snippets[$snippet['name']] = $snippet;
6a488035
TO
127 $this->_isSorted = FALSE;
128 return $snippet;
129 }
130
a0ee3941 131 /**
100fef9d 132 * @param string $name
a0ee3941
EM
133 * @param $snippet
134 */
6a488035
TO
135 public function update($name, $snippet) {
136 $this->_snippets[$name] = array_merge($this->_snippets[$name], $snippet);
137 $this->_isSorted = FALSE;
138 }
139
a0ee3941 140 /**
aefd7f6b
EM
141 * Get snippet.
142 *
100fef9d 143 * @param string $name
a0ee3941
EM
144 *
145 * @return mixed
146 */
aefd7f6b
EM
147 public function get($name) {
148 return !empty($this->_snippets[$name]) ? $this->_snippets[$name] : NULL;
6a488035
TO
149 }
150
151 /**
d09edf64 152 * Render all the snippets in a region.
6a488035 153 *
6a0b768e
TO
154 * @param string $default
155 * HTML, the initial content of the region.
156 * @param bool $allowCmsOverride
157 * Allow CMS to override rendering of region.
6a488035
TO
158 * @return string, HTML
159 */
160 public function render($default, $allowCmsOverride = TRUE) {
161 // $default is just another part of the region
162 if (is_array($this->_snippets['default'])) {
163 $this->_snippets['default']['markup'] = $default;
164 }
165 // We hand as much of the work off to the CMS as possible
166 $cms = CRM_Core_Config::singleton()->userSystem;
167
168 if (!$this->_isSorted) {
be2fb01f 169 uasort($this->_snippets, ['CRM_Core_Region', '_cmpSnippet']);
6a488035
TO
170 $this->_isSorted = TRUE;
171 }
172
173 $smarty = CRM_Core_Smarty::singleton();
174 $html = '';
175 foreach ($this->_snippets as $snippet) {
176 if ($snippet['disabled']) {
177 continue;
178 }
22e263ad 179 switch ($snippet['type']) {
6a488035
TO
180 case 'markup':
181 $html .= $snippet['markup'];
182 break;
2aa397bc 183
6a488035
TO
184 case 'template':
185 $tmp = $smarty->get_template_vars('snippet');
186 $smarty->assign('snippet', $snippet);
187 $html .= $smarty->fetch($snippet['template']);
188 $smarty->assign('snippet', $tmp);
189 break;
2aa397bc 190
6a488035 191 case 'callback':
16fff44f 192 $args = isset($snippet['arguments']) ? $snippet['arguments'] : array(&$snippet, &$html);
6a488035
TO
193 $html .= call_user_func_array($snippet['callback'], $args);
194 break;
2aa397bc 195
6a488035
TO
196 case 'scriptUrl':
197 if (!$allowCmsOverride || !$cms->addScriptUrl($snippet['scriptUrl'], $this->_name)) {
198 $html .= sprintf("<script type=\"text/javascript\" src=\"%s\">\n</script>\n", $snippet['scriptUrl']);
199 }
200 break;
2aa397bc 201
6a488035 202 case 'jquery':
3cc60a06 203 $snippet['script'] = sprintf("CRM.\$(function(\$) {\n%s\n});", $snippet['jquery']);
e7483cbe 204 // no break - continue processing as script
6a488035
TO
205 case 'script':
206 if (!$allowCmsOverride || !$cms->addScript($snippet['script'], $this->_name)) {
207 $html .= sprintf("<script type=\"text/javascript\">\n%s\n</script>\n", $snippet['script']);
208 }
209 break;
2aa397bc 210
6a488035
TO
211 case 'styleUrl':
212 if (!$allowCmsOverride || !$cms->addStyleUrl($snippet['styleUrl'], $this->_name)) {
213 $html .= sprintf("<link href=\"%s\" rel=\"stylesheet\" type=\"text/css\"/>\n", $snippet['styleUrl']);
214 }
215 break;
2aa397bc 216
6a488035
TO
217 case 'style':
218 if (!$allowCmsOverride || !$cms->addStyle($snippet['style'], $this->_name)) {
219 $html .= sprintf("<style type=\"text/css\">\n%s\n</style>\n", $snippet['style']);
220 }
221 break;
2aa397bc 222
6a488035
TO
223 default:
224 require_once 'CRM/Core/Error.php';
481a74f4 225 CRM_Core_Error::fatal(ts('Snippet type %1 is unrecognized',
be2fb01f 226 [1 => $snippet['type']]));
6a488035
TO
227 }
228 }
229 return $html;
230 }
231
a0ee3941
EM
232 /**
233 * @param $a
234 * @param $b
235 *
236 * @return int
237 */
00be9182 238 public static function _cmpSnippet($a, $b) {
4f99ca55
TO
239 if ($a['weight'] < $b['weight']) {
240 return -1;
2aa397bc 241 }
4f99ca55
TO
242 if ($a['weight'] > $b['weight']) {
243 return 1;
2aa397bc 244 }
6a488035 245 // fallback to name sort; don't really want to do this, but it makes results more stable
4f99ca55
TO
246 if ($a['name'] < $b['name']) {
247 return -1;
2aa397bc 248 }
4f99ca55
TO
249 if ($a['name'] > $b['name']) {
250 return 1;
2aa397bc 251 }
6a488035
TO
252 return 0;
253 }
254
255 /**
d09edf64 256 * Add block of static HTML to a region.
6a488035 257 *
6a0b768e
TO
258 * @param string $markup
259 * HTML.
6a488035 260 *
353ffa53
TO
261 * public function addMarkup($markup) {
262 * return $this->add(array(
263 * 'type' => 'markup',
264 * 'markup' => $markup,
265 * ));
266 * }
267 *
268 * /**
d09edf64 269 * Add a Smarty template file to a region.
6a488035
TO
270 *
271 * Note: File is not evaluated until the page is rendered
272 *
6a0b768e
TO
273 * @param string $template
274 * Path to the Smarty template file.
6a488035 275 *
353ffa53
TO
276 * public function addTemplate($template) {
277 * return $this->add(array(
278 * 'type' => 'template',
279 * 'template' => $template,
280 * ));
281 * }
282 *
283 * /**
d09edf64 284 * Use a callback function to extend a region.
6a488035
TO
285 *
286 * @param mixed $callback
6a0b768e
TO
287 * @param array $arguments
288 * Optional, array of parameters for callback; if omitted, the default arguments are ($snippetSpec, $html).
6a488035 289 *
353ffa53
TO
290 * public function addCallback($callback, $arguments = FALSE) {
291 * return $this->add(array(
292 * 'type' => 'callback',
293 * 'callback' => $callback,
294 * 'arguments' => $arguments,
295 * ));
296 * }
297 */
96025800 298
6a488035 299}