MultipleActionController.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2020 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of the MIT License.
7  *
8  * See the LICENSE file distributed with this work for
9  * additional information.
10  */
12 
17 
18 /**
19  * MultipleActionController executes multiple actions by passing them to the
20  * appropriate controllers and returning all results at once.
21  *
22  * The controller supports the following actions:
23  *
24  * <div class="controller-action">
25  * <div> __Action__ _default_ </div>
26  * <div>
27  * Execute the given actions.
28  * | Parameter | Description
29  * |------------------------|-------------------------
30  * | _in_ `data` | An associative array with unique/sortable keys and values that describe an action to perform
31  * | _out_ `data` | An associative array with the same keys and values that describe the response of each action
32  * | __Response Actions__ | |
33  * | `ok` | In all cases
34  * </div>
35  * </div>
36  *
37  * The data array may contain the following special variables, that will be replaced by the described values:
38  * - `{last_created_oid:type}` will be replaced by the oid lastly created object of the given type
39  *
40  * An example of input data in JSON:
41  * @code
42  data: {
43  action1: {
44  action: "create",
45  params: {
46  oid: "Author:wcmffb298f3784dd49548a05d43d7bf88590",
47  name: "Ingo Herwig"
48  }
49  },
50  action2: {
51  action: "read",
52  params: {
53  oid: "{last_created_oid:Author}"
54  }
55  }
56  }
57  * @endcode
58  *
59  * The output data for the preceding request could look like
60  * @code
61  data: {
62  action1: {
63  oid: "Author:123",
64  ...
65  },
66  action2: {
67  object: {
68  oid: "Author:123",
69  modified: "2001-01-01 01:01",
70  creator: "admin"
71  ...
72  }
73  }
74  }
75  * @endcode
76  *
77  * @author ingo herwig <ingo@wemove.com>
78  */
80 
81  /**
82  * @see Controller::validate()
83  */
84  protected function validate() {
85  // check if we have an array of arrays
86  $request = $this->getRequest();
87  $response = $this->getResponse();
88  if ($request->hasValue('data')) {
89  $data = $request->getValue('data');
90  foreach($data as $key => $value) {
91  if (!is_array($value)) {
92  $response->addError(ApplicationError::get('PARAMETER_INVALID',
93  ['invalidParameters' => ['data.'.$key]]));
94  return false;
95  }
96  }
97  }
98  else {
99  $response->addError(ApplicationError::get('PARAMETER_MISSING',
100  ['missingParameters' => ['data']]));
101  return false;
102  }
103  return true;
104  }
105 
106  /**
107  * @see Controller::doExecute()
108  */
109  protected function doExecute($method=null) {
110  // create and execute requests for the actions given in data
111  $request = $this->getRequest();
112  $response = $this->getResponse();
113  $logger = $this->getLogger();
114 
115  $results = [];
116  $data = $request->getValue('data');
117  $actions = array_keys($data);
118  $numActions = sizeof($actions);
119  $exceptions = [];
120 
121  for($i=0; $i<$numActions; $i++) {
122  $actionId = $actions[$i];
123  if ($logger->isDebugEnabled()) {
124  $logger->debug("processing action: ".$actionId.":\n".StringUtil::getDump($data[$actionId]));
125  }
126  // replace special variables
127  $this->replaceVariables($data[$actionId]);
128 
129  // create the request
130  $actionData = $data[$actionId];
131  $context = isset($actionData['context']) ? $actionData['context'] : '';
132  $action = isset($actionData['action']) ? $actionData['action'] : '';
133  // since serializer may replace the params key by the actual
134  // node data, we use actionData as fallback
135  $params = isset($actionData['params']) ? $actionData['params'] : $actionData;
136  $requestPart = ObjectFactory::getNewInstance('request');
137  $requestPart->setContext($context);
138  $requestPart->setAction($action);
139  $requestPart->setValues($params);
140  $requestPart->setFormat('null');
141  $requestPart->setResponseFormat('null');
142  $responsePart = ObjectFactory::getNewInstance('response');
143 
144  // execute the request
145  try {
146  $this->getActionMapper()->processAction($requestPart, $responsePart);
147  }
148  catch (\Exception $ex) {
149  $exceptions[] = $ex;
150  }
151  if ($responsePart->hasErrors()) {
152  foreach ($responsePart->getErrors() as $error) {
153  $response->addError($error);
154  }
155  }
156 
157  // collect the result
158  $results[$actionId] = $responsePart != null ? $responsePart->getValues() : [];
159  }
160  if ($logger->isDebugEnabled()) {
161  $logger->debug($results);
162  }
163  // add errors from exceptions
164  foreach ($exceptions as $ex) {
165  $response->addError(ApplicationError::fromException($ex));
166  }
167  $response->setValue('data', $results);
168  $response->setAction('ok');
169  }
170 
171  /**
172  * Check the given data array for special variables to replace
173  * Variables have either the form 'variable_name' or 'variable_name:column_separated_parameters'
174  * @param $data A reference to the associative data array
175  */
176  private function replaceVariables(&$data) {
177  $logger = $this->getLogger();
178  $keys = array_keys($data);
179  for($i=0; $i<sizeof($keys); $i++) {
180  $key = $keys[$i];
181  $value = $data[$key];
182 
183  // replace variables
184  $newKey = $this->replaceVariablesString($key);
185  $newValue = $this->replaceVariablesString($value);
186 
187  // replace entry
188  if ($key != $newKey || $value != $newValue) {
189  if ($logger->isDebugEnabled()) {
190  if ($key != $newKey) {
191  $logger->debug("Replace $key by $newKey");
192  }
193  if ($value != $newValue) {
194  $logger->debug("Replace $value by $newValue");
195  }
196  }
197  unset($data[$key]);
198  $data[$newKey] = $newValue;
199  }
200  }
201  }
202 
203  /**
204  * Check the given string for special variables to replace
205  * Variables have either the form 'variable_name' or 'variable_name:column_separated_parameters'
206  * @param $value The string
207  * @return String
208  */
209  private function replaceVariablesString($value) {
210  if (!is_string($value)) {
211  return $value;
212  }
213  preg_match_all('/\{([^\{]+)\}/', $value, $variableMatches);
214  $variables = $variableMatches[1];
215  foreach($variables as $variable) {
216  preg_match('/^([^:]+)[:]*(.*)$/', $variable, $matches);
217  if (sizeof($matches > 0)) {
218  $variableName = $matches[1];
219  $parameters = $matches[2];
220  $persistenceFacade = $this->getPersistenceFacade();
221 
222  // last_created_oid
223  if ($variableName == 'last_created_oid') {
224  $type = $parameters;
225  if ($persistenceFacade->isKnownType($type)) {
226  $oid = $persistenceFacade->getLastCreatedOID($type);
227  $value = preg_replace("/{".$variable."}/", $oid, $value);
228  }
229  }
230 
231  // oid reference
232  if ($persistenceFacade->isKnownType($variableName)) {
233  $type = $variableName;
234  $oid = $persistenceFacade->getLastCreatedOID($type);
235  $value = $oid;
236  }
237  }
238  }
239  return $value;
240  }
241 }
242 ?>
static getDump($variable, $strlen=100, $width=25, $depth=10, $i=0, &$objects=[])
Get the dump of a variable as string.
Definition: StringUtil.php:29
static fromException(\Exception $ex)
Factory method for transforming an exception into an ApplicationError instance.
StringUtil provides support for string manipulation.
Definition: StringUtil.php:18
getLogger()
Get the Logger instance.
Definition: Controller.php:267
ApplicationError is used to signal errors that occur while processing a request.
static get($code, $data=null)
Factory method for retrieving a predefined error instance.
getActionMapper()
Get the ActionMapper instance.
Definition: Controller.php:299
getPersistenceFacade()
Get the PersistenceFacade instance.
Definition: Controller.php:283
getRequest()
Get the Request instance.
Definition: Controller.php:251
static getNewInstance($name, $dynamicConfiguration=[])
Application controllers.
Definition: namespaces.php:3
Controller is the base class of all controllers.
Definition: Controller.php:49
getResponse()
Get the Response instance.
Definition: Controller.php:259
MultipleActionController executes multiple actions by passing them to the appropriate controllers and...
ObjectFactory implements the service locator pattern by wrapping a Factory instance and providing sta...