diff --git a/CHANGELOG b/CHANGELOG index b441dba0c..27639668f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,12 +8,17 @@ Version 1.1a to be released Version 1.0.1 to be released ---------------------------- -- Fixed issue #38: CHhtml links and buttons don't work when they are updated via ajax (Qiang) +- Fixed issue #38: CHtml links and buttons don't work when they are updated via ajax (Qiang) - Fixed issue #41: missing function CHttpRequest:: getCsrfTokenFromCookie() (Qiang) - Fixed issue #42: Wrong links in crud generated admin view (Qiang) - Fixed issue #45: Many-to-many relation does not work when both foreign tables are the same (Qiang) +- Fixed issue #47: Wrong url parsing when CUrlManager is set in path format (Qiang) - Fixed issue #48: Typo in CActiveRecord::setAttribute (Qiang) - Fixed issue #49: Invalid markup generated by yiic tool (Qiang) +- Fixed issue #53: tabular form input causes AR to fail (Qiang) +- Fixed issue #54: typoe in CHtml::listOptions (Qiang) +- Fixed issue #56: Allow specifying customized 'on' when a validator can be applied (Qiang) +- Fixed inaccurate error message when adding an item as a child of itself in CAuthManager (Qiang) - Added CHtml::activeId and CHtml::activeName (Qiang) - Added German and Spanish core message translations (mikl, sebas) diff --git a/UPGRADE b/UPGRADE index 3cde80519..e51436f4b 100644 --- a/UPGRADE +++ b/UPGRADE @@ -11,28 +11,6 @@ for both A and B. Upgrading from v1.0.0 --------------------- - -Upgrading from v1.0rc ---------------------- -- CLinkPager is changed significantly in both the appearance and the generated HTML code. -- CController::clientScript is removed. Please use Yii::app()->clientScript instead. -- CClientScript is changed. The methods registerBodyScript() and - registerBodyScriptFile() are removed. Please use registerScript() and - registerScriptFile() with appropriate position parameter. - - -Upgrading from v1.0b --------------------- - - -Upgrading from v1.0a --------------------- -- The getIsNewRecord and setIsNewRecord methods are removed from CActiveRecord. - Please use CActiveRecord.isNewRecord property directly. -- CWebUser.login is now taking an identity object as parameter. - CWebUser.switchTo is removed. You should now implement identity class - instead of overriding CWebUser. CWebUser.roles property is removed - and the roles property is removed from access rules of CAccessControlFilter. - Consider using "authManager" application component to do RBAC. -- Yii::t() is changed. A category parameter is added as the first parameter. - And the message to be translated should not contain category information anymore. \ No newline at end of file +- An $on parameter is added to both CModel::beforeValidate() and afterValidate(). + If you override these methods in your child classes (form models, AR classes), + make sure you change the method signature accordingly. diff --git a/docs/guide/database.ar.txt b/docs/guide/database.ar.txt index fde7d31b0..65ba0343d 100644 --- a/docs/guide/database.ar.txt +++ b/docs/guide/database.ar.txt @@ -382,11 +382,10 @@ anything coming from the client side. AR performs data validation automatically when [save()|CActiveRecord::save] is being invoked. The validation is based on -the rules specified by the [rules()|CModel::rules] which should be -overridden in AR classes. For more details about how to specifying -validation rules, refer to in the "Working with Form" -section. Below we show an example of typical workflow with validation: +the rules specified by in the [rules()|CModel::rules] method of the AR class. +For more details about how to specify validation rules, refer to +the [Data Validation](/doc/guide/form.model#declaring-validation-rules) +section. Below is the typical workflow needed by saving a record: ~~~ [php] @@ -413,8 +412,8 @@ $post->save(); If there are many columns, we would see a long list of such assignments. This can be alleviated by making use of the -[attributes|CActiveRecord::attributes] property as shown below. Again, more -details can be found in the "Working with Form" section. +[attributes|CActiveRecord::attributes] property as shown below. More +details can be found in the [Creating Action](/doc/guide/form.action) section. ~~~ [php] @@ -461,8 +460,8 @@ Customization [CActiveRecord] provides a few placeholder methods that can be overridden in child classes to customize its workflow. - - [beforeValidate|CActiveRecord::beforeValidate] and -[afterValidate|CActiveRecord::afterValidate]: these are invoked before and + - [beforeValidate|CModel::beforeValidate] and +[afterValidate|CModel::afterValidate]: these are invoked before and after validation is performed. - [beforeSave|CActiveRecord::beforeSave] and diff --git a/docs/guide/form.model.txt b/docs/guide/form.model.txt index e5788a5c3..ec719cc11 100644 --- a/docs/guide/form.model.txt +++ b/docs/guide/form.model.txt @@ -95,13 +95,15 @@ Each rule returned by `rules()` must be of the following format: ~~~ [php] -array('AttributeList', 'Validator', ...additional options) +array('AttributeList', 'Validator', 'on'=>'ScenarioList', ...additional options) ~~~ where `AttributeList` is a string of comma-separated attribute names which -need to be validated with the rule; `Validator` specifies what kind of -validation should be performed; and additional options are used to -configure the validation. +need to be validated according to the rule; `Validator` specifies what kind of +validation should be performed; the `on` parameter is optional which specifies +a list of scenarios that the rule should be applied; and additional options +are name-value pairs which are used to initialize the corresponding validator's +property values. There are three ways to specify `Validator` in a validation rule. First, `Validator` can be the name of a method in the model class, like @@ -193,6 +195,39 @@ returns a value indicating whether the validation is successful without any error. For active record model, when we call [save()|CActiveRecord::save] to save the model to database, validation will be automatically performed. +By default, when we call [validate()|CModel::validate], all attributes +specified in [rules()|CModel::rules] will be validated. Sometimes we may +only want to validate part of those attributes, while sometimes we want to +apply different validation rules in different scenarios. We can achieve +these goals by passing two parameters to [validate()|CModel::validate]. +For example, if we only want to validate three attributes under +the `register` scenario, we can use the following code: + +~~~ +[php] +$model->validate(array('username','password','password_repeat'), 'register'); +~~~ + +In order to allow scenario-based validation as described above, we should +specify the `on` option in the relevant validation rules. For the above +example, we would need the following rules: + +~~~ +[php] +public function rules() +{ + return array( + array('username, password', 'required'), + array('password_repeat', 'required', 'on'=>'register'), + array('password', 'compare', 'on'=>'register'), + ); +} +~~~ + +As a result, the first rule will be applied in all scenarios, while the +next two rules will only be applied in the `register` scenario. + + Retrieving Validation Errors ---------------------------- diff --git a/docs/guide/quickstart.installation.txt b/docs/guide/quickstart.installation.txt index 7e56c164e..af62dbf34 100644 --- a/docs/guide/quickstart.installation.txt +++ b/docs/guide/quickstart.installation.txt @@ -20,7 +20,7 @@ all the requirements of using Yii. You can do so by accessing the requirement checker script at the following URL in a Web browser: ~~~ -http://hostname/path/to/yii/framework/requirements/index.php +http://hostname/path/to/yii/requirements/index.php ~~~ The minimum requirement by Yii is that your Web server supports PHP 5.1.0 diff --git a/framework/base/CModel.php b/framework/base/CModel.php index 177f5a34c..e233b20c4 100644 --- a/framework/base/CModel.php +++ b/framework/base/CModel.php @@ -29,16 +29,26 @@ abstract class CModel extends CComponent * Errors found during the validation can be retrieved via {@link getErrors}. * @param array the list of attributes to be validated. Defaults to null, * meaning every attribute as listed in {@link rules} will be validated. + * @param string the set of the validation rules that should be applied. + * This is used to match the {@link CValidator::on on} property set in + * the validation rules. Defaults to null, meaning all validation rules + * should be applied. If this parameter is a non-empty string (e.g. 'register'), + * then only those validation rules whose {@link CValidator::on on} property + * is not set or contains this string (e.g. 'register') will be applied. + * NOTE: this parameter has been available since version 1.0.1. * @return boolean whether the validation is successful without any error. */ - public function validate($attributes=null) + public function validate($attributes=null,$on=null) { $this->clearErrors(); - if($this->beforeValidate()) + if($this->beforeValidate($on)) { - foreach($this->createValidators() as $validator) - $validator->validate($this,$attributes); - $this->afterValidate(); + foreach($this->getValidators() as $validator) + { + if(empty($on) || empty($validator->on) || is_array($validator->on) && in_array($on,$validator->on)) + $validator->validate($this,$attributes); + } + $this->afterValidate($on); return !$this->hasErrors(); } else @@ -48,9 +58,12 @@ abstract class CModel extends CComponent /** * This method is invoked before validation starts. * You may override this method to do preliminary checks before validation. + * @param string the set of the validation rules that should be applied. See {@link validate} + * for more details about this parameter. + * NOTE: this parameter has been available since version 1.0.1. * @return boolean whether validation should be executed. Defaults to true. */ - protected function beforeValidate() + protected function beforeValidate($on) { return true; } @@ -58,8 +71,11 @@ abstract class CModel extends CComponent /** * This method is invoked after validation ends. * You may override this method to do postprocessing after validation. + * @param string the set of the validation rules that should be applied. See {@link validate} + * for more details about this parameter. + * NOTE: this parameter has been available since version 1.0.1. */ - protected function afterValidate() + protected function afterValidate($on) { } @@ -80,6 +96,15 @@ abstract class CModel extends CComponent return $validators; } + /** + * @return array a list of validators created according to {@link rules}. + * @since 1.0.1 + */ + protected function getValidators() + { + return $this->createValidators(); + } + /** * Returns the attribute labels. * Attribute labels are mainly used in error messages of validation. @@ -117,8 +142,8 @@ abstract class CModel extends CComponent * When using a built-in validator class, you can use an alias name instead of the full class name. * For example, you can use "required" instead of "system.validators.CRequiredValidator". * For more details, see {@link CValidator}. - *
  • on: this is used in {@link CActiveRecord}. It specifies when the validation should be performed. - * It can be either 'insert' or 'update'. If not set, the validation will apply to both 'insert' and 'update' operations.
  • + *
  • on: this specifies when the validation rule should be performed. Please see {@link validate} + * for more details about this option.
  • *
  • additional parameters are used to initialize the corresponding validator properties. See {@link CValidator} * for possible properties.
  • * diff --git a/framework/db/ar/CActiveRecord.php b/framework/db/ar/CActiveRecord.php index dd9f36b49..1653239b5 100644 --- a/framework/db/ar/CActiveRecord.php +++ b/framework/db/ar/CActiveRecord.php @@ -262,7 +262,7 @@ * {@link CHasManyRelation}. * * CActiveRecord has built-in validation functionality that validates the user input data - * before they are saved to database. To use the validation, override {@link rules()} as follows, + * before they are saved to database. To use the validation, override {@link CModel::rules()} as follows, *
      * class Post extends CActiveRecord
      * {
    @@ -291,8 +291,10 @@
      *   When using a built-in validator class, you can use an alias name instead of the full class name.
      *   For example, you can use "required" instead of "system.validators.CRequiredValidator".
      *   For more details, see {@link CValidator}.
    - * 
  • on: specifies when the validation should be performed. It can be either 'insert' or 'update'. If - * not set, the validation will apply to both 'insert' and 'update' operations.
  • + *
  • on: this specifies when the validation rule should be performed. Please see {@link CModel::validate} + * for more details about this option. NOTE: if the validation is triggered by the {@link save} call, + * it will use 'insert' to indicate an insertion operation, and 'update' an updating operation. + * You may thus specify a rule with the 'on' option so that it is applied only to insertion or updating.
  • *
  • additional parameters are used to initialize the corresponding validator properties. See {@link CValidator} * for possible properties.
  • * @@ -773,46 +775,31 @@ abstract class CActiveRecord extends CModel * after insertion the primary key will be populated with the value * generated automatically by the database. * @param boolean whether to perform validation before saving the record. + * If the validation fails, the record will not be saved to database. + * The validation will be performed under either 'insert' or 'update' scenario, + * depending on whether {@link isNewRecord} is true or false. * @param array list of attributes that need to be saved. Defaults to null, * meaning all attributes that are loaded from DB will be saved. * @return boolean whether the saving succeeds */ public function save($runValidation=true,$attributes=null) { - if(!$runValidation || $this->validate($attributes)) - { - if($this->isNewRecord) - return $this->insert($attributes); - else - return $this->update($attributes); - } + if(!$runValidation || $this->validate($attributes,$this->isNewRecord?'insert':'update')) + return $this->isNewRecord ? $this->insert($attributes) : $this->update($attributes); else return false; } /** - * Performs the validation. - * This method executes every validation rule as declared in {@link rules}. - * Errors found during the validation can be retrieved via {@link getErrors}. - * @param array the list of attributes to be validated. Defaults to null, - * meaning every attribute as listed in {@link rules} will be validated. - * @return boolean whether the validation is successful without any error. + * Returns a list of validators created according to {@link CModel::rules}. + * This overrides the parent implementation so that the validators are only + * created once for each type of AR. + * @return array a list of validators created according to {@link CModel::rules}. + * @since 1.0.1 */ - public function validate($attributes=null) + protected function getValidators() { - $this->clearErrors(); - if($this->beforeValidate()) - { - foreach($this->getMetaData()->getValidators() as $validator) - { - if($validator->on===null || ($validator->on==='insert')===$this->isNewRecord) - $validator->validate($this,$attributes); - } - $this->afterValidate(); - return !$this->hasErrors(); - } - else - return false; + return $this->getMetaData()->getValidators(); } /** @@ -1375,23 +1362,6 @@ abstract class CActiveRecord extends CModel } return $records; } - - /** - * @return array validators built based on {@link rules()}. - */ - public function createValidators() - { - $validators=array(); - foreach($this->rules() as $rule) - { - if(isset($rule[0],$rule[1])) // attributes, validator name - $validators[]=CValidator::createValidator($rule[1],$this,$rule[0],array_slice($rule,2)); - else - throw new CDbException(Yii::t('yii','{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.', - array('{class}'=>get_class($this)))); - } - return $validators; - } } diff --git a/framework/validators/CValidator.php b/framework/validators/CValidator.php index 4c59db7f9..d2274e897 100644 --- a/framework/validators/CValidator.php +++ b/framework/validators/CValidator.php @@ -22,8 +22,7 @@ * of the problematic attribute. Different validators may define additional * placeholders. *
  • {@link on}: string, in which scenario should the validator be in effect. - * This can be either "insert" or "update". If not set, the validator will - * apply in both scenarios.
  • + * This is used to match the 'on' parameter supplied when calling {@link CModel::validate}. * * * When using {@link createValidator} to create a validator, the following aliases @@ -62,12 +61,8 @@ abstract class CValidator extends CComponent * recognize "{attribute}" placeholder, which will be replaced with the label of the attribute. */ public $message; - /** - * @var string when this validator should be applied. It is either "insert" or "update". - * If not set, the validator will be applied in both scenarios. - * This is only used for validating CActiveRecord. - */ - public $on; + + private $_on; /** * Validates a single attribute. @@ -149,6 +144,28 @@ abstract class CValidator extends CComponent $this->validateAttribute($object,$attribute); } + /** + * @return array the set of tags (e.g. insert, register) that indicate when this validator should be applied. + * If this is empty, it means the validator should be applied in all situations. + * @since 1.0.1 + */ + public function getOn() + { + return $this->_on; + } + + /** + * @param mixed the set of tags (e.g. insert, register) that indicate when this validator should be applied. + * This can be either an array of the tag names or a string of comma-separated tag names. + * @since 1.0.1 + */ + public function setOn($value) + { + if(is_string($value)) + $value=preg_split('/[\s,]+/',$value,-1,PREG_SPLIT_NO_EMPTY); + $this->_on=$value; + } + /** * Adds an error about the specified attribute to the active record. * This is a helper method that performs message selection and internationalization. diff --git a/framework/web/CUrlManager.php b/framework/web/CUrlManager.php index 2d7a34d92..9248e594d 100644 --- a/framework/web/CUrlManager.php +++ b/framework/web/CUrlManager.php @@ -175,7 +175,10 @@ class CUrlManager extends CApplicationComponent { $url=rtrim($this->getBaseUrl().'/'.$route,'/'); foreach($params as $key=>$value) - $url.='/'.urlencode($key).'/'.urlencode($value); + { + if("$value"!=='') + $url.='/'.urlencode($key).'/'.urlencode($value); + } return $url.$this->urlSuffix; } else @@ -250,7 +253,7 @@ class CUrlManager extends CApplicationComponent $segs=explode('/',$pathInfo.'/'); $n=count($segs); for($i=2;$i<$n-1;$i+=2) - $_GET[$segs[$i]]=$segs[$i+1]; + $_GET[urldecode($segs[$i])]=urldecode($segs[$i+1]); return $segs[0].'/'.$segs[1]; } @@ -388,7 +391,7 @@ class CUrlRule extends CComponent { if(isset($this->params[$key])) $tr["<$key>"]=$value; - else + else if("$value"!=='') $rest[]=urlencode($key).$sep.urlencode($value); } $url=strtr($this->template,$tr); @@ -427,14 +430,14 @@ class CUrlRule extends CComponent foreach($matches as $key=>$value) { if(is_string($key)) - $_GET[$key]=$value; + $_GET[$key]=urldecode($value); } if($pathInfo!==$matches[0]) { $segs=explode('/',ltrim(substr($pathInfo,strlen($matches[0])),'/')); $n=count($segs); for($i=0;$i<$n-1;$i+=2) - $_GET[$segs[$i]]=$segs[$i+1]; + $_GET[urldecode($segs[$i])]=urldecode($segs[$i+1]); } return $this->route; } diff --git a/framework/web/auth/CDbAuthManager.php b/framework/web/auth/CDbAuthManager.php index 22531757c..58d670333 100644 --- a/framework/web/auth/CDbAuthManager.php +++ b/framework/web/auth/CDbAuthManager.php @@ -103,6 +103,9 @@ class CDbAuthManager extends CAuthManager */ public function addItemChild($itemName,$childName) { + if($itemName===$childName) + throw new CException(Yii::t('yii','Cannot add "{name}" as a child of itself.', + array('{name}'=>$itemName))); $sql="SELECT * FROM {$this->itemTable} WHERE name=:name1 OR name=:name2"; $command=$this->db->createCommand($sql); $command->bindValue(':name1',$itemName); @@ -133,7 +136,7 @@ class CDbAuthManager extends CAuthManager $command->execute(); } else - throw new CException(Yii::t('yii','Either "{parent}" or "{child}" does not exist.',array('{child}'=>$childName,'{name}'=>$itemName))); + throw new CException(Yii::t('yii','Either "{parent}" or "{child}" does not exist.',array('{child}'=>$childName,'{parent}'=>$itemName))); } /** diff --git a/framework/web/helpers/CHtml.php b/framework/web/helpers/CHtml.php index 7f0372826..396d20607 100644 --- a/framework/web/helpers/CHtml.php +++ b/framework/web/helpers/CHtml.php @@ -846,11 +846,11 @@ class CHtml */ public static function activeRadioButton($model,$attribute,$htmlOptions=array()) { + self::resolveNameID($model,$attribute,$htmlOptions); if(!isset($htmlOptions['value'])) $htmlOptions['value']=1; if($model->$attribute) $htmlOptions['checked']='checked'; - self::resolveNameID($model,$attribute,$htmlOptions); self::clientChange('click',$htmlOptions); return self::hiddenField($htmlOptions['name'],$htmlOptions['value']?0:-1,array('id'=>self::ID_PREFIX.$htmlOptions['id'])) . self::activeInputField('radio',$model,$attribute,$htmlOptions); @@ -871,11 +871,11 @@ class CHtml */ public static function activeCheckBox($model,$attribute,$htmlOptions=array()) { + self::resolveNameID($model,$attribute,$htmlOptions); if(!isset($htmlOptions['value'])) $htmlOptions['value']=1; if($model->$attribute) $htmlOptions['checked']='checked'; - self::resolveNameID($model,$attribute,$htmlOptions); self::clientChange('click',$htmlOptions); return self::hiddenField($htmlOptions['name'],'',array('id'=>self::ID_PREFIX.$htmlOptions['id'])) @@ -897,9 +897,9 @@ class CHtml */ public static function activeDropDownList($model,$attribute,$data,$htmlOptions=array()) { + self::resolveNameID($model,$attribute,$htmlOptions); $selection=$model->$attribute; $options="\n".self::listOptions($selection,$data,$htmlOptions); - self::resolveNameID($model,$attribute,$htmlOptions); self::clientChange('change',$htmlOptions); if($model->hasErrors($attribute)) self::addErrorCss($htmlOptions); @@ -950,8 +950,8 @@ class CHtml */ public static function activeCheckBoxList($model,$attribute,$data,$htmlOptions=array()) { - $selection=$model->$attribute; self::resolveNameID($model,$attribute,$htmlOptions); + $selection=$model->$attribute; if($model->hasErrors($attribute)) self::addErrorCss($htmlOptions); $name=$htmlOptions['name']; @@ -982,8 +982,8 @@ class CHtml */ public static function activeRadioButtonList($model,$attribute,$data,$htmlOptions=array()) { - $selection=$model->$attribute; self::resolveNameID($model,$attribute,$htmlOptions); + $selection=$model->$attribute; if($model->hasErrors($attribute)) self::addErrorCss($htmlOptions); $name=$htmlOptions['name']; @@ -1165,7 +1165,7 @@ class CHtml { $content.='\n"; $dummy=array(); - $content.=self::listOptions($value,$selection,$dummy); + $content.=self::listOptions($selection,$value,$dummy); $content.=''."\n"; } else if(!is_array($selection) && !strcmp($key,$selection) || is_array($selection) && in_array($key,$selection)) diff --git a/framework/web/widgets/CTextHighlighter.php b/framework/web/widgets/CTextHighlighter.php index cf0b58e40..32b9f2f39 100644 --- a/framework/web/widgets/CTextHighlighter.php +++ b/framework/web/widgets/CTextHighlighter.php @@ -102,7 +102,7 @@ class CTextHighlighter extends COutputProcessor $highlighter=empty($this->language)?false:Text_Highlighter::factory($this->language); if($highlighter===false) - $o='
    '.htmlentities($content).'
    '; + $o='
    '.CHtml::encode($content).'
    '; else { $highlighter->setRenderer(new Text_Highlighter_Renderer_Html($options));