ListController.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 
21 
22 /**
23  * ListController is used to load Node lists.
24  *
25  * The controller supports the following actions:
26  *
27  * <div class="controller-action">
28  * <div> __Action__ _default_ </div>
29  * <div>
30  * Load the specified list of Node instances.
31  * | Parameter | Description
32  * |------------------------|-------------------------
33  * | _in_ `className` | The entity type to list instances of
34  * | _in_ `limit` | The maximum number of instances to return. If omitted, all instances (beginning at the offset parameter) will be returned (optional)
35  * | _in_ `offset` | The index of the first instance to return, based on the current sorting. The index is 0-based. If omitted, 0 is assumed (optional)
36  * | _in_ `sortFieldName` | The field name to sort the list by. Must be one of the fields of the type selected by the className parameter. If omitted, the sorting is undefined (optional)
37  * | _in_ `sortDirection` | The direction to sort the list. Must be either _asc_ for ascending or _desc_ for descending (optional, default: _asc_)
38  * | _in_ `query` | A query condition encoded in RQL to be used with StringQuery::setRQLConditionString()
39  * | _in_ `translateValues` | Boolean whether list values should be translated to their display values (optional, default: _false_)
40  * | _in_ `completeObjects` | Boolean whether to return all object attributes or only the display values using NodeUtil::removeNonDisplayValues (optional, default: _false_)
41  * | _in_ `values` | Comma separated list of node values to return, if `completeObjects` is set to false (optional, default: display values)
42  * | _out_ `list` | Array of Node instances according to the given input parameters
43  * | _out_ `totalCount` | The total number of instances matching the passed parameters
44  * | __Response Actions__ | |
45  * | `ok` | In all cases
46  * </div>
47  * </div>
48  *
49  * @author ingo herwig <ingo@wemove.com>
50  */
51 class ListController extends Controller {
52 
53  /**
54  * @see Controller::validate()
55  */
56  protected function validate() {
57  $request = $this->getRequest();
58  $response = $this->getResponse();
59  if (!$request->hasValue('className') ||
60  !$this->getPersistenceFacade()->isKnownType($request->getValue('className')))
61  {
62  $response->addError(ApplicationError::get('PARAMETER_INVALID',
63  ['invalidParameters' => ['className']]));
64  return false;
65  }
66  // check for permission to read instances of className
67  if (!$this->getPermissionManager()->authorize($request->getValue('className'), '', PersistenceAction::READ)) {
68  $response->addError(ApplicationError::get('PERMISSION_DENIED'));
69  return false;
70  }
71  if($request->hasValue('limit') && intval($request->getValue('limit')) < 0) {
72  $this->getLogger()->warn(ApplicationError::get('LIMIT_NEGATIVE'));
73  }
74  // NOTE we can't check for sortFieldName here, because it might not belong to
75  // the main type, but to a related type.
76  if($request->hasValue('sortDirection')) {
77  $sortDirection = $request->getValue('sortDirection');
78  if (strtolower($sortDirection) != 'asc' && strtolower($sortDirection) != 'desc') {
79  $response->addError(ApplicationError::get('SORT_DIRECTION_UNKNOWN'));
80  return false;
81  }
82  }
83  if (!$this->checkLanguageParameter()) {
84  return false;
85  }
86  // NOTE we can't check for offset out of bounds here
87  // do default validation
88  return parent::validate();
89  }
90 
91  /**
92  * @see Controller::doExecute()
93  */
94  protected function doExecute($method=null) {
95  $request = $this->getRequest();
96  $permissionManager = $this->getPermissionManager();
97 
98  // get the query
99  $query = $request->hasValue('query') ? urldecode($request->getValue('query')) : null;
100 
101  // get objects using the paging parameters
102  $pagingInfo = null;
103  if ($request->hasValue('limit')) {
104  $pagingInfo = new PagingInfo($request->getValue('limit'));
105  $pagingInfo->setOffset($request->getValue('offset'));
106  }
107  $className = $request->getValue('className');
108 
109  // add sort term
110  $sortArray = null;
111  $orderBy = $request->getValue('sortFieldName');
112  if (strlen($orderBy) > 0) {
113  $sortArray = [$orderBy." ".$request->getValue('sortDirection')];
114  }
115  // get the objects
116  $objects = $this->getObjects($className, $query, $sortArray, $pagingInfo);
117 
118  // collect the nodes
119  $nodes = [];
120  for($i=0,$count=sizeof($objects); $i<$count; $i++) {
121  $curObject = $objects[$i];
122 
123  // check if we can read the object
124  if ($permissionManager->authorize($curObject->getOID(), '', PersistenceAction::READ)) {
125  $nodes[] = $curObject;
126  }
127  }
128  $totalCount = $pagingInfo != null ? $pagingInfo->getTotalCount() : sizeof($nodes);
129 
130  // translate all nodes to the requested language if requested
131  if ($this->isLocalizedRequest()) {
132  $localization = $this->getLocalization();
133  for ($i=0,$count=sizeof($nodes); $i<$count; $i++) {
134  $nodes[$i] = $localization->loadTranslation($nodes[$i], $request->getValue('language'), true, true);
135  }
136  }
137 
138  // allow subclasses to modify the model
139  $this->modifyModel($nodes);
140 
141  // assign response values
142  $response = $this->getResponse();
143  $response->setValue('list', $nodes);
144  $response->setValue('totalCount', $totalCount);
145 
146  // success
147  $response->setAction('ok');
148  }
149 
150  /**
151  * Get the object to display. The default implementation uses a StringQuery instance for the
152  * object retrieval. Subclasses may override this. If filter is an empty string, all nodes of the given
153  * type will be selected.
154  * @param $type The object type
155  * @param $queryCondition The query condition passed from the view (to be used with StringQuery).
156  * @param $sortArray An array of attributes to order by (with an optional ASC|DESC appended)
157  * @param $pagingInfo The current PagingInfo instance
158  * @return Array of Node instances
159  */
160  protected function getObjects($type, $queryCondition, $sortArray, $pagingInfo) {
161  $persistenceFacade = $this->getPersistenceFacade();
162  if (!$persistenceFacade->isKnownType($type)) {
163  return [];
164  }
165 
166  $request = $this->getRequest();
167  $response = $this->getResponse();
168  $query = new StringQuery($type);
169  $query->setRQLConditionString($queryCondition);
170  try {
171  $objects = $query->execute(BuildDepth::SINGLE, $sortArray, $pagingInfo);
172  }
173  catch (UnknownFieldException $ex) {
174  if ($ex->getField() == $request->getValue('sortFieldName')) {
175  $response->addError(ApplicationError::get('SORT_FIELD_UNKNOWN'));
176  }
177  }
178  if ($this->getLogger()->isDebugEnabled()) {
179  $this->getLogger()->debug("Load objects with query: ".$query->getLastQueryString());
180  }
181  return $objects;
182  }
183 
184  /**
185  * Modify the model passed to the view.
186  * @note subclasses will override this to implement special application requirements.
187  * @param $nodes A reference to the array of Node instances passed to the view
188  */
189  protected function modifyModel(&$nodes) {
190  $request = $this->getRequest();
191  // TODO: put this into subclass ListController
192 
193  // remove all attributes except for display values
194  if ($request->getBooleanValue('completeObjects', false) == false) {
195  for($i=0,$count=sizeof($nodes); $i<$count; $i++) {
196  $node = $nodes[$i];
197  $displayValues = $request->hasValue('values') ? array_map('trim', explode(',', $request->getValue('values'))) :
198  $node->getProperty('displayValues');
199  $valueNames = array_merge($node->getValueNames(), $node->getRelationNames());
200  foreach($valueNames as $name) {
201  if (!in_array($name, $displayValues)) {
202  $node->removeValue($name);
203  }
204  }
205  }
206  }
207  // render values
208  if ($request->getBooleanValue('translateValues', false) == true) {
210  }
211  }
212 }
213 ?>
getObjects($type, $queryCondition, $sortArray, $pagingInfo)
Get the object to display.
getPermissionManager()
Get the PermissionManager instance.
Definition: Controller.php:291
BuildDepth values are used to define the depth when loading object trees.
Definition: BuildDepth.php:19
StringQuery executes queries from a string representation.
Definition: StringQuery.php:53
getLogger()
Get the Logger instance.
Definition: Controller.php:267
ListController is used to load Node lists.
static translateValues(&$nodes, $language=null, $itemDelim=", ")
Translate all list values in a list of Nodes.
Definition: NodeUtil.php:249
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.
getPersistenceFacade()
Get the PersistenceFacade instance.
Definition: Controller.php:283
getRequest()
Get the Request instance.
Definition: Controller.php:251
UnknownFieldException signals an unknown field.
Application controllers.
Definition: namespaces.php:3
getLocalization()
Get the Localization instance.
Definition: Controller.php:307
modifyModel(&$nodes)
Modify the model passed to the view.
Controller is the base class of all controllers.
Definition: Controller.php:49
PagingInfo contains information about a paged list.
Definition: PagingInfo.php:18
isLocalizedRequest()
Check if the current request is localized.
Definition: Controller.php:366
getResponse()
Get the Response instance.
Definition: Controller.php:259
NodeUtil provides services for the Node class.
Definition: NodeUtil.php:28
PersistenceAction values are used to define actions on PersistentObject instances.
checkLanguageParameter()
Checks the language request parameter and adds an response error, if it is not contained in the Local...
Definition: Controller.php:381