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