UrlHelper.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. * UrlHelper.
  11. *
  12. * @package symfony
  13. * @subpackage helper
  14. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  15. * @version SVN: $Id: UrlHelper.php 14507 2009-01-06 19:12:41Z Kris.Wallsmith $
  16. */
  17. function link_to2($name, $routeName, $params, $options = array())
  18. {
  19. $params = array_merge(array('sf_route' => $routeName), is_object($params) ? array('sf_subject' => $params) : $params);
  20. return link_to1($name, $params, $options);
  21. }
  22. function link_to1($name, $internal_uri, $options = array())
  23. {
  24. $html_options = _parse_attributes($options);
  25. $html_options = _convert_options_to_javascript($html_options);
  26. $absolute = false;
  27. if (isset($html_options['absolute_url']))
  28. {
  29. $html_options['absolute'] = $html_options['absolute_url'];
  30. unset($html_options['absolute_url']);
  31. }
  32. if (isset($html_options['absolute']))
  33. {
  34. $absolute = (boolean) $html_options['absolute'];
  35. unset($html_options['absolute']);
  36. }
  37. $html_options['href'] = url_for($internal_uri, $absolute);
  38. if (isset($html_options['query_string']))
  39. {
  40. $html_options['href'] .= '?'.$html_options['query_string'];
  41. unset($html_options['query_string']);
  42. }
  43. if (isset($html_options['anchor']))
  44. {
  45. $html_options['href'] .= '#'.$html_options['anchor'];
  46. unset($html_options['anchor']);
  47. }
  48. if (is_object($name))
  49. {
  50. if (method_exists($name, '__toString'))
  51. {
  52. $name = $name->__toString();
  53. }
  54. else
  55. {
  56. throw new sfException(sprintf('Object of class "%s" cannot be converted to string (Please create a __toString() method).', get_class($name)));
  57. }
  58. }
  59. if (!strlen($name))
  60. {
  61. $name = $html_options['href'];
  62. }
  63. return content_tag('a', $name, $html_options);
  64. }
  65. function url_for2($routeName, $params = array(), $absolute = false)
  66. {
  67. $params = array_merge(array('sf_route' => $routeName), is_object($params) ? array('sf_subject' => $params) : $params);
  68. return url_for1($params, $absolute);
  69. }
  70. function url_for1($internal_uri, $absolute = false)
  71. {
  72. return sfContext::getInstance()->getController()->genUrl($internal_uri, $absolute);
  73. }
  74. /**
  75. * Returns a routed URL based on the module/action passed as argument
  76. * and the routing configuration.
  77. *
  78. * <b>Examples:</b>
  79. * <code>
  80. * echo url_for('my_module/my_action');
  81. * => /path/to/my/action
  82. * echo url_for('@my_rule');
  83. * => /path/to/my/action
  84. * echo url_for('@my_rule', true);
  85. * => http://myapp.example.com/path/to/my/action
  86. * </code>
  87. *
  88. * @param string $internal_uri 'module/action' or '@rule' of the action
  89. * @param bool $absolute return absolute path?
  90. * @return string routed URL
  91. */
  92. function url_for()
  93. {
  94. // for BC with 1.1
  95. $arguments = func_get_args();
  96. if (is_array($arguments[0]) || '@' == substr($arguments[0], 0, 1) || false !== strpos($arguments[0], '/'))
  97. {
  98. return call_user_func_array('url_for1', $arguments);
  99. }
  100. else
  101. {
  102. return call_user_func_array('url_for2', $arguments);
  103. }
  104. }
  105. /**
  106. * Creates a <a> link tag of the given name using a routed URL
  107. * based on the module/action passed as argument and the routing configuration.
  108. * It's also possible to pass a string instead of a module/action pair to
  109. * get a link tag that just points without consideration.
  110. * If null is passed as a name, the link itself will become the name.
  111. * If an object is passed as a name, the object string representation is used.
  112. * One of the options serves for for creating javascript confirm alerts where
  113. * if you pass 'confirm' => 'Are you sure?', the link will be guarded
  114. * with a JS popup asking that question. If the user accepts, the link is processed,
  115. * otherwise not.
  116. *
  117. * <b>Options:</b>
  118. * - 'absolute' - if set to true, the helper outputs an absolute URL
  119. * - 'query_string' - to append a query string (starting by ?) to the routed url
  120. * - 'anchor' - to append an anchor (starting by #) to the routed url
  121. * - 'confirm' - displays a javascript confirmation alert when the link is clicked
  122. * - 'popup' - if set to true, the link opens a new browser window
  123. * - 'post' - if set to true, the link submits a POST request instead of GET (caution: do not use inside a form)
  124. * - 'method' - if set to post, delete, or put, the link submits a request with the given HTTP method instead of GET (caution: do not use inside a form)
  125. *
  126. * <b>Note:</b> The 'popup', 'post', and 'method' options are not compatible with each other.
  127. *
  128. * <b>Examples:</b>
  129. * <code>
  130. * echo link_to('Delete this page', 'my_module/my_action');
  131. * => <a href="/path/to/my/action">Delete this page</a>
  132. * echo link_to('Visit Hoogle', 'http://www.hoogle.com');
  133. * => <a href="http://www.hoogle.com">Visit Hoogle</a>
  134. * echo link_to('Delete this page', 'my_module/my_action', array('id' => 'myid', 'confirm' => 'Are you sure?', 'absolute' => true));
  135. * => <a href="http://myapp.example.com/path/to/my/action" id="myid" onclick="return confirm('Are you sure?');">Delete this page</a>
  136. * </code>
  137. *
  138. * @param string $name name of the link, i.e. string to appear between the <a> tags
  139. * @param string $internal_uri 'module/action' or '@rule' of the action
  140. * @param array $options additional HTML compliant <a> tag parameters
  141. * @return string XHTML compliant <a href> tag
  142. * @see url_for
  143. */
  144. function link_to()
  145. {
  146. // for BC with 1.1
  147. $arguments = func_get_args();
  148. if (empty($arguments[1]) || '@' == substr($arguments[1], 0, 1) || false !== strpos($arguments[1], '/'))
  149. {
  150. return call_user_func_array('link_to1', $arguments);
  151. }
  152. else
  153. {
  154. if (!array_key_exists(2, $arguments))
  155. {
  156. $arguments[2] = array();
  157. }
  158. return call_user_func_array('link_to2', $arguments);
  159. }
  160. }
  161. function url_for_form(sfForm $form, $routePrefix)
  162. {
  163. $format = '%s/%s';
  164. if ('@' == $routePrefix[0])
  165. {
  166. $format = '%s_%s';
  167. $routePrefix = substr($routePrefix, 1);
  168. }
  169. $uri = sprintf($format, $routePrefix, $form->getObject()->isNew() ? 'create' : 'update');
  170. return url_for($uri, $form->getObject());
  171. }
  172. function form_tag_for(sfForm $form, $routePrefix, $attributes = array())
  173. {
  174. return $form->renderFormTag(url_for_form($form, $routePrefix), $attributes);
  175. }
  176. /**
  177. * If the condition passed as first argument is true,
  178. * creates a <a> link tag of the given name using a routed URL
  179. * based on the module/action passed as argument and the routing configuration.
  180. * If the condition is false, the given name is returned between <span> tags
  181. *
  182. * <b>Options:</b>
  183. * - 'tag' - the HTML tag that must enclose the name if the condition is false, defaults to <span>
  184. * - 'absolute' - if set to true, the helper outputs an absolute URL
  185. * - 'query_string' - to append a query string (starting by ?) to the routed url
  186. * - 'anchor' - to append an anchor (starting by #) to the routed url
  187. * - 'confirm' - displays a javascript confirmation alert when the link is clicked
  188. * - 'popup' - if set to true, the link opens a new browser window
  189. * - 'post' - if set to true, the link submits a POST request instead of GET (caution: do not use inside a form)
  190. *
  191. * <b>Examples:</b>
  192. * <code>
  193. * echo link_to_if($user->isAdministrator(), 'Delete this page', 'my_module/my_action');
  194. * => <a href="/path/to/my/action">Delete this page</a>
  195. * echo link_to_if(!$user->isAdministrator(), 'Delete this page', 'my_module/my_action');
  196. * => <span>Delete this page</span>
  197. * </code>
  198. *
  199. * @param bool $condition condition
  200. * @param string $name name of the link, i.e. string to appear between the <a> tags
  201. * @param string $internal_uri 'module/action' or '@rule' of the action
  202. * @param array $options additional HTML compliant <a> tag parameters
  203. * @return string XHTML compliant <a href> tag or name
  204. * @see link_to
  205. */
  206. function link_to_if($condition, $name = '', $internal_uri = '', $options = array())
  207. {
  208. $html_options = _parse_attributes($options);
  209. if ($condition)
  210. {
  211. unset($html_options['tag']);
  212. return link_to($name, $internal_uri, $html_options);
  213. }
  214. else
  215. {
  216. unset($html_options['query_string']);
  217. unset($html_options['absolute_url']);
  218. unset($html_options['absolute']);
  219. $tag = _get_option($html_options, 'tag', 'span');
  220. return content_tag($tag, $name, $html_options);
  221. }
  222. }
  223. /**
  224. * If the condition passed as first argument is false,
  225. * creates a <a> link tag of the given name using a routed URL
  226. * based on the module/action passed as argument and the routing configuration.
  227. * If the condition is true, the given name is returned between <span> tags
  228. *
  229. * <b>Options:</b>
  230. * - 'tag' - the HTML tag that must enclose the name if the condition is true, defaults to <span>
  231. * - 'absolute' - if set to true, the helper outputs an absolute URL
  232. * - 'query_string' - to append a query string (starting by ?) to the routed url
  233. * - 'anchor' - to append an anchor (starting by #) to the routed url
  234. * - 'confirm' - displays a javascript confirmation alert when the link is clicked
  235. * - 'popup' - if set to true, the link opens a new browser window
  236. * - 'post' - if set to true, the link submits a POST request instead of GET (caution: do not use inside a form)
  237. *
  238. * <b>Examples:</b>
  239. * <code>
  240. * echo link_to_unless($user->isAdministrator(), 'Delete this page', 'my_module/my_action');
  241. * => <span>Delete this page</span>
  242. * echo link_to_unless(!$user->isAdministrator(), 'Delete this page', 'my_module/my_action');
  243. * => <a href="/path/to/my/action">Delete this page</a>
  244. * </code>
  245. *
  246. * @param bool $condition condition
  247. * @param string $name name of the link, i.e. string to appear between the <a> tags
  248. * @param string $internal_uri 'module/action' or '@rule' of the action
  249. * @param array $options additional HTML compliant <a> tag parameters
  250. * @return string XHTML compliant <a href> tag or name
  251. * @see link_to
  252. */
  253. function link_to_unless($condition, $name = '', $internal_uri = '', $options = array())
  254. {
  255. return link_to_if(!$condition, $name, $internal_uri, $options);
  256. }
  257. /**
  258. * Returns a URL rooted at the web root
  259. *
  260. * @param string $path The route to append
  261. * @param bool $absolute If true, an absolute path is returned (optional)
  262. * @return The web URL root
  263. */
  264. function public_path($path, $absolute = false)
  265. {
  266. $request = sfContext::getInstance()->getRequest();
  267. $root = $request->getRelativeUrlRoot();
  268. if ($absolute)
  269. {
  270. $source = 'http';
  271. if ($request->isSecure())
  272. {
  273. $source .= 's';
  274. }
  275. $source .='://'.$request->getHost().$root;
  276. }
  277. else
  278. {
  279. $source = $root;
  280. }
  281. if (substr($path, 0, 1) != '/')
  282. {
  283. $path = '/'.$path;
  284. }
  285. return $source.$path;
  286. }
  287. /**
  288. * Creates an <input> button tag of the given name pointing to a routed URL
  289. * based on the module/action passed as argument and the routing configuration.
  290. * The syntax is similar to the one of link_to.
  291. *
  292. * <b>Options:</b>
  293. * - 'absolute' - if set to true, the helper outputs an absolute URL
  294. * - 'query_string' - to append a query string (starting by ?) to the routed url
  295. * - 'anchor' - to append an anchor (starting by #) to the routed url
  296. * - 'confirm' - displays a javascript confirmation alert when the button is clicked
  297. * - 'popup' - if set to true, the button opens a new browser window
  298. * - 'post' - if set to true, the button submits a POST request instead of GET (caution: do not use inside a form)
  299. *
  300. * <b>Examples:</b>
  301. * <code>
  302. * echo button_to('Delete this page', 'my_module/my_action');
  303. * => <input value="Delete this page" type="button" onclick="document.location.href='/path/to/my/action';" />
  304. * </code>
  305. *
  306. * @param string $name name of the button
  307. * @param string $internal_uri 'module/action' or '@rule' of the action
  308. * @param array $options additional HTML compliant <input> tag parameters
  309. * @return string XHTML compliant <input> tag
  310. * @see url_for, link_to
  311. */
  312. function button_to($name, $internal_uri, $options = array())
  313. {
  314. $html_options = _parse_attributes($options);
  315. $html_options['value'] = $name;
  316. if (isset($html_options['post']) && $html_options['post'])
  317. {
  318. if (isset($html_options['popup']))
  319. {
  320. throw new sfConfigurationException('You can\'t use "popup" and "post" together.');
  321. }
  322. $html_options['type'] = 'submit';
  323. unset($html_options['post']);
  324. $html_options = _convert_options_to_javascript($html_options);
  325. return form_tag($internal_uri, array('method' => 'post', 'class' => 'button_to')).content_tag('div', tag('input', $html_options)).'</form>';
  326. }
  327. $url = url_for($internal_uri);
  328. if (isset($html_options['query_string']))
  329. {
  330. $url = $url.'?'.$html_options['query_string'];
  331. unset($html_options['query_string']);
  332. }
  333. if (isset($html_options['anchor']))
  334. {
  335. $url = $url.'#'.$html_options['anchor'];
  336. unset($html_options['anchor']);
  337. }
  338. $url = "'".$url."'";
  339. $html_options['type'] = 'button';
  340. if (isset($html_options['popup']))
  341. {
  342. $html_options = _convert_options_to_javascript($html_options, $url);
  343. unset($html_options['popup']);
  344. }
  345. else
  346. {
  347. $html_options['onclick'] = "document.location.href=".$url.";";
  348. $html_options = _convert_options_to_javascript($html_options);
  349. }
  350. return tag('input', $html_options);
  351. }
  352. /**
  353. * Creates a <a> link tag to the given email (with href="mailto:...").
  354. * If null is passed as a name, the email itself will become the name.
  355. *
  356. * <b>Options:</b>
  357. * - 'encode' - if set to true, the email address appears with various random encoding for each letter.
  358. * The mail link still works when encoded, but the address doesn't appear in clear
  359. * in the source. Use it to prevent spam (efficiency not guaranteed).
  360. *
  361. * <b>Examples:</b>
  362. * <code>
  363. * echo mail_to('webmaster@example.com');
  364. * => <a href="mailto:webmaster@example.com">webmaster@example.com</a>
  365. * echo mail_to('webmaster@example.com', 'send us an email');
  366. * => <a href="mailto:webmaster@example.com">send us an email</a>
  367. * echo mail_to('webmaster@example.com', 'send us an email', array('encode' => true));
  368. * => <a href="
  369. &#x6d;a&#x69;&#x6c;&#x74;&#111;&#58;&#x77;&#x65;b&#x6d;as&#116;&#x65;&#114;
  370. &#64;&#101;&#x78;&#x61;&#x6d;&#x70;&#108;&#x65;&#46;&#99;&#x6f;&#109;
  371. ">send us an email</a>
  372. * </code>
  373. *
  374. * @param string $email target email
  375. * @param string $name name of the link, i.e. string to appear between the <a> tags
  376. * @param array $options additional HTML compliant <a> tag parameters
  377. * @param array $default_value
  378. * @return string XHTML compliant <a href> tag
  379. * @see link_to
  380. */
  381. function mail_to($email, $name = '', $options = array(), $default_value = array())
  382. {
  383. $html_options = _parse_attributes($options);
  384. $html_options = _convert_options_to_javascript($html_options);
  385. $default_tmp = _parse_attributes($default_value);
  386. $default = array();
  387. foreach ($default_tmp as $key => $value)
  388. {
  389. $default[] = urlencode($key).'='.urlencode($value);
  390. }
  391. $options = count($default) ? '?'.implode('&', $default) : '';
  392. if (isset($html_options['encode']) && $html_options['encode'])
  393. {
  394. unset($html_options['encode']);
  395. $html_options['href'] = _encodeText('mailto:'.$email.$options);
  396. if (!$name)
  397. {
  398. $name = _encodeText($email);
  399. }
  400. }
  401. else
  402. {
  403. $html_options['href'] = 'mailto:'.$email.$options;
  404. if (!$name)
  405. {
  406. $name = $email;
  407. }
  408. }
  409. return content_tag('a', $name, $html_options);
  410. }
  411. function _convert_options_to_javascript($html_options, $url = 'this.href')
  412. {
  413. // confirm
  414. $confirm = isset($html_options['confirm']) ? $html_options['confirm'] : '';
  415. unset($html_options['confirm']);
  416. // popup
  417. $popup = isset($html_options['popup']) ? $html_options['popup'] : '';
  418. unset($html_options['popup']);
  419. // method
  420. $method = isset($html_options['method']) ? $html_options['method'] : (isset($html_options['post']) && $html_options['post'] ? 'post' : false);
  421. unset($html_options['post'], $html_options['method']);
  422. $onclick = isset($html_options['onclick']) ? $html_options['onclick'] : '';
  423. if ($popup && $method)
  424. {
  425. throw new sfConfigurationException('You can\'t use "popup", "method" and "post" in the same link.');
  426. }
  427. else if ($confirm && $popup)
  428. {
  429. $html_options['onclick'] = $onclick.'if ('._confirm_javascript_function($confirm).') { '._popup_javascript_function($popup, $url).' };return false;';
  430. }
  431. else if ($confirm && $method)
  432. {
  433. $html_options['onclick'] = $onclick.'if ('._confirm_javascript_function($confirm).') { '._method_javascript_function($method).' };return false;';
  434. }
  435. else if ($confirm)
  436. {
  437. if ($onclick)
  438. {
  439. $html_options['onclick'] = 'if ('._confirm_javascript_function($confirm).') { return '.$onclick.'} else return false;';
  440. }
  441. else
  442. {
  443. $html_options['onclick'] = 'return '._confirm_javascript_function($confirm).';';
  444. }
  445. }
  446. else if ($method)
  447. {
  448. $html_options['onclick'] = $onclick._method_javascript_function($method).'return false;';
  449. }
  450. else if ($popup)
  451. {
  452. $html_options['onclick'] = $onclick._popup_javascript_function($popup, $url).'return false;';
  453. }
  454. return $html_options;
  455. }
  456. function _confirm_javascript_function($confirm)
  457. {
  458. return "confirm('".escape_javascript($confirm)."')";
  459. }
  460. function _popup_javascript_function($popup, $url = '')
  461. {
  462. if (is_array($popup))
  463. {
  464. if (isset($popup[1]))
  465. {
  466. return "var w=window.open(".$url.",'".$popup[0]."','".$popup[1]."');w.focus();";
  467. }
  468. else
  469. {
  470. return "var w=window.open(".$url.",'".$popup[0]."');w.focus();";
  471. }
  472. }
  473. else
  474. {
  475. return "var w=window.open(".$url.");w.focus();";
  476. }
  477. }
  478. function _post_javascript_function()
  479. {
  480. return _method_javascript_function('POST');
  481. }
  482. function _method_javascript_function($method)
  483. {
  484. $function = "var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'post'; f.action = this.href;";
  485. if ('post' != strtolower($method))
  486. {
  487. $function .= "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); ";
  488. $function .= sprintf("m.setAttribute('name', 'sf_method'); m.setAttribute('value', '%s'); f.appendChild(m);", strtolower($method));
  489. }
  490. // CSRF protection
  491. $form = new sfForm();
  492. if ($form->isCSRFProtected())
  493. {
  494. $function .= "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); ";
  495. $function .= sprintf("m.setAttribute('name', '%s'); m.setAttribute('value', '%s'); f.appendChild(m);", $form->getCSRFFieldName(), $form->getCSRFToken());
  496. }
  497. $function .= "f.submit();";
  498. return $function;
  499. }
  500. function _encodeText($text)
  501. {
  502. $encoded_text = '';
  503. for ($i = 0; $i < strlen($text); $i++)
  504. {
  505. $char = $text{$i};
  506. $r = rand(0, 100);
  507. # roughly 10% raw, 45% hex, 45% dec
  508. # '@' *must* be encoded. I insist.
  509. if ($r > 90 && $char != '@')
  510. {
  511. $encoded_text .= $char;
  512. }
  513. else if ($r < 45)
  514. {
  515. $encoded_text .= '&#x'.dechex(ord($char)).';';
  516. }
  517. else
  518. {
  519. $encoded_text .= '&#'.ord($char).';';
  520. }
  521. }
  522. return $encoded_text;
  523. }