Fixing bug produced by mail_to which badly escaped UTF-8 characters when using javasc...
[akelos.git] / lib / AkActionView / helpers / url_helper.php
blob62c48c3467c529e1a40b6b47e88acd4124ef0fbb
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 // +----------------------------------------------------------------------+
5 // | Akelos Framework - http://www.akelos.org |
6 // +----------------------------------------------------------------------+
7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
8 // | Released under the GNU Lesser General Public License, see LICENSE.txt|
9 // +----------------------------------------------------------------------+
11 /**
12 * @package ActionView
13 * @subpackage Helpers
14 * @author Bermi Ferrer <bermi a.t akelos c.om>
15 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
16 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
19 require_once(AK_LIB_DIR.DS.'AkActionView'.DS.'helpers'.DS.'javascript_helper.php');
21 class UrlHelper
23 function setController(&$controller)
25 $this->_controller =& $controller;
28 /**
29 * Returns the URL for the set of +$options+ provided. This takes the same options
30 * as url_for. For a list, see the documentation for AKActionController::urlFor.
31 * Note that it'll set ('only_path' => true) so you'll get /controller/action instead of the
32 * http://example.com/controller/action part (makes it harder to parse httpd log files)
34 function url_for($options = array(), $parameters_for_method_reference = null)
36 $default_options = array(
37 'only_path' => true
39 $options = array_merge($default_options, $options);
40 return $this->_controller->urlFor($options, $parameters_for_method_reference);
44 function modify_current_url($options_to_add = array(), $options_to_exclude = array(), $remove_unnecesary_options = true)
46 $options_to_exclude = $remove_unnecesary_options ? array_merge(array('ak','lang',AK_SESSION_NAME,'AK_SESSID','PHPSESSID'), $options_to_exclude) : $options_to_exclude;
47 $options_to_add = array_merge(array_merge(array('action'=>$this->_controller->Request->getAction(), 'controller' => $this->_controller->Request->getController()),$this->_controller->Request->getUrlParams()),$options_to_add);
48 foreach ($options_to_exclude as $option_to_exclude){
49 unset($options_to_add[$option_to_exclude]);
51 return $this->url_for($options_to_add);
54 /**
55 * Creates a link tag of the given +name+ using an URL created by the set of +options+. See the valid options in
56 * the documentation for ActionController::urlFor. It's also possible to pass a string instead of an array of options
57 * to get a link tag that just points without consideration. If null is passed as a name, the link itself will become
58 * the name.
60 * The html_options has three special features. One for creating javascript confirm alerts where if you pass
61 * 'confirm' => 'Are you sure?', the link will be guarded with a JS popup asking that question.
62 * If the user accepts, the link is processed, otherwise not.
64 * Another for creating a popup window, which is done by either passing 'popup' with true or the options of the window in
65 * Javascript form.
67 * And a third for making the link do a POST request (instead of the regular GET) through a dynamically added form
68 * element that is instantly submitted. Note that if the user has turned off Javascript, the request will fall back on
69 * the GET. So its your responsibility to determine what the action should be once it arrives at the controller.
70 * The POST form is turned on by passing 'post' as true. Note, it's not possible to use POST requests and popup targets
71 * at the same time (an exception will be thrown).
73 * Examples:
74 * $url_helper->link_to('Delete this page', array('action' => 'destroy', 'id' => $page->id ), array('confirm' => 'Are you sure?'));
75 * $url_helper->link_to('Help', array('action' => 'help'), array('popup' => true));
76 * $url_helper->link_to('Busy loop', array('action' => 'busy'), array('popup' => array('new_window', 'height=300,width=600')));
77 * $url_helper->link_to('Destroy account', array('action' => 'destroy'), array('confirm' => 'Are you sure?'), array('post' => true));
79 function link_to($name = null, $options = array(), $html_options = array(), $parameters_for_method_reference = null)
81 if (!empty($html_options)) {
82 $this->convert_options_to_javascript($html_options);
83 $tag_options = TagHelper::_tag_options($html_options);
85 else{
86 $tag_options = null;
88 $url = is_string($options) ? $options : $this->url_for($options, $parameters_for_method_reference);
89 $name = empty($name) ? $url : $name;
90 return "<a href=\"{$url}\"{$tag_options}>{$name}</a>";
93 /**
94 * Generates a form containing a sole button that submits to the
95 * URL given by _$options_. Use this method instead of +link_to+
96 * for actions that do not have the safe HTTP GET semantics
97 * implied by using a hypertext link.
99 * The parameters are the same as for +link_to+. Any _html_options_
100 * that you pass will be applied to the inner +input+ element.
101 * In particular, pass
103 * 'disabled' => true/false
105 * as part of _html_options_ to control whether the button is
106 * disabled. The generated form element is given the class
107 * 'button-to', to which you can attach CSS styles for display
108 * purposes.
110 * Example 1:
112 * // inside of controller for "feeds"
113 * $url_helper->button_to('Edit', array('action' => 'edit', 'id' => 3));
115 * Generates the following HTML (sans formatting):
117 * <form method="post" action="/feeds/edit/3" class="button-to">
118 * <div><input type="submit" value="Edit" /></div>
119 * </form>
121 * Example 2:
123 * $url_helper->button_to('Destroy', array('action' => 'destroy', 'id' => 3 , 'confirm' => 'Are you sure?'));
125 * Generates the following HTML (sans formatting):
127 * <form method="post" action="/feeds/destroy/3" class="button-to">
128 * <div><input onclick="return confirm('Are you sure?');" value="Destroy" type="submit" /></div>
129 * </form>
131 * Note: This method generates HTML code that represents a form.
132 * Forms are "block" content, which means that you should not try to
133 * insert them into your HTML where only inline content is expected.
134 * For example, you can legally insert a form inside of a <div> or
135 * <td> element or in between <p> elements, but not in the middle of
136 * a run of text, nor can you place a form within another form.
137 * (Bottom line: Always validate your HTML before going public.)
139 function button_to($name, $options = array(), $html_options = array())
141 $html_options = $this->_convert_boolean_attributes($html_options, 'disabled');
143 if(!empty($html_options['confirm'])){
144 $html_options['onclick'] = 'return '.$this->_confirm_javascript_function($html_options['confirm']).';';
145 unset($html_options['confirm']);
148 $url = is_string($options) ? $options : $this->url_for($options);
150 $name = !empty($name) ? $name : (is_string($options) ? $options : TagHelper::escape_once($this->url_for($options)));
152 $html_options = array_merge($html_options,array('type'=>'submit','value'=>$name));
153 return '<form method="post" action="'.$url.'" class="button-to"><div>'.
154 TagHelper::tag('input', $html_options) . '</div></form>';
159 * Creates a link tag of the given +$name+ using an URL created by the set of +$options+, unless the current
160 * request uri is the same as the link's, in which case only the name is returned.
161 * This is useful for creating link bars where you don't want to link
162 * to the page currently being viewed.
164 function link_to_unless_current($name, $options = array(), $html_options = array(), $parameters_for_method_reference)
166 return $this->current_page($options) ? $this->link_to_unless($options, $name, $options, $html_options, $parameters_for_method_reference) : null;
170 * Create a link tag of the given +$name+ using an URL created by the set of +options+, unless +condition+
171 * is true, in which case only the name is returned.
173 function link_to_unless($condition, $name, $options = array(), $html_options = array(), $parameters_for_method_reference)
175 if ($condition !== true) {
176 return $this->link_to($name, $options, $html_options, $parameters_for_method_reference);
178 return '';
182 * Create a link tag of the given +name+ using an URL created by the set of +$options+, if +$condition+
183 * is true, in which case only the name is returned.
185 function link_to_if($condition, $name, $options = array(), $html_options = array(), $parameters_for_method_reference)
187 return $condition ? $this->link_to_unless($condition, $name, $options, $html_options, $parameters_for_method_reference) : null;
191 * Returns true if the current page uri is generated by the options passed (in url_for format).
193 function current_page($options)
195 return ($this->url_for($options) == $this->_controller->_getCompleteRequestUri());
199 * Creates a link tag for starting an email to the specified <tt>email_address</tt>, which is also used as the name of the
200 * link unless +$name+ is specified. Additional HTML options, such as class or id, can be passed in the
201 * <tt>$html_options</tt> array.
203 * You can also make it difficult for spiders to harvest email address by obfuscating them.
204 * Examples:
205 * $url_helper->mail_to('me@domain.com', 'My email', array('encode' => 'javascript')) =>
206 * <script type="text/javascript" language="javascript">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>
208 * $url_helper->mail_to('me@domain.com', 'My email', array('encode' => 'hex')) =>
209 * <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
211 * You can also specify the cc address, bcc address, subject, and body parts of the message header to create a complex e-mail
212 * using the corresponding +cc+, +bcc+, +subject+, and +body+ <tt>html_options</tt> keys. Each of these options are URI escaped
213 * and then appended to the <tt>email_address</tt> before being output. <b>Be aware that javascript keywords will not be
214 * escaped and may break this feature when encoding with javascript.</b>
216 * Examples:
217 * $url_helper->mail_to("me@domain.com", "My email", array('cc' => "ccaddress@domain.com", 'bcc' => "bccaddress@domain.com", 'subject' => "This is an example email", 'body' => "This is the body of the message.")) # =>
218 * <a href="mailto:me@domain.com?cc="ccaddress@domain.com"&bcc="bccaddress@domain.com"&body="This%20is%20the%20body%20of%20the%20message."&subject="This%20is%20an%20example%20email">My email</a>
220 function mail_to($email_address, $name = null, $html_options = array())
222 $name = empty($name) ? $email_address : $name;
224 $default_options = array(
225 'cc' => null,
226 'bcc' => null,
227 'subject' => null,
228 'body' => null,
229 'encode' => ''
232 $options = array_merge($default_options, $html_options);
233 $encode = $options['encode'];
235 $string = '';
236 $extras = '';
237 $extras .= !empty($options['cc']) ? "cc=".urlencode(trim($options['cc'])).'&' : '';
238 $extras .= !empty($options['bcc']) ? "bcc=".urlencode(trim($options['bcc'])).'&' : '';
239 $extras .= !empty($options['body']) ? "body=".urlencode(trim($options['body'])).'&' : '';
240 $extras .= !empty($options['subject']) ? "subject=".urlencode(trim($options['subject'])).'&' : '';
242 $extras = empty($extras) ? '' : '?'.str_replace('+','%20',rtrim($extras,'&'));
244 $html_options = Ak::delete($html_options, 'cc','bcc','subject','body','encode');
246 if ($encode == 'javascript'){
247 $tmp = "document.write('".TagHelper::content_tag('a', htmlentities($name, null, Ak::locale('charset')), array_merge($html_options,array('href' => 'mailto:'.$email_address.$extras )))."');";
248 for ($i=0; $i < strlen($tmp); $i++){
249 $string.='%'.dechex(ord($tmp[$i]));
251 return "<script type=\"text/javascript\">eval(unescape('$string'))</script>";
253 }elseif ($encode == 'hex'){
254 $encoded_email_address = '';
255 $encoded_email_for_name = '';
256 for ($i=0;$i<strlen($email_address);$i++){
257 if(preg_match('/\w/',$email_address[$i])){
258 $encoded_email_address .= sprintf('%%%x',ord($email_address[$i]));
259 }else{
260 if ($email_address[$i] == '@') {
261 $encoded_email_address .= '%40';
263 elseif ($email_address[$i] == '.'){
264 $encoded_email_address .= '%2e';
266 else{
267 $encoded_email_address .= $email_address[$i];
270 $encoded_email_for_name .= (rand(1,2)%2 ? '&#'.ord($email_address[$i]).';' : '&#x'.dechex(ord($email_address[$i])).';');
273 $name = str_replace($email_address,$encoded_email_for_name,$name);
275 return TagHelper::content_tag('a', $name, array_merge($html_options,array('href' => 'mailto:'.$encoded_email_address.$extras)));
277 }else{
278 return TagHelper::content_tag('a', $name, array_merge($html_options,array('href' => 'mailto:'.$email_address.$extras)));
284 function convert_options_to_javascript(&$html_options)
286 foreach (array('confirm', 'popup', 'post') as $option){
287 $$option = isset($html_options[$option]) ? $html_options[$option] : false;
288 unset($html_options[$option]);
291 $onclick = '';
292 if ($popup && $post){
293 trigger_error(Ak::t("You can't use popup and post in the same link"), E_USER_ERROR);
295 }elseif($confirm && $popup){
296 $onclick = 'if ('.$this->_confirm_javascript_function($confirm).') { '.$this->_popup_javascript_function($popup).' };return false;';
298 }elseif ($confirm && $post) {
299 $onclick = 'if ('.$this->_confirm_javascript_function($confirm).') { '.$this->_post_javascript_function().' };return false;';
301 }elseif ($confirm) {
302 $onclick = 'return '.$this->_confirm_javascript_function($confirm).';';
304 }elseif ($post) {
305 $onclick = $this->_post_javascript_function().'return false;';
307 }elseif ($popup) {
308 $onclick = $this->_popup_javascript_function($popup).'return false;';
311 $html_options['onclick'] = empty($html_options['onclick']) ? $onclick : $html_options['onclick'].$onclick;
314 function _confirm_javascript_function($confirm)
316 return "confirm('".JavaScriptHelper::escape_javascript($confirm)."')";
321 function _popup_javascript_function($popup)
323 return is_array($popup) ? "window.open(this.href,'".array_shift($popup)."','".array_pop($popup)."');" : "window.open(this.href);";
326 function _post_javascript_function()
328 return "var f = document.createElement('form'); document.body.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit();";
332 * processes the _html_options_ array, converting the boolean
333 * attributes from true/false form into the form required by
334 * html/xhtml. (an attribute is considered to be boolean if
335 * its name is listed in the given _$boolean_attributes_ array.)
337 * more specifically, for each boolean attribute in _$html_option_
338 * given as:
340 * "attr" => bool_value
342 * if the associated _bool_value_ evaluates to true, it is
343 * replaced with the attribute's name; otherwise the attribute is
344 * removed from the _html_options_ array. (see the xhtml 1.0 spec,
345 * section 4.5 "attribute minimization" for more:
346 * http://www.w3.org/tr/xhtml1/ *h-4.5)
348 * returns the updated _$html_options_ array, which is also modified
349 * in place.
351 * example:
353 * $url_helper->convert_boolean_attributes( $html_options,
354 * array('checked','disabled','readonly' ) );
356 function _convert_boolean_attributes(&$html_options, $boolean_attributes)
358 $boolean_attributes = (array)$boolean_attributes;
359 foreach ($boolean_attributes as $boolean_attribute){
360 if(empty($html_options[$boolean_attribute])){
361 unset($html_options[$boolean_attribute]);
362 }else {
363 $html_options[$boolean_attribute] = $boolean_attribute;
366 return $html_options;