Move pear/mail from packages to composer.json.
[civicrm-core.git] / tests / phpunit / CRM / Core / ResourcesTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * Tests for linking to resource files
30 * @group headless
31 */
32 class CRM_Core_ResourcesTest extends CiviUnitTestCase {
33
34 /**
35 * @var CRM_Core_Resources
36 */
37 protected $res;
38
39 /**
40 * @var CRM_Extension_Mapper
41 */
42 protected $mapper;
43
44 /**
45 * @var string for testing cache buster generation
46 */
47 protected $cacheBusterString = 'xBkdk3';
48
49 protected $originalRequest;
50 protected $originalGet;
51
52 public function setUp() {
53 parent::setUp();
54
55 list ($this->basedir, $this->container, $this->mapper) = $this->_createMapper();
56 $cache = new CRM_Utils_Cache_Arraycache(array());
57 $this->res = new CRM_Core_Resources($this->mapper, $cache, NULL);
58 $this->res->setCacheCode('resTest');
59 CRM_Core_Resources::singleton($this->res);
60
61 // Templates injected into regions should normally be file names, but for unit-testing it's handy to use "string:" notation
62 require_once 'CRM/Core/Smarty/resources/String.php';
63 civicrm_smarty_register_string_resource();
64
65 $this->originalRequest = $_REQUEST;
66 $this->originalGet = $_GET;
67 }
68
69 /**
70 * Restore globals so this test doesn't interfere with others.
71 */
72 public function tearDown() {
73 $_REQUEST = $this->originalRequest;
74 $_GET = $this->originalGet;
75 }
76
77 public function testAddScriptFile() {
78 $this->res
79 ->addScriptFile('com.example.ext', 'foo%20bar.js', 0, 'testAddScriptFile')
80 ->addScriptFile('com.example.ext', 'foo%20bar.js', 0, 'testAddScriptFile')// extra
81 ->addScriptFile('civicrm', 'foo%20bar.js', 0, 'testAddScriptFile');
82
83 $smarty = CRM_Core_Smarty::singleton();
84 $actual = $smarty->fetch('string:{crmRegion name=testAddScriptFile}{/crmRegion}');
85 $expected = "" // stable ordering: alphabetical by (snippet.weight,snippet.name)
86 . "<script type=\"text/javascript\" src=\"http://core-app/foo%20bar.js?r=resTest\">\n</script>\n"
87 . "<script type=\"text/javascript\" src=\"http://ext-dir/com.example.ext/foo%20bar.js?r=resTest\">\n</script>\n";
88 $this->assertEquals($expected, $actual);
89 }
90
91 /**
92 * When adding a script file, any ts() expressions should be translated and added to the 'strings'
93 *
94 * FIXME: This can't work because the tests run in English and CRM_Core_Resources optimizes
95 * away the English data from $settings['strings']
96 * public function testAddScriptFile_strings() {
97 * file_put_contents($this->mapper->keyToBasePath('com.example.ext') . '/hello.js', 'alert(ts("Hello world"));');
98 * $this->res->addScriptFile('com.example.ext', 'hello.js', 0, 'testAddScriptFile_strings');
99 * $settings = $this->res->getSettings();
100 * $expected = array('Hello world');
101 * $this->assertEquals($expected, $settings['strings']);
102 * }
103 */
104
105 /**
106 * Ensure that adding a script URL creates expected markup.
107 */
108 public function testAddScriptURL() {
109 $this->res
110 ->addScriptUrl('/whiz/foo%20bar.js', 0, 'testAddScriptURL')
111 ->addScriptUrl('/whiz/foo%20bar.js', 0, 'testAddScriptURL')// extra
112 ->addScriptUrl('/whizbang/foo%20bar.js', 0, 'testAddScriptURL');
113
114 $smarty = CRM_Core_Smarty::singleton();
115 $actual = $smarty->fetch('string:{crmRegion name=testAddScriptURL}{/crmRegion}');
116 $expected = "" // stable ordering: alphabetical by (snippet.weight,snippet.name)
117 . "<script type=\"text/javascript\" src=\"/whiz/foo%20bar.js\">\n</script>\n"
118 . "<script type=\"text/javascript\" src=\"/whizbang/foo%20bar.js\">\n</script>\n";
119 $this->assertEquals($expected, $actual);
120 }
121
122 public function testAddScript() {
123 $this->res
124 ->addScript('alert("hi");', 0, 'testAddScript')
125 ->addScript('alert("there");', 0, 'testAddScript');
126
127 $smarty = CRM_Core_Smarty::singleton();
128 $actual = $smarty->fetch('string:{crmRegion name=testAddScript}{/crmRegion}');
129 $expected = ""
130 . "<script type=\"text/javascript\">\nalert(\"hi\");\n</script>\n"
131 . "<script type=\"text/javascript\">\nalert(\"there\");\n</script>\n";
132 $this->assertEquals($expected, $actual);
133 }
134
135 public function testAddVars() {
136 $this->res
137 ->addVars('food', array('fruit' => array('mine' => 'apple', 'ours' => 'banana')))
138 ->addVars('food', array('fruit' => array('mine' => 'new apple', 'yours' => 'orange')));
139 $this->assertTreeEquals(
140 array(
141 'vars' => array(
142 'food' => array(
143 'fruit' => array(
144 'yours' => 'orange',
145 'mine' => 'new apple',
146 'ours' => 'banana',
147 ),
148 ),
149 ),
150 ),
151 $this->res->getSettings()
152 );
153 }
154
155 public function testAddSetting() {
156 $this->res
157 ->addSetting(array('fruit' => array('mine' => 'apple')))
158 ->addSetting(array('fruit' => array('yours' => 'orange')));
159 $this->assertTreeEquals(
160 array('fruit' => array('yours' => 'orange', 'mine' => 'apple')),
161 $this->res->getSettings()
162 );
163 $actual = $this->res->renderSetting();
164 $expected = json_encode(array('fruit' => array('yours' => 'orange', 'mine' => 'apple')));
165 $this->assertTrue(strpos($actual, $expected) !== FALSE);
166 }
167
168 public function testAddSettingHook() {
169 $test = $this;
170 Civi::dispatcher()->addListener('hook_civicrm_alterResourceSettings', function($event) use ($test) {
171 $test->assertEquals('apple', $event->data['fruit']['mine']);
172 $event->data['fruit']['mine'] = 'banana';
173 });
174 $this->res->addSetting(array('fruit' => array('mine' => 'apple')));
175 $settings = $this->res->getSettings();
176 $this->assertTreeEquals(array('fruit' => array('mine' => 'banana')), $settings);
177 }
178
179 public function testAddSettingFactory() {
180 $this->res->addSettingsFactory(function () {
181 return array('fruit' => array('yours' => 'orange'));
182 });
183 $this->res->addSettingsFactory(function () {
184 return array('fruit' => array('mine' => 'apple'));
185 });
186
187 $actual = $this->res->getSettings();
188 $expected = array('fruit' => array('yours' => 'orange', 'mine' => 'apple'));
189 $this->assertTreeEquals($expected, $actual);
190 }
191
192 public function testAddSettingAndSettingFactory() {
193 $this->res->addSetting(array('fruit' => array('mine' => 'apple')));
194
195 $muckableValue = array('fruit' => array('yours' => 'orange', 'theirs' => 'apricot'));
196 $this->res->addSettingsFactory(function () use (&$muckableValue) {
197 return $muckableValue;
198 });
199 $actual = $this->res->getSettings();
200 $expected = array('fruit' => array('mine' => 'apple', 'yours' => 'orange', 'theirs' => 'apricot'));
201 $this->assertTreeEquals($expected, $actual);
202
203 // note: the setting is not fixed based on what the factory returns when registered; it's based
204 // on what the factory returns when getSettings is called
205 $muckableValue = array('fruit' => array('yours' => 'banana'));
206 $actual = $this->res->getSettings();
207 $expected = array('fruit' => array('mine' => 'apple', 'yours' => 'banana'));
208 $this->assertTreeEquals($expected, $actual);
209 }
210
211 public function testCrmJS() {
212 $smarty = CRM_Core_Smarty::singleton();
213
214 $actual = $smarty->fetch('string:{crmScript ext=com.example.ext file=foo%20bar.js region=testCrmJS}');
215 $this->assertEquals('', $actual);
216
217 $actual = $smarty->fetch('string:{crmScript url=/whiz/foo%20bar.js region=testCrmJS weight=1}');
218 $this->assertEquals('', $actual);
219
220 $actual = $smarty->fetch('string:{crmRegion name=testCrmJS}{/crmRegion}');
221 $expected = "" // stable ordering: alphabetical by (snippet.weight,snippet.name)
222 . "<script type=\"text/javascript\" src=\"http://ext-dir/com.example.ext/foo%20bar.js?r=resTest\">\n</script>\n"
223 . "<script type=\"text/javascript\" src=\"/whiz/foo%20bar.js\">\n</script>\n";
224 $this->assertEquals($expected, $actual);
225 }
226
227 public function testAddStyleFile() {
228 $this->res
229 ->addStyleFile('com.example.ext', 'foo%20bar.css', 0, 'testAddStyleFile')
230 ->addStyleFile('com.example.ext', 'foo%20bar.css', 0, 'testAddStyleFile')// extra
231 ->addStyleFile('civicrm', 'foo%20bar.css', 0, 'testAddStyleFile');
232
233 $smarty = CRM_Core_Smarty::singleton();
234 $actual = $smarty->fetch('string:{crmRegion name=testAddStyleFile}{/crmRegion}');
235 $expected = "" // stable ordering: alphabetical by (snippet.weight,snippet.name)
236 . "<link href=\"http://core-app/foo%20bar.css?r=resTest\" rel=\"stylesheet\" type=\"text/css\"/>\n"
237 . "<link href=\"http://ext-dir/com.example.ext/foo%20bar.css?r=resTest\" rel=\"stylesheet\" type=\"text/css\"/>\n";
238 $this->assertEquals($expected, $actual);
239 }
240
241 public function testAddStyleURL() {
242 $this->res
243 ->addStyleUrl('/whiz/foo%20bar.css', 0, 'testAddStyleURL')
244 ->addStyleUrl('/whiz/foo%20bar.css', 0, 'testAddStyleURL')// extra
245 ->addStyleUrl('/whizbang/foo%20bar.css', 0, 'testAddStyleURL');
246
247 $smarty = CRM_Core_Smarty::singleton();
248 $actual = $smarty->fetch('string:{crmRegion name=testAddStyleURL}{/crmRegion}');
249 $expected = "" // stable ordering: alphabetical by (snippet.weight,snippet.name)
250 . "<link href=\"/whiz/foo%20bar.css\" rel=\"stylesheet\" type=\"text/css\"/>\n"
251 . "<link href=\"/whizbang/foo%20bar.css\" rel=\"stylesheet\" type=\"text/css\"/>\n";
252 $this->assertEquals($expected, $actual);
253 }
254
255 public function testAddStyle() {
256 $this->res
257 ->addStyle('body { background: black; }', 0, 'testAddStyle')
258 ->addStyle('body { text-color: black; }', 0, 'testAddStyle');
259
260 $smarty = CRM_Core_Smarty::singleton();
261 $actual = $smarty->fetch('string:{crmRegion name=testAddStyle}{/crmRegion}');
262 $expected = ""
263 . "<style type=\"text/css\">\nbody { background: black; }\n</style>\n"
264 . "<style type=\"text/css\">\nbody { text-color: black; }\n</style>\n";
265 $this->assertEquals($expected, $actual);
266 }
267
268 public function testCrmCSS() {
269 $smarty = CRM_Core_Smarty::singleton();
270
271 $actual = $smarty->fetch('string:{crmStyle ext=com.example.ext file=foo%20bar.css region=testCrmCSS}');
272 $this->assertEquals('', $actual);
273
274 $actual = $smarty->fetch('string:{crmStyle url=/whiz/foo%20bar.css region=testCrmCSS weight=1}');
275 $this->assertEquals('', $actual);
276
277 $actual = $smarty->fetch('string:{crmRegion name=testCrmCSS}{/crmRegion}');
278 $expected = "" // stable ordering: alphabetical by (snippet.weight,snippet.name)
279 . "<link href=\"http://ext-dir/com.example.ext/foo%20bar.css?r=resTest\" rel=\"stylesheet\" type=\"text/css\"/>\n"
280 . "<link href=\"/whiz/foo%20bar.css\" rel=\"stylesheet\" type=\"text/css\"/>\n";
281 $this->assertEquals($expected, $actual);
282 }
283
284 public function testGetURL() {
285 $this->assertEquals(
286 'http://core-app/dir/file%20name.txt',
287 $this->res->getURL('civicrm', 'dir/file%20name.txt')
288 );
289 $this->assertEquals(
290 'http://ext-dir/com.example.ext/dir/file%20name.txt',
291 $this->res->getURL('com.example.ext', 'dir/file%20name.txt')
292 );
293 $this->assertEquals(
294 'http://core-app/',
295 $this->res->getURL('civicrm')
296 );
297 $this->assertEquals(
298 'http://ext-dir/com.example.ext/',
299 $this->res->getURL('com.example.ext')
300 );
301 }
302
303 public function testCrmResURL() {
304 $smarty = CRM_Core_Smarty::singleton();
305
306 $actual = $smarty->fetch('string:{crmResURL ext=com.example.ext file=foo%20bar.png}');
307 $this->assertEquals('http://ext-dir/com.example.ext/foo%20bar.png', $actual);
308
309 $actual = $smarty->fetch('string:{crmResURL ext=com.example.ext file=foo%20bar.png addCacheCode=1}');
310 $this->assertEquals('http://ext-dir/com.example.ext/foo%20bar.png?r=resTest', $actual);
311
312 $actual = $smarty->fetch('string:{crmResURL ext=com.example.ext}');
313 $this->assertEquals('http://ext-dir/com.example.ext/', $actual);
314 }
315
316 public function testGlob() {
317 $this->assertEquals(
318 array('info.xml'),
319 $this->res->glob('com.example.ext', 'info.xml')
320 );
321 $this->assertEquals(
322 array('js/example.js'),
323 $this->res->glob('com.example.ext', 'js/*.js')
324 );
325 $this->assertEquals(
326 array('js/example.js'),
327 $this->res->glob('com.example.ext', array('js/*.js'))
328 );
329 }
330
331 /**
332 * @dataProvider ajaxModeData
333 */
334 public function testIsAjaxMode($query, $result) {
335 $_REQUEST = $_GET = $query;
336 $this->assertEquals($result, CRM_Core_Resources::isAjaxMode());
337 }
338
339 public function ajaxModeData() {
340 return array(
341 array(array('q' => 'civicrm/ajax/foo'), TRUE),
342 array(array('q' => 'civicrm/angularprofiles/template'), TRUE),
343 array(array('q' => 'civicrm/test/page'), FALSE),
344 array(array('q' => 'civicrm/test/page', 'snippet' => 'json'), TRUE),
345 array(array('q' => 'civicrm/test/page', 'snippet' => 'foo'), FALSE),
346 );
347 }
348
349 /**
350 * @param CRM_Utils_Cache_Interface $cache
351 * @param string $cacheKey
352 *
353 * @return array
354 * [string $basedir, CRM_Extension_Container_Interface, CRM_Extension_Mapper]
355 */
356 public function _createMapper(CRM_Utils_Cache_Interface $cache = NULL, $cacheKey = NULL) {
357 $basedir = rtrim($this->createTempDir('ext-'), '/');
358 mkdir("$basedir/com.example.ext");
359 mkdir("$basedir/com.example.ext/js");
360 file_put_contents("$basedir/com.example.ext/info.xml", "<extension key='com.example.ext' type='report'><file>oddball</file></extension>");
361 file_put_contents("$basedir/com.example.ext/js/example.js", "alert('Boo!');");
362 // not needed for now // file_put_contents("$basedir/weird/bar/oddball.php", "<?php\n");
363 $c = new CRM_Extension_Container_Basic($basedir, 'http://ext-dir', $cache, $cacheKey);
364 $mapper = new CRM_Extension_Mapper($c, NULL, NULL, '/pathto/civicrm', 'http://core-app');
365 return array($basedir, $c, $mapper);
366 }
367
368 /**
369 * @param string $url
370 * @param string $expected
371 *
372 * @dataProvider urlForCacheCodeProvider
373 */
374 public function testAddingCacheCode($url, $expected) {
375 $resources = CRM_Core_Resources::singleton();
376 $resources->setCacheCode($this->cacheBusterString);
377 $this->assertEquals($expected, $resources->addCacheCode($url));
378 }
379
380 /**
381 * @return array
382 */
383 public function urlForCacheCodeProvider() {
384 return array(
385 array(
386 'http://www.civicrm.org',
387 'http://www.civicrm.org?r=' . $this->cacheBusterString,
388 ),
389 array(
390 'www.civicrm.org/custom.css?foo=bar',
391 'www.civicrm.org/custom.css?foo=bar&r=' . $this->cacheBusterString,
392 ),
393 array(
394 'civicrm.org/custom.css?car=blue&foo=bar',
395 'civicrm.org/custom.css?car=blue&foo=bar&r=' . $this->cacheBusterString,
396 ),
397 );
398 }
399
400 }