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";
}
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));