initial commit
[namibia] / module / Workspace / src / Workspace / Service / DataBin.php
1 <?php
2 namespace Workspace\Service;
3
4
5
6 /**
7  * Basic data node to provide global functionality to workflow services.
8  * @author andre.fourie
9  */
10 class DataBin extends Workflow
11 {
12         /**
13          * Contract types
14          */
15         const ACTION_TYPE_SESSION               = 'Session';
16         const ACTION_TYPE_LIST                  = 'List';
17         const ACTION_TYPE_SELECT_LIST   = 'SelectList';
18         const ACTION_TYPE_GRID                  = 'Grid';
19         const ACTION_TYPE_VIEW                  = 'View';
20         const ACTION_TYPE_CREATE                = 'Create';
21         const ACTION_TYPE_UPDATE                = 'Update';
22         const ACTION_TYPE_DELETE                = 'Delete';
23         const ACTION_TYPE_UNDELETE              = 'UnDelete';
24         const ACTION_TYPE_PDF                   = 'Pdf';
25         const ACTION_TYPE_REPORT                = 'Report';
26
27         /**
28          * Standard workflow.
29          * @param \Workspace\Workflow $workflowNode
30          */
31         public function setWorkflow(\Workspace\Workflow $workflowNode)
32         {
33                 $this->workflowNode = $workflowNode;
34                 $this->em = $this->workflowNode->getEntityManager();
35         }
36
37
38
39         /**
40          * Route contract and execute requests according to meta.
41          * @param string $name
42          * @param array $arguments
43          * @throws \Exception
44          * @return \Workspace\Contract\AbstractBase|array
45          */
46         public function __call($name, $arguments)
47         {
48                 #-> Is this a contract, execution or direct execution request?
49                 if ('contractRoute' == substr($name, 0, 13))
50                 {
51                         #-> Route Contract request, establish action and retrieve meta.
52                         $action = substr($name, 13);
53                         $meta   = 'metaRoute' . $action;
54                         if (!isset($this->$meta))
55                         {
56                                 throw new \Exception('contractRoute:Requested DataBin routing meta structure not defined.');
57                         }
58                         $meta = $this->$meta;
59                         if (isset($meta['Flags']) && is_array($meta['Flags']))
60                         {
61                                 foreach ($meta['Flags'] as $flag => $value)
62                                 {
63                                         \Utility\Registry::setOnce($flag, $value);
64                                 }
65                         }
66                         return $this->conRoute($meta, $arguments[0], $arguments[1]);
67                 }
68                 elseif ('executeRoute' == substr($name, 0, 12))
69                 {
70                         #-> Route Execution request, establish action and retrieve meta.
71                         $action = substr($name, 12);
72                         $meta   = 'metaRoute' . $action;
73                         $meta   = $this->$meta;
74                         if (isset($meta['Flags']) && is_array($meta['Flags']))
75                         {
76                                 foreach ($meta['Flags'] as $flag => $value)
77                                 {
78                                         \Utility\Registry::setOnce($flag, $value);
79                                 }
80                         }
81                         return $this->exeRoute($meta, $arguments[0], $arguments[1], $arguments[2]);
82                 }
83                 elseif ('contract' == substr($name, 0, 8))
84                 {
85                         #-> Task Contract request, establish action and retrieve meta.
86                         $action = substr($name, 8);
87                         $meta = 'meta' . $action;
88                         if (!isset($this->$meta))
89                         {
90                                 throw new \Exception('contract:Requested DataBin meta structure not defined.');
91                         }
92                         $meta = $this->$meta;
93                         if (isset($meta['Flags']) && is_array($meta['Flags']))
94                         {
95                                 foreach ($meta['Flags'] as $flag => $value)
96                                 {
97                                         \Utility\Registry::setOnce($flag, $value);
98                                 }
99                         }
100                         $hardLink = array(
101                                         'List',
102                                         'SelectList',
103                                         'Grid',
104                                         'View',
105                                         'Create',
106                                         'Update',
107                                         'Delete'
108                         );
109                         if (in_array($action, array(
110                                         'List', 'SelectList', 'Grid', 'View',
111                                         'Create', 'Update', 'Delete'
112                                         )))
113                         {
114                                 $meta['Type'] = $action;
115                         }
116                         if (!isset($meta['Type']))
117                         {
118                                 throw new \Exception(
119                                                 'DataBin meta structure provided without handling method and no core Type.'
120                                                 );
121                         }
122                         switch ($meta['Type'])
123                         {
124                                 case self::ACTION_TYPE_SESSION:
125                                         return $this->conSession($meta, $arguments[0], $arguments[1]);
126                                         break;
127                                 case self::ACTION_TYPE_LIST:
128                                         return $this->conList($meta, $arguments[0], $arguments[1]);
129                                         break;
130                                 case self::ACTION_TYPE_SELECT_LIST:
131                                         return $this->conSelectList($meta, $arguments[0], $arguments[1]);
132                                         break;
133                                 case self::ACTION_TYPE_GRID:
134                                         return $this->conGrid($meta, $arguments[0], $arguments[1]);
135                                         break;
136                                 case self::ACTION_TYPE_VIEW:
137                                         return $this->conView($meta, $arguments[0], $arguments[1]);
138                                         break;
139                                 case self::ACTION_TYPE_CREATE:
140                                         return $this->conCreate($meta, $arguments[0], $arguments[1]);
141                                         break;
142                                 case self::ACTION_TYPE_UPDATE:
143                                         return $this->conUpdate($meta, $arguments[0], $arguments[1]);
144                                         break;
145                                 case self::ACTION_TYPE_DELETE:
146                                         return $this->conDelete($meta, $arguments[0], $arguments[1]);
147                                         break;
148                                 case self::ACTION_TYPE_UNDELETE:
149                                         return $this->conUnDelete($meta, $arguments[0], $arguments[1]);
150                                         break;
151                                 case self::ACTION_TYPE_PDF:
152                                         return $this->conPdf($meta, $arguments[0], $arguments[1]);
153                                         break;
154                                 case self::ACTION_TYPE_REPORT:
155                                         return $this->conReport($meta, $arguments[0], $arguments[1]);
156                                         break;
157                                 default:
158                                         throw new \Exception(
159                                                         'Undefined core Type specified in DataBin meta structure.'
160                                                         );
161                                         break;
162                         }
163                 }
164                 elseif ('execute' == substr($name, 0, 7))
165                 {
166                         #-> Task Execution request, establish action and retrieve meta.
167                         $action = substr($name, 7);
168                         $meta   = 'meta' . $action;
169                         $meta   = $this->$meta;
170                         if (isset($meta['Flags']) && is_array($meta['Flags']))
171                         {
172                                 foreach ($meta['Flags'] as $flag => $value)
173                                 {
174                                         \Utility\Registry::setOnce($flag, $value);
175                                 }
176                         }
177                         switch ($meta['Type'])
178                         {
179                                 case self::ACTION_TYPE_SESSION:
180                                         return $this->exeSession($meta, $arguments[0], $arguments[1]);
181                                         break;
182                                 case self::ACTION_TYPE_LIST:
183                                         return $this->exeList($meta, $arguments[0], $arguments[1]);
184                                         break;
185                                 case self::ACTION_TYPE_SELECT_LIST:
186                                         return $this->exeSelectList($meta, $arguments[0], $arguments[1]);
187                                         break;
188                                 case self::ACTION_TYPE_GRID:
189                                         return $this->exeGrid($meta, $arguments[0], $arguments[1]);
190                                         break;
191                                 case self::ACTION_TYPE_VIEW:
192                                         return $this->exeView($meta, $arguments[0], $arguments[1]);
193                                         break;
194                                 case self::ACTION_TYPE_CREATE:
195                                         return $this->exeCreate($meta, $arguments[0], $arguments[1]);
196                                         break;
197                                 case self::ACTION_TYPE_UPDATE:
198                                         return $this->exeUpdate($meta, $arguments[0], $arguments[1]);
199                                         break;
200                                 case self::ACTION_TYPE_DELETE:
201                                         return $this->exeDelete($meta, $arguments[0], $arguments[1]);
202                                         break;
203                                 case self::ACTION_TYPE_UNDELETE:
204                                         return $this->exeUnDelete($meta, $arguments[0], $arguments[1]);
205                                         break;
206                                 case self::ACTION_TYPE_PDF:
207                                         return $this->exePdf($meta, $arguments[0], $arguments[1]);
208                                         break;
209                                 case self::ACTION_TYPE_REPORT:
210                                         return $this->exeReport($meta, $arguments[0], $arguments[1]);
211                                         break;
212                         }
213                 }
214         }
215
216
217         /**
218          * Contract for session based operation.
219          * @param array $meta
220          * @param object|null $jobRecord
221          * @param array $input
222          * @return \Workspace\Contract\AbstractBase
223          * @throws \Exception
224          */
225         public function conSession($meta, $jobRecord, array $input = array())
226         {
227                 #-> Build contract.
228                 if (isset($this->meta['JobField']) && is_null($jobRecord))
229                 {
230                         throw new \Exception('`JobId` root parameter is required for this contract.');
231                 }
232                 $options = $this->buildOptions($meta);
233                 $requirement = $this->buildRequirements($meta);
234                 $contract = (isset($meta['Contract']) && self::CONTRACT_TYPE_RECCURING == $meta['Contract'])
235                         ? new \Workspace\Contract\Recurring($options, $requirement)
236                         : new \Workspace\Contract\UseOnce($options, $requirement);
237                 return $this->buildConditions($contract, $meta);
238         }
239
240         /**
241          * Store data to session.
242          * @param array $meta
243          * @param object|null $jobRecord
244          * @param \Workspace\Utility\ServiceInputParams $contract
245          * @return array
246          */
247         public function exeSession($meta, $jobRecord, \Workspace\Utility\ServiceInputParams $contract)
248         {
249                 #-> Store input to session.
250                 $session = new \Zend\Session\Container($meta['Namespace']);
251                 !isset($session->options)
252                         && $session->options = array();
253                 $session->options[] = $contract->options;
254                 foreach ($meta['RequiredInput'] as $group => $fields)
255                 {
256                         isset($session->$group)
257                                 || $session->$group = array();
258                         $session->$group = array_merge($session->$group, $contract->data->$group);
259                 }
260                 foreach ($meta['OptionalInput'] as $group => $fields)
261                 {
262                         isset($session->$group)
263                                 || $session->$group = array();
264                         !isset($meta['RequiredInput'][$group])
265                                 && isset($contract->data->$group)
266                                 && $session->$group = array_merge($session->$group, $contract->data->$group);
267                 }
268
269                 #-> Handle post execution actions.
270                 if (isset($meta['ExecuteAfter']))
271                 {
272                         foreach ($meta['ExecuteAfter'] as $action)
273                         {
274                                 $this->$action($meta, $jobRecord, $session, $contract);
275                         }
276                 }
277
278                 #-> Done.
279                 return $contract->success('Data stored.');
280         }
281
282
283         /**
284          * Contract to route item to a new state.
285          * @param array $meta
286          * @param object|null $jobRecord
287          * @param array $input
288          * @return \Workspace\Contract\AbstractBase
289          * @throws \Exception
290          */
291         public function conRoute($meta, $jobRecord, array $input = array())
292         {
293                 #-> Build contract.
294                 if (isset($meta['RequireAuth'])
295                         && true == $meta['RequireAuth']
296                         && !\Utility\Registry::isAuthenticated())
297                 {
298                         throw new \Exception('Authentication required for this functionality.');
299                 }
300                 if (isset($this->meta['JobField']) && is_null($jobRecord))
301                 {
302                         throw new \Exception('`JobId` root parameter is required for this contract.');
303                 }
304                 if (isset($meta['Surrogate']))
305                 {
306                         $baseMeta = $meta['Surrogate'];
307                         $meta = $this->$baseMeta;
308                 }
309                 $options = $this->buildOptions($meta);
310                 $requirement = $this->buildRequirements($meta);
311                 $contract = new \Workspace\Contract\UseOnce($options, $requirement);
312                 return $this->buildConditions($contract, $meta);
313         }
314
315         /**
316          * Route item to a new state.
317          * @param array $meta
318          * @param object|null $jobRecord
319          * @param string $currentState
320          * @param \Workspace\Utility\ServiceInputParams $contract
321          * @return array
322          * @throws \Exception
323          */
324         public function exeRoute($meta, $jobRecord, $currentState, \Workspace\Utility\ServiceInputParams $contract)
325         {
326                 if (isset($meta['RequireAuth'])
327                         && true == $meta['RequireAuth']
328                         && !\Utility\Registry::isAuthenticated())
329                 {
330                         throw new \Exception('Authentication required for this functionality.');
331                 }
332                 if (isset($meta['Surrogate']))
333                 {
334                         $baseMeta = $meta['Surrogate'];
335                         $surrogate = $this->$baseMeta;
336                         switch ($surrogate['Type'])
337                         {
338                                 case 'List':
339                                         $this->exeList($surrogate, $jobRecord, $contract);
340                                         break;
341                                 case 'SelectList':
342                                         $this->exeSelectList($surrogate, $jobRecord, $contract);
343                                         break;
344                                 case 'Grid':
345                                         $this->exeGrid($surrogate, $jobRecord, $contract);
346                                         break;
347                                 case 'View':
348                                         $this->exeView($surrogate, $jobRecord, $contract);
349                                         break;
350                                 case 'Create':
351                                         $this->exeCreate($surrogate, $jobRecord, $contract);
352                                         break;
353                                 case 'Update':
354                                         $this->exeUpdate($surrogate, $jobRecord, $contract);
355                                         break;
356                                 case 'Delete':
357                                         throw new \Exception('Route may not surrogate Delete functionality.');
358                                         break;
359                         }
360                 }
361                 return array(
362                                 'Destination' => $meta['Destination'],
363                                 'Data' => isset($contract->data->Context)
364                                         ? $contract->data->Context
365                                         : array()
366                 );
367         }
368
369
370         /**
371          * Contract to list entries.
372          * @param array $meta
373          * @param object|null $jobRecord
374          * @param array $input
375          * @return \Workspace\Contract\AbstractBase
376          * @throws \Exception
377          */
378         public function conList($meta, $jobRecord, array $input = array())
379         {
380                 #-> Build contract.
381                 if (isset($meta['RequireAuth'])
382                         && true == $meta['RequireAuth']
383                         && !\Utility\Registry::isAuthenticated())
384                 {
385                         throw new \Exception('Authentication required for this functionality.');
386                 }
387                 if (isset($this->meta['JobField']) && is_null($jobRecord))
388                 {
389                         throw new \Exception('`JobId` root parameter is required for this contract.');
390                 }
391                 $options = $this->buildOptions($meta);
392                 $requirement = $this->buildRequirements($meta);
393                 $contract = (isset($meta['Contract']) && self::CONTRACT_TYPE_RECCURING == $meta['Contract'])
394                         ? new \Workspace\Contract\Recurring($options, $requirement)
395                         : new \Workspace\Contract\UseOnce($options, $requirement);
396                 return $this->buildConditions($contract, $meta);
397         }
398
399         /**
400          * List entries with optional filtering. Will exclude archived entries by default.
401          * @param array $meta
402          * @param object|null $jobRecord
403          * @param \Workspace\Utility\ServiceInputParams $contract
404          * @return array
405          * @throws \Exception
406          */
407         public function exeList($meta, $jobRecord, \Workspace\Utility\ServiceInputParams $contract)
408         {
409                 #-> Filtering.
410                 if (isset($meta['RequireAuth'])
411                         && true == $meta['RequireAuth']
412                         && !\Utility\Registry::isAuthenticated())
413                 {
414                         throw new \Exception('Authentication required for this functionality.');
415                 }
416                 $baseEntity = $this->meta['Entity'];
417                 $criteria = array();
418                 if (isset($this->meta['JobField']))
419                 {
420                         $criteria[$this->meta['JobField']] = $jobRecord->id;
421                 }
422                 isset($contract->data->Filter)
423                         && is_array($contract->data->Filter)
424                         && !empty($contract->data->Filter)
425                         && $criteria = array_merge($criteria, $contract->data->Filter);
426                 isset($meta['Filter'])
427                         && is_array($meta['Filter'])
428                         && !empty($meta['Filter'])
429                         && $criteria = array_merge($criteria, $meta['Filter']);
430                 $orderBy = !empty($meta['OrderBy'])
431                         ? $meta['OrderBy']
432                         : array();
433                 $expand = isset($meta['Expand'])
434                         ? $meta['Expand']
435                         : array();
436
437                 #-> Done.
438                 return $contract->success(
439                                 'List retrieved.',
440                                 $this->dataList($meta['Fields'], $expand, $criteria, $orderBy)
441                                 );
442         }
443
444         /**
445          * List entries with optional filtering. Will exclude archived entries by default.
446          * @param array $fields
447          * @param array $expand
448          * @param array $criteria
449          * @return array
450          */
451         public function dataList(array $fields, array $expand = array(), array $criteria = array(), $orderBy = array())
452         {
453                 #-> Collect entries.
454                 $baseEntity = $this->meta['Entity'];
455                 $baseEntity::ARCHIVE
456                         && !isset($criteria['archived'])
457                         && $criteria['archived'] = false;
458                 $records = !empty($criteria)
459                         ? $this->em->getRepository($this->meta['Entity'])
460                                 ->findBy($criteria, $orderBy)
461                         : $this->em->getRepository($this->meta['Entity'])
462                                 ->findBy(array(), $orderBy);
463
464                 #-> Cleanup.
465                 foreach ($records as $rowId => $record)
466                 {
467                         $records[$rowId] = $record->toArray($expand, $fields);
468                 }
469
470                 #-> Done.
471                 return $records;
472         }
473
474
475         /**
476          * Contract to list entries in id => label format. Will exclude archived entries by default.
477          * @param array $meta
478          * @param object|null $jobRecord
479          * @param array $input
480          * @return \Workspace\Contract\AbstractBase
481          * @throws \Exception
482          */
483         public function conSelectList($meta, $jobRecord, array $input = array())
484         {
485                 #-> Build contract.
486                 if (isset($meta['RequireAuth'])
487                         && true == $meta['RequireAuth']
488                         && !\Utility\Registry::isAuthenticated())
489                 {
490                         throw new \Exception('Authentication required for this functionality.');
491                 }
492                 if (isset($this->meta['JobField']) && is_null($jobRecord))
493                 {
494                         throw new \Exception('`JobId` root parameter is required for this contract.');
495                 }
496                 $options = $this->buildOptions($meta);
497                 $requirement = $this->buildRequirements($meta);
498                 $contract = (isset($meta['Contract']) && self::CONTRACT_TYPE_RECCURING == $meta['Contract'])
499                         ? new \Workspace\Contract\Recurring($options, $requirement)
500                         : new \Workspace\Contract\UseOnce($options, $requirement);
501                 return $this->buildConditions($contract, $meta);
502         }
503
504         /**
505          * List entries in id => label format with optional filtering. Will exclude archived entries by default.
506          * @param array $meta
507          * @param object|null $jobRecord
508          * @param \Workspace\Utility\ServiceInputParams $contract
509          * @return array
510          * @throws \Exception
511          */
512         public function exeSelectList($meta, $jobRecord, \Workspace\Utility\ServiceInputParams $contract)
513         {
514                 #-> Filtering.
515                 if (isset($meta['RequireAuth'])
516                         && true == $meta['RequireAuth']
517                         && !\Utility\Registry::isAuthenticated())
518                 {
519                         throw new \Exception('Authentication required for this functionality.');
520                 }
521                 $base = $this->meta['Base'];
522                 $baseEntity = $this->meta['Entity'];
523                 $criteria = array();
524                 $baseEntity::ARCHIVE
525                         && $criteria['archived'] = false;
526                 if (isset($this->meta['JobField']) && !is_null($jobRecord))
527                 {
528                         $criteria[$this->meta['JobField']] = $jobRecord->id;
529                 }
530                 isset($contract->data->Filter)
531                         && is_array($contract->data->Filter)
532                         && !empty($contract->data->Filter)
533                         && $criteria = array_merge($criteria, $contract->data->Filter);
534                 isset($meta['Filter'])
535                         && is_array($meta['Filter'])
536                         && !empty($meta['Filter'])
537                         && $criteria = array_merge($criteria, $meta['Filter']);
538
539                 #-> Done.
540                 return $contract->success(
541                                 'List retrieved.',
542                                 $this->selectList(
543                                                 $meta['Label']['Format'],
544                                                 $meta['Label']['Fields'],
545                                                 $criteria
546                                                 )
547                                 );
548         }
549
550         /**
551          * List entries in id => label format with optional filtering. Will exclude archived entries by default.
552          * @param string $labelFormat
553          * @param array $labelFields
554          * @param array $criteria
555          * @return array
556          */
557         public function selectList($labelFormat, array $labelFields, array $criteria = array())
558         {
559                 #-> Collect entries.
560                 $baseEntity = $this->meta['Entity'];
561                 $baseEntity::ARCHIVE
562                         && !isset($criteria['archived'])
563                         && $criteria['archived'] = false;
564                 $negCriteria = array();
565                 foreach ($criteria as $field => $value)
566                 {
567                         if (!is_array($value) && !is_object($value) && '!=' == substr($value, 0, 2))
568                         {
569                                 $negCriteria[$field] = substr($value, 2);
570                                 unset($criteria[$field]);
571                         }
572                 }
573                 $orderBy = array();
574                 if (isset($labelFields[0]))
575                 {
576                         $orderBy[$labelFields[0]] = 'ASC';
577                 }
578                 $records = !empty($criteria)
579                         ? $this->em->getRepository($this->meta['Entity'])
580                                 ->findBy($criteria, $orderBy)
581                         : $this->em->getRepository($this->meta['Entity'])
582                                 ->findBy(array(), $orderBy);
583
584                 #-> Cleanup.
585                 $search  = array();
586                 foreach ($labelFields as $key => $fieldName)
587                 {
588                         !is_numeric($key)
589                                 && $fieldName = $key;
590                         $search[] = '[' . $fieldName . ']';
591                 }
592                 $data = array();
593
594                 #-> Build list.
595                 foreach ($records as $rowId => $record)
596                 {
597                         foreach ($negCriteria as $field => $value)
598                         {
599                                 if ($value == $record->$field)
600                                 {
601                                         continue 2;
602                                 }
603                         }
604                         $replace = array();
605                         foreach ($labelFields as $key => $fieldName)
606                         {
607                                 !is_numeric($key)
608                                         && $fieldName = $key;
609                                 $replace[] = !is_numeric($key)
610                                         ? (!is_null($record->$key) ? $record->$key : $fieldName)
611                                         : $record->$fieldName;
612                         }
613                         $data[] = array(
614                                         'value' => $record->id,
615                                         'label' => str_replace(
616                                                 $search, $replace,
617                                                 $labelFormat
618                                         )
619                         );
620                         unset($records[$rowId]);
621                 }
622
623                 #-> Done.
624                 return $data;
625         }
626
627
628         /**
629          * Contract for data grid.
630          * @param array $meta
631          * @param object|null $jobRecord
632          * @param array $input
633          * @return \Workspace\Contract\AbstractBase
634          * @throws \Exception
635          */
636         public function conGrid($meta, $jobRecord, array $input = array())
637         {
638                 #-> Build contract.
639                 if (isset($meta['RequireAuth'])
640                         && true == $meta['RequireAuth']
641                         && !\Utility\Registry::isAuthenticated())
642                 {
643                         throw new \Exception('Authentication required for this functionality.');
644                 }
645                 /* if (isset($this->meta['JobField']) && is_null($jobRecord))
646                 {
647                         throw new \Exception('`JobId` root parameter is required for this contract.');
648                 } */
649                 $options = $this->buildOptions($meta);
650                 $requirement = $this->buildRequirements($meta);
651                 $contract = (isset($meta['Contract']) && self::CONTRACT_TYPE_RECCURING == $meta['Contract'])
652                         ? new \Workspace\Contract\Recurring($options, $requirement)
653                         : new \Workspace\Contract\UseOnce($options, $requirement);
654                 return $this->buildConditions($contract, $meta);
655         }
656
657         /**
658          * List entries with optional filtering. Will exclude archived entries by default.
659          * @param array $meta
660          * @param object|null $jobRecord
661          * @param \Workspace\Utility\ServiceInputParams $contract
662          * @return array
663          * @throws \Exception
664          */
665         public function exeGrid($meta, $jobRecord, \Workspace\Utility\ServiceInputParams $contract)
666         {
667                 #-> Filtering.
668                 if (isset($meta['RequireAuth'])
669                         && true == $meta['RequireAuth']
670                         && !\Utility\Registry::isAuthenticated())
671                 {
672                         throw new \Exception('Authentication required for this functionality.');
673                 }
674                 $base = $this->meta['Base'];
675                 $baseEntity = $this->meta['Entity'];
676                 $criteria = array();
677                 if (isset($this->meta['JobField']) && !is_null($jobRecord))
678                 {
679                         $meta['Filter'][$this->meta['JobField']] = $jobRecord->id;
680                 }
681
682
683                 #-> Session memory.
684                 $session = new \Zend\Session\Container('DataBin_Grid_' . $contract->hash);
685                 if (\Utility\Registry::initSessionStorage('DataBin_Grid_' . $contract->hash))
686                 {
687                         $session->Filter = array();
688                         $session->OrderBy = isset($meta['OrderBy'])
689                                 ? $meta['OrderBy']
690                                 : array();
691                         $session->Page = 1;
692                         $session->NumberOfRecords = $meta['NumberOfRecords'];
693                 }
694                 else
695                 {
696                         !isset($session->Filter)
697                                 && $session->Filter = array();
698                         !isset($session->NumberOfRecords)
699                                 && $session->NumberOfRecords = $meta['NumberOfRecords'];
700                         !isset($session->Page)
701                                 && $session->Page = 1;
702                         !isset($session->OrderBy)
703                                 && $session->OrderBy = isset($meta['OrderBy'])
704                                         ? $meta['OrderBy']
705                                         : array();
706                 }
707                 isset($contract->data->Grid['NumberOfRecords'])
708                         && is_numeric($contract->data->Grid['NumberOfRecords'])
709                         && $session->NumberOfRecords = $contract->data->Grid['NumberOfRecords'];
710                 isset($contract->data->Grid['Page'])
711                         && is_numeric($contract->data->Grid['Page'])
712                         && $session->Page = $contract->data->Grid['Page'];
713                 isset($contract->data->Grid['Filter'])
714                         && is_array($contract->data->Grid['Filter'])
715                         && $session->Filter = $contract->data->Grid['Filter'];
716                 isset($contract->data->Grid['OrderBy'])
717                         && is_array($contract->data->Grid['OrderBy'])
718                         && $session->OrderBy = $contract->data->Grid['OrderBy'];
719
720                 #-> Collect entries.
721                 $export = ( isset($meta['Export'])
722                                         && $meta['Export']
723                                         && isset($contract->options->ExportToExcel)
724                                         && $contract->options->ExportToExcel )
725                         ? true
726                         : false;
727                 if ($export && isset($meta['ExportMeta']))
728                 {
729                         $swap = $meta['ExportMeta'];
730                         foreach ($swap as $key => $item)
731                         {
732                                 $meta[$key] = $item;
733                         }
734                 }
735                 $response = $this->grid(
736                                 $meta['Query'],
737                                 $meta['Selection'],
738                                 $session->NumberOfRecords,
739                                 $session->Page,
740                                 array_merge(
741                                         $meta['Filter'],
742                                         $session->Filter
743                                 ),
744                                 $session->OrderBy,
745                                 isset($meta['GroupBy'])
746                                         ? $meta['GroupBy']
747                                         : '',
748                                 $meta['Fields'],
749                                 $meta['Base'],
750                                 $export,
751                                 isset($meta['Swap'])
752                                         ? $meta['Swap']
753                                         : ''
754                                 );
755                 $response['Meta']['Filters'] = $session->Filter;
756
757                 #-> Check if an excel export is allowed and requested.
758                 if ($export)
759                 {
760                         #-> Build excel report.
761                         $reportService = $meta['Builder'];
762                         $report = new $reportService();
763                         $report->process(array(), array(), $response['DataSet']);
764                         $reportWriter  = $meta['Writer'];
765
766                         #-> Output.
767                         $output = isset($meta['Output'])
768                                 ? $meta['Output']
769                                 : 'Download';
770                         if ('Download' == $output)
771                         {
772                                 $writer = new $reportWriter($report);
773                                 $writer->output();
774                                 exit(0);
775                         }
776                         elseif ('File' == $output)
777                         {
778                                 $writer = new $reportWriter($report);
779                                 $writer->output($meta['FilePath']);
780                                 return $contract->success('Report saved to file.');
781                         }
782                         elseif ('Raw' == $output)
783                         {
784                                 return $contract->success('Report Generated', array(
785                                                 'Title'        => $report->getTitle(),
786                                                 'Description'  => $report->getDescription(),
787                                                 'QueryDetails' => $report->getQueries(),
788                                                 'Headers'      => $report->getHeaders(),
789                                                 'Fields'       => $report->getFields(),
790                                                 'TotalFields'  => $report->getTotalFields(),
791                                                 'Notes'        => $report->getNotes(),
792                                                 'Data'         => $report->getDataIntersection()
793                                 ));
794                         }
795                         else
796                         {
797                                 $writer = new $reportWriter($report);
798                                 return $contract->success('Report Generated', $writer->output());
799                         }
800                 }
801
802                 #-> Done.
803                 return $contract->success('Grid retrieved.', $response);
804         }
805
806         /**
807          * Retrieve data grid from provided query.
808          * The DQL query should contain [WHERE] and [ORDER] for relevant hydration.
809          * Filtering allows for smart filtering: 'profile.firstName' => '!NULL'.
810          * numberOfRecords AND page must be greater than 0 to collect a paged dataset.
811          *
812          * @param string $dql
813          * @param string $selection
814          * @param integer $numberOfRecords
815          * @param integer $page
816          * @param array $filter
817          * @param array $order
818          * @param array $fields
819          * @return array
820          */
821         public function grid($dql, $selection, $numberOfRecords, $page,
822                         array $filter = array(), array $order = array(), $group = '',
823                         array $fields = array(), $baseTable = '', $forExport = false, $swap = '')
824         {
825                 #-> Establish size of dataset.
826                 !empty($group)
827                         && $group = 'GROUP BY ' . $group;
828                 $where = \Utility\Doctrine::dqlFilter($filter, $baseTable);
829                 $query = str_replace(
830                                 array('[SELECTION]', '[WHERE]', '[ORDER]', '[GROUP]', '[SWAP]'),
831                                 array("COUNT(DISTINCT $baseTable.id) AS total", $where['Where'], '', ''),
832                                 $dql
833                                 );
834                 /* if ('development' == \Utility\Registry::getConfigParam('Instance')) //1517 == \Utility\Registry::getAuthParam('id'))
835                 {
836                         \Utility\Debug::errorLog('----------------------', '----------------------');
837                         \Utility\Debug::errorLog('----------------------', '----------------------');
838                         \Utility\Debug::errorLog('----------------------', '----------------------');
839                         \Utility\Debug::errorLog('GRID COUNT QUERY', $query);
840                         \Utility\Debug::errorLog('GRID COUNT PARAMS', $where['Params']);
841                 } */
842                 $query = $this->em->createQuery($query);
843                 !empty($where['Params'])
844                         && $query->setParameters($where['Params']);
845                 //\Utility\Debug::errorLog('COUNT', $query->getSQL());
846                 $numRecsRes = $query->getSingleResult();
847                 $numRecs = (int) $numRecsRes['total'];
848                 if (0 == $numberOfRecords)
849                 {
850                         $numPages = (0 < $numRecs)
851                                 ? 1
852                                 : 0;
853                 }
854                 else
855                 {
856                         $numPages = (0 < $numRecs)
857                                 ? ceil($numRecs / $numberOfRecords)
858                                 : 0;
859                 }
860
861                 #-> Retrieve paged dataset.
862                 $query = str_replace(
863                                 array('[SELECTION]', '[WHERE]', '[ORDER]', '[GROUP]', '[SWAP]'),
864                                 array($selection, $where['Where'], \Utility\Doctrine::dqlOrder($order), $group, $swap),
865                                 $dql
866                                 );
867                 /* if ('development' == \Utility\Registry::getConfigParam('Instance'))
868                 {
869                         \Utility\Debug::errorLog('----------------------', '----------------------');
870                         \Utility\Debug::errorLog('GRID QUERY', $query);
871                         \Utility\Debug::errorLog('GRID PARAMS', $where['Params']);
872                 } */
873                 $query = $this->em->createQuery($query);
874                 !empty($where['Params'])
875                         && $query->setParameters($where['Params']);
876                 if (!$forExport)
877                 {
878                         (0 < $numberOfRecords)
879                                 && $query->setMaxResults($numberOfRecords);
880                         (0 < $page && 0 < $numberOfRecords)
881                                 && $query->setFirstResult(($page - 1) * $numberOfRecords);
882                 }
883 //              \Utility\Debug::errorLog('QUERY', $query->getSQL());
884 //        \Utility\Debug::errorLog('PARAMS', $query->getParameters());
885                 /* if ('development' == \Utility\Registry::getConfigParam('Instance'))
886                 {
887                         \Utility\Debug::errorLog('----------------------', '----------------------');
888                         \Utility\Debug::errorLog('GRID DATA', $query->getArrayResult());
889                 } */
890                 return array(
891                                 'Meta' => array(
892                                         'TotalRecords' => $numRecs,
893                                         'TotalPages'   => $numPages,
894                                         'CurrentPage'  => $page,
895                                         'Filters'      => $filter,
896                                         'Order'        => $order
897                                 ),
898                                 'DataSet' => (!$forExport
899                                                 ? \Utility\Doctrine::extractData($fields, $query->getArrayResult())
900                                                 : $query->getScalarResult())
901                                 );
902         }
903
904
905         /**
906          * Contract to create new entry.
907          * @param array $meta
908          * @param object|null $jobRecord
909          * @param array $input
910          * @return \Workspace\Contract\AbstractBase
911          * @throws \Exception
912          */
913         public function conCreate($meta, $jobRecord, array $input = array())
914         {
915                 #-> Build contract.
916                 if (isset($meta['RequireAuth'])
917                         && true == $meta['RequireAuth']
918                         && !\Utility\Registry::isAuthenticated())
919                 {
920                         throw new \Exception('Authentication required for this functionality.');
921                 }
922                 if (isset($this->meta['JobField']) && is_null($jobRecord))
923                 {
924                         throw new \Exception('`JobId` root parameter is required for this contract.');
925                 }
926                 $options = $this->buildOptions($meta);
927                 $requirement = $this->buildRequirements($meta);
928                 $contract = (isset($meta['Contract']) && self::CONTRACT_TYPE_RECCURING == $meta['Contract'])
929                         ? new \Workspace\Contract\Recurring($options, $requirement)
930                         : new \Workspace\Contract\UseOnce($options, $requirement);
931                 return $this->buildConditions($contract, $meta);
932         }
933
934         /**
935          * Create new entry.
936          * @param array $meta
937          * @param object|null $jobRecord
938          * @param \Workspace\Utility\ServiceInputParams $contract
939          * @return array
940          * @throws \Exception
941          */
942         public function exeCreate($meta, $jobRecord, \Workspace\Utility\ServiceInputParams $contract)
943         {
944                 #-> Context data publication.
945                 if (isset($meta['RequireAuth'])
946                         && true == $meta['RequireAuth']
947                         && !\Utility\Registry::isAuthenticated())
948                 {
949                         throw new \Exception('Authentication required for this functionality.');
950                 }
951                 isset($contract->data->Context)
952                         && \Utility\Registry::setOnce(
953                                 'Service.' . $this->meta['Base'] . '.Context',
954                                 $contract->data->Context
955                                 );
956
957                 #-> Create entry.
958                   $base = $this->meta['Base'];
959                   $data = $contract->data;
960                   if (isset($meta['ExecuteBefore']))
961                   {
962                            foreach ($meta['ExecuteBefore'] as $action)
963                            {
964                             $data = $this->$action($meta, $data);
965                            }
966                   }
967                   $data = $data->$base;
968
969
970
971                 if (isset($meta['RelatedEntityFromInput']))
972                 {
973                         foreach ($meta['RelatedEntityFromInput'] as $context => $relation)
974                         {
975                                 $workflow = $relation['Workflow'];
976                                 $service  = $relation['Service'];
977                                 $service  = new $service();
978                                 $service->setWorkflow(new $workflow());
979                                 $relId = $service->create($contract->data->$context)->id;
980                                 $data[$relation['Field']] = $relId;
981                         }
982                 }
983                 if (isset($this->meta['JobField']))
984                 {
985                         $data[$this->meta['JobField']] = $jobRecord->id;
986                 }
987                 $record = $this->create($data);
988
989                 #-> Handle post execution actions.
990                 $feed = null;
991                 if (isset($meta['ExecuteAfter']))
992                 {
993                         foreach ($meta['ExecuteAfter'] as $action)
994                         {
995                                 $feed = $this->$action($meta, $jobRecord, $record, $contract);
996                         }
997                 }
998
999                 #-> Done.
1000                 return $contract->success('Entry created.', array(
1001                                 'id'   => $record->id,
1002                                 'feed' => $feed
1003                 ));
1004         }
1005
1006         /**
1007          * Create a new entry.
1008          * @param array $data
1009          * @return object
1010          */
1011         public function create(array $data)
1012         {
1013                 #-> Create entry.
1014                 $base = $this->meta['Base'];
1015                 $baseEntity = $this->meta['Entity'];
1016                 if (isset($this->meta['References']) && !empty($this->meta['References']))
1017                 {
1018                         foreach ($this->meta['References'] as $field => $entity)
1019                         {
1020                                 isset($data[$field])
1021                                         && $data[$field] = $this->em->getReference($entity, $data[$field]);
1022                         }
1023                 }
1024                 $record = new $baseEntity();
1025                 $record->fromArray($data);
1026                 $this->em->persist($record);
1027                 $this->em->flush();
1028
1029                 #-> Check for post-create requirement.
1030                 if (defined($baseEntity . '::HAVE_POST_INSERT'))
1031                 {
1032                         $record->postInsert();
1033                         $this->em->flush();
1034                 }
1035
1036                 #-> Broadcast the data change.
1037                 /*switch ($baseEntity::PUSH_SYNCH_STRATEGY)
1038                 {
1039                         case 'Build':
1040                                 \Utility\Comms\Ape::broadcastBuildDatasetChange($this->meta['DatasetName']);
1041                                 break;
1042                         case 'Update':
1043                                 if (defined($baseEntity . '::JOB_QUEUE'))
1044                                 {
1045                                         if (1 != $record->queueStatus)
1046                                         {
1047                                                 break;
1048                                         }
1049                                 }
1050                                 \Utility\Comms\Ape::broadcastUpdateDatasetChange(
1051                                                 $this->meta['DatasetName'],
1052                                                 \Utility\Comms\Ape::CHANGE_TYPE_CREATE,
1053                                                 array($record->toSynchArray())
1054                                 );
1055                                 break;
1056                 }*/
1057                 \Utility\Event::trigger($this->meta['Base'] . '.Create', $record);
1058                 return $record;
1059         }
1060
1061
1062         /**
1063          * Contract to view an entry.
1064          * @param array $meta
1065          * @param object|null $jobRecord
1066          * @param array $input
1067          * @return \Workspace\Contract\AbstractBase
1068          * @throws \Exception
1069          */
1070         public function conView($meta, $jobRecord, array $input = array())
1071         {
1072                 #-> Safety checks.
1073                 if (isset($meta['RequireAuth'])
1074                         && true == $meta['RequireAuth']
1075                         && !\Utility\Registry::isAuthenticated())
1076                 {
1077                         throw new \Exception('Authentication required for this functionality.');
1078                 }
1079                 if (isset($this->meta['JobField']) && is_null($jobRecord))
1080                 {
1081                         throw new \Exception('`JobId` root parameter is required for this contract.');
1082                 }
1083                 if (!isset($input['id']) || !is_numeric($input['id']) || 0 == $input['id'])
1084                 {
1085                         throw new \Exception('A valid record `id` is required to setup the contract.');
1086                 }
1087                 $base = $this->meta['Base'];
1088                 $record = $this->em
1089                         ->getRepository($this->meta['Entity'])
1090                         ->find($input['id']);
1091                 if (is_null($record))
1092                 {
1093                         throw new \Exception('Could not find record.');
1094                 }
1095                 if (isset($this->meta['JobField']))
1096                 {
1097                         $field = $this->meta['JobField'];
1098                         if ($record->$field->id != $jobRecord->id)
1099                         {
1100                                 throw new \Exception('This record does not belong to the specified JobId.');
1101                         }
1102                 }
1103
1104                 #-> Build contract.
1105                 $contract = new \Workspace\Contract\Recurring(
1106                                 new \Workspace\UseCase\Options(),
1107                                 new \Workspace\UseCase\Requirement()
1108                                 );
1109                 return $contract->setData($record->toArray(
1110                                 is_array($meta) && isset($meta['Expand'])
1111                                         ? $meta['Expand']
1112                                         : array(),
1113                                 is_array($meta) && isset($meta['Intersect'])
1114                                         ? $meta['Intersect']
1115                                         : array(),
1116                                 false,
1117                                 isset($meta['ExpandDepth'])
1118                                         ? $meta['ExpandDepth']
1119                                         : 1
1120                                 ));
1121         }
1122
1123         /**
1124          * View an entry.
1125          * @param array $meta
1126          * @param object|null $jobRecord
1127          * @param \Workspace\Utility\ServiceInputParams $contract
1128          * @return array
1129          * @throws \Exception
1130          */
1131         public function exeView(array $meta, $jobRecord, \Workspace\Utility\ServiceInputParams $contract)
1132         {
1133                 #-> Retrieve entry.
1134                 if (isset($meta['RequireAuth'])
1135                         && true == $meta['RequireAuth']
1136                         && !\Utility\Registry::isAuthenticated())
1137                 {
1138                         throw new \Exception('Authentication required for this functionality.');
1139                 }
1140                 return $contract->success('Entry retrieved.', $this->view(
1141                                 $contract->data->id,
1142                                 isset($meta['Expand'])
1143                                         ? $meta['Expand']
1144                                         : array(),
1145                                 isset($meta['Intersect'])
1146                                         ? $meta['Intersect']
1147                                         : array(),
1148                                 false,
1149                                 isset($meta['ExpandDepth'])
1150                                         ? $meta['ExpandDepth']
1151                                         : 1
1152                                 ));
1153         }
1154
1155         /**
1156          * View an entry.
1157          * @param integer $id
1158          * @param array $expand
1159          * @param array $intersect
1160          * @param boolean $showIdentifiers
1161          * @param integer $expandAll
1162          * @return array
1163          */
1164         public function view(
1165                         $id, array $expand = array(), array $intersect = array(),
1166                         $showIdentifiers = false, $expandAll = 1
1167                         )
1168         {
1169                 #-> Retrieve entry.
1170                 $record = $this->em->getReference($this->meta['Entity'], $id);
1171                 return $record->toArray($expand, $intersect, $showIdentifiers, $expandAll);
1172         }
1173
1174
1175         /**
1176          * Contract to update an entry.
1177          * @param array $meta
1178          * @param object|null $jobRecord
1179          * @param array $input
1180          * @return \Workspace\Contract\AbstractBase
1181          * @throws \Exception
1182          */
1183         public function conUpdate($meta, $jobRecord, array $input = array())
1184         {
1185                 #-> Safety checks.
1186                 if (isset($meta['RequireAuth'])
1187                         && true == $meta['RequireAuth']
1188                         && !\Utility\Registry::isAuthenticated())
1189                 {
1190                         throw new \Exception('Authentication required for this functionality.');
1191                 }
1192                 if (isset($this->meta['JobField']) && is_null($jobRecord))
1193                 {
1194                         throw new \Exception('`JobId` root parameter is required for this contract.');
1195                 }
1196                 if (!isset($input['id']) || !is_numeric($input['id']) || 0 == $input['id'])
1197                 {
1198                         throw new \Exception('A valid record `id` parameter in the `Packet` root is required to setup the contract.');
1199                 }
1200                 $base = $this->meta['Base'];
1201                 $record = $this->em
1202                         ->getRepository($this->meta['Entity'])
1203                         ->find($input['id']);
1204                 if (is_null($record))
1205                 {
1206                         throw new \Exception('Could not find record.');
1207                 }
1208                 if (isset($this->meta['JobField']))
1209                 {
1210                         $field = $this->meta['JobField'];
1211                         if ($record->$field->id != $jobRecord->id)
1212                         {
1213                                 throw new \Exception('This record does not belong to the specified JobId.');
1214                         }
1215                 }
1216
1217                 #-> Build contract.
1218                 $options = $this->buildOptions($meta);
1219                 $requirement = $this->buildRequirements($meta);
1220                 $contract = (isset($meta['Contract']) && self::CONTRACT_TYPE_RECCURING == $meta['Contract'])
1221                         ? new \Workspace\Contract\Recurring($options, $requirement)
1222                         : new \Workspace\Contract\UseOnce($options, $requirement);
1223                 $contract = $this->buildConditions($contract, $meta);
1224
1225                 #-> Handle additional contract.
1226                 if (isset($meta['ConditionalContract']))
1227                 {
1228                         foreach ($meta['ConditionalContract'] as $action)
1229                         {
1230                                 $this->$action($meta, $jobRecord, $record, $contract);
1231                         }
1232                 }
1233
1234                 #-> Done.
1235                 $expand = isset($meta['Expand'])
1236                         ? $meta['Expand']
1237                         : array();
1238                 return $contract->setData($record->toArray($expand, array(), true));
1239         }
1240
1241         /**
1242          * Update an entry.
1243          * @param array $meta
1244          * @param object|null $jobRecord
1245          * @param \Workspace\Utility\ServiceInputParams $contract
1246          * @return array
1247          * @throws \Exception
1248          */
1249         public function exeUpdate($meta, $jobRecord, \Workspace\Utility\ServiceInputParams $contract)
1250         {
1251                 #-> Context data publication.
1252                 if (isset($meta['RequireAuth'])
1253                         && true == $meta['RequireAuth']
1254                         && !\Utility\Registry::isAuthenticated())
1255                 {
1256                         throw new \Exception('Authentication required for this functionality.');
1257                 }
1258                 isset($contract->data->Context)
1259                         && \Utility\Registry::setOnce(
1260                                 'Service.' . $this->meta['Base'] . '.Context',
1261                                 $contract->data->Context
1262                                 );
1263
1264                 #-> Handle related entity updates.
1265                 $base = $this->meta['Base'];
1266                 $record = null;
1267                 $data = isset($contract->data->$base)
1268                         ? $contract->data->$base
1269                         : array();
1270                 if (isset($meta['ExecuteBefore']))
1271                 {
1272                         foreach ($meta['ExecuteBefore'] as $action)
1273                         {
1274                                 $res = $this->$action($meta, $jobRecord, $contract, $data);
1275                                 if (is_object($res))
1276                                 {
1277                                         $contract->data = $res;
1278                                         $data = isset($contract->data->$base)
1279                                                 ? $contract->data->$base
1280                                                 : array();
1281                                 }
1282                                 else
1283                                 {
1284                                         $data = $res;
1285                                 }
1286                         }
1287                 }
1288                 if (isset($meta['RelatedEntityFromInput']))
1289                 {
1290                         $record = $this->em->getRepository($this->meta['Entity'])
1291                                 ->find($contract->data->id);
1292                         foreach ($meta['RelatedEntityFromInput'] as $context => $relation)
1293                         {
1294                                 $workflow = $relation['Workflow'];
1295                                 $service  = $relation['Service'];
1296                                 $field    = $relation['Field'];
1297                                 $service  = new $service();
1298                                 $service->setWorkflow(new $workflow());
1299                                 if (!is_null($record->$field))
1300                                 {
1301                                         $service->update(
1302                                                         $record->$field->id,
1303                                                         $contract->data->$context
1304                                                         );
1305                                 }
1306                                 else
1307                                 {
1308                                         $relId = $service->create($contract->data->$context)->id;
1309                                         $data[$field] = $relId;
1310                                 }
1311                         }
1312                 }
1313
1314                 #-> Update entry.
1315                 !empty($data)
1316                         && $record = $this->update(
1317                                                 $contract->data->id, $data, $record
1318                                                 );
1319
1320                 #-> Handle post execution actions.
1321                         $feed = null;
1322                 if (isset($meta['ExecuteAfter']))
1323                 {
1324                         is_null($record)
1325                                 && $record = $this->em->getRepository($this->meta['Entity'])
1326                                                 ->find($contract->data->id);
1327                         foreach ($meta['ExecuteAfter'] as $action)
1328                         {
1329                                 $feed = $this->$action($meta, $jobRecord, $record, $contract);
1330                         }
1331                 }
1332
1333                 #-> Done.
1334                 return $contract->success('Entry updated.', array(
1335                                 'id'   => !is_null($record) ? $record->id : $contract->data->id,
1336                                 'feed' => $feed
1337                 ));
1338         }
1339
1340         /**
1341          * Update an entry.
1342          * @param integer $id
1343          * @param array $data
1344          * @param object|null $record
1345          * @return object
1346          */
1347         public function update($id, array $data, $record = null)
1348         {
1349                 #-> Update entry.
1350                 $base = $this->meta['Base'];
1351                 $baseEntity = $this->meta['Entity'];
1352                 $record = is_null($record)
1353                         ? $this->em->getRepository($this->meta['Entity'])
1354                                 ->find($id)
1355                         : $record;
1356                 defined($baseEntity . '::JOB_QUEUE')
1357                         && $queueStatus = $record->queueStatus;
1358                 if (isset($this->meta['References']) && !empty($this->meta['References']))
1359                 {
1360                         foreach ($this->meta['References'] as $field => $entity)
1361                         {
1362                                 $data[$field] = isset($data[$field]) && !empty($data[$field])
1363                                         ? $this->em->getReference($entity, $data[$field])
1364                                         : null;
1365                         }
1366                 }
1367                 $record->fromArray($data);
1368                 $this->em->flush();
1369
1370                 #-> Check for post-update requirement.
1371                 if (defined($baseEntity . '::HAVE_POST_UPDATE'))
1372                 {
1373                         $record->postUpdate();
1374                         $this->em->flush();
1375                 }
1376
1377                 #-> Broadcast the data change.
1378                 /*switch ($baseEntity::PUSH_SYNCH_STRATEGY)
1379                 {
1380                         case 'Build':
1381                                 \Utility\Comms\Ape::broadcastBuildDatasetChange($this->meta['DatasetName']);
1382                                 break;
1383                         case 'Update':
1384                                 $type = \Utility\Comms\Ape::CHANGE_TYPE_UPDATE;
1385                                 $updateData = $record->toSynchArray();
1386                                 if (defined($baseEntity . '::JOB_QUEUE'))
1387                                 {
1388                                         if (1 != $record->queueStatus
1389                                                 && 1 != $queueStatus)
1390                                         {
1391                                                 break;
1392                                         }
1393                                         else
1394                                         {
1395                                                 if ($record->queueStatus == $queueStatus)
1396                                                 {
1397                                                         $type = \Utility\Comms\Ape::CHANGE_TYPE_UPDATE;
1398                                                 }
1399                                                 elseif (0 == $queueStatus)
1400                                                 {
1401                                                         $type = \Utility\Comms\Ape::CHANGE_TYPE_CREATE;
1402                                                 }
1403                                                 else
1404                                                 {
1405                                                         $type = \Utility\Comms\Ape::CHANGE_TYPE_DELETE;
1406                                                         $updateData = array('id' => $record->id);
1407                                                 }
1408                                         }
1409                                 }
1410                                 \Utility\Comms\Ape::broadcastUpdateDatasetChange(
1411                                                 $this->meta['DatasetName'],
1412                                                 $type,
1413                                                 array($updateData)
1414                                 );
1415                                 break;
1416                 }*/
1417                 \Utility\Event::trigger($this->meta['Base'] . '.Update', $record);
1418                 return $record;
1419         }
1420
1421
1422         /**
1423          * Contract to delete an entry.
1424          * @param array $meta
1425          * @param object|null $jobRecord
1426          * @param array $input
1427          * @return \Workspace\Contract\AbstractBase
1428          * @throws \Exception
1429          */
1430         public function conDelete($meta, $jobRecord, array $input = array())
1431         {
1432                 #-> Safety checks.
1433                 if (isset($meta['RequireAuth'])
1434                         && true == $meta['RequireAuth']
1435                         && !\Utility\Registry::isAuthenticated())
1436                 {
1437                         throw new \Exception('Authentication required for this functionality.');
1438                 }
1439                 if (isset($this->meta['JobField']) && is_null($jobRecord))
1440                 {
1441                         throw new \Exception('`JobId` root parameter is required for this contract.');
1442                 }
1443                 if (!isset($input['id']) || !is_numeric($input['id']) || 0 == $input['id'])
1444                 {
1445                         throw new \Exception('A valid record `id` is required to setup the contract.');
1446                 }
1447                 $base = $this->meta['Base'];
1448                 $record = $this->em
1449                         ->getRepository($this->meta['Entity'])
1450                         ->find($input['id']);
1451                 if (is_null($record))
1452                 {
1453                         throw new \Exception('Could not find record.');
1454                 }
1455                 if (isset($this->meta['JobField']))
1456                 {
1457                         $field = $this->meta['JobField'];
1458                         if ($record->$field->id != $jobRecord->id)
1459                         {
1460                                 throw new \Exception('This record does not belong to the specified JobId.');
1461                         }
1462                 }
1463
1464                 #-> Build contract.
1465                 $options = $this->buildOptions($meta);
1466                 $requirement = $this->buildRequirements($meta);
1467                 $contract = (isset($meta['Contract']) && self::CONTRACT_TYPE_RECCURING == $meta['Contract'])
1468                         ? new \Workspace\Contract\Recurring($options, $requirement)
1469                         : new \Workspace\Contract\UseOnce($options, $requirement);
1470                 $contract = $this->buildConditions($contract, $meta);
1471                 return $contract->setData($record->toArray(array(), array(), true));
1472         }
1473
1474         /**
1475          * Delete an entry.
1476          * @param array $meta
1477          * @param object|null $jobRecord
1478          * @param \Workspace\Utility\ServiceInputParams $contract
1479          * @return array
1480          * @throws \Exception
1481          */
1482         public function exeDelete($meta, $jobRecord, \Workspace\Utility\ServiceInputParams $contract)
1483         {
1484                 #-> Context data publication.
1485                 if (isset($meta['RequireAuth'])
1486                         && true == $meta['RequireAuth']
1487                         && !\Utility\Registry::isAuthenticated())
1488                 {
1489                         throw new \Exception('Authentication required for this functionality.');
1490                 }
1491                 isset($contract->data->Context)
1492                         && \Utility\Registry::setOnce(
1493                                 'Service.' . $this->meta['Base'] . '.Context',
1494                                 $contract->data->Context
1495                                 );
1496
1497                 #-> Execute before.
1498                 $record = $this->em->getRepository($this->meta['Entity'])
1499                         ->find($contract->data->id);
1500                 $base = $this->meta['Base'];
1501                 $data = isset($contract->data->$base)
1502                         ? $contract->data->$base
1503                         : array();
1504                 if (isset($meta['ExecuteBefore']))
1505                 {
1506                         foreach ($meta['ExecuteBefore'] as $action)
1507                         {
1508                                 $data = $this->$action($meta, $record, $contract, $data);
1509                         }
1510                 }
1511
1512                 #-> Handle related entity updates.
1513                 if (isset($meta['RelatedEntityFromInput']))
1514                 {
1515                         foreach ($meta['RelatedEntityFromInput'] as $context => $relation)
1516                         {
1517                                 $workflow = $relation['Workflow'];
1518                                 $service  = $relation['Service'];
1519                                 $field    = $relation['Field'];
1520                                 $service  = new $service();
1521                                 $service->setWorkflow(new $workflow());
1522                                 $service->delete($record->$field->id);
1523                         }
1524                 }
1525
1526                 #-> Delete/Archive entry.
1527                 $this->delete($contract->data->id, $record);
1528
1529                 #-> Handle post execution actions.
1530                 if (isset($meta['ExecuteAfter']))
1531                 {
1532                         foreach ($meta['ExecuteAfter'] as $action)
1533                         {
1534                                 $this->$action($meta, $jobRecord, $record, $contract);
1535                         }
1536                 }
1537
1538                 #-> Done.
1539                 return $contract->success('Entry deleted.', array(
1540                                 'id' => $contract->data->id
1541                 ));
1542         }
1543
1544         /**
1545          * Delete an entry.
1546          * @param integer $id
1547          * @param object|null $record
1548          * @return boolean
1549          */
1550         public function delete($id, $record = null)
1551         {
1552                 #-> Delete/Archive entry.
1553                 $base = $this->meta['Base'];
1554                 $baseEntity = $this->meta['Entity'];
1555                 $record = is_null($record)
1556                         ? $this->em->getReference($this->meta['Entity'], $id)
1557                         : $record;
1558                 if ($baseEntity::ARCHIVE)
1559                 {
1560                         $record->archived = true;
1561                 }
1562                 else
1563                 {
1564                         $this->em->remove($record);
1565                 }
1566                 $this->em->flush();
1567
1568                 #-> Broadcast the data change.
1569                 /*switch ($baseEntity::PUSH_SYNCH_STRATEGY)
1570                 {
1571                         case 'Build':
1572                                 \Utility\Comms\Ape::broadcastBuildDatasetChange($this->meta['DatasetName']);
1573                                 break;
1574                         case 'Update':
1575                                 if (defined($baseEntity . '::JOB_QUEUE'))
1576                                 {
1577                                         $record = $this->em->getRepository($this->meta['Entity'])
1578                                                 ->find($id);
1579                                         if (1 != $record->queueStatus)
1580                                         {
1581                                                 break;
1582                                         }
1583                                 }
1584                                 \Utility\Comms\Ape::broadcastUpdateDatasetChange(
1585                                                 $this->meta['DatasetName'],
1586                                                 \Utility\Comms\Ape::CHANGE_TYPE_DELETE,
1587                                                 array(array('id' => $id))
1588                                 );
1589                                 break;
1590                 }*/
1591                 \Utility\Event::trigger($this->meta['Base'] . '.Delete', $record);
1592                 return true;
1593         }
1594
1595
1596         /**
1597          * Contract to undelete an entry.
1598          * @param array $meta
1599          * @param object|null $jobRecord
1600          * @param array $input
1601          * @return \Workspace\Contract\AbstractBase
1602          * @throws \Exception
1603          */
1604         public function conUnDelete($meta, $jobRecord, array $input = array())
1605         {
1606                 #-> Check permissions.
1607                 $this->checkPermissions($meta);
1608
1609                 #-> Safety checks.
1610                 if (isset($this->meta['JobField']) && is_null($jobRecord))
1611                 {
1612                         throw new \Exception('`JobId` root parameter is required for this contract.');
1613                 }
1614                 if (!isset($input['id']) || !is_numeric($input['id']) || 0 == $input['id'])
1615                 {
1616                         throw new \Exception('A valid record `id` is required to setup the contract.');
1617                 }
1618                 $entityName = '\\' == substr($this->meta['Entity'], 0, 1)
1619                         ? substr($this->meta['Entity'], 1)
1620                         : $this->meta['Entity'];
1621                 $record = $this->em
1622                         ->getRepository($entityName)
1623                         ->find($input['id']);
1624                 if (is_null($record))
1625                 {
1626                         throw new \Exception('Could not find record.');
1627                 }
1628                 if (isset($this->meta['JobField']))
1629                 {
1630                         $field = $this->meta['JobField'];
1631                         if ($record->$field->id != $jobRecord->id)
1632                         {
1633                                 throw new \Exception('This record does not belong to the specified JobId.');
1634                         }
1635                 }
1636
1637                 #-> Build contract.
1638                 $options = $this->buildOptions($meta);
1639                 $requirement = $this->buildRequirements($meta);
1640                 $contract = (isset($meta['Contract']) && self::CONTRACT_TYPE_RECCURING == $meta['Contract'])
1641                         ? new \Workspace\Contract\Recurring($options, $requirement)
1642                         : new \Workspace\Contract\UseOnce($options, $requirement);
1643                 $contract = $this->buildConditions($contract, $meta);
1644                 return $contract->setData($record->toArray(array(), array(), true));
1645         }
1646
1647         /**
1648          * UnDelete an entry.
1649          * @param array $meta
1650          * @param object|null $jobRecord
1651          * @param \Workspace\Utility\ServiceInputParams $contract
1652          * @return array
1653          */
1654         public function exeUnDelete($meta, $jobRecord, \Workspace\Utility\ServiceInputParams $contract)
1655         {
1656                 #-> Context data publication.
1657                 isset($contract->data->Context)
1658                         && \Utility\Registry::setOnce(
1659                                 'Service.' . $this->meta['Base'] . '.Context',
1660                                 $contract->data->Context
1661                                 );
1662
1663                 #-> Handle related entity updates.
1664                 $entityName = '\\' == substr($this->meta['Entity'], 0, 1)
1665                         ? substr($this->meta['Entity'], 1)
1666                         : $this->meta['Entity'];
1667                 $record = $this->em->getRepository($entityName)->find($contract->data->id);
1668                 if (isset($meta['RelatedEntityFromInput']))
1669                 {
1670                         foreach ($meta['RelatedEntityFromInput'] as $context => $relation)
1671                         {
1672                                 $workflow = $relation['Workflow'];
1673                                 $service  = $relation['Service'];
1674                                 $field    = $relation['Field'];
1675                                 $service  = new $service();
1676                                 $service->setWorkflow(\Utility\Registry::getServiceManager()->get($workflow));
1677                                 $service->undelete($record->$field->id);
1678                         }
1679                 }
1680
1681                 #-> Delete/Archive entry.
1682                 $this->undelete($contract->data->id, $record);
1683
1684                 #-> Handle post execution actions.
1685                 if (isset($meta['ExecuteAfter']))
1686                 {
1687                         foreach ($meta['ExecuteAfter'] as $action)
1688                         {
1689                                 $this->$action($meta, $jobRecord, $record, $contract);
1690                         }
1691                 }
1692
1693                 #-> Done.
1694                 return $contract->success('Entry deleted.', array(
1695                                 'id' => $contract->data->id
1696                 ));
1697         }
1698
1699         /**
1700          * UnDelete an entry.
1701          * @param integer $id
1702          * @param object|null $record
1703          * @return boolean
1704          */
1705         public function undelete($id, $record = null)
1706         {
1707                 #-> Delete/Archive entry.
1708                 $base = $this->meta['Base'];
1709                 $baseEntity = $this->meta['Entity'];
1710                 $this->em->clear();
1711                 $record = $this->em->getRepository($this->meta['Entity'])->find($id);
1712                 $record->archived = false;
1713                 $this->em->flush();
1714
1715                 #-> Clear cache.
1716                 $base = $this->meta['Base'];
1717
1718                 #-> Broadcast the data change.
1719                 /*switch ($baseEntity::PUSH_SYNCH_STRATEGY)
1720                 {
1721                         case 'Build':
1722                                 \Utility\Comms\Ape::broadcastBuildDatasetChange($this->meta['DatasetName']);
1723                                 break;
1724                         case 'Update':
1725                                 if (defined($baseEntity . '::JOB_QUEUE'))
1726                                 {
1727                                         $record = $this->em->getRepository($this->meta['Entity'])
1728                                                 ->find($id);
1729                                         if (1 != $record->queueStatus)
1730                                         {
1731                                                 break;
1732                                         }
1733                                 }
1734                                 \Utility\Comms\Ape::broadcastUpdateDatasetChange(
1735                                                 $this->meta['DatasetName'],
1736                                                 \Utility\Comms\Ape::CHANGE_TYPE_UNDELETE,
1737                                                 array(array('id' => $id))
1738                                 );
1739                                 break;
1740                 }*/
1741                 $action = $baseEntity::ARCHIVE
1742                         ? '.UnArchive'
1743                         : '.UnDelete';
1744                 \Utility\Event::trigger($this->meta['Base'] . $action, $record);
1745                 return true;
1746         }
1747
1748
1749         /**
1750          * Contract to build a report.
1751          * @param array $meta
1752          * @param object|null $jobRecord
1753          * @param array $input
1754          * @return \Workspace\Contract\AbstractBase
1755          * @throws \Exception
1756          */
1757         public function conReport($meta, $jobRecord, array $input = array())
1758         {
1759                 #-> Build contract.
1760                 if (isset($meta['RequireAuth'])
1761                         && true == $meta['RequireAuth']
1762                         && !\Utility\Registry::isAuthenticated())
1763                 {
1764                         throw new \Exception('Authentication required for this functionality.');
1765                 }
1766                 if (isset($this->meta['JobField']) && is_null($jobRecord))
1767                 {
1768                         throw new \Exception('`JobId` root parameter is required for this contract.');
1769                 }
1770                 $options     = $this->buildOptions($meta);
1771                 $requirement = $this->buildRequirements($meta);
1772                 $contract    = new \Workspace\Contract\Recurring($options, $requirement);
1773                 return $this->buildConditions($contract, $meta);
1774         }
1775
1776         /**
1777          * Build a report
1778          * @param array $meta
1779          * @param object|null $jobRecord
1780          * @param \Workspace\Utility\ServiceInputParams $contract
1781          * @return array
1782          * @throws \Exception
1783          */
1784         public function exeReport($meta, $jobRecord, \Workspace\Utility\ServiceInputParams $contract)
1785         {
1786                 #-> Build Report.
1787                 if (isset($meta['RequireAuth'])
1788                         && true == $meta['RequireAuth']
1789                         && !\Utility\Registry::isAuthenticated())
1790                 {
1791                         throw new \Exception('Authentication required for this functionality.');
1792                 }
1793                 $options = $contract->options;
1794                 $input = isset($contract->data->Report)
1795                         ? $contract->data->Report
1796                         : array();
1797                 if (isset($this->meta['JobField']))
1798                 {
1799                         $input[$this->meta['JobField']] = $jobRecord->id;
1800                 }
1801                 $reportService = $meta['Builder'];
1802                 $report = new $reportService();
1803                 $report->process($input, $options);
1804                 $reportWriter  = $meta['Writer'];
1805
1806                 #-> Output.
1807                 $output = isset($meta['Output'])
1808                         ? $meta['Output']
1809                         : 'Download';
1810                 if ('Download' == $output)
1811                 {
1812                         $writer = new $reportWriter($report);
1813                         $writer->output();
1814                         exit(0);
1815                 }
1816                 elseif ('File' == $output)
1817                 {
1818                         $writer = new $reportWriter($report);
1819                         $writer->output($meta['FilePath']);
1820                         return $contract->success('Report saved to file.');
1821                 }
1822                 elseif ('Raw' == $output)
1823                 {
1824                         return $contract->success('Report Generated', array(
1825                                                 'Title'        => $report->getTitle(),
1826                                                 'Description'  => $report->getDescription(),
1827                                                 'QueryDetails' => $report->getQueries(),
1828                                                 'Headers'      => $report->getHeaders(),
1829                                                 'Fields'       => $report->getFields(),
1830                                                 'TotalFields'  => $report->getTotalFields(),
1831                                                 'Notes'        => $report->getNotes(),
1832                                                 'Data'         => $report->getDataIntersection()
1833                                                 ));
1834                 }
1835                 else
1836                 {
1837                         $writer = new $reportWriter($report);
1838                         return $contract->success('Report Generated', $writer->output());
1839                 }
1840         }
1841
1842
1843         /**
1844          * Contract to build a report.
1845          * @param array $meta
1846          * @param object|null $jobRecord
1847          * @param array $input
1848          * @return \Workspace\Contract\AbstractBase
1849          * @throws \Exception
1850          */
1851         public function conPdf($meta, $jobRecord, array $input = array())
1852         {
1853                 #-> Build contract.
1854                 if (isset($meta['RequireAuth'])
1855                         && true == $meta['RequireAuth']
1856                         && !\Utility\Registry::isAuthenticated())
1857                 {
1858                         throw new \Exception('Authentication required for this functionality.');
1859                 }
1860                 if (isset($this->meta['JobField']) && is_null($jobRecord))
1861                 {
1862                         throw new \Exception('`JobId` root parameter is required for this contract.');
1863                 }
1864                 $options     = $this->buildOptions($meta);
1865                 $requirement = $this->buildRequirements($meta);
1866                 $contract    = new \Workspace\Contract\Recurring($options, $requirement);
1867                 return $this->buildConditions($contract, $meta);
1868         }
1869
1870         /**
1871          * Build a report
1872          * @param array $meta
1873          * @param object|null $jobRecord
1874          * @param \Workspace\Utility\ServiceInputParams $contract
1875          * @return array
1876          * @throws \Exception
1877          */
1878         public function exePdf($meta, $jobRecord, \Workspace\Utility\ServiceInputParams $contract)
1879         {
1880                 #-> Build Report.
1881                 if (isset($meta['RequireAuth'])
1882                         && true == $meta['RequireAuth']
1883                         && !\Utility\Registry::isAuthenticated())
1884                 {
1885                         throw new \Exception('Authentication required for this functionality.');
1886                 }
1887                 $options = $contract->options;
1888                 $input = isset($contract->data->Report)
1889                         ? $contract->data->Report
1890                         : array();
1891                 $input['jobRecord'] = $jobRecord;
1892         $input['contract'] = $contract->data;
1893                 if (isset($this->meta['JobField']))
1894                 {
1895                         $input[$this->meta['JobField']] = $jobRecord->id;
1896                 }
1897                 $pdfService = $meta['Builder'];
1898                 $pdf = new $pdfService();
1899                 $pdf->process($input, $options);
1900                 $pdfWriter  = $meta['Writer'];
1901
1902                 #-> Output.
1903                 $output = isset($meta['Output'])
1904                         ? $meta['Output']
1905                         : 'Download';
1906                 if ('Download' == $output)
1907                 {
1908                         $writer = new $pdfWriter($pdf);
1909                         $writer->output();
1910                         exit(0);
1911                 }
1912                 elseif ('File' == $output)
1913                 {
1914                         $writer = new $pdfWriter($pdf);
1915                         file_put_contents($meta['FilePath'], $writer->output(''));
1916                         return $contract->success('PDF saved to file.');
1917                 }
1918                 elseif ('Raw' == $output)
1919                 {
1920                         return $contract->success('PDF Generated', array(
1921                                                 'Title' => $pdf->getTitle(),
1922                                                 'HTML'  => $pdf->getHtml()
1923                                                 ));
1924                 }
1925                 else
1926                 {
1927                         $writer = new $pdfWriter($pdf);
1928                         return $contract->success('PDF Generated', $writer->output());
1929                 }
1930         }
1931
1932 }