initial commit
[namibia] / module / Workspace / src / Workspace / Contract / AbstractBase.php
1 <?php
2 namespace Workspace\Contract;
3
4
5
6 /**
7  * Abstract contract.
8  * @author andre.fourie
9  */
10 abstract class AbstractBase
11 {
12         /**
13          * @var string
14          */
15         protected $lifeTime;
16         /**
17          * @var array
18          */
19         protected $inputs = array();
20         /**
21          * @var array
22          */
23         protected $options;
24         /**
25          * @var array
26          */
27         protected $requirement;
28         /**
29          * @var array
30          */
31         protected $conditionalCases = array();
32         /**
33          * @var array
34          */
35         protected $caseConditions = array();
36         /**
37          * @var array
38          */
39         protected $optionData = array();
40         /**
41          * @var array
42          */
43         protected $contractData = array();
44         /**
45          * @var array
46          */
47         protected $validation = array();
48         /**
49          * @var array
50          */
51         protected $data = array();
52
53
54         /**
55          * Initialize contract with base use case.
56          * @param \Workspace\UseCase\Requirement $requirement
57          * @param array $packagedContract
58          * @param array $contractData
59          * @return \Workspace\Contract\UseOnce
60          */
61         public function __construct(
62                         \Workspace\UseCase\Options $options = null,
63                         \Workspace\UseCase\Requirement $requirement = null,
64                         array $packagedContract = array(),
65                         array $contractData = array(),
66                         array $optionData = array()
67                         )
68         {
69                 if (!is_null($options) && !is_null($requirement))
70                 {
71                         $this->options = $options->package();
72                         $this->requirement = $requirement->getRequirements();
73                         $this->inputs = $requirement->getInputs();
74                 }
75                 else if (!empty($packagedContract))
76                 {
77                         $this->hydrate($packagedContract);
78                 }
79                 if (!empty($contractData))
80                 {
81                         $this->setContractData($contractData);
82                 }
83                 if (!empty($optionData))
84                 {
85                         $options = new \Workspace\UseCase\Options($this->options, $optionData);
86                         $this->optionData = $options->getOptions();
87                 }
88         }
89
90         /**
91          * Set base requirement.
92          * @param \Workspace\UseCase\Requirement $requirement
93          * @return \Workspace\Contract\AbstractBase
94          */
95         public function setRequirement(\Workspace\UseCase\Requirement $requirement)
96         {
97                 $this->requirement = $requirement->getRequirements();
98                 $this->inputs      = $requirement->getInputs();
99                 return $this;
100         }
101
102         /**
103          * Add a conditional use case.
104          * @param string $name
105          * @param \Workspace\UseCase\ConditionBase $condition
106          * @param \Workspace\UseCase\Requirement $requirement
107          * @return \Workspace\Contract\UseOnce
108          */
109         public function addConditionalCase($name, \Workspace\UseCase\AbstractBase $condition, \Workspace\UseCase\Requirement $requirement)
110         {
111                 $this->conditionalCases[$name] = array(
112                                 'Condition'   => $condition->getPrerequisites(),
113                                 'Requirement' => $requirement->getRequirements()
114                 );
115                 $stack = $requirement->getInputs();
116                 foreach ($stack as $group => $fieldSet)
117                 {
118                         isset($this->inputs[$group])
119                                 || $this->inputs[$group] = array();
120                         $this->inputs[$group] = array_merge($this->inputs[$group], $fieldSet);
121                 }
122                 return $this;
123         }
124
125         /**
126          * Set contract dataset.
127          * @param array $data
128          * @return \Workspace\Contract\AbstractBase
129          */
130         public function setData(array $data)
131         {
132                 $this->data = $data;
133                 return $this;
134         }
135
136         /**
137          * Package this contract into an array.
138          * @return array
139          */
140         public function package()
141         {
142                 return array(
143                                 'LifeTime'    => $this->lifeTime,
144                                 'Options'     => $this->options,
145                                 'Requirement' => $this->requirement,
146                                 'Conditional' => $this->conditionalCases,
147                                 'Inputs'      => $this->inputs,
148                                 'Data'        => $this->data
149                 );
150         }
151
152         /**
153          * Hydrate contract from package.
154          * @param array $packagedContract
155          * @return \Workspace\Contract\UseOnce
156          */
157         public function hydrate(array $packagedContract)
158         {
159                 $this->lifeTime          = $packagedContract['LifeTime'];
160                 $this->options           = $packagedContract['Options'];
161                 $this->requirement       = $packagedContract['Requirement'];
162                 $this->conditionalCases  = $packagedContract['Conditional'];
163                 $this->inputs            = $packagedContract['Inputs'];
164                 $this->data              = $packagedContract['Data'];
165                 return $this;
166         }
167
168         /**
169          * Set data inputs for contract.
170          * @param array $input
171          * @return \Workspace\Contract\AbstractBase
172          */
173         public function setContractData(array $input)
174         {
175                 $this->contractData = array();
176                 isset($input['id'])
177                         && $this->contractData['id'] = $input['id'];
178                 foreach ($this->inputs as $group => $fieldSet)
179                 {
180                         $this->contractData[$group] = array();
181                         foreach ($fieldSet as $field => $spec)
182                         {
183                                 isset($input[$group][$field])
184                                         && $this->contractData[$group][$field] = $input[$group][$field];
185                         }
186                 }
187                 return $this;
188         }
189
190         /**
191          * Retrieve filtered contract data.
192          * @return array
193          */
194         public function getSignedContract($requestId, $hash)
195         {
196                 $options = new \Workspace\Utility\ServiceInput('ParamSet', $this->optionData);
197                 $data = new \Workspace\Utility\ServiceInput('ParamSet', $this->contractData);
198                 $conditions = new \Workspace\Utility\ServiceInput('ParamSet', array(), $this->caseConditions);
199                 $contract = new \Workspace\Utility\ServiceInput('Contract', array(
200                                 'requestId'  => $requestId,
201                                 'hash'       => $hash,
202                                 'options'    => $options->pack(),
203                                 'data'       => $data->pack(),
204                                 'conditions' => $conditions->pack()
205                 ));
206                 return $contract->pack();
207         }
208
209         /**
210          * Cleanup array values.
211          * @param array $input
212          * @return array
213          */
214         protected function cleanupStack(array $input)
215         {
216                 foreach ($input as $field => $value)
217                 {
218                         if (is_array($value))
219                         {
220                                 $input[$field] = $this->cleanupStack($value);
221                                 conitnue;
222                         }
223                         $input[$field] = strip_tags($value);
224                 }
225                 return $input;
226         }
227
228         /**
229          * Cleanup inputs.
230          */
231         protected function cleanupInputs()
232         {
233                 $this->inputs = $this->cleanupStack($this->inputs);
234         }
235
236         /**
237          * Evaluate the contract. IMPORTANT: setContractData() must be called before this method.
238          * @param \Workspace\Workflow $workflow
239          * @return boolean
240          */
241         public function isValid(\Workspace\Workflow $workflow)
242         {
243                 $valid = true;
244                 $messages = array();
245                 $messages['Base'] = $this->checkRequiredInputs($workflow, $this->requirement, $this->contractData);
246                 !empty($messages['Base'])
247                         && $valid = false;
248                 foreach ($this->conditionalCases as $caseId => $case)
249                 {
250                         if ($this->checkCondition($case['Condition'], $this->contractData))
251                         {
252                                 $this->caseConditions[$caseId] = $caseId;
253                                 $msgs = $this->checkRequiredInputs($workflow, $case['Requirement'], $this->contractData);
254                                 if (!empty($msgs))
255                                 {
256                                         $valid = false;
257                                         isset($messages['Conditional'])
258                                                 || $messages['Conditional'] = array();
259                                         $messages['Conditional'][] = array('Condition' => $case['Condition'], 'Messages' => $msgs);
260                                 }
261                         }
262                 }
263                 $this->validation = $messages;
264                 $validator = new \Utility\Service\Validator();
265                 if (!$validator->validateGroupedInputSet($this->inputs, $this->contractData))
266                 {
267                         $valid = false;
268                         $this->validation['ValidationErrors'] = $validator->getMessages();
269                 }
270                 return $valid;
271         }
272
273         /**
274          * Retrieve validation error messages.
275          * @return array
276          */
277         public function getMessages()
278         {
279                 return $this->validation;
280         }
281
282         /**
283          * Evaluate a condition collection.
284          * @param array $conditions
285          * @param array $input
286          * @return boolean
287          */
288         protected function checkCondition(array $conditions, array $input)
289         {
290                 $valid = true;
291                 return $this->checkStack($conditions['Operator'], $conditions['Conditions'], $input);
292                 /* foreach ($conditions as $condition)
293                 {
294                         $cons = $condition['Conditions'];
295                         $check = $this->checkStack($condition['Operator'], $condition['Conditions'], $input);
296                         $valid = $valid && $check;
297                 }
298                 return $valid */;
299         }
300
301         /**
302          * Evaluate condition stack.
303          * @param string $base
304          * @param array $conditions
305          * @param array $input
306          * @return boolean
307          */
308         protected function checkStack($base, array $conditions, array $input)
309         {
310                 $valid = ('and' == $base)
311                         ? true
312                         : false;
313                 foreach ($conditions as $condition)
314                 {
315                         if (!isset($condition['Conditions']))
316                         {
317                                 list($group, $field) = explode('.', $condition['Input']);
318                                 if (!isset($input[$group][$field]))
319                                 {
320                                         if ('and' == $base)
321                                         {
322                                                 return false;
323                                         }
324                                         else
325                                         {
326                                                 continue;
327                                         }
328                                 }
329                                 switch($condition['Operator'])
330                                 {
331                                         case '=':
332                                                 $check = ($input[$group][$field] == $condition['Value']);
333                                                 break;
334                                         case '!=':
335                                                 $check = ($input[$group][$field] != $condition['Value']);
336                                                 break;
337                                         case '<':
338                                                 $check = ($input[$group][$field] < $condition['Value']);
339                                                 break;
340                                         case '>':
341                                                 $check = ($input[$group][$field] > $condition['Value']);
342                                                 break;
343                                         case 'IN':
344                                                 $check = in_array($input[$group][$field], $condition['Value']);
345                                                 break;
346                                         case 'NOT IN':
347                                                 $check = !in_array($input[$group][$field], $condition['Value']);
348                                                 break;
349                                 }
350                         }
351                         else
352                         {
353                                 $check = $this->checkStack($condition['Operator'], $condition['Conditions'], $input);
354                         }
355                         $valid = ('and' == $base)
356                                 ? $valid && $check
357                                 : $valid || $check;
358                         if ('and' == $base && !$valid)
359                         {
360                                 return false;
361                         }
362                         if ('or' == $base && $valid)
363                         {
364                                 return true;
365                         }
366                 }
367                 return $valid;
368         }
369
370         /**
371          * Check that Required Fields are provided and that Unique Constraints adhered to.
372          * @param \Workspace\Workflow $workflow
373          * @param array $case
374          * @param array $input
375          * @return array
376          */
377         protected function checkRequiredInputs(\Workspace\Workflow $workflow, array $case, array $inputs)
378         {
379                 $messages = array();
380                 foreach ($case as $group => $requirements)
381                 {
382                         $input = isset($inputs[$group])
383                                 ? $inputs[$group]
384                                 : array();
385                         foreach ($requirements as $requiredInput)
386                         {
387                                 if (!isset($input[$requiredInput])
388                                 || (empty($input[$requiredInput]) && !$this->inputs[$group][$requiredInput]['Options']['AllowEmpty'])
389                                 || (0 === $input[$requiredInput] && !$this->inputs[$group][$requiredInput]['Options']['AllowZero'])
390                                 || (is_null($input[$requiredInput]) && !$this->inputs[$group][$requiredInput]['Options']['AllowNull']))
391                                 {
392                                         isset($messages[$group])
393                                                 || $messages[$group] = array();
394                                         $messages[$group][] = 'Required input `' . $requiredInput . '` not provided.';
395                                 }
396                                 if ($this->inputs[$group][$requiredInput]['Options']['Unique'])
397                                 {
398                                         $contract = new \Workspace\Utility\ServiceInput('Contract', array(
399                                                         'Group' => $group,
400                                                         'Field' => $requiredInput,
401                                                         'Value' => $input[$requiredInput]
402                                         ));
403                                         $serviceResponse = $workflow->fieldIsUnique($contract->pack());
404                                         if ('Error' == $serviceResponse['Status'])
405                                         {
406                                                 $messages[$group][] = $serviceResponse['Message'];
407                                         }
408                                 }
409                         }
410                 }
411                 return $messages;
412         }
413
414 }