1 <?php
2 /**
3 * <p>
4 * This validator should be used to validate the 'status' attribute for an active record
5 * object before it is saved. It tests if the transition that is about to occur is valid.<br/>
6 * Moreover, if <strong>$enableSwValidation</strong> is set to <b>true</b>, this validator applies all
7 * validators that may have been defined by the model for the scenario associated to the transition
8 * being done.<br/>
9 * Scenario names associated with a transition, have the following format :
10 * <pre>
11 * sw:[currentStatus]-[nextStatus]
12 * </pre>
13 * For instance, if the model being validated is currently in status 'A' and it is sent in status 'B', the
14 * corresponding scenario name is 'sw:A-B'. Note that if the destination status doesn't belong to the same
15 * workflow as the current status, [nextStatus] must be in the form 'workflowId/statusId' (e.g 'sw:A-workflow/B').
16 * Eventually, when the model enters in a workflow, the scenario name is '-[nextStatus]' where 'nextStatus'
17 * includes the workflow Id (e.g 'sw:-workflowIs/statusId').
18 * </p>
19 * <p>
20 * If this validator is initialized with parameter <b>match</b> set to TRUE, then transitions scenario defined
21 * for validators are assumed to be regular expressions. If the current transition matches, then the associated
22 * validator is executed.<br/>
23 * For instance, if validator 'required' for attribute A applies to scenarion 'sw:/S1_.?/' then each time the
24 * model leaves status S1, then the <em>required</em> validator will be applied.
25 * </p>
26 */
27 class SWValidator extends CValidator
28 {
29 /**
30 * @var boolean (default FALSE) Enables simpleWorkflow Validation. When TRUE, the SWValidator not only
31 * validates status change for the model, but also applies all validators that may have been created and
32 * which are associated with the scenario for the transition being done. Such scenario names are based on
33 * both the current and the next status name.
34 */
35 public $enableSwValidation=false;
36 /**
37 * @var boolean (default FALSE) When true, the scenario name is evaluated as a regular expression that must
38 * match the transition name being done.
39 */
40 public $match=false;
41
42 const SW_SCENARIO_STATUS_SEPARATOR='-';
43 const SW_SCENARIO_PREFIX='sw:';
44 private $_lenPrefix=null;
45 /**
46 * Validate status change and applies all validators defined by the model for the current transition scenario if
47 * enableSwValidation is TRUE. If validator parameter 'match' is true, the transition scenario is matched
48 * against validator scenario (which are assumed to be regular expressions).
49 *
50 * @see validators/CValidator::validateAttribute()
51 * @param CModel $model the model to validate
52 * @param string $attribute the model attribute to validate
53 */
54 protected function validateAttribute($model,$attribute)
55 {
56 $value=$model->$attribute;
57
58 if($model->swValidate($attribute,$value)==true and $this->enableSwValidation ===true){
59
60 $swScenario=$this->_getSWScenarioName($model, $value);
61
62 if(!empty($swScenario))
63 {
64 if($this->match === true){
65
66 // validator scenario are Regular Expression that must match the transition scenarion
67 // for the validator to be executed.
68
69 $validators=$model->getValidatorList();
70 foreach($validators as $validator)
71 {
72 if($this->_validatorMatches($validator,$swScenario)){
73 $validator->validate($model);
74 }
75 }
76 }else {
77 $swScenario=SWValidator::SW_SCENARIO_PREFIX.$swScenario;
78 // execute only validator defined for the current transition scenario ($swScenario)
79
80 // getValidators returns validators with no scenario, and the ones
81 // that apply to the current scenario (swScenario).
82
83 $saveScenario=$model->getScenario();
84 $model->setScenario($swScenario);
85
86 $validators=$model->getValidators();
87
88 foreach($model->getValidators() as $validator)
89 {
90 // only run validators that applies to the current (swScenario) scenario
91
92 if(isset($validator->on[$swScenario])){
93 $validator->validate($model);
94 }
95 }
96 // restore original scenario so validation can continue.
97 $model->setScenario($saveScenario);
98 }
99 }
100 }
101 }
102 /**
103 * Create the scenario name for the current transition. Scenario name has following format : <br/>
104 * <pre> [currentStatus]-[nextStatus]</pre>
105 *
106 * @param CModel $model the model being validated
107 * @param string $nxtStatus the next status name (destination status for the model)
108 * @return string SW scenario name for this transition
109 *
110 */
111 private function _getSWScenarioName($model,$nxtStatus)
112 {
113 $swScenario=null;
114 $nextNode=$model->swCreateNode($nxtStatus);
115 $curNode=$model->swGetStatus();
116 if( $curNode != null )
117 {
118 $swScenario=$curNode->getId().SWValidator::SW_SCENARIO_STATUS_SEPARATOR;
119 if($curNode->getWorkflowId()!=$nextNode->getWorkflowId()){
120 $swScenario.=$nextNode->toString();
121 }else {
122 $swScenario.=$nextNode->getId();
123 }
124 }else {
125 $swScenario=SWValidator::SW_SCENARIO_STATUS_SEPARATOR.$nextNode->toString();
126 }
127 return $swScenario;
128 }
129 /**
130 * Check that a CValidator based object is defined for a scenario that matches
131 * the simple workflow scenario passed as argument.
132 *
133 * @param $validator CValidator validator to test
134 * @param $swScenario string simple workflow scenario defined as a regular expression
135 */
136 private function _validatorMatches($validator,$swScenario)
137 {
138 $bResult=false;
139 if(isset($validator->on)){
140 $validatorScenarios=(is_array($validator->on)?$validator->on:array($validator->on));
141 foreach ($validatorScenarios as $valScenario)
142 {
143 // SW Scenario validator must begin with a non-empty prefix (default 'sw:')
144 // and then define a valide regular expression
145
146 $re=$this->_extractSwScenarioPattern($valScenario);
147
148 if( $re != null )
149 {
150 if(preg_match($re, $swScenario)){
151 $bResult=true;
152 break;
153 }
154 }
155 }
156 }
157 return $bResult;
158 }
159 /**
160 * Extract a regular expression pattern out of a simepleWorkflow scenario name
161 *
162 * @param $valScenario String validator scenario name (example : 'sw:/^status1-.*$/')
163 * @return String regular expression (example : '/^status1-.*$/')
164 */
165 private function _extractSwScenarioPattern($valScenario)
166 {
167 $pattern=null;
168
169 if($this->_lenPrefix==null){
170 $this->_lenPrefix=strlen(SWValidator::SW_SCENARIO_PREFIX);
171 }
172
173 if( $this->_lenPrefix != 0 &&
174 strpos($valScenario, SWValidator::SW_SCENARIO_PREFIX) === 0)
175 {
176 $pattern=substr($valScenario, $this->_lenPrefix);
177 }
178 return $pattern;
179 }
180 }
181 ?>
182