Skip to content

Commit a4c6c8e

Browse files
committed
Smarty - Add support for {url}frontend://civicrm/profile{/url} (etal)
1 parent da80590 commit a4c6c8e

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

CRM/Core/Smarty/plugins/block.url.php

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/**
4+
* Generate a URL. This is thin wrapper for the Civi::url() helper.
5+
*
6+
* @see Civi::url()
7+
*
8+
* Ex: Generate a backend URL.
9+
* {url}backend://civicrm/admin?reset=1{/url}
10+
*
11+
* Ex: Generate a backend URL. Assign it to a Smarty variable.
12+
* {url assign=tmpVar}backend://civicrm/admin?reset=1{/url}
13+
*
14+
* Ex: Generate a backend URL. Set optional flags: (t)ext, (s)sl, (a)bsolute.
15+
* {url flags=tsa}backend://civicrm/admin?reset=1{/url}
16+
*
17+
* Ex: Generate a URL in the current (active) routing scheme. Add named variables. (Values are escaped).
18+
* {url verb="Eat" target="Apples and bananas"}//civicrm/fruit?method=[verb]&data=[target]{/url}
19+
*
20+
* Ex: As above, but use numerical variables.
21+
* {url 1="Eat" 2="Apples and bananas"}//civicrm/fruit?method=[1]&data=[2]{/url}
22+
*
23+
* Ex: Generate a URL. Add some pre-escaped variables using Smarty {$foo}.
24+
* {assign var=myEscapedAction value="Eat"}
25+
* {assign var=myEscapedData value="Apples+and+bananas"}
26+
* {url}//civicrm/fruit?method={$myEscapedAction}&data={$myEscapedData}{/url}
27+
*
28+
* @param array $params
29+
* The following parameters have specific meanings:
30+
* - "assign" (string) - Assign output to a Smarty variable
31+
* - "flags" (string) - List of options, as per `Civi::url(...$flags)`
32+
* All other parameters will be passed-through as variables for the URL.
33+
* @param string $text
34+
* Contents of block.
35+
* @param CRM_Core_Smarty $smarty
36+
* The Smarty object.
37+
* @return string
38+
*/
39+
function smarty_block_url($params, $text, &$smarty) {
40+
if ($text === NULL) {
41+
return NULL;
42+
}
43+
44+
$flags = 'h' . ($params['flags'] ?? '');
45+
$assign = $params['assign'] ?? NULL;
46+
unset($params['flags'], $params['assign']);
47+
48+
$url = (string) Civi::url($text, $flags)->addVars($params);
49+
50+
// This could be neat, but see discussion in CRM_Core_Smarty_plugins_UrlTest for why it's currently off.
51+
// $url->setVarsCallback([$smarty, 'get_template_vars']);
52+
53+
if ($assign !== NULL) {
54+
$smarty->assign([$assign => $url]);
55+
return '';
56+
}
57+
else {
58+
return $url;
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
/**
4+
* Class CRM_Core_Smarty_plugins_UrlTest
5+
* @group headless
6+
*/
7+
class CRM_Core_Smarty_plugins_UrlTest extends CiviUnitTestCase {
8+
9+
public function setUp(): void {
10+
parent::setUp();
11+
require_once 'CRM/Core/Smarty.php';
12+
13+
// Templates should normally be file names, but for unit-testing it's handy to use "string:" notation
14+
require_once 'CRM/Core/Smarty/resources/String.php';
15+
civicrm_smarty_register_string_resource();
16+
17+
$this->useTransaction();
18+
}
19+
20+
/**
21+
* @return array
22+
*/
23+
public function urlCases() {
24+
$literal = function(string $s) {
25+
return '!' . preg_quote($s, '!') . '!';
26+
};
27+
28+
$cases = [];
29+
$cases[] = [
30+
// Generate an ordinary, HTML-style URL.
31+
$literal('q=civicrm/profile/view&amp;id=123&amp;gid=456'),
32+
'{url}//civicrm/profile/view?id=123&gid=456{/url}',
33+
];
34+
$cases[] = [
35+
// Here, we assign the plain-text variable and then use it for JS expression
36+
'!window.location = ".*q=civicrm/profile/view&id=123&gid=456"!',
37+
'{url assign=myUrl flags=t}//civicrm/profile/view?id=123&gid=456{/url}' .
38+
'window.location = "{$myUrl}";',
39+
];
40+
$cases[] = [
41+
$literal('q=civicrm/profile/view&amp;id=999&amp;message=hello+world'),
42+
'{url 1="999" 2="hello world"}//civicrm/profile/view?id=[1]&message=[2]{/url}',
43+
];
44+
$cases[] = [
45+
$literal('q=civicrm/profile/view&amp;id=123&amp;message=hello+world'),
46+
'{url msg="hello world"}//civicrm/profile/view?id=123&message=[msg]{/url}',
47+
];
48+
$cases[] = [
49+
// Define a temporary variable for use in the URL.
50+
$literal('q=civicrm/profile/view&amp;id=123&amp;message=this+%26+that'),
51+
'{url msg="this & that"}//civicrm/profile/view?id=123&message=[msg]{/url}',
52+
];
53+
$cases[] = [
54+
// We have a Smarty variable which already included escaped data. Smarty should do substitution.
55+
$literal('q=civicrm/profile/view&amp;id=123&amp;message=this+%2B+that'),
56+
'{assign var=msg value="this+%2B+that"}' .
57+
'{url flags=%}//civicrm/profile/view?id=123&message={$msg}{/url}',
58+
];
59+
60+
// This example is neat - you just replace `{$msg}` with `[msg]`, and then you get encoded URL data.
61+
// But... it's pretty shallow. You can't use Smarty expressions or modifiers. Additionally,
62+
// enabling this mode increases the risk of accidental collisions between Smarty variables
63+
// and deep-form-params. So I've left it disabled for now.
64+
//
65+
// $cases[] = [
66+
// // We have a Smarty variable with canonical (unescaped) data. Use it as URL variable.
67+
// $literal('q=civicrm/profile/view&amp;id=123&amp;message=this+%2B+that'),
68+
// '{assign var=msg value="this + that"}' .
69+
// '{url}//civicrm/profile/view?id=123&message=[msg]{/url}',
70+
// ];
71+
72+
// return CRM_Utils_Array::subset($cases, [2]);
73+
return $cases;
74+
}
75+
76+
/**
77+
* @dataProvider urlCases
78+
* @param string $expected
79+
* @param string $input
80+
*/
81+
public function testUrl($expected, $input) {
82+
$smarty = CRM_Core_Smarty::singleton();
83+
$actual = $smarty->fetch('string:' . $input);
84+
$this->assertRegExp($expected, $actual, "Process input=[$input]");
85+
}
86+
87+
}

0 commit comments

Comments
 (0)