These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Base "abstract" class creating reports on your data. |
||
4 | * |
||
5 | * Creating reports |
||
6 | * ================ |
||
7 | * |
||
8 | * Creating a new report is a matter overloading a few key methods |
||
9 | * |
||
10 | * {@link title()}: Return the title - i18n is your responsibility |
||
11 | * {@link description()}: Return the description - i18n is your responsibility |
||
12 | * {@link sourceQuery()}: Return a SS_List of the search results |
||
13 | * {@link columns()}: Return information about the columns in this report. |
||
14 | * {@link parameterFields()}: Return a FieldList of the fields that can be used to filter this |
||
15 | * report. |
||
16 | * |
||
17 | * If you wish to modify the report in more extreme ways, you could overload these methods instead. |
||
18 | * |
||
19 | * {@link getReportField()}: Return a FormField in the place where your report's TableListField |
||
20 | * usually appears. |
||
21 | * {@link getCMSFields()}: Return the FieldList representing the complete right-hand area of the |
||
22 | * report, including the title, description, parameter fields, and results. |
||
23 | * |
||
24 | * Showing reports to the user |
||
25 | * =========================== |
||
26 | * |
||
27 | * Right now, all subclasses of SS_Report will be shown in the ReportAdmin. In SS3 there is only |
||
28 | * one place where reports can go, so this class is greatly simplifed from its version in SS2. |
||
29 | * |
||
30 | * @package reports |
||
31 | */ |
||
32 | class SS_Report extends ViewableData |
||
33 | { |
||
34 | /** |
||
35 | * This is the title of the report, |
||
36 | * used by the ReportAdmin templates. |
||
37 | * |
||
38 | * @var string |
||
39 | */ |
||
40 | protected $title = ''; |
||
41 | |||
42 | /** |
||
43 | * This is a description about what this |
||
44 | * report does. Used by the ReportAdmin |
||
45 | * templates. |
||
46 | * |
||
47 | * @var string |
||
48 | */ |
||
49 | protected $description = ''; |
||
50 | |||
51 | /** |
||
52 | * The class of object being managed by this report. |
||
53 | * Set by overriding in your subclass. |
||
54 | */ |
||
55 | protected $dataClass = 'SiteTree'; |
||
56 | |||
57 | /** |
||
58 | * A field that specifies the sort order of this report |
||
59 | * @var int |
||
60 | */ |
||
61 | protected $sort = 0; |
||
62 | |||
63 | /** |
||
64 | * Reports which should not be collected and returned in get_reports |
||
65 | * @var array |
||
66 | */ |
||
67 | public static $excluded_reports = array( |
||
68 | 'SS_Report', |
||
69 | 'SS_ReportWrapper', |
||
70 | 'SideReportWrapper' |
||
71 | ); |
||
72 | |||
73 | /** |
||
74 | * Return the title of this report. |
||
75 | * |
||
76 | * You have two ways of specifying the description: |
||
77 | * - overriding description(), which lets you support i18n |
||
78 | * - defining the $description property |
||
79 | */ |
||
80 | public function title() |
||
81 | { |
||
82 | return $this->title; |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * Allows access to title as a property |
||
87 | * |
||
88 | * @return string |
||
89 | */ |
||
90 | public function getTitle() |
||
91 | { |
||
92 | return $this->title(); |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * Return the description of this report. |
||
97 | * |
||
98 | * You have two ways of specifying the description: |
||
99 | * - overriding description(), which lets you support i18n |
||
100 | * - defining the $description property |
||
101 | */ |
||
102 | public function description() |
||
103 | { |
||
104 | return $this->description; |
||
105 | } |
||
106 | |||
107 | /** |
||
108 | * Return the {@link SQLQuery} that provides your report data. |
||
109 | */ |
||
110 | public function sourceQuery($params) |
||
111 | { |
||
112 | if ($this->hasMethod('sourceRecords')) { |
||
113 | return $this->sourceRecords()->dataQuery(); |
||
114 | } else { |
||
115 | user_error("Please override sourceQuery()/sourceRecords() and columns() or, if necessary, override getReportField()", E_USER_ERROR); |
||
116 | } |
||
117 | } |
||
118 | |||
119 | /** |
||
120 | * Return a SS_List records for this report. |
||
121 | */ |
||
122 | public function records($params) |
||
123 | { |
||
124 | if ($this->hasMethod('sourceRecords')) { |
||
125 | return $this->sourceRecords($params, null, null); |
||
126 | } else { |
||
127 | $query = $this->sourceQuery(); |
||
128 | $results = new ArrayList(); |
||
129 | foreach ($query->execute() as $data) { |
||
130 | $class = $this->dataClass(); |
||
131 | $result = new $class($data); |
||
132 | $results->push($result); |
||
133 | } |
||
134 | return $results; |
||
135 | } |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * Return the data class for this report |
||
140 | */ |
||
141 | public function dataClass() |
||
142 | { |
||
143 | return $this->dataClass; |
||
144 | } |
||
145 | |||
146 | |||
147 | public function getLink($action = null) |
||
148 | { |
||
149 | return Controller::join_links( |
||
150 | 'admin/reports/', |
||
151 | "$this->class", |
||
152 | '/', // trailing slash needed if $action is null! |
||
153 | "$action" |
||
154 | ); |
||
155 | } |
||
156 | |||
157 | |||
158 | /** |
||
159 | * counts the number of objects returned |
||
160 | * @param Array $params - any parameters for the sourceRecords |
||
161 | * @return Int |
||
162 | */ |
||
163 | public function getCount($params = array()) |
||
164 | { |
||
165 | $sourceRecords = $this->sourceRecords($params, null, null); |
||
166 | if (!$sourceRecords instanceof SS_List) { |
||
167 | user_error($this->class."::sourceRecords does not return an SS_List", E_USER_NOTICE); |
||
168 | return "-1"; |
||
169 | } |
||
170 | return $sourceRecords->count(); |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Exclude certain reports classes from the list of Reports in the CMS |
||
175 | * @param $reportClass Can be either a string with the report classname or an array of reports classnames |
||
176 | */ |
||
177 | public static function add_excluded_reports($reportClass) |
||
178 | { |
||
179 | if (is_array($reportClass)) { |
||
180 | self::$excluded_reports = array_merge(self::$excluded_reports, $reportClass); |
||
181 | } else { |
||
182 | if (is_string($reportClass)) { |
||
183 | //add to the excluded reports, so this report doesn't get used |
||
184 | self::$excluded_reports[] = $reportClass; |
||
185 | } |
||
186 | } |
||
187 | } |
||
188 | |||
189 | /** |
||
190 | * Return an array of excluded reports. That is, reports that will not be included in |
||
191 | * the list of reports in report admin in the CMS. |
||
192 | * @return array |
||
193 | */ |
||
194 | public static function get_excluded_reports() |
||
195 | { |
||
196 | return self::$excluded_reports; |
||
197 | } |
||
198 | |||
199 | /** |
||
200 | * Return the SS_Report objects making up the given list. |
||
201 | * @return Array of SS_Report objects |
||
202 | */ |
||
203 | public static function get_reports() |
||
204 | { |
||
205 | $reports = ClassInfo::subclassesFor(get_called_class()); |
||
206 | |||
207 | $reportsArray = array(); |
||
208 | if ($reports && count($reports) > 0) { |
||
209 | //collect reports into array with an attribute for 'sort' |
||
210 | foreach ($reports as $report) { |
||
211 | if (in_array($report, self::$excluded_reports)) { |
||
212 | continue; |
||
213 | } //don't use the SS_Report superclass |
||
214 | $reflectionClass = new ReflectionClass($report); |
||
215 | if ($reflectionClass->isAbstract()) { |
||
216 | continue; |
||
217 | } //don't use abstract classes |
||
218 | |||
219 | $reportObj = new $report; |
||
220 | if (method_exists($reportObj, 'sort')) { |
||
221 | $reportObj->sort = $reportObj->sort(); |
||
222 | } //use the sort method to specify the sort field |
||
223 | $reportsArray[$report] = $reportObj; |
||
224 | } |
||
225 | } |
||
226 | |||
227 | uasort($reportsArray, function ($a, $b) { |
||
228 | if ($a->sort == $b->sort) { |
||
229 | return 0; |
||
230 | } else { |
||
231 | return ($a->sort < $b->sort) ? -1 : 1; |
||
232 | } |
||
233 | }); |
||
234 | |||
235 | return $reportsArray; |
||
236 | } |
||
237 | |||
238 | /////////////////////// UI METHODS /////////////////////// |
||
239 | |||
240 | |||
241 | /** |
||
242 | * Returns a FieldList with which to create the CMS editing form. |
||
243 | * You can use the extend() method of FieldList to create customised forms for your other |
||
244 | * data objects. |
||
245 | * |
||
246 | * @uses getReportField() to render a table, or similar field for the report. This |
||
247 | * method should be defined on the SS_Report subclasses. |
||
248 | * |
||
249 | * @return FieldList |
||
250 | */ |
||
251 | public function getCMSFields() |
||
252 | { |
||
253 | $fields = new FieldList(); |
||
254 | |||
255 | if ($title = $this->title()) { |
||
256 | $fields->push(new LiteralField('ReportTitle', "<h3>{$title}</h3>")); |
||
257 | } |
||
258 | |||
259 | if ($description = $this->description()) { |
||
260 | $fields->push(new LiteralField('ReportDescription', "<p>" . $description . "</p>")); |
||
261 | } |
||
262 | |||
263 | // Add search fields is available |
||
264 | if ($this->hasMethod('parameterFields') && $parameterFields = $this->parameterFields()) { |
||
265 | foreach ($parameterFields as $field) { |
||
266 | // Namespace fields for easier handling in form submissions |
||
267 | $field->setName(sprintf('filters[%s]', $field->getName())); |
||
268 | $field->addExtraClass('no-change-track'); // ignore in changetracker |
||
269 | $fields->push($field); |
||
270 | } |
||
271 | |||
272 | // Add a search button |
||
273 | $fields->push(new FormAction('updatereport', _t('GridField.Filter'))); |
||
274 | } |
||
275 | |||
276 | $fields->push($this->getReportField()); |
||
277 | |||
278 | $this->extend('updateCMSFields', $fields); |
||
279 | |||
280 | return $fields; |
||
281 | } |
||
282 | |||
283 | public function getCMSActions() |
||
284 | { |
||
285 | // getCMSActions() can be extended with updateCMSActions() on a extension |
||
286 | $actions = new FieldList(); |
||
287 | $this->extend('updateCMSActions', $actions); |
||
288 | return $actions; |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Return a field, such as a {@link GridField} that is |
||
293 | * used to show and manipulate data relating to this report. |
||
294 | * |
||
295 | * Generally, you should override {@link columns()} and {@link records()} to make your report, |
||
296 | * but if they aren't sufficiently flexible, then you can override this method. |
||
297 | * |
||
298 | * @return FormField subclass |
||
299 | */ |
||
300 | public function getReportField() |
||
301 | { |
||
302 | // TODO Remove coupling with global state |
||
303 | $params = isset($_REQUEST['filters']) ? $_REQUEST['filters'] : array(); |
||
304 | $items = $this->sourceRecords($params, null, null); |
||
305 | |||
306 | $gridFieldConfig = GridFieldConfig::create()->addComponents( |
||
307 | new GridFieldToolbarHeader(), |
||
308 | new GridFieldSortableHeader(), |
||
309 | new GridFieldDataColumns(), |
||
310 | new GridFieldPaginator(), |
||
311 | new GridFieldButtonRow('after'), |
||
312 | new GridFieldPrintButton('buttons-after-left'), |
||
313 | new GridFieldExportButton('buttons-after-left') |
||
314 | ); |
||
315 | $gridField = new GridField('Report', $this->title(), $items, $gridFieldConfig); |
||
316 | $columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns'); |
||
317 | $displayFields = array(); |
||
318 | $fieldCasting = array(); |
||
319 | $fieldFormatting = array(); |
||
320 | |||
321 | // Parse the column information |
||
322 | foreach ($this->columns() as $source => $info) { |
||
323 | if (is_string($info)) { |
||
324 | $info = array('title' => $info); |
||
325 | } |
||
326 | |||
327 | if (isset($info['formatting'])) { |
||
328 | $fieldFormatting[$source] = $info['formatting']; |
||
329 | } |
||
330 | if (isset($info['csvFormatting'])) { |
||
331 | $csvFieldFormatting[$source] = $info['csvFormatting']; |
||
0 ignored issues
–
show
|
|||
332 | } |
||
333 | if (isset($info['casting'])) { |
||
334 | $fieldCasting[$source] = $info['casting']; |
||
335 | } |
||
336 | |||
337 | if (isset($info['link']) && $info['link']) { |
||
338 | $link = singleton('CMSPageEditController')->Link('show'); |
||
339 | $fieldFormatting[$source] = '<a href=\"' . $link . '/$ID\">$value</a>'; |
||
340 | } |
||
341 | |||
342 | $displayFields[$source] = isset($info['title']) ? $info['title'] : $source; |
||
343 | } |
||
344 | $columns->setDisplayFields($displayFields); |
||
345 | $columns->setFieldCasting($fieldCasting); |
||
346 | $columns->setFieldFormatting($fieldFormatting); |
||
347 | |||
348 | return $gridField; |
||
349 | } |
||
350 | |||
351 | /** |
||
352 | * @param Member $member |
||
353 | * @return boolean |
||
354 | */ |
||
355 | public function canView($member = null) |
||
356 | { |
||
357 | if (!$member && $member !== false) { |
||
358 | $member = Member::currentUser(); |
||
359 | } |
||
360 | |||
361 | return true; |
||
362 | } |
||
363 | |||
364 | |||
365 | /** |
||
366 | * Return the name of this report, which |
||
367 | * is used by the templates to render the |
||
368 | * name of the report in the report tree, |
||
369 | * the left hand pane inside ReportAdmin. |
||
370 | * |
||
371 | * @return string |
||
372 | */ |
||
373 | public function TreeTitle() |
||
374 | { |
||
375 | return $this->title(); |
||
376 | } |
||
377 | } |
||
378 | |||
379 | /** |
||
380 | * SS_ReportWrapper is a base class for creating report wappers. |
||
381 | * |
||
382 | * Wrappers encapsulate an existing report to alter their behaviour - they are implementations of |
||
383 | * the standard GoF decorator pattern. |
||
384 | * |
||
385 | * This base class ensure that, by default, wrappers behave in the same way as the report that is |
||
386 | * being wrapped. You should override any methods that need to behave differently in your subclass |
||
387 | * of SS_ReportWrapper. |
||
388 | * |
||
389 | * It also makes calls to 2 empty methods that you can override {@link beforeQuery()} and |
||
390 | * {@link afterQuery()} |
||
391 | * |
||
392 | * @package reports |
||
393 | */ |
||
394 | abstract class SS_ReportWrapper extends SS_Report |
||
395 | { |
||
396 | protected $baseReport; |
||
397 | |||
398 | public function __construct($baseReport) |
||
399 | { |
||
400 | $this->baseReport = is_string($baseReport) ? new $baseReport : $baseReport; |
||
401 | $this->dataClass = $this->baseReport->dataClass(); |
||
402 | parent::__construct(); |
||
403 | } |
||
404 | |||
405 | public function ID() |
||
406 | { |
||
407 | return get_class($this->baseReport) . '_' . get_class($this); |
||
408 | } |
||
409 | |||
410 | /////////////////////////////////////////////////////////////////////////////////////////// |
||
411 | // Filtering |
||
412 | |||
413 | public function parameterFields() |
||
414 | { |
||
415 | return $this->baseReport->parameterFields(); |
||
416 | } |
||
417 | |||
418 | /////////////////////////////////////////////////////////////////////////////////////////// |
||
419 | // Columns |
||
420 | |||
421 | public function columns() |
||
422 | { |
||
423 | return $this->baseReport->columns(); |
||
424 | } |
||
425 | |||
426 | /////////////////////////////////////////////////////////////////////////////////////////// |
||
427 | // Querying |
||
428 | |||
429 | /** |
||
430 | * Override this method to perform some actions prior to querying. |
||
431 | */ |
||
432 | public function beforeQuery($params) |
||
433 | { |
||
434 | } |
||
435 | |||
436 | /** |
||
437 | * Override this method to perform some actions after querying. |
||
438 | */ |
||
439 | public function afterQuery() |
||
440 | { |
||
441 | } |
||
442 | |||
443 | public function sourceQuery($params) |
||
444 | { |
||
445 | if ($this->baseReport->hasMethod('sourceRecords')) { |
||
446 | // The default implementation will create a fake query from our sourceRecords() method |
||
447 | return parent::sourceQuery($params); |
||
448 | } elseif ($this->baseReport->hasMethod('sourceQuery')) { |
||
449 | $this->beforeQuery($params); |
||
450 | $query = $this->baseReport->sourceQuery($params); |
||
451 | $this->afterQuery(); |
||
452 | return $query; |
||
453 | } else { |
||
454 | user_error("Please override sourceQuery()/sourceRecords() and columns() in your base report", E_USER_ERROR); |
||
455 | } |
||
456 | } |
||
457 | |||
458 | public function sourceRecords($params = array(), $sort = null, $limit = null) |
||
459 | { |
||
460 | $this->beforeQuery($params); |
||
461 | $records = $this->baseReport->sourceRecords($params, $sort, $limit); |
||
462 | $this->afterQuery(); |
||
463 | return $records; |
||
464 | } |
||
465 | |||
466 | |||
467 | /////////////////////////////////////////////////////////////////////////////////////////// |
||
468 | // Pass-through |
||
469 | |||
470 | public function title() |
||
471 | { |
||
472 | return $this->baseReport->title(); |
||
473 | } |
||
474 | |||
475 | public function group() |
||
476 | { |
||
477 | return $this->baseReport->hasMethod('group') ? $this->baseReport->group() : 'Group'; |
||
478 | } |
||
479 | |||
480 | public function sort() |
||
481 | { |
||
482 | return $this->baseReport->hasMethod('sort') ? $this->baseReport->sort() : 0; |
||
483 | } |
||
484 | |||
485 | public function description() |
||
486 | { |
||
487 | return $this->baseReport->description(); |
||
488 | } |
||
489 | |||
490 | public function canView($member = null) |
||
491 | { |
||
492 | return $this->baseReport->canView($member); |
||
493 | } |
||
494 | } |
||
495 |
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.
Let’s take a look at an example:
As you can see in this example, the array
$myArray
is initialized the first time when the foreach loop is entered. You can also see that the value of thebar
key is only written conditionally; thus, its value might result from a previous iteration.This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.