diff options
Diffstat (limited to 'framework/web/helpers')
| -rw-r--r-- | framework/web/helpers/CGoogleApi.php | 71 | ||||
| -rw-r--r-- | framework/web/helpers/CHtml.php | 2122 | ||||
| -rw-r--r-- | framework/web/helpers/CJSON.php | 704 | ||||
| -rw-r--r-- | framework/web/helpers/CJavaScript.php | 120 |
4 files changed, 3017 insertions, 0 deletions
diff --git a/framework/web/helpers/CGoogleApi.php b/framework/web/helpers/CGoogleApi.php new file mode 100644 index 0000000..7bb08fe --- /dev/null +++ b/framework/web/helpers/CGoogleApi.php @@ -0,0 +1,71 @@ +<?php +/** + * CGoogleApi class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CGoogleApi provides helper methods to easily access {@link http://code.google.com/apis/ajax/ Google AJAX APIs}. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CGoogleApi.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.helpers + */ +class CGoogleApi +{ + public static $bootstrapUrl='http://www.google.com/jsapi'; + + /** + * Renders the jsapi script file. + * @param string $apiKey the API key. Null if you do not have a key. + * @return string the script tag that loads Google jsapi. + */ + public static function init($apiKey=null) + { + if($apiKey===null) + return CHtml::scriptFile(self::$bootstrapUrl); + else + return CHtml::scriptFile(self::$bootstrapUrl.'?key='.$apiKey); + } + + /** + * Loads the specified API module. + * Note that you should call {@link init} first. + * @param string $name the module name + * @param string $version the module version + * @param array $options additional js options that are to be passed to the load() function. + * @return string the js code for loading the module. You can use {@link CHtml::script()} + * to enclose it in a script tag. + */ + public static function load($name,$version='1',$options=array()) + { + if(empty($options)) + return "google.load(\"{$name}\",\"{$version}\");"; + else + return "google.load(\"{$name}\",\"{$version}\",".CJavaScript::encode($options).");"; + } + + /** + * Registers the specified API module. + * This is similar to {@link load} except that it registers the loading code + * with {@link CClientScript} instead of returning it. + * This method also registers the jsapi script needed by the loading call. + * @param string $name the module name + * @param string $version the module version + * @param array $options additional js options that are to be passed to the load() function. + * @param string $apiKey the API key. Null if you do not have a key. + */ + public static function register($name,$version='1',$options=array(),$apiKey=null) + { + $cs=Yii::app()->getClientScript(); + $url=$apiKey===null?self::$bootstrapUrl:self::$bootstrapUrl.'?key='.$apiKey; + $cs->registerScriptFile($url); + + $js=self::load($name,$version,$options); + $cs->registerScript($name,$js,CClientScript::POS_HEAD); + } +}
\ No newline at end of file diff --git a/framework/web/helpers/CHtml.php b/framework/web/helpers/CHtml.php new file mode 100644 index 0000000..cc265d6 --- /dev/null +++ b/framework/web/helpers/CHtml.php @@ -0,0 +1,2122 @@ +<?php +/** + * CHtml class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * CHtml is a static class that provides a collection of helper methods for creating HTML views. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CHtml.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.helpers + * @since 1.0 + */ +class CHtml +{ + const ID_PREFIX='yt'; + /** + * @var string the CSS class for displaying error summaries (see {@link errorSummary}). + */ + public static $errorSummaryCss='errorSummary'; + /** + * @var string the CSS class for displaying error messages (see {@link error}). + */ + public static $errorMessageCss='errorMessage'; + /** + * @var string the CSS class for highlighting error inputs. Form inputs will be appended + * with this CSS class if they have input errors. + */ + public static $errorCss='error'; + /** + * @var string the CSS class for required labels. Defaults to 'required'. + * @see label + */ + public static $requiredCss='required'; + /** + * @var string the HTML code to be prepended to the required label. + * @see label + */ + public static $beforeRequiredLabel=''; + /** + * @var string the HTML code to be appended to the required label. + * @see label + */ + public static $afterRequiredLabel=' <span class="required">*</span>'; + /** + * @var integer the counter for generating automatic input field names. + */ + public static $count=0; + + /** + * Sets the default style for attaching jQuery event handlers. + * + * If set to true (default), live/delegated style is used. Event handlers + * are attached to the document body and can process events from descendant + * elements that are added to the document at a later time. + * + * If set to false, direct style is used. Event handlers are attached directly + * to the DOM element, that must already exist on the page. Elements injected + * into the page at a later time will not be processed. + * + * You can override this setting for a particular element by setting the htmlOptions live attribute + * (see {@link clientChange}). + * + * For more information about attaching jQuery event handler see {@link http://api.jquery.com/on/} + * @since 1.1.9 + * @see clientChange + */ + public static $liveEvents = true; + + /** + * Encodes special characters into HTML entities. + * The {@link CApplication::charset application charset} will be used for encoding. + * @param string $text data to be encoded + * @return string the encoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function encode($text) + { + return htmlspecialchars($text,ENT_QUOTES,Yii::app()->charset); + } + + /** + * Decodes special HTML entities back to the corresponding characters. + * This is the opposite of {@link encode()}. + * @param string $text data to be decoded + * @return string the decoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + * @since 1.1.8 + */ + public static function decode($text) + { + return htmlspecialchars_decode($text,ENT_QUOTES); + } + + /** + * Encodes special characters in an array of strings into HTML entities. + * Both the array keys and values will be encoded if needed. + * If a value is an array, this method will also encode it recursively. + * The {@link CApplication::charset application charset} will be used for encoding. + * @param array $data data to be encoded + * @return array the encoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function encodeArray($data) + { + $d=array(); + foreach($data as $key=>$value) + { + if(is_string($key)) + $key=htmlspecialchars($key,ENT_QUOTES,Yii::app()->charset); + if(is_string($value)) + $value=htmlspecialchars($value,ENT_QUOTES,Yii::app()->charset); + else if(is_array($value)) + $value=self::encodeArray($value); + $d[$key]=$value; + } + return $d; + } + + /** + * Generates an HTML element. + * @param string $tag the tag name + * @param array $htmlOptions the element attributes. The values will be HTML-encoded using {@link encode()}. + * If an 'encode' attribute is given and its value is false, + * the rest of the attribute values will NOT be HTML-encoded. + * Since version 1.1.5, attributes whose value is null will not be rendered. + * @param mixed $content the content to be enclosed between open and close element tags. It will not be HTML-encoded. + * If false, it means there is no body content. + * @param boolean $closeTag whether to generate the close tag. + * @return string the generated HTML element tag + */ + public static function tag($tag,$htmlOptions=array(),$content=false,$closeTag=true) + { + $html='<' . $tag . self::renderAttributes($htmlOptions); + if($content===false) + return $closeTag ? $html.' />' : $html.'>'; + else + return $closeTag ? $html.'>'.$content.'</'.$tag.'>' : $html.'>'.$content; + } + + /** + * Generates an open HTML element. + * @param string $tag the tag name + * @param array $htmlOptions the element attributes. The values will be HTML-encoded using {@link encode()}. + * If an 'encode' attribute is given and its value is false, + * the rest of the attribute values will NOT be HTML-encoded. + * Since version 1.1.5, attributes whose value is null will not be rendered. + * @return string the generated HTML element tag + */ + public static function openTag($tag,$htmlOptions=array()) + { + return '<' . $tag . self::renderAttributes($htmlOptions) . '>'; + } + + /** + * Generates a close HTML element. + * @param string $tag the tag name + * @return string the generated HTML element tag + */ + public static function closeTag($tag) + { + return '</'.$tag.'>'; + } + + /** + * Encloses the given string within a CDATA tag. + * @param string $text the string to be enclosed + * @return string the CDATA tag with the enclosed content. + */ + public static function cdata($text) + { + return '<![CDATA[' . $text . ']]>'; + } + + /** + * Generates a meta tag that can be inserted in the head section of HTML page. + * @param string $content content attribute of the meta tag + * @param string $name name attribute of the meta tag. If null, the attribute will not be generated + * @param string $httpEquiv http-equiv attribute of the meta tag. If null, the attribute will not be generated + * @param array $options other options in name-value pairs (e.g. 'scheme', 'lang') + * @return string the generated meta tag + */ + public static function metaTag($content,$name=null,$httpEquiv=null,$options=array()) + { + if($name!==null) + $options['name']=$name; + if($httpEquiv!==null) + $options['http-equiv']=$httpEquiv; + $options['content']=$content; + return self::tag('meta',$options); + } + + /** + * Generates a link tag that can be inserted in the head section of HTML page. + * Do not confuse this method with {@link link()}. The latter generates a hyperlink. + * @param string $relation rel attribute of the link tag. If null, the attribute will not be generated. + * @param string $type type attribute of the link tag. If null, the attribute will not be generated. + * @param string $href href attribute of the link tag. If null, the attribute will not be generated. + * @param string $media media attribute of the link tag. If null, the attribute will not be generated. + * @param array $options other options in name-value pairs + * @return string the generated link tag + */ + public static function linkTag($relation=null,$type=null,$href=null,$media=null,$options=array()) + { + if($relation!==null) + $options['rel']=$relation; + if($type!==null) + $options['type']=$type; + if($href!==null) + $options['href']=$href; + if($media!==null) + $options['media']=$media; + return self::tag('link',$options); + } + + /** + * Encloses the given CSS content with a CSS tag. + * @param string $text the CSS content + * @param string $media the media that this CSS should apply to. + * @return string the CSS properly enclosed + */ + public static function css($text,$media='') + { + if($media!=='') + $media=' media="'.$media.'"'; + return "<style type=\"text/css\"{$media}>\n/*<![CDATA[*/\n{$text}\n/*]]>*/\n</style>"; + } + + /** + * Registers a 'refresh' meta tag. + * This method can be invoked anywhere in a view. It will register a 'refresh' + * meta tag with {@link CClientScript} so that the page can be refreshed in + * the specified seconds. + * @param integer $seconds the number of seconds to wait before refreshing the page + * @param string $url the URL to which the page should be redirected to. If empty, it means the current page. + * @since 1.1.1 + */ + public static function refresh($seconds, $url='') + { + $content="$seconds"; + if($url!=='') + $content.=';'.self::normalizeUrl($url); + Yii::app()->clientScript->registerMetaTag($content,null,'refresh'); + } + + /** + * Links to the specified CSS file. + * @param string $url the CSS URL + * @param string $media the media that this CSS should apply to. + * @return string the CSS link. + */ + public static function cssFile($url,$media='') + { + if($media!=='') + $media=' media="'.$media.'"'; + return '<link rel="stylesheet" type="text/css" href="'.self::encode($url).'"'.$media.' />'; + } + + /** + * Encloses the given JavaScript within a script tag. + * @param string $text the JavaScript to be enclosed + * @return string the enclosed JavaScript + */ + public static function script($text) + { + return "<script type=\"text/javascript\">\n/*<![CDATA[*/\n{$text}\n/*]]>*/\n</script>"; + } + + /** + * Includes a JavaScript file. + * @param string $url URL for the JavaScript file + * @return string the JavaScript file tag + */ + public static function scriptFile($url) + { + return '<script type="text/javascript" src="'.self::encode($url).'"></script>'; + } + + /** + * Generates an opening form tag. + * This is a shortcut to {@link beginForm}. + * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.) + * @param string $method form method (e.g. post, get) + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated form tag. + */ + public static function form($action='',$method='post',$htmlOptions=array()) + { + return self::beginForm($action,$method,$htmlOptions); + } + + /** + * Generates an opening form tag. + * Note, only the open tag is generated. A close tag should be placed manually + * at the end of the form. + * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.) + * @param string $method form method (e.g. post, get) + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated form tag. + * @see endForm + */ + public static function beginForm($action='',$method='post',$htmlOptions=array()) + { + $htmlOptions['action']=$url=self::normalizeUrl($action); + $htmlOptions['method']=$method; + $form=self::tag('form',$htmlOptions,false,false); + $hiddens=array(); + if(!strcasecmp($method,'get') && ($pos=strpos($url,'?'))!==false) + { + foreach(explode('&',substr($url,$pos+1)) as $pair) + { + if(($pos=strpos($pair,'='))!==false) + $hiddens[]=self::hiddenField(urldecode(substr($pair,0,$pos)),urldecode(substr($pair,$pos+1)),array('id'=>false)); + } + } + $request=Yii::app()->request; + if($request->enableCsrfValidation && !strcasecmp($method,'post')) + $hiddens[]=self::hiddenField($request->csrfTokenName,$request->getCsrfToken(),array('id'=>false)); + if($hiddens!==array()) + $form.="\n".self::tag('div',array('style'=>'display:none'),implode("\n",$hiddens)); + return $form; + } + + /** + * Generates a closing form tag. + * @return string the generated tag + * @see beginForm + */ + public static function endForm() + { + return '</form>'; + } + + /** + * Generates a stateful form tag. + * A stateful form tag is similar to {@link form} except that it renders an additional + * hidden field for storing persistent page states. You should use this method to generate + * a form tag if you want to access persistent page states when the form is submitted. + * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.) + * @param string $method form method (e.g. post, get) + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated form tag. + */ + public static function statefulForm($action='',$method='post',$htmlOptions=array()) + { + return self::form($action,$method,$htmlOptions)."\n". + self::tag('div',array('style'=>'display:none'),self::pageStateField('')); + } + + /** + * Generates a hidden field for storing persistent page states. + * This method is internally used by {@link statefulForm}. + * @param string $value the persistent page states in serialized format + * @return string the generated hidden field + */ + public static function pageStateField($value) + { + return '<input type="hidden" name="'.CController::STATE_INPUT_NAME.'" value="'.$value.'" />'; + } + + /** + * Generates a hyperlink tag. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag. + * @param mixed $url a URL or an action route that can be used to create a URL. + * See {@link normalizeUrl} for more details about how to specify this parameter. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated hyperlink + * @see normalizeUrl + * @see clientChange + */ + public static function link($text,$url='#',$htmlOptions=array()) + { + if($url!=='') + $htmlOptions['href']=self::normalizeUrl($url); + self::clientChange('click',$htmlOptions); + return self::tag('a',$htmlOptions,$text); + } + + /** + * Generates a mailto link. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag. + * @param string $email email address. If this is empty, the first parameter (link body) will be treated as the email address. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated mailto link + * @see clientChange + */ + public static function mailto($text,$email='',$htmlOptions=array()) + { + if($email==='') + $email=$text; + return self::link($text,'mailto:'.$email,$htmlOptions); + } + + /** + * Generates an image tag. + * @param string $src the image URL + * @param string $alt the alternative text display + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated image tag + */ + public static function image($src,$alt='',$htmlOptions=array()) + { + $htmlOptions['src']=$src; + $htmlOptions['alt']=$alt; + return self::tag('img',$htmlOptions); + } + + /** + * Generates a button. + * @param string $label the button label + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function button($label='button',$htmlOptions=array()) + { + if(!isset($htmlOptions['name'])) + { + if(!array_key_exists('name',$htmlOptions)) + $htmlOptions['name']=self::ID_PREFIX.self::$count++; + } + if(!isset($htmlOptions['type'])) + $htmlOptions['type']='button'; + if(!isset($htmlOptions['value'])) + $htmlOptions['value']=$label; + self::clientChange('click',$htmlOptions); + return self::tag('input',$htmlOptions); + } + + /** + * Generates a button using HTML button tag. + * This method is similar to {@link button} except that it generates a 'button' + * tag instead of 'input' tag. + * @param string $label the button label. Note that this value will be directly inserted in the button element + * without being HTML-encoded. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function htmlButton($label='button',$htmlOptions=array()) + { + if(!isset($htmlOptions['name'])) + $htmlOptions['name']=self::ID_PREFIX.self::$count++; + if(!isset($htmlOptions['type'])) + $htmlOptions['type']='button'; + self::clientChange('click',$htmlOptions); + return self::tag('button',$htmlOptions,$label); + } + + /** + * Generates a submit button. + * @param string $label the button label + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function submitButton($label='submit',$htmlOptions=array()) + { + $htmlOptions['type']='submit'; + return self::button($label,$htmlOptions); + } + + /** + * Generates a reset button. + * @param string $label the button label + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function resetButton($label='reset',$htmlOptions=array()) + { + $htmlOptions['type']='reset'; + return self::button($label,$htmlOptions); + } + + /** + * Generates an image submit button. + * @param string $src the image URL + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function imageButton($src,$htmlOptions=array()) + { + $htmlOptions['src']=$src; + $htmlOptions['type']='image'; + return self::button('submit',$htmlOptions); + } + + /** + * Generates a link submit button. + * @param string $label the button label + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function linkButton($label='submit',$htmlOptions=array()) + { + if(!isset($htmlOptions['submit'])) + $htmlOptions['submit']=isset($htmlOptions['href']) ? $htmlOptions['href'] : ''; + return self::link($label,'#',$htmlOptions); + } + + /** + * Generates a label tag. + * @param string $label label text. Note, you should HTML-encode the text if needed. + * @param string $for the ID of the HTML element that this label is associated with. + * If this is false, the 'for' attribute for the label tag will not be rendered. + * @param array $htmlOptions additional HTML attributes. + * The following HTML option is recognized: + * <ul> + * <li>required: if this is set and is true, the label will be styled + * with CSS class 'required' (customizable with CHtml::$requiredCss), + * and be decorated with {@link CHtml::beforeRequiredLabel} and + * {@link CHtml::afterRequiredLabel}.</li> + * </ul> + * @return string the generated label tag + */ + public static function label($label,$for,$htmlOptions=array()) + { + if($for===false) + unset($htmlOptions['for']); + else + $htmlOptions['for']=$for; + if(isset($htmlOptions['required'])) + { + if($htmlOptions['required']) + { + if(isset($htmlOptions['class'])) + $htmlOptions['class'].=' '.self::$requiredCss; + else + $htmlOptions['class']=self::$requiredCss; + $label=self::$beforeRequiredLabel.$label.self::$afterRequiredLabel; + } + unset($htmlOptions['required']); + } + return self::tag('label',$htmlOptions,$label); + } + + /** + * Generates a text field input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see inputField + */ + public static function textField($name,$value='',$htmlOptions=array()) + { + self::clientChange('change',$htmlOptions); + return self::inputField('text',$name,$value,$htmlOptions); + } + + /** + * Generates a hidden input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated input field + * @see inputField + */ + public static function hiddenField($name,$value='',$htmlOptions=array()) + { + return self::inputField('hidden',$name,$value,$htmlOptions); + } + + /** + * Generates a password field input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see inputField + */ + public static function passwordField($name,$value='',$htmlOptions=array()) + { + self::clientChange('change',$htmlOptions); + return self::inputField('password',$name,$value,$htmlOptions); + } + + /** + * Generates a file input. + * Note, you have to set the enclosing form's 'enctype' attribute to be 'multipart/form-data'. + * After the form is submitted, the uploaded file information can be obtained via $_FILES[$name] (see + * PHP documentation). + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated input field + * @see inputField + */ + public static function fileField($name,$value='',$htmlOptions=array()) + { + return self::inputField('file',$name,$value,$htmlOptions); + } + + /** + * Generates a text area input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated text area + * @see clientChange + * @see inputField + */ + public static function textArea($name,$value='',$htmlOptions=array()) + { + $htmlOptions['name']=$name; + if(!isset($htmlOptions['id'])) + $htmlOptions['id']=self::getIdByName($name); + else if($htmlOptions['id']===false) + unset($htmlOptions['id']); + self::clientChange('change',$htmlOptions); + return self::tag('textarea',$htmlOptions,isset($htmlOptions['encode']) && !$htmlOptions['encode'] ? $value : self::encode($value)); + } + + /** + * Generates a radio button. + * @param string $name the input name + * @param boolean $checked whether the radio button is checked + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * Since version 1.1.2, a special option named 'uncheckValue' is available that can be used to specify + * the value returned when the radio button is not checked. When set, a hidden field is rendered so that + * when the radio button is not checked, we can still obtain the posted uncheck value. + * If 'uncheckValue' is not set or set to NULL, the hidden field will not be rendered. + * @return string the generated radio button + * @see clientChange + * @see inputField + */ + public static function radioButton($name,$checked=false,$htmlOptions=array()) + { + if($checked) + $htmlOptions['checked']='checked'; + else + unset($htmlOptions['checked']); + $value=isset($htmlOptions['value']) ? $htmlOptions['value'] : 1; + self::clientChange('click',$htmlOptions); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck=null; + + if($uncheck!==null) + { + // add a hidden field so that if the radio button is not selected, it still submits a value + if(isset($htmlOptions['id']) && $htmlOptions['id']!==false) + $uncheckOptions=array('id'=>self::ID_PREFIX.$htmlOptions['id']); + else + $uncheckOptions=array('id'=>false); + $hidden=self::hiddenField($name,$uncheck,$uncheckOptions); + } + else + $hidden=''; + + // add a hidden field so that if the radio button is not selected, it still submits a value + return $hidden . self::inputField('radio',$name,$value,$htmlOptions); + } + + /** + * Generates a check box. + * @param string $name the input name + * @param boolean $checked whether the check box is checked + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * Since version 1.1.2, a special option named 'uncheckValue' is available that can be used to specify + * the value returned when the checkbox is not checked. When set, a hidden field is rendered so that + * when the checkbox is not checked, we can still obtain the posted uncheck value. + * If 'uncheckValue' is not set or set to NULL, the hidden field will not be rendered. + * @return string the generated check box + * @see clientChange + * @see inputField + */ + public static function checkBox($name,$checked=false,$htmlOptions=array()) + { + if($checked) + $htmlOptions['checked']='checked'; + else + unset($htmlOptions['checked']); + $value=isset($htmlOptions['value']) ? $htmlOptions['value'] : 1; + self::clientChange('click',$htmlOptions); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck=null; + + if($uncheck!==null) + { + // add a hidden field so that if the radio button is not selected, it still submits a value + if(isset($htmlOptions['id']) && $htmlOptions['id']!==false) + $uncheckOptions=array('id'=>self::ID_PREFIX.$htmlOptions['id']); + else + $uncheckOptions=array('id'=>false); + $hidden=self::hiddenField($name,$uncheck,$uncheckOptions); + } + else + $hidden=''; + + // add a hidden field so that if the checkbox is not selected, it still submits a value + return $hidden . self::inputField('checkbox',$name,$value,$htmlOptions); + } + + /** + * Generates a drop down list. + * @param string $name the input name + * @param string $select the selected value + * @param array $data data for generating the list options (value=>display). + * You may use {@link listData} to generate this data. + * Please refer to {@link listOptions} on how this data is used to generate the list options. + * Note, the values and labels will be automatically HTML-encoded by this method. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are recognized. See {@link clientChange} and {@link tag} for more details. + * In addition, the following options are also supported specifically for dropdown list: + * <ul> + * <li>encode: boolean, specifies whether to encode the values. Defaults to true.</li> + * <li>prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.</li> + * <li>empty: string, specifies the text corresponding to empty selection. Its value is empty. + * The 'empty' option can also be an array of value-label pairs. + * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li> + * <li>options: array, specifies additional attributes for each OPTION tag. + * The array keys must be the option values, and the array values are the extra + * OPTION tag attributes in the name-value pairs. For example, + * <pre> + * array( + * 'value1'=>array('disabled'=>true, 'label'=>'value 1'), + * 'value2'=>array('label'=>'value 2'), + * ); + * </pre> + * </li> + * </ul> + * @return string the generated drop down list + * @see clientChange + * @see inputField + * @see listData + */ + public static function dropDownList($name,$select,$data,$htmlOptions=array()) + { + $htmlOptions['name']=$name; + if(!isset($htmlOptions['id'])) + $htmlOptions['id']=self::getIdByName($name); + else if($htmlOptions['id']===false) + unset($htmlOptions['id']); + self::clientChange('change',$htmlOptions); + $options="\n".self::listOptions($select,$data,$htmlOptions); + return self::tag('select',$htmlOptions,$options); + } + + /** + * Generates a list box. + * @param string $name the input name + * @param mixed $select the selected value(s). This can be either a string for single selection or an array for multiple selections. + * @param array $data data for generating the list options (value=>display) + * You may use {@link listData} to generate this data. + * Please refer to {@link listOptions} on how this data is used to generate the list options. + * Note, the values and labels will be automatically HTML-encoded by this method. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized. See {@link clientChange} and {@link tag} for more details. + * In addition, the following options are also supported specifically for list box: + * <ul> + * <li>encode: boolean, specifies whether to encode the values. Defaults to true.</li> + * <li>prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.</li> + * <li>empty: string, specifies the text corresponding to empty selection. Its value is empty. + * The 'empty' option can also be an array of value-label pairs. + * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li> + * <li>options: array, specifies additional attributes for each OPTION tag. + * The array keys must be the option values, and the array values are the extra + * OPTION tag attributes in the name-value pairs. For example, + * <pre> + * array( + * 'value1'=>array('disabled'=>true, 'label'=>'value 1'), + * 'value2'=>array('label'=>'value 2'), + * ); + * </pre> + * </li> + * </ul> + * @return string the generated list box + * @see clientChange + * @see inputField + * @see listData + */ + public static function listBox($name,$select,$data,$htmlOptions=array()) + { + if(!isset($htmlOptions['size'])) + $htmlOptions['size']=4; + if(isset($htmlOptions['multiple'])) + { + if(substr($name,-2)!=='[]') + $name.='[]'; + } + return self::dropDownList($name,$select,$data,$htmlOptions); + } + + /** + * Generates a check box list. + * A check box list allows multiple selection, like {@link listBox}. + * As a result, the corresponding POST value is an array. + * @param string $name name of the check box list. You can use this name to retrieve + * the selected value(s) once the form is submitted. + * @param mixed $select selection of the check boxes. This can be either a string + * for single selection or an array for multiple selections. + * @param array $data value-label pairs used to generate the check box list. + * Note, the values will be automatically HTML-encoded, while the labels will not. + * @param array $htmlOptions addtional HTML options. The options will be applied to + * each checkbox input. The following special options are recognized: + * <ul> + * <li>template: string, specifies how each checkbox is rendered. Defaults + * to "{input} {label}", where "{input}" will be replaced by the generated + * check box input tag while "{label}" be replaced by the corresponding check box label.</li> + * <li>separator: string, specifies the string that separates the generated check boxes.</li> + * <li>checkAll: string, specifies the label for the "check all" checkbox. + * If this option is specified, a 'check all' checkbox will be displayed. Clicking on + * this checkbox will cause all checkboxes checked or unchecked.</li> + * <li>checkAllLast: boolean, specifies whether the 'check all' checkbox should be + * displayed at the end of the checkbox list. If this option is not set (default) + * or is false, the 'check all' checkbox will be displayed at the beginning of + * the checkbox list.</li> + * <li>labelOptions: array, specifies the additional HTML attributes to be rendered + * for every label tag in the list.</li> + * </ul> + * @return string the generated check box list + */ + public static function checkBoxList($name,$select,$data,$htmlOptions=array()) + { + $template=isset($htmlOptions['template'])?$htmlOptions['template']:'{input} {label}'; + $separator=isset($htmlOptions['separator'])?$htmlOptions['separator']:"<br/>\n"; + unset($htmlOptions['template'],$htmlOptions['separator']); + + if(substr($name,-2)!=='[]') + $name.='[]'; + + if(isset($htmlOptions['checkAll'])) + { + $checkAllLabel=$htmlOptions['checkAll']; + $checkAllLast=isset($htmlOptions['checkAllLast']) && $htmlOptions['checkAllLast']; + } + unset($htmlOptions['checkAll'],$htmlOptions['checkAllLast']); + + $labelOptions=isset($htmlOptions['labelOptions'])?$htmlOptions['labelOptions']:array(); + unset($htmlOptions['labelOptions']); + + $items=array(); + $baseID=self::getIdByName($name); + $id=0; + $checkAll=true; + + foreach($data as $value=>$label) + { + $checked=!is_array($select) && !strcmp($value,$select) || is_array($select) && in_array($value,$select); + $checkAll=$checkAll && $checked; + $htmlOptions['value']=$value; + $htmlOptions['id']=$baseID.'_'.$id++; + $option=self::checkBox($name,$checked,$htmlOptions); + $label=self::label($label,$htmlOptions['id'],$labelOptions); + $items[]=strtr($template,array('{input}'=>$option,'{label}'=>$label)); + } + + if(isset($checkAllLabel)) + { + $htmlOptions['value']=1; + $htmlOptions['id']=$id=$baseID.'_all'; + $option=self::checkBox($id,$checkAll,$htmlOptions); + $label=self::label($checkAllLabel,$id,$labelOptions); + $item=strtr($template,array('{input}'=>$option,'{label}'=>$label)); + if($checkAllLast) + $items[]=$item; + else + array_unshift($items,$item); + $name=strtr($name,array('['=>'\\[',']'=>'\\]')); + $js=<<<EOD +$('#$id').click(function() { + $("input[name='$name']").prop('checked', this.checked); +}); +$("input[name='$name']").click(function() { + $('#$id').prop('checked', !$("input[name='$name']:not(:checked)").length); +}); +$('#$id').prop('checked', !$("input[name='$name']:not(:checked)").length); +EOD; + $cs=Yii::app()->getClientScript(); + $cs->registerCoreScript('jquery'); + $cs->registerScript($id,$js); + } + + return self::tag('span',array('id'=>$baseID),implode($separator,$items)); + } + + /** + * Generates a radio button list. + * A radio button list is like a {@link checkBoxList check box list}, except that + * it only allows single selection. + * @param string $name name of the radio button list. You can use this name to retrieve + * the selected value(s) once the form is submitted. + * @param string $select selection of the radio buttons. + * @param array $data value-label pairs used to generate the radio button list. + * Note, the values will be automatically HTML-encoded, while the labels will not. + * @param array $htmlOptions addtional HTML options. The options will be applied to + * each radio button input. The following special options are recognized: + * <ul> + * <li>template: string, specifies how each radio button is rendered. Defaults + * to "{input} {label}", where "{input}" will be replaced by the generated + * radio button input tag while "{label}" will be replaced by the corresponding radio button label.</li> + * <li>separator: string, specifies the string that separates the generated radio buttons. Defaults to new line (<br/>).</li> + * <li>labelOptions: array, specifies the additional HTML attributes to be rendered + * for every label tag in the list.</li> + * </ul> + * @return string the generated radio button list + */ + public static function radioButtonList($name,$select,$data,$htmlOptions=array()) + { + $template=isset($htmlOptions['template'])?$htmlOptions['template']:'{input} {label}'; + $separator=isset($htmlOptions['separator'])?$htmlOptions['separator']:"<br/>\n"; + unset($htmlOptions['template'],$htmlOptions['separator']); + + $labelOptions=isset($htmlOptions['labelOptions'])?$htmlOptions['labelOptions']:array(); + unset($htmlOptions['labelOptions']); + + $items=array(); + $baseID=self::getIdByName($name); + $id=0; + foreach($data as $value=>$label) + { + $checked=!strcmp($value,$select); + $htmlOptions['value']=$value; + $htmlOptions['id']=$baseID.'_'.$id++; + $option=self::radioButton($name,$checked,$htmlOptions); + $label=self::label($label,$htmlOptions['id'],$labelOptions); + $items[]=strtr($template,array('{input}'=>$option,'{label}'=>$label)); + } + return self::tag('span',array('id'=>$baseID),implode($separator,$items)); + } + + /** + * Generates a link that can initiate AJAX requests. + * @param string $text the link body (it will NOT be HTML-encoded.) + * @param mixed $url the URL for the AJAX request. If empty, it is assumed to be the current URL. See {@link normalizeUrl} for more details. + * @param array $ajaxOptions AJAX options (see {@link ajax}) + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated link + * @see normalizeUrl + * @see ajax + */ + public static function ajaxLink($text,$url,$ajaxOptions=array(),$htmlOptions=array()) + { + if(!isset($htmlOptions['href'])) + $htmlOptions['href']='#'; + $ajaxOptions['url']=$url; + $htmlOptions['ajax']=$ajaxOptions; + self::clientChange('click',$htmlOptions); + return self::tag('a',$htmlOptions,$text); + } + + /** + * Generates a push button that can initiate AJAX requests. + * @param string $label the button label + * @param mixed $url the URL for the AJAX request. If empty, it is assumed to be the current URL. See {@link normalizeUrl} for more details. + * @param array $ajaxOptions AJAX options (see {@link ajax}) + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button + */ + public static function ajaxButton($label,$url,$ajaxOptions=array(),$htmlOptions=array()) + { + $ajaxOptions['url']=$url; + $htmlOptions['ajax']=$ajaxOptions; + return self::button($label,$htmlOptions); + } + + /** + * Generates a push button that can submit the current form in POST method. + * @param string $label the button label + * @param mixed $url the URL for the AJAX request. If empty, it is assumed to be the current URL. See {@link normalizeUrl} for more details. + * @param array $ajaxOptions AJAX options (see {@link ajax}) + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button + */ + public static function ajaxSubmitButton($label,$url,$ajaxOptions=array(),$htmlOptions=array()) + { + $ajaxOptions['type']='POST'; + $htmlOptions['type']='submit'; + return self::ajaxButton($label,$url,$ajaxOptions,$htmlOptions); + } + + /** + * Generates the JavaScript that initiates an AJAX request. + * @param array $options AJAX options. The valid options are specified in the jQuery ajax documentation. + * The following special options are added for convenience: + * <ul> + * <li>update: string, specifies the selector whose HTML content should be replaced + * by the AJAX request result.</li> + * <li>replace: string, specifies the selector whose target should be replaced + * by the AJAX request result.</li> + * </ul> + * Note, if you specify the 'success' option, the above options will be ignored. + * @return string the generated JavaScript + * @see http://docs.jquery.com/Ajax/jQuery.ajax#options + */ + public static function ajax($options) + { + Yii::app()->getClientScript()->registerCoreScript('jquery'); + if(!isset($options['url'])) + $options['url']='js:location.href'; + else + $options['url']=self::normalizeUrl($options['url']); + if(!isset($options['cache'])) + $options['cache']=false; + if(!isset($options['data']) && isset($options['type'])) + $options['data']='js:jQuery(this).parents("form").serialize()'; + foreach(array('beforeSend','complete','error','success') as $name) + { + if(isset($options[$name]) && strpos($options[$name],'js:')!==0) + $options[$name]='js:'.$options[$name]; + } + if(isset($options['update'])) + { + if(!isset($options['success'])) + $options['success']='js:function(html){jQuery("'.$options['update'].'").html(html)}'; + unset($options['update']); + } + if(isset($options['replace'])) + { + if(!isset($options['success'])) + $options['success']='js:function(html){jQuery("'.$options['replace'].'").replaceWith(html)}'; + unset($options['replace']); + } + return 'jQuery.ajax('.CJavaScript::encode($options).');'; + } + + /** + * Generates the URL for the published assets. + * @param string $path the path of the asset to be published + * @param boolean $hashByName whether the published directory should be named as the hashed basename. + * If false, the name will be the hashed dirname of the path being published. + * Defaults to false. Set true if the path being published is shared among + * different extensions. + * @return string the asset URL + */ + public static function asset($path,$hashByName=false) + { + return Yii::app()->getAssetManager()->publish($path,$hashByName); + } + + /** + * Normalizes the input parameter to be a valid URL. + * + * If the input parameter is an empty string, the currently requested URL will be returned. + * + * If the input parameter is a non-empty string, it is treated as a valid URL and will + * be returned without any change. + * + * If the input parameter is an array, it is treated as a controller route and a list of + * GET parameters, and the {@link CController::createUrl} method will be invoked to + * create a URL. In this case, the first array element refers to the controller route, + * and the rest key-value pairs refer to the additional GET parameters for the URL. + * For example, <code>array('post/list', 'page'=>3)</code> may be used to generate the URL + * <code>/index.php?r=post/list&page=3</code>. + * + * @param mixed $url the parameter to be used to generate a valid URL + * @return string the normalized URL + */ + public static function normalizeUrl($url) + { + if(is_array($url)) + { + if(isset($url[0])) + { + if(($c=Yii::app()->getController())!==null) + $url=$c->createUrl($url[0],array_splice($url,1)); + else + $url=Yii::app()->createUrl($url[0],array_splice($url,1)); + } + else + $url=''; + } + return $url==='' ? Yii::app()->getRequest()->getUrl() : $url; + } + + /** + * Generates an input HTML tag. + * This method generates an input HTML tag based on the given input name and value. + * @param string $type the input type (e.g. 'text', 'radio') + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes for the HTML tag (see {@link tag}). + * @return string the generated input tag + */ + protected static function inputField($type,$name,$value,$htmlOptions) + { + $htmlOptions['type']=$type; + $htmlOptions['value']=$value; + $htmlOptions['name']=$name; + if(!isset($htmlOptions['id'])) + $htmlOptions['id']=self::getIdByName($name); + else if($htmlOptions['id']===false) + unset($htmlOptions['id']); + return self::tag('input',$htmlOptions); + } + + /** + * Generates a label tag for a model attribute. + * The label text is the attribute label and the label is associated with + * the input for the attribute (see {@link CModel::getAttributeLabel}. + * If the attribute has input error, the label's CSS class will be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. The following special options are recognized: + * <ul> + * <li>required: if this is set and is true, the label will be styled + * with CSS class 'required' (customizable with CHtml::$requiredCss), + * and be decorated with {@link CHtml::beforeRequiredLabel} and + * {@link CHtml::afterRequiredLabel}.</li> + * <li>label: this specifies the label to be displayed. If this is not set, + * {@link CModel::getAttributeLabel} will be called to get the label for display. + * If the label is specified as false, no label will be rendered.</li> + * </ul> + * @return string the generated label tag + */ + public static function activeLabel($model,$attribute,$htmlOptions=array()) + { + if(isset($htmlOptions['for'])) + { + $for=$htmlOptions['for']; + unset($htmlOptions['for']); + } + else + $for=self::getIdByName(self::resolveName($model,$attribute)); + if(isset($htmlOptions['label'])) + { + if(($label=$htmlOptions['label'])===false) + return ''; + unset($htmlOptions['label']); + } + else + $label=$model->getAttributeLabel($attribute); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + return self::label($label,$for,$htmlOptions); + } + + /** + * Generates a label tag for a model attribute. + * This is an enhanced version of {@link activeLabel}. It will render additional + * CSS class and mark when the attribute is required. + * In particular, it calls {@link CModel::isAttributeRequired} to determine + * if the attribute is required. + * If so, it will add a CSS class {@link CHtml::requiredCss} to the label, + * and decorate the label with {@link CHtml::beforeRequiredLabel} and + * {@link CHtml::afterRequiredLabel}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated label tag + */ + public static function activeLabelEx($model,$attribute,$htmlOptions=array()) + { + $realAttribute=$attribute; + self::resolveName($model,$attribute); // strip off square brackets if any + $htmlOptions['required']=$model->isAttributeRequired($attribute); + return self::activeLabel($model,$realAttribute,$htmlOptions); + } + + /** + * Generates a text field input for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see activeInputField + */ + public static function activeTextField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + self::clientChange('change',$htmlOptions); + return self::activeInputField('text',$model,$attribute,$htmlOptions); + } + + /** + * Generates a hidden input for a model attribute. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated input field + * @see activeInputField + */ + public static function activeHiddenField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + return self::activeInputField('hidden',$model,$attribute,$htmlOptions); + } + + /** + * Generates a password field input for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see activeInputField + */ + public static function activePasswordField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + self::clientChange('change',$htmlOptions); + return self::activeInputField('password',$model,$attribute,$htmlOptions); + } + + /** + * Generates a text area input for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated text area + * @see clientChange + */ + public static function activeTextArea($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + self::clientChange('change',$htmlOptions); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + $text=self::resolveValue($model,$attribute); + return self::tag('textarea',$htmlOptions,isset($htmlOptions['encode']) && !$htmlOptions['encode'] ? $text : self::encode($text)); + } + + /** + * Generates a file input for a model attribute. + * Note, you have to set the enclosing form's 'enctype' attribute to be 'multipart/form-data'. + * After the form is submitted, the uploaded file information can be obtained via $_FILES (see + * PHP documentation). + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated input field + * @see activeInputField + */ + public static function activeFileField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + // add a hidden field so that if a model only has a file field, we can + // still use isset($_POST[$modelClass]) to detect if the input is submitted + $hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false); + return self::hiddenField($htmlOptions['name'],'',$hiddenOptions) + . self::activeInputField('file',$model,$attribute,$htmlOptions); + } + + /** + * Generates a radio button for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * A special option named 'uncheckValue' is available that can be used to specify + * the value returned when the radio button is not checked. By default, this value is '0'. + * Internally, a hidden field is rendered so that when the radio button is not checked, + * we can still obtain the posted uncheck value. + * If 'uncheckValue' is set as NULL, the hidden field will not be rendered. + * @return string the generated radio button + * @see clientChange + * @see activeInputField + */ + public static function activeRadioButton($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + if(!isset($htmlOptions['value'])) + $htmlOptions['value']=1; + if(!isset($htmlOptions['checked']) && self::resolveValue($model,$attribute)==$htmlOptions['value']) + $htmlOptions['checked']='checked'; + self::clientChange('click',$htmlOptions); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck='0'; + + $hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false); + $hidden=$uncheck!==null ? self::hiddenField($htmlOptions['name'],$uncheck,$hiddenOptions) : ''; + + // add a hidden field so that if the radio button is not selected, it still submits a value + return $hidden . self::activeInputField('radio',$model,$attribute,$htmlOptions); + } + + /** + * Generates a check box for a model attribute. + * The attribute is assumed to take either true or false value. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * A special option named 'uncheckValue' is available that can be used to specify + * the value returned when the checkbox is not checked. By default, this value is '0'. + * Internally, a hidden field is rendered so that when the checkbox is not checked, + * we can still obtain the posted uncheck value. + * If 'uncheckValue' is set as NULL, the hidden field will not be rendered. + * @return string the generated check box + * @see clientChange + * @see activeInputField + */ + public static function activeCheckBox($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + if(!isset($htmlOptions['value'])) + $htmlOptions['value']=1; + if(!isset($htmlOptions['checked']) && self::resolveValue($model,$attribute)==$htmlOptions['value']) + $htmlOptions['checked']='checked'; + self::clientChange('click',$htmlOptions); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck='0'; + + $hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false); + $hidden=$uncheck!==null ? self::hiddenField($htmlOptions['name'],$uncheck,$hiddenOptions) : ''; + + return $hidden . self::activeInputField('checkbox',$model,$attribute,$htmlOptions); + } + + /** + * Generates a drop down list for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data data for generating the list options (value=>display) + * You may use {@link listData} to generate this data. + * Please refer to {@link listOptions} on how this data is used to generate the list options. + * Note, the values and labels will be automatically HTML-encoded by this method. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are recognized. See {@link clientChange} and {@link tag} for more details. + * In addition, the following options are also supported: + * <ul> + * <li>encode: boolean, specifies whether to encode the values. Defaults to true.</li> + * <li>prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.</li> + * <li>empty: string, specifies the text corresponding to empty selection. Its value is empty. + * The 'empty' option can also be an array of value-label pairs. + * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li> + * <li>options: array, specifies additional attributes for each OPTION tag. + * The array keys must be the option values, and the array values are the extra + * OPTION tag attributes in the name-value pairs. For example, + * <pre> + * array( + * 'value1'=>array('disabled'=>true, 'label'=>'value 1'), + * 'value2'=>array('label'=>'value 2'), + * ); + * </pre> + * </li> + * </ul> + * @return string the generated drop down list + * @see clientChange + * @see listData + */ + public static function activeDropDownList($model,$attribute,$data,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + $selection=self::resolveValue($model,$attribute); + $options="\n".self::listOptions($selection,$data,$htmlOptions); + self::clientChange('change',$htmlOptions); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + if(isset($htmlOptions['multiple'])) + { + if(substr($htmlOptions['name'],-2)!=='[]') + $htmlOptions['name'].='[]'; + } + return self::tag('select',$htmlOptions,$options); + } + + /** + * Generates a list box for a model attribute. + * The model attribute value is used as the selection. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data data for generating the list options (value=>display) + * You may use {@link listData} to generate this data. + * Please refer to {@link listOptions} on how this data is used to generate the list options. + * Note, the values and labels will be automatically HTML-encoded by this method. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are recognized. See {@link clientChange} and {@link tag} for more details. + * In addition, the following options are also supported: + * <ul> + * <li>encode: boolean, specifies whether to encode the values. Defaults to true.</li> + * <li>prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.</li> + * <li>empty: string, specifies the text corresponding to empty selection. Its value is empty. + * The 'empty' option can also be an array of value-label pairs. + * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li> + * <li>options: array, specifies additional attributes for each OPTION tag. + * The array keys must be the option values, and the array values are the extra + * OPTION tag attributes in the name-value pairs. For example, + * <pre> + * array( + * 'value1'=>array('disabled'=>true, 'label'=>'value 1'), + * 'value2'=>array('label'=>'value 2'), + * ); + * </pre> + * </li> + * </ul> + * @return string the generated list box + * @see clientChange + * @see listData + */ + public static function activeListBox($model,$attribute,$data,$htmlOptions=array()) + { + if(!isset($htmlOptions['size'])) + $htmlOptions['size']=4; + return self::activeDropDownList($model,$attribute,$data,$htmlOptions); + } + + /** + * Generates a check box list for a model attribute. + * The model attribute value is used as the selection. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * Note that a check box list allows multiple selection, like {@link listBox}. + * As a result, the corresponding POST value is an array. In case no selection + * is made, the corresponding POST value is an empty string. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data value-label pairs used to generate the check box list. + * Note, the values will be automatically HTML-encoded, while the labels will not. + * @param array $htmlOptions addtional HTML options. The options will be applied to + * each checkbox input. The following special options are recognized: + * <ul> + * <li>template: string, specifies how each checkbox is rendered. Defaults + * to "{input} {label}", where "{input}" will be replaced by the generated + * check box input tag while "{label}" will be replaced by the corresponding check box label.</li> + * <li>separator: string, specifies the string that separates the generated check boxes.</li> + * <li>checkAll: string, specifies the label for the "check all" checkbox. + * If this option is specified, a 'check all' checkbox will be displayed. Clicking on + * this checkbox will cause all checkboxes checked or unchecked.</li> + * <li>checkAllLast: boolean, specifies whether the 'check all' checkbox should be + * displayed at the end of the checkbox list. If this option is not set (default) + * or is false, the 'check all' checkbox will be displayed at the beginning of + * the checkbox list.</li> + * <li>encode: boolean, specifies whether to encode HTML-encode tag attributes and values. Defaults to true.</li> + * </ul> + * Since 1.1.7, a special option named 'uncheckValue' is available. It can be used to set the value + * that will be returned when the checkbox is not checked. By default, this value is ''. + * Internally, a hidden field is rendered so when the checkbox is not checked, we can still + * obtain the value. If 'uncheckValue' is set to NULL, there will be no hidden field rendered. + * @return string the generated check box list + * @see checkBoxList + */ + public static function activeCheckBoxList($model,$attribute,$data,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + $selection=self::resolveValue($model,$attribute); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + $name=$htmlOptions['name']; + unset($htmlOptions['name']); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck=''; + + $hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false); + $hidden=$uncheck!==null ? self::hiddenField($name,$uncheck,$hiddenOptions) : ''; + + return $hidden . self::checkBoxList($name,$selection,$data,$htmlOptions); + } + + /** + * Generates a radio button list for a model attribute. + * The model attribute value is used as the selection. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data value-label pairs used to generate the radio button list. + * Note, the values will be automatically HTML-encoded, while the labels will not. + * @param array $htmlOptions addtional HTML options. The options will be applied to + * each radio button input. The following special options are recognized: + * <ul> + * <li>template: string, specifies how each radio button is rendered. Defaults + * to "{input} {label}", where "{input}" will be replaced by the generated + * radio button input tag while "{label}" will be replaced by the corresponding radio button label.</li> + * <li>separator: string, specifies the string that separates the generated radio buttons. Defaults to new line (<br/>).</li> + * <li>encode: boolean, specifies whether to encode HTML-encode tag attributes and values. Defaults to true.</li> + * </ul> + * Since version 1.1.7, a special option named 'uncheckValue' is available that can be used to specify the value + * returned when the radio button is not checked. By default, this value is ''. Internally, a hidden field is + * rendered so that when the radio button is not checked, we can still obtain the posted uncheck value. + * If 'uncheckValue' is set as NULL, the hidden field will not be rendered. + * @return string the generated radio button list + * @see radioButtonList + */ + public static function activeRadioButtonList($model,$attribute,$data,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + $selection=self::resolveValue($model,$attribute); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + $name=$htmlOptions['name']; + unset($htmlOptions['name']); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck=''; + + $hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false); + $hidden=$uncheck!==null ? self::hiddenField($name,$uncheck,$hiddenOptions) : ''; + + return $hidden . self::radioButtonList($name,$selection,$data,$htmlOptions); + } + + /** + * Displays a summary of validation errors for one or several models. + * @param mixed $model the models whose input errors are to be displayed. This can be either + * a single model or an array of models. + * @param string $header a piece of HTML code that appears in front of the errors + * @param string $footer a piece of HTML code that appears at the end of the errors + * @param array $htmlOptions additional HTML attributes to be rendered in the container div tag. + * A special option named 'firstError' is recognized, which when set true, will + * make the error summary to show only the first error message of each attribute. + * If this is not set or is false, all error messages will be displayed. + * This option has been available since version 1.1.3. + * @return string the error summary. Empty if no errors are found. + * @see CModel::getErrors + * @see errorSummaryCss + */ + public static function errorSummary($model,$header=null,$footer=null,$htmlOptions=array()) + { + $content=''; + if(!is_array($model)) + $model=array($model); + if(isset($htmlOptions['firstError'])) + { + $firstError=$htmlOptions['firstError']; + unset($htmlOptions['firstError']); + } + else + $firstError=false; + foreach($model as $m) + { + foreach($m->getErrors() as $errors) + { + foreach($errors as $error) + { + if($error!='') + $content.="<li>$error</li>\n"; + if($firstError) + break; + } + } + } + if($content!=='') + { + if($header===null) + $header='<p>'.Yii::t('yii','Please fix the following input errors:').'</p>'; + if(!isset($htmlOptions['class'])) + $htmlOptions['class']=self::$errorSummaryCss; + return self::tag('div',$htmlOptions,$header."\n<ul>\n$content</ul>".$footer); + } + else + return ''; + } + + /** + * Displays the first validation error for a model attribute. + * @param CModel $model the data model + * @param string $attribute the attribute name + * @param array $htmlOptions additional HTML attributes to be rendered in the container div tag. + * @return string the error display. Empty if no errors are found. + * @see CModel::getErrors + * @see errorMessageCss + */ + public static function error($model,$attribute,$htmlOptions=array()) + { + self::resolveName($model,$attribute); // turn [a][b]attr into attr + $error=$model->getError($attribute); + if($error!='') + { + if(!isset($htmlOptions['class'])) + $htmlOptions['class']=self::$errorMessageCss; + return self::tag('div',$htmlOptions,$error); + } + else + return ''; + } + + /** + * Generates the data suitable for list-based HTML elements. + * The generated data can be used in {@link dropDownList}, {@link listBox}, {@link checkBoxList}, + * {@link radioButtonList}, and their active-versions (such as {@link activeDropDownList}). + * Note, this method does not HTML-encode the generated data. You may call {@link encodeArray} to + * encode it if needed. + * Please refer to the {@link value} method on how to specify value field, text field and group field. + * @param array $models a list of model objects. This parameter + * can also be an array of associative arrays (e.g. results of {@link CDbCommand::queryAll}). + * @param string $valueField the attribute name for list option values + * @param string $textField the attribute name for list option texts + * @param string $groupField the attribute name for list option group names. If empty, no group will be generated. + * @return array the list data that can be used in {@link dropDownList}, {@link listBox}, etc. + */ + public static function listData($models,$valueField,$textField,$groupField='') + { + $listData=array(); + if($groupField==='') + { + foreach($models as $model) + { + $value=self::value($model,$valueField); + $text=self::value($model,$textField); + $listData[$value]=$text; + } + } + else + { + foreach($models as $model) + { + $group=self::value($model,$groupField); + $value=self::value($model,$valueField); + $text=self::value($model,$textField); + $listData[$group][$value]=$text; + } + } + return $listData; + } + + /** + * Evaluates the value of the specified attribute for the given model. + * The attribute name can be given in a dot syntax. For example, if the attribute + * is "author.firstName", this method will return the value of "$model->author->firstName". + * A default value (passed as the last parameter) will be returned if the attribute does + * not exist or is broken in the middle (e.g. $model->author is null). + * The model can be either an object or an array. If the latter, the attribute is treated + * as a key of the array. For the example of "author.firstName", if would mean the array value + * "$model['author']['firstName']". + * @param mixed $model the model. This can be either an object or an array. + * @param string $attribute the attribute name (use dot to concatenate multiple attributes) + * @param mixed $defaultValue the default value to return when the attribute does not exist + * @return mixed the attribute value + */ + public static function value($model,$attribute,$defaultValue=null) + { + foreach(explode('.',$attribute) as $name) + { + if(is_object($model)) + $model=$model->$name; + else if(is_array($model) && isset($model[$name])) + $model=$model[$name]; + else + return $defaultValue; + } + return $model; + } + + /** + * Generates a valid HTML ID based on name. + * @param string $name name from which to generate HTML ID + * @return string the ID generated based on name. + */ + public static function getIdByName($name) + { + return str_replace(array('[]', '][', '[', ']'), array('', '_', '_', ''), $name); + } + + /** + * Generates input field ID for a model attribute. + * @param CModel $model the data model + * @param string $attribute the attribute + * @return string the generated input field ID + */ + public static function activeId($model,$attribute) + { + return self::getIdByName(self::activeName($model,$attribute)); + } + + /** + * Generates input field name for a model attribute. + * Unlike {@link resolveName}, this method does NOT modify the attribute name. + * @param CModel $model the data model + * @param string $attribute the attribute + * @return string the generated input field name + */ + public static function activeName($model,$attribute) + { + $a=$attribute; // because the attribute name may be changed by resolveName + return self::resolveName($model,$a); + } + + /** + * Generates an input HTML tag for a model attribute. + * This method generates an input HTML tag based on the given data model and attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * This enables highlighting the incorrect input. + * @param string $type the input type (e.g. 'text', 'radio') + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes for the HTML tag + * @return string the generated input tag + */ + protected static function activeInputField($type,$model,$attribute,$htmlOptions) + { + $htmlOptions['type']=$type; + if($type==='text' || $type==='password') + { + if(!isset($htmlOptions['maxlength'])) + { + foreach($model->getValidators($attribute) as $validator) + { + if($validator instanceof CStringValidator && $validator->max!==null) + { + $htmlOptions['maxlength']=$validator->max; + break; + } + } + } + else if($htmlOptions['maxlength']===false) + unset($htmlOptions['maxlength']); + } + + if($type==='file') + unset($htmlOptions['value']); + else if(!isset($htmlOptions['value'])) + $htmlOptions['value']=self::resolveValue($model,$attribute); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + return self::tag('input',$htmlOptions); + } + + /** + * Generates the list options. + * @param mixed $selection the selected value(s). This can be either a string for single selection or an array for multiple selections. + * @param array $listData the option data (see {@link listData}) + * @param array $htmlOptions additional HTML attributes. The following two special attributes are recognized: + * <ul> + * <li>encode: boolean, specifies whether to encode the values. Defaults to true.</li> + * <li>prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.</li> + * <li>empty: string, specifies the text corresponding to empty selection. Its value is empty. + * The 'empty' option can also be an array of value-label pairs. + * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li> + * <li>options: array, specifies additional attributes for each OPTION tag. + * The array keys must be the option values, and the array values are the extra + * OPTION tag attributes in the name-value pairs. For example, + * <pre> + * array( + * 'value1'=>array('disabled'=>true, 'label'=>'value 1'), + * 'value2'=>array('label'=>'value 2'), + * ); + * </pre> + * </li> + * <li>key: string, specifies the name of key attribute of the selection object(s). + * This is used when the selection is represented in terms of objects. In this case, + * the property named by the key option of the objects will be treated as the actual selection value. + * This option defaults to 'primaryKey', meaning using the 'primaryKey' property value of the objects in the selection. + * This option has been available since version 1.1.3.</li> + * </ul> + * @return string the generated list options + */ + public static function listOptions($selection,$listData,&$htmlOptions) + { + $raw=isset($htmlOptions['encode']) && !$htmlOptions['encode']; + $content=''; + if(isset($htmlOptions['prompt'])) + { + $content.='<option value="">'.strtr($htmlOptions['prompt'],array('<'=>'<', '>'=>'>'))."</option>\n"; + unset($htmlOptions['prompt']); + } + if(isset($htmlOptions['empty'])) + { + if(!is_array($htmlOptions['empty'])) + $htmlOptions['empty']=array(''=>$htmlOptions['empty']); + foreach($htmlOptions['empty'] as $value=>$label) + $content.='<option value="'.self::encode($value).'">'.strtr($label,array('<'=>'<', '>'=>'>'))."</option>\n"; + unset($htmlOptions['empty']); + } + + if(isset($htmlOptions['options'])) + { + $options=$htmlOptions['options']; + unset($htmlOptions['options']); + } + else + $options=array(); + + $key=isset($htmlOptions['key']) ? $htmlOptions['key'] : 'primaryKey'; + if(is_array($selection)) + { + foreach($selection as $i=>$item) + { + if(is_object($item)) + $selection[$i]=$item->$key; + } + } + else if(is_object($selection)) + $selection=$selection->$key; + + foreach($listData as $key=>$value) + { + if(is_array($value)) + { + $content.='<optgroup label="'.($raw?$key : self::encode($key))."\">\n"; + $dummy=array('options'=>$options); + if(isset($htmlOptions['encode'])) + $dummy['encode']=$htmlOptions['encode']; + $content.=self::listOptions($selection,$value,$dummy); + $content.='</optgroup>'."\n"; + } + else + { + $attributes=array('value'=>(string)$key, 'encode'=>!$raw); + if(!is_array($selection) && !strcmp($key,$selection) || is_array($selection) && in_array($key,$selection)) + $attributes['selected']='selected'; + if(isset($options[$key])) + $attributes=array_merge($attributes,$options[$key]); + $content.=self::tag('option',$attributes,$raw?(string)$value : self::encode((string)$value))."\n"; + } + } + + unset($htmlOptions['key']); + + return $content; + } + + /** + * Generates the JavaScript with the specified client changes. + * @param string $event event name (without 'on') + * @param array $htmlOptions HTML attributes which may contain the following special attributes + * specifying the client change behaviors: + * <ul> + * <li>submit: string, specifies the URL that the button should submit to. If empty, the current requested URL will be used.</li> + * <li>params: array, name-value pairs that should be submitted together with the form. This is only used when 'submit' option is specified.</li> + * <li>csrf: boolean, whether a CSRF token should be submitted when {@link CHttpRequest::enableCsrfValidation} is true. Defaults to false. + * You may want to set this to be true if there is no enclosing form around this element. + * This option is meaningful only when 'submit' option is set.</li> + * <li>return: boolean, the return value of the javascript. Defaults to false, meaning that the execution of + * javascript would not cause the default behavior of the event.</li> + * <li>confirm: string, specifies the message that should show in a pop-up confirmation dialog.</li> + * <li>ajax: array, specifies the AJAX options (see {@link ajax}).</li> + * <li>live: boolean, whether the event handler should be attached with live/delegate or direct style. If not set, {@link liveEvents} will be used. This option has been available since version 1.1.6.</li> + * </ul> + * This parameter has been available since version 1.1.1. + */ + protected static function clientChange($event,&$htmlOptions) + { + if(!isset($htmlOptions['submit']) && !isset($htmlOptions['confirm']) && !isset($htmlOptions['ajax'])) + return; + + if(isset($htmlOptions['live'])) + { + $live=$htmlOptions['live']; + unset($htmlOptions['live']); + } + else + $live = self::$liveEvents; + + if(isset($htmlOptions['return']) && $htmlOptions['return']) + $return='return true'; + else + $return='return false'; + + if(isset($htmlOptions['on'.$event])) + { + $handler=trim($htmlOptions['on'.$event],';').';'; + unset($htmlOptions['on'.$event]); + } + else + $handler=''; + + if(isset($htmlOptions['id'])) + $id=$htmlOptions['id']; + else + $id=$htmlOptions['id']=isset($htmlOptions['name'])?$htmlOptions['name']:self::ID_PREFIX.self::$count++; + + $cs=Yii::app()->getClientScript(); + $cs->registerCoreScript('jquery'); + + if(isset($htmlOptions['submit'])) + { + $cs->registerCoreScript('yii'); + $request=Yii::app()->getRequest(); + if($request->enableCsrfValidation && isset($htmlOptions['csrf']) && $htmlOptions['csrf']) + $htmlOptions['params'][$request->csrfTokenName]=$request->getCsrfToken(); + if(isset($htmlOptions['params'])) + $params=CJavaScript::encode($htmlOptions['params']); + else + $params='{}'; + if($htmlOptions['submit']!=='') + $url=CJavaScript::quote(self::normalizeUrl($htmlOptions['submit'])); + else + $url=''; + $handler.="jQuery.yii.submitForm(this,'$url',$params);{$return};"; + } + + if(isset($htmlOptions['ajax'])) + $handler.=self::ajax($htmlOptions['ajax'])."{$return};"; + + if(isset($htmlOptions['confirm'])) + { + $confirm='confirm(\''.CJavaScript::quote($htmlOptions['confirm']).'\')'; + if($handler!=='') + $handler="if($confirm) {".$handler."} else return false;"; + else + $handler="return $confirm;"; + } + + if($live) + $cs->registerScript('Yii.CHtml.#' . $id, "$('body').on('$event','#$id',function(){{$handler}});"); + else + $cs->registerScript('Yii.CHtml.#' . $id, "$('#$id').on('$event', function(){{$handler}});"); + unset($htmlOptions['params'],$htmlOptions['submit'],$htmlOptions['ajax'],$htmlOptions['confirm'],$htmlOptions['return'],$htmlOptions['csrf']); + } + + /** + * Generates input name and ID for a model attribute. + * This method will update the HTML options by setting appropriate 'name' and 'id' attributes. + * This method may also modify the attribute name if the name + * contains square brackets (mainly used in tabular input). + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions the HTML options + */ + public static function resolveNameID($model,&$attribute,&$htmlOptions) + { + if(!isset($htmlOptions['name'])) + $htmlOptions['name']=self::resolveName($model,$attribute); + if(!isset($htmlOptions['id'])) + $htmlOptions['id']=self::getIdByName($htmlOptions['name']); + else if($htmlOptions['id']===false) + unset($htmlOptions['id']); + } + + /** + * Generates input name for a model attribute. + * Note, the attribute name may be modified after calling this method if the name + * contains square brackets (mainly used in tabular input) before the real attribute name. + * @param CModel $model the data model + * @param string $attribute the attribute + * @return string the input name + */ + public static function resolveName($model,&$attribute) + { + if(($pos=strpos($attribute,'['))!==false) + { + if($pos!==0) // e.g. name[a][b] + return get_class($model).'['.substr($attribute,0,$pos).']'.substr($attribute,$pos); + if(($pos=strrpos($attribute,']'))!==false && $pos!==strlen($attribute)-1) // e.g. [a][b]name + { + $sub=substr($attribute,0,$pos+1); + $attribute=substr($attribute,$pos+1); + return get_class($model).$sub.'['.$attribute.']'; + } + if(preg_match('/\](\w+\[.*)$/',$attribute,$matches)) + { + $name=get_class($model).'['.str_replace(']','][',trim(strtr($attribute,array(']['=>']','['=>']')),']')).']'; + $attribute=$matches[1]; + return $name; + } + } + return get_class($model).'['.$attribute.']'; + } + + /** + * Evaluates the attribute value of the model. + * This method can recognize the attribute name written in array format. + * For example, if the attribute name is 'name[a][b]', the value "$model->name['a']['b']" will be returned. + * @param CModel $model the data model + * @param string $attribute the attribute name + * @return mixed the attribute value + * @since 1.1.3 + */ + public static function resolveValue($model,$attribute) + { + if(($pos=strpos($attribute,'['))!==false) + { + if($pos===0) // [a]name[b][c], should ignore [a] + { + if(preg_match('/\](\w+)/',$attribute,$matches)) + $attribute=$matches[1]; + if(($pos=strpos($attribute,'['))===false) + return $model->$attribute; + } + $name=substr($attribute,0,$pos); + $value=$model->$name; + foreach(explode('][',rtrim(substr($attribute,$pos+1),']')) as $id) + { + if(is_array($value) && isset($value[$id])) + $value=$value[$id]; + else + return null; + } + return $value; + } + else + return $model->$attribute; + } + + /** + * Appends {@link errorCss} to the 'class' attribute. + * @param array $htmlOptions HTML options to be modified + */ + protected static function addErrorCss(&$htmlOptions) + { + if(isset($htmlOptions['class'])) + $htmlOptions['class'].=' '.self::$errorCss; + else + $htmlOptions['class']=self::$errorCss; + } + + /** + * Renders the HTML tag attributes. + * Since version 1.1.5, attributes whose value is null will not be rendered. + * Special attributes, such as 'checked', 'disabled', 'readonly', will be rendered + * properly based on their corresponding boolean value. + * @param array $htmlOptions attributes to be rendered + * @return string the rendering result + */ + public static function renderAttributes($htmlOptions) + { + static $specialAttributes=array( + 'checked'=>1, + 'declare'=>1, + 'defer'=>1, + 'disabled'=>1, + 'ismap'=>1, + 'multiple'=>1, + 'nohref'=>1, + 'noresize'=>1, + 'readonly'=>1, + 'selected'=>1, + ); + + if($htmlOptions===array()) + return ''; + + $html=''; + if(isset($htmlOptions['encode'])) + { + $raw=!$htmlOptions['encode']; + unset($htmlOptions['encode']); + } + else + $raw=false; + + if($raw) + { + foreach($htmlOptions as $name=>$value) + { + if(isset($specialAttributes[$name])) + { + if($value) + $html .= ' ' . $name . '="' . $name . '"'; + } + else if($value!==null) + $html .= ' ' . $name . '="' . $value . '"'; + } + } + else + { + foreach($htmlOptions as $name=>$value) + { + if(isset($specialAttributes[$name])) + { + if($value) + $html .= ' ' . $name . '="' . $name . '"'; + } + else if($value!==null) + $html .= ' ' . $name . '="' . self::encode($value) . '"'; + } + } + return $html; + } +} diff --git a/framework/web/helpers/CJSON.php b/framework/web/helpers/CJSON.php new file mode 100644 index 0000000..f329453 --- /dev/null +++ b/framework/web/helpers/CJSON.php @@ -0,0 +1,704 @@ +<?php +/** +* JSON (JavaScript Object Notation) is a lightweight data-interchange +* format. It is easy for humans to read and write. It is easy for machines +* to parse and generate. It is based on a subset of the JavaScript +* Programming Language, Standard ECMA-262 3rd Edition - December 1999. +* This feature can also be found in Python. JSON is a text format that is +* completely language independent but uses conventions that are familiar +* to programmers of the C-family of languages, including C, C++, C#, Java, +* JavaScript, Perl, TCL, and many others. These properties make JSON an +* ideal data-interchange language. +* +* This package provides a simple encoder and decoder for JSON notation. It +* is intended for use with client-side Javascript applications that make +* use of HTTPRequest to perform server communication functions - data can +* be encoded into JSON notation for use in a client-side javascript, or +* decoded from incoming Javascript requests. JSON format is native to +* Javascript, and can be directly eval()'ed with no further parsing +* overhead +* +* All strings should be in ASCII or UTF-8 format! +* +* LICENSE: Redistribution and use in source and binary forms, with or +* without modification, are permitted provided that the following +* conditions are met: Redistributions of source code must retain the +* above copyright notice, this list of conditions and the following +* disclaimer. Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +* DAMAGE. +* +* @author Michal Migurski <mike-json@teczno.com> +* @author Matt Knapp <mdknapp[at]gmail[dot]com> +* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> +* @copyright 2005 Michal Migurski +* @license http://www.opensource.org/licenses/bsd-license.php +* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 +*/ + +/** + * CJSON converts PHP data to and from JSON format. + * + * @author Michal Migurski <mike-json@teczno.com> + * @author Matt Knapp <mdknapp[at]gmail[dot]com> + * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> + * @version $Id: CJSON.php 3204 2011-05-05 21:36:32Z alexander.makarow $ + * @package system.web.helpers + * @since 1.0 + */ +class CJSON +{ + /** + * Marker constant for JSON::decode(), used to flag stack state + */ + const JSON_SLICE = 1; + + /** + * Marker constant for JSON::decode(), used to flag stack state + */ + const JSON_IN_STR = 2; + + /** + * Marker constant for JSON::decode(), used to flag stack state + */ + const JSON_IN_ARR = 4; + + /** + * Marker constant for JSON::decode(), used to flag stack state + */ + const JSON_IN_OBJ = 8; + + /** + * Marker constant for JSON::decode(), used to flag stack state + */ + const JSON_IN_CMT = 16; + + /** + * Encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * If var is a string, it will be converted to UTF-8 format first before being encoded. + * @return string JSON string representation of input var + */ + public static function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return str_replace(',','.',(float)$var); // locale-independent representation + + case 'string': + if (($enc=strtoupper(Yii::app()->charset))!=='UTF-8') + $var=iconv($enc, 'UTF-8', $var); + + if(function_exists('json_encode')) + return json_encode($var); + + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c+1})); + $c+=1; + $utf16 = self::utf8ToUTF16BE($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c+1}), + ord($var{$c+2})); + $c+=2; + $utf16 = self::utf8ToUTF16BE($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c+1}), + ord($var{$c+2}), + ord($var{$c+3})); + $c+=3; + $utf16 = self::utf8ToUTF16BE($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c+1}), + ord($var{$c+2}), + ord($var{$c+3}), + ord($var{$c+4})); + $c+=4; + $utf16 = self::utf8ToUTF16BE($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c+1}), + ord($var{$c+2}), + ord($var{$c+3}), + ord($var{$c+4}), + ord($var{$c+5})); + $c+=5; + $utf16 = self::utf8ToUTF16BE($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + return '{' . + join(',', array_map(array('CJSON', 'nameValue'), + array_keys($var), + array_values($var))) + . '}'; + } + + // treat it like a regular array + return '[' . join(',', array_map(array('CJSON', 'encode'), $var)) . ']'; + + case 'object': + if ($var instanceof Traversable) + { + $vars = array(); + foreach ($var as $k=>$v) + $vars[$k] = $v; + } + else + $vars = get_object_vars($var); + return '{' . + join(',', array_map(array('CJSON', 'nameValue'), + array_keys($vars), + array_values($vars))) + . '}'; + + default: + return ''; + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + protected static function nameValue($name, $value) + { + return self::encode(strval($name)) . ':' . self::encode($value); + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param string $str string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + protected static function reduceString($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * @param boolean $useArray whether to use associative array to represent object data + * @return mixed number, boolean, string, array, or object corresponding to given JSON input string. + * Note that decode() always returns strings in ASCII or UTF-8 format! + * @access public + */ + public static function decode($str, $useArray=true) + { + if(function_exists('json_decode')) + return json_decode($str,$useArray); + + $str = self::reduceString($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').+(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c+2), 2))) + . chr(hexdec(substr($chrs, ($c+4), 2))); + $utf8 .= self::utf16beToUTF8($utf16); + $c+=5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(self::JSON_IN_ARR); + $arr = array(); + } else { + if ($useArray) { + $stk = array(self::JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(self::JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => self::JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = self::reduceString($chrs); + + if ($chrs == '') { + if (reset($stk) == self::JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == self::JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => self::JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == self::JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, self::decode($slice,$useArray)); + + } elseif (reset($stk) == self::JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = self::decode($parts[1],$useArray); + $val = self::decode($parts[2],$useArray); + + if ($useArray) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = self::decode($parts[2],$useArray); + + if ($useArray) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != self::JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => self::JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == self::JSON_IN_STR) && + (($chrs{$c - 1} != "\\") || + ($chrs{$c - 1} == "\\" && $chrs{$c - 2} == "\\"))) { + // found a quote, we're in a string, and it's not escaped + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(self::JSON_SLICE, self::JSON_IN_ARR, self::JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => self::JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == self::JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(self::JSON_SLICE, self::JSON_IN_ARR, self::JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => self::JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == self::JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(self::JSON_SLICE, self::JSON_IN_ARR, self::JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => self::JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == self::JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == self::JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == self::JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * This function returns any UTF-8 encoded text as a list of + * Unicode values: + * @param string $str string to convert + * @return string + * @author Scott Michael Reynen <scott@randomchaos.com> + * @link http://www.randomchaos.com/document.php?source=php_and_unicode + * @see unicodeToUTF8() + */ + protected static function utf8ToUnicode( &$str ) + { + $unicode = array(); + $values = array(); + $lookingFor = 1; + + for ($i = 0; $i < strlen( $str ); $i++ ) + { + $thisValue = ord( $str[ $i ] ); + if ( $thisValue < 128 ) + $unicode[] = $thisValue; + else + { + if ( count( $values ) == 0 ) + $lookingFor = ( $thisValue < 224 ) ? 2 : 3; + $values[] = $thisValue; + if ( count( $values ) == $lookingFor ) + { + $number = ( $lookingFor == 3 ) ? + ( ( $values[0] % 16 ) * 4096 ) + ( ( $values[1] % 64 ) * 64 ) + ( $values[2] % 64 ): + ( ( $values[0] % 32 ) * 64 ) + ( $values[1] % 64 ); + $unicode[] = $number; + $values = array(); + $lookingFor = 1; + } + } + } + return $unicode; + } + + /** + * This function converts a Unicode array back to its UTF-8 representation + * @param string $str string to convert + * @return string + * @author Scott Michael Reynen <scott@randomchaos.com> + * @link http://www.randomchaos.com/document.php?source=php_and_unicode + * @see utf8ToUnicode() + */ + protected static function unicodeToUTF8( &$str ) + { + $utf8 = ''; + foreach( $str as $unicode ) + { + if ( $unicode < 128 ) + { + $utf8.= chr( $unicode ); + } + elseif ( $unicode < 2048 ) + { + $utf8.= chr( 192 + ( ( $unicode - ( $unicode % 64 ) ) / 64 ) ); + $utf8.= chr( 128 + ( $unicode % 64 ) ); + } + else + { + $utf8.= chr( 224 + ( ( $unicode - ( $unicode % 4096 ) ) / 4096 ) ); + $utf8.= chr( 128 + ( ( ( $unicode % 4096 ) - ( $unicode % 64 ) ) / 64 ) ); + $utf8.= chr( 128 + ( $unicode % 64 ) ); + } + } + return $utf8; + } + + /** + * UTF-8 to UTF-16BE conversion. + * + * Maybe really UCS-2 without mb_string due to utf8ToUnicode limits + * @param string $str string to convert + * @param boolean $bom whether to output BOM header + * @return string + */ + protected static function utf8ToUTF16BE(&$str, $bom = false) + { + $out = $bom ? "\xFE\xFF" : ''; + if(function_exists('mb_convert_encoding')) + return $out.mb_convert_encoding($str,'UTF-16BE','UTF-8'); + + $uni = self::utf8ToUnicode($str); + foreach($uni as $cp) + $out .= pack('n',$cp); + return $out; + } + + /** + * UTF-8 to UTF-16BE conversion. + * + * Maybe really UCS-2 without mb_string due to utf8ToUnicode limits + * @param string $str string to convert + * @return string + */ + protected static function utf16beToUTF8(&$str) + { + $uni = unpack('n*',$str); + return self::unicodeToUTF8($uni); + } +} diff --git a/framework/web/helpers/CJavaScript.php b/framework/web/helpers/CJavaScript.php new file mode 100644 index 0000000..7454f17 --- /dev/null +++ b/framework/web/helpers/CJavaScript.php @@ -0,0 +1,120 @@ +<?php +/** + * CJavaScript helper class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CJavaScript is a helper class containing JavaScript-related handling functions. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CJavaScript.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @package system.web.helpers + * @since 1.0 + */ +class CJavaScript +{ + /** + * Quotes a javascript string. + * After processing, the string can be safely enclosed within a pair of + * quotation marks and serve as a javascript string. + * @param string $js string to be quoted + * @param boolean $forUrl whether this string is used as a URL + * @return string the quoted string + */ + public static function quote($js,$forUrl=false) + { + if($forUrl) + return strtr($js,array('%'=>'%25',"\t"=>'\t',"\n"=>'\n',"\r"=>'\r','"'=>'\"','\''=>'\\\'','\\'=>'\\\\','</'=>'<\/')); + else + return strtr($js,array("\t"=>'\t',"\n"=>'\n',"\r"=>'\r','"'=>'\"','\''=>'\\\'','\\'=>'\\\\','</'=>'<\/')); + } + + /** + * Encodes a PHP variable into javascript representation. + * + * Example: + * <pre> + * $options=array('key1'=>true,'key2'=>123,'key3'=>'value'); + * echo CJavaScript::encode($options); + * // The following javascript code would be generated: + * // {'key1':true,'key2':123,'key3':'value'} + * </pre> + * + * For highly complex data structures use {@link jsonEncode} and {@link jsonDecode} + * to serialize and unserialize. + * + * @param mixed $value PHP variable to be encoded + * @return string the encoded string + */ + public static function encode($value) + { + if(is_string($value)) + { + if(strpos($value,'js:')===0) + return substr($value,3); + else + return "'".self::quote($value)."'"; + } + else if($value===null) + return 'null'; + else if(is_bool($value)) + return $value?'true':'false'; + else if(is_integer($value)) + return "$value"; + else if(is_float($value)) + { + if($value===-INF) + return 'Number.NEGATIVE_INFINITY'; + else if($value===INF) + return 'Number.POSITIVE_INFINITY'; + else + return rtrim(sprintf('%.16F',$value),'0'); // locale-independent representation + } + else if(is_object($value)) + return self::encode(get_object_vars($value)); + else if(is_array($value)) + { + $es=array(); + if(($n=count($value))>0 && array_keys($value)!==range(0,$n-1)) + { + foreach($value as $k=>$v) + $es[]="'".self::quote($k)."':".self::encode($v); + return '{'.implode(',',$es).'}'; + } + else + { + foreach($value as $v) + $es[]=self::encode($v); + return '['.implode(',',$es).']'; + } + } + else + return ''; + } + + /** + * Returns the JSON representation of the PHP data. + * @param mixed $data the data to be encoded + * @return string the JSON representation of the PHP data. + */ + public static function jsonEncode($data) + { + return CJSON::encode($data); + } + + /** + * Decodes a JSON string. + * @param string $data the data to be decoded + * @param boolean $useArray whether to use associative array to represent object data + * @return mixed the decoded PHP data + */ + public static function jsonDecode($data,$useArray=true) + { + return CJSON::decode($data,$useArray); + } +} |
