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