Merge pull request #15877 from demeritcowboy/casereport-unittest
[civicrm-core.git] / CRM / Core / Selector / Controller.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * This class is a generic class to be used when we want to display
15 * a list of rows along with a set of associated actions
16 *
17 * Centralizing this code enables us to write a generic lister and enables
18 * us to automate the export process. To use this class, the object has to
19 * implement the Selector/Api.interface.php class
20 *
21 * @package CRM
22 * @copyright CiviCRM LLC https://civicrm.org/licensing
23 * $Id$
24 *
25 */
26 class CRM_Core_Selector_Controller {
27
28 /**
29 * Constants to determine if we should store
30 * the output in the session or template
31 * @var int
32 */
33 // move the values from the session to the template
34 const SESSION = 1, TEMPLATE = 2,
35 TRANSFER = 4, EXPORT = 8, SCREEN = 16, PDF = 32;
36
37 /**
38 * A CRM Object that implements CRM_Core_Selector_API.
39 * @var object
40 */
41 protected $_object;
42
43 /**
44 * @var CRM_Utils_Sort
45 */
46 protected $_sort;
47
48 /**
49 * The current column to sort on
50 * @var int
51 */
52 protected $_sortID;
53
54 /**
55 * The sortOrder array
56 * @var array
57 */
58 protected $_sortOrder;
59
60 /**
61 * @var CRM_Utils_Pager
62 */
63 protected $_pager;
64
65 /**
66 * The pageID
67 * @var int
68 */
69 protected $_pageID;
70
71 /**
72 * Offset
73 * @var int
74 */
75 protected $_pagerOffset;
76
77 /**
78 * Number of rows to return
79 * @var int
80 */
81 protected $_pagerRowCount;
82
83 /**
84 * Total number of rows
85 * @var int
86 */
87 protected $_total;
88
89 /**
90 * The objectAction for the WebObject
91 * @var int
92 */
93 protected $_action;
94
95 /**
96 * This caches the content for the display system
97 *
98 * @var string
99 */
100 protected $_content;
101
102 /**
103 * Is this object being embedded in another object. If
104 * so the display routine needs to not do any work. (The
105 * parent object takes care of the display)
106 *
107 * @var bool
108 */
109 protected $_embedded = FALSE;
110
111 /**
112 * Are we in print mode? if so we need to modify the display
113 * functionality to do a minimal display :)
114 *
115 * @var bool
116 */
117 protected $_print = FALSE;
118
119 /**
120 * The storage object (typically a form or a page)
121 *
122 * @var Object
123 */
124 protected $_store;
125
126 /**
127 * Output target, session, template or both?
128 *
129 * @var int
130 */
131 protected $_output;
132
133 /**
134 * Prefif for the selector variables
135 *
136 * @var int
137 */
138 protected $_prefix;
139
140 /**
141 * Cache the smarty template for efficiency reasons
142 *
143 * @var CRM_Core_Smarty
144 */
145 public static $_template;
146
147 /**
148 * Array of properties that the controller dumps into the output object
149 *
150 * @var array
151 */
152 public static $_properties = ['columnHeaders', 'rows', 'rowsEmpty'];
153
154 /**
155 * Should we compute actions dynamically (since they are quite verbose)
156 *
157 * @var bool
158 */
159 protected $_dynamicAction = FALSE;
160
161 /**
162 * Class constructor.
163 *
164 * @param CRM_Core_Selector_API $object
165 * An object that implements the selector API.
166 * @param int $pageID
167 * Default pageID.
168 * @param int $sortID
169 * Default sortID.
170 * @param int $action
171 * The actions to potentially support.
172 * @param CRM_Core_Page|CRM_Core_Form $store place in session to store some values
173 * @param int $output
174 * What do we so with the output, session/template//both.
175 *
176 * @param null $prefix
177 * @param null $case
178 *
179 * @return \CRM_Core_Selector_Controller
180 */
181 public function __construct($object, $pageID, $sortID, $action, $store = NULL, $output = self::TEMPLATE, $prefix = NULL, $case = NULL) {
182
183 $this->_object = $object;
184 $this->_pageID = $pageID ? $pageID : 1;
185 $this->_sortID = $sortID ? $sortID : NULL;
186 $this->_action = $action;
187 $this->_store = $store;
188 $this->_output = $output;
189 $this->_prefix = $prefix;
190 $this->_case = $case;
191
192 // fix sortID
193 if ($this->_sortID && strpos($this->_sortID, '_') === FALSE) {
194 $this->_sortID .= '_u';
195 }
196
197 $params = [
198 'pageID' => $this->_pageID,
199 ];
200
201 // let the constructor initialize this, should happen only once
202 if (!isset(self::$_template)) {
203 self::$_template = CRM_Core_Smarty::singleton();
204 }
205
206 $this->_sortOrder = &$this->_object->getSortOrder($action);
207 $this->_sort = new CRM_Utils_Sort($this->_sortOrder, $this->_sortID);
208
209 /*
210 * if we are in transfer mode, do not goto database, use the
211 * session values instead
212 */
213
214 if ($output == self::TRANSFER) {
215 $params['total'] = $this->_store->get($this->_prefix . 'rowCount');
216 }
217 else {
218 $params['total'] = $this->_object->getTotalCount($action, $this->_case);
219 }
220
221 $this->_total = $params['total'];
222 $this->_object->getPagerParams($action, $params);
223
224 /*
225 * Set the default values of RowsPerPage
226 */
227
228 $storeRowCount = $store->get($this->_prefix . CRM_Utils_Pager::PAGE_ROWCOUNT);
229 if ($storeRowCount) {
230 $params['rowCount'] = $storeRowCount;
231 }
232 elseif (!isset($params['rowCount'])) {
233 $params['rowCount'] = CRM_Utils_Pager::ROWCOUNT;
234 }
235
236 $this->_pager = new CRM_Utils_Pager($params);
237 list($this->_pagerOffset, $this->_pagerRowCount) = $this->_pager->getOffsetAndRowCount();
238 }
239
240 /**
241 * Have the GET vars changed, i.e. pageId or sortId that forces us to recompute the search values
242 *
243 * @param int $reset
244 * Are we being reset.
245 *
246 * @return bool
247 * if the GET params are different from the session params
248 */
249 public function hasChanged($reset) {
250
251 /**
252 * if we are in reset state, i.e the store is cleaned out, we return false
253 * we also return if we dont have a record of the sort id or page id
254 */
255 if ($reset ||
256 $this->_store->get($this->_prefix . CRM_Utils_Pager::PAGE_ID) == NULL ||
257 $this->_store->get($this->_prefix . CRM_Utils_Sort::SORT_ID) == NULL
258 ) {
259 return FALSE;
260 }
261
262 if ($this->_store->get($this->_prefix . CRM_Utils_Pager::PAGE_ID) != $this->_pager->getCurrentPageID() ||
263 $this->_store->get($this->_prefix . CRM_Utils_Sort::SORT_ID) != $this->_sort->getCurrentSortID() ||
264 $this->_store->get($this->_prefix . CRM_Utils_Sort::SORT_DIRECTION) != $this->_sort->getCurrentSortDirection()
265 ) {
266 return TRUE;
267 }
268 return FALSE;
269 }
270
271 /**
272 * Heart of the Controller. This is where all the action takes place
273 *
274 * - The rows are fetched and stored depending on the type of output needed
275 *
276 * - For export/printing all rows are selected.
277 *
278 * - for displaying on screen paging parameters are used to display the
279 * required rows.
280 *
281 * - also depending on output type of session or template rows are appropriately stored in session
282 * or template variables are updated.
283 *
284 *
285 * @return void
286 */
287 public function run() {
288
289 // get the column headers
290 $columnHeaders = &$this->_object->getColumnHeaders($this->_action, $this->_output);
291
292 $contextArray = explode('_', get_class($this->_object));
293
294 $contextName = strtolower($contextArray[1]);
295
296 // fix contribute and member
297 if ($contextName == 'contribute') {
298 $contextName = 'contribution';
299 }
300 elseif ($contextName == 'member') {
301 $contextName = 'membership';
302 }
303
304 // we need to get the rows if we are exporting or printing them
305 if ($this->_output == self::EXPORT || $this->_output == self::SCREEN) {
306 // get rows (without paging criteria)
307 $rows = self::getRows($this);
308 CRM_Utils_Hook::searchColumns($contextName, $columnHeaders, $rows, $this);
309 if ($this->_output == self::EXPORT) {
310 // export the rows.
311 CRM_Core_Error::deprecatedFunctionWarning('This code is believed to be unreachable & to be later removed. Please log how you reached it in gitlab');
312 CRM_Core_Report_Excel::writeCSVFile($this->_object->getExportFileName(),
313 $columnHeaders,
314 $rows
315 );
316 CRM_Utils_System::civiExit();
317 }
318 else {
319 // assign to template and display them.
320 self::$_template->assign_by_ref('rows', $rows);
321 self::$_template->assign_by_ref('columnHeaders', $columnHeaders);
322 }
323 }
324 else {
325 // output requires paging/sorting capability
326 $rows = self::getRows($this);
327 CRM_Utils_Hook::searchColumns($contextName, $columnHeaders, $rows, $this);
328 $reorderedHeaders = [];
329 $noWeightHeaders = [];
330 foreach ($columnHeaders as $key => $columnHeader) {
331 // So far only contribution selector sets weight, so just use key if not.
332 // Extension writers will need to fix other getColumnHeaders (or add a wrapper)
333 // to extend.
334 if (isset($columnHeader['weight'])) {
335 $reorderedHeaders[$columnHeader['weight']] = $columnHeader;
336 }
337 else {
338 $noWeightHeaders[$key] = $columnHeader;
339 }
340 }
341 ksort($reorderedHeaders);
342 // Merge headers not containing weight to ordered headers
343 $finalColumnHeaders = array_merge($reorderedHeaders, $noWeightHeaders);
344
345 $rowsEmpty = count($rows) ? FALSE : TRUE;
346 $qill = $this->getQill();
347 $summary = $this->getSummary();
348 // if we need to store in session, lets update session
349 if ($this->_output & self::SESSION) {
350 $this->_store->set("{$this->_prefix}columnHeaders", $finalColumnHeaders);
351 if ($this->_dynamicAction) {
352 $this->_object->removeActions($rows);
353 }
354 $this->_store->set("{$this->_prefix}rows", $rows);
355 $this->_store->set("{$this->_prefix}rowCount", $this->_total);
356 $this->_store->set("{$this->_prefix}rowsEmpty", $rowsEmpty);
357 $this->_store->set("{$this->_prefix}qill", $qill);
358 $this->_store->set("{$this->_prefix}summary", $summary);
359 }
360 else {
361 self::$_template->assign_by_ref("{$this->_prefix}pager", $this->_pager);
362 self::$_template->assign_by_ref("{$this->_prefix}sort", $this->_sort);
363
364 self::$_template->assign_by_ref("{$this->_prefix}columnHeaders", $finalColumnHeaders);
365 self::$_template->assign_by_ref("{$this->_prefix}rows", $rows);
366 self::$_template->assign("{$this->_prefix}rowsEmpty", $rowsEmpty);
367 self::$_template->assign("{$this->_prefix}qill", $qill);
368 self::$_template->assign("{$this->_prefix}summary", $summary);
369 }
370
371 // always store the current pageID and sortID
372 $this->_store->set($this->_prefix . CRM_Utils_Pager::PAGE_ID,
373 $this->_pager->getCurrentPageID()
374 );
375 $this->_store->set($this->_prefix . CRM_Utils_Sort::SORT_ID,
376 $this->_sort->getCurrentSortID()
377 );
378 $this->_store->set($this->_prefix . CRM_Utils_Sort::SORT_DIRECTION,
379 $this->_sort->getCurrentSortDirection()
380 );
381 $this->_store->set($this->_prefix . CRM_Utils_Sort::SORT_ORDER,
382 $this->_sort->orderBy()
383 );
384 $this->_store->set($this->_prefix . CRM_Utils_Pager::PAGE_ROWCOUNT,
385 $this->_pager->_perPage
386 );
387 }
388 }
389
390 /**
391 * Retrieve rows.
392 *
393 * @param CRM_Core_Form $form
394 *
395 * @return array
396 * Array of rows
397 */
398 public function getRows($form) {
399 if ($form->_output == self::EXPORT || $form->_output == self::SCREEN) {
400 //get rows (without paging criteria)
401 return $form->_object->getRows($form->_action, 0, 0, $form->_sort, $form->_output);
402 }
403 else {
404 return $form->_object->getRows($form->_action, $form->_pagerOffset, $form->_pagerRowCount,
405 $form->_sort, $form->_output, $form->_case
406 );
407 }
408 }
409
410 /**
411 * Default function for qill, if needed to be implemented, we
412 * expect the subclass to do it
413 *
414 * @return string
415 * the status message
416 */
417 public function getQill() {
418 return $this->_object->getQill();
419 }
420
421 /**
422 * @return mixed
423 */
424 public function getSummary() {
425 return $this->_object->getSummary();
426 }
427
428 /**
429 * Getter for pager.
430 *
431 * @return CRM_Utils_Pager
432 */
433 public function getPager() {
434 return $this->_pager;
435 }
436
437 /**
438 * Getter for sort.
439 *
440 * @return CRM_Utils_Sort
441 */
442 public function getSort() {
443 return $this->_sort;
444 }
445
446 /**
447 * Move the variables from the session to the template.
448 *
449 * @return void
450 */
451 public function moveFromSessionToTemplate() {
452 self::$_template->assign_by_ref("{$this->_prefix}pager", $this->_pager);
453
454 $rows = $this->_store->get("{$this->_prefix}rows");
455
456 if ($rows) {
457 if ($this->_dynamicAction) {
458 $this->_object->addActions($rows);
459 }
460
461 self::$_template->assign("{$this->_prefix}aToZ",
462 $this->_store->get("{$this->_prefix}AToZBar")
463 );
464 }
465
466 self::$_template->assign_by_ref("{$this->_prefix}sort", $this->_sort);
467 self::$_template->assign("{$this->_prefix}columnHeaders", $this->_store->get("{$this->_prefix}columnHeaders"));
468 self::$_template->assign("{$this->_prefix}rows", $rows);
469 self::$_template->assign("{$this->_prefix}rowsEmpty", $this->_store->get("{$this->_prefix}rowsEmpty"));
470 self::$_template->assign("{$this->_prefix}qill", $this->_store->get("{$this->_prefix}qill"));
471 self::$_template->assign("{$this->_prefix}summary", $this->_store->get("{$this->_prefix}summary"));
472
473 if ($this->_embedded) {
474 return;
475 }
476
477 self::$_template->assign('tplFile', $this->_object->getHookedTemplateFileName());
478 if ($this->_print) {
479 $content = self::$_template->fetch('CRM/common/print.tpl');
480 }
481 else {
482 $config = CRM_Core_Config::singleton();
483 $content = self::$_template->fetch('CRM/common/' . strtolower($config->userFramework) . '.tpl');
484 }
485 echo CRM_Utils_System::theme($content, $this->_print);
486 }
487
488 /**
489 * Setter for embedded.
490 *
491 * @param bool $embedded
492 *
493 * @return void
494 */
495 public function setEmbedded($embedded) {
496 $this->_embedded = $embedded;
497 }
498
499 /**
500 * Getter for embedded.
501 *
502 * @return bool
503 * return the embedded value
504 */
505 public function getEmbedded() {
506 return $this->_embedded;
507 }
508
509 /**
510 * Setter for print.
511 *
512 * @param bool $print
513 *
514 * @return void
515 */
516 public function setPrint($print) {
517 $this->_print = $print;
518 }
519
520 /**
521 * Getter for print.
522 *
523 * @return bool
524 * return the print value
525 */
526 public function getPrint() {
527 return $this->_print;
528 }
529
530 /**
531 * @param $value
532 */
533 public function setDynamicAction($value) {
534 $this->_dynamicAction = $value;
535 }
536
537 /**
538 * @return bool
539 */
540 public function getDynamicAction() {
541 return $this->_dynamicAction;
542 }
543
544 }