Compare commits
359 Commits
Author | SHA1 | Date |
---|---|---|
github-actions | 6942278b41 | |
Guy Sartorelli | c9c6c3ff1e | |
Guy Sartorelli | f5b5daa711 | |
github-actions | aa3d0a3b6a | |
Guy Sartorelli | aa085022a1 | |
Will Rossiter | a25d0432c4 | |
github-actions | 030c56777c | |
Guy Sartorelli | 03869a7535 | |
Steve Boyd | 2c3f2ca64d | |
Guy Sartorelli | 92b5d968da | |
Steve Boyd | f6aee12f07 | |
Guy Sartorelli | 2db20aba1a | |
Guy Sartorelli | cea17947ea | |
Maxime Rainville | 48b103bf97 | |
Steve Boyd | 454a3df1e7 | |
Guy Sartorelli | 4cb8e8f880 | |
Guy Sartorelli | f23076b4cf | |
Guy Sartorelli | 2bf6bc1a9c | |
Guy Sartorelli | e88a56243d | |
Steve Boyd | a75e054b99 | |
Steve Boyd | bd74939c14 | |
Robbie Averill | 01f2663179 | |
Robbie Averill | 213f7167a6 | |
Robbie Averill | c5f42d99a3 | |
Steve Boyd | d21503c88c | |
Steve Boyd | eacecdbb56 | |
Guy Sartorelli | c0bdbc3d3b | |
Steve Boyd | 567124b34a | |
Guy Sartorelli | bd7fb5a45d | |
Guy Sartorelli | f18dd784e5 | |
Guy Sartorelli | 86d338680a | |
Steve Boyd | fca925673f | |
Guy Sartorelli | 8e3d6cb3eb | |
Steve Boyd | 0bceb0903c | |
Guy Sartorelli | 6d565028c8 | |
Maxime Rainville | ede88ee491 | |
Steve Boyd | 013c5d74cf | |
GuySartorelli | 375c31904f | |
Steve Boyd | 0cb265434a | |
Maxime Rainville | 411a98e45b | |
Steve Boyd | cb71f6b7f2 | |
Maxime Rainville | 6dd0627678 | |
Steve Boyd | 1b2a9a8926 | |
Maxime Rainville | 3ab86f3d0a | |
Steve Boyd | 9f76ec2a43 | |
Steve Boyd | 908dc49dca | |
Steve Boyd | 22839ec8c2 | |
Steve Boyd | 8098a7c61f | |
Steve Boyd | 1d9940aab0 | |
Steve Boyd | 27288dc508 | |
Steve Boyd | e101f1e034 | |
Steve Boyd | 90bdc6f3ce | |
Steve Boyd | 82b5059265 | |
Serge Latyntcev | da8bc5c240 | |
Steve Boyd | ee4c391682 | |
Maxime Rainville | b62a6c66d1 | |
Andre Kiste | 466ec19b60 | |
Serge Latyntcev | 3314f95da6 | |
Guy Marriott | 8f40ddb5eb | |
Robbie Averill | 6ed13f7e07 | |
Robbie Averill | 9b9e673e4b | |
Steve Boyd | b0cb0b695e | |
Robbie Averill | 275a1bc7f4 | |
Maxime Rainville | f931dc37df | |
Robbie Averill | cb55795013 | |
Maxime Rainville | 0499717d7c | |
Garion Herman | f681448dbb | |
Robbie Averill | 2a77c2454f | |
Garion Herman | f2deee8fa0 | |
Steve Boyd | 5677d2d61d | |
Garion Herman | 7436e11d4f | |
Robbie Averill | 34474804a2 | |
Serge Latyntcev | 476f7b06f3 | |
Garion Herman | 7f34088211 | |
Serge Latyntcev | f3db5f72aa | |
Serge Latyntcev | d020ed86a1 | |
Serge Latyntsev | 9e95803970 | |
Garion Herman | 8be3660409 | |
Serge Latyntcev | 0bb704decf | |
Ingo Schommer | b5232aa7ab | |
Robbie Averill | 250813f79c | |
Robbie Averill | 1f1278b820 | |
Robbie Averill | 9b89812058 | |
Aaron Carlino | c3c38aa976 | |
Robbie Averill | ed3ffee31e | |
Stéphane Guevremont | 9d94712c04 | |
Aaron Carlino | c797d128a2 | |
Aaron Carlino | 423cc180cd | |
Maxime Rainville | bc70b87721 | |
Robbie Averill | 9270206c3b | |
Maxime Rainville | ebe98f487a | |
Dylan Wagstaff | a344110a10 | |
Aaron Carlino | 5a3bd39609 | |
Maxime Rainville | f074256a16 | |
Guy Marriott | 52a234410d | |
Guy Marriott | d6b1c071b6 | |
Guy Marriott | edecbabe61 | |
Aaron Carlino | dea66bc037 | |
Vagrant Default User | c48cb248dc | |
Aaron Carlino | 9ec437d033 | |
Aaron Carlino | 6e3f8ff982 | |
Robbie Averill | 7d4d7c05e6 | |
Robbie Averill | 9dd2cb35ac | |
Aaron Carlino | f1f0f521b5 | |
Aaron Carlino | 057dc90ebc | |
Maxime Rainville | 67254da185 | |
Robbie Averill | 786446fb67 | |
Maxime Rainville | ee1c77fa7d | |
Dylan Wagstaff | eb7be779f2 | |
Robbie Averill | 62b3f0c74d | |
Robbie Averill | f3230c78d4 | |
Aaron Carlino | 9e4be5b4f6 | |
Daniel Hensby | c316c6bb7e | |
Daniel Hensby | e9e5cdcc22 | |
Maxime Rainville | 3bbcfdde85 | |
Daniel Hensby | e9c73e9332 | |
root | 8c7acb3d24 | |
Daniel Hensby | 46de3d8f19 | |
Daniel Hensby | a3768f5f60 | |
Daniel Hensby | c90f1ed47c | |
Robbie Averill | e1296d4813 | |
Robbie Averill | 693889cb3e | |
Robbie Averill | dce7df600b | |
Maxime Rainville | 650e17b3fd | |
Damian Mooyman | 745fdf87cf | |
Damian Mooyman | fd62136c8f | |
Dylan Wagstaff | 326e19ac8e | |
Damian Mooyman | 9b239fd9a3 | |
Damian Mooyman | 75dfeb2b47 | |
Damian Mooyman | 2a36be9bed | |
Ingo Schommer | 86850eefb7 | |
Damian Mooyman | 7eacd8a3c3 | |
Damian Mooyman | cb14e856ff | |
Damian Mooyman | dee598eb0e | |
Damian Mooyman | ac23686777 | |
Damian Mooyman | 00cff693a6 | |
Daniel Hensby | 4c549d653a | |
Damian Mooyman | 7c0795eb68 | |
Daniel Hensby | 7e927aed74 | |
Daniel Hensby | b7b1b4d024 | |
Daniel Hensby | 2f59965515 | |
Daniel Hensby | 01fd709d74 | |
Daniel Hensby | 5ddb796d15 | |
Damian Mooyman | 8443529646 | |
Robbie Averill | 8a29918ac5 | |
Damian Mooyman | 4d3ad97a69 | |
Damian Mooyman | a9db6dee3a | |
Damian Mooyman | b006a9968a | |
Robbie Averill | b4a3529339 | |
Christopher Joe | 4d8053611d | |
Damian Mooyman | ed16d96842 | |
Christopher Joe | 86f845972f | |
Will Rossiter | 5b335ad511 | |
Damian Mooyman | 6ac1dea913 | |
Damian Mooyman | e575bff1af | |
Daniel Hensby | 14eeb10d31 | |
Daniel Hensby | b406d8724f | |
Damian Mooyman | cd94e99d89 | |
Daniel Hensby | ece79fd2b2 | |
Damian Mooyman | ec40ffb2cf | |
Robbie Averill | 26dc7373ba | |
Daniel Hensby | a548f0290e | |
Damian Mooyman | f7815b52e5 | |
Daniel Hensby | 0f387ad6a1 | |
Christopher Joe | 6fe1e2c5b2 | |
Damian Mooyman | 8020e36409 | |
Damian Mooyman | c5ca89a173 | |
Damian Mooyman | 7a2344117e | |
Chris Joe | 4a80bd9166 | |
Damian Mooyman | ca60b94f2e | |
Damian Mooyman | 418ac59e90 | |
Ingo Schommer | 9ce1eedf2f | |
Chris Joe | 04e4c7d1a4 | |
Damian Mooyman | 9afcf7780a | |
Daniel Hensby | 448955d82f | |
Saophalkun Ponlu | cda78e2d88 | |
Saophalkun Ponlu | 5351ba670f | |
Damian Mooyman | e9c7273bb4 | |
Daniel Hensby | d0f65850fa | |
Daniel Hensby | 6025de29cc | |
Daniel Hensby | e8fef7ebdd | |
Daniel Hensby | 28b79c88ff | |
Marco Hermo | 09bd6fc231 | |
Daniel Hensby | 879ce3086c | |
Daniel Hensby | 36d745ab24 | |
Daniel Hensby | 92ffc87e97 | |
Robbie Averill | 85c79d4e66 | |
Robbie Averill | 4aefa50087 | |
Robbie Averill | c3d2ba06c6 | |
Robbie Averill | f4af1fab77 | |
Christopher Joe | bf4e7224e0 | |
Damian Mooyman | fdbd6015d8 | |
Damian Mooyman | 62419d748d | |
Damian Mooyman | 614ebcfcd9 | |
Chris Joe | e532d01821 | |
Damian Mooyman | b1ec225246 | |
Daniel Hensby | 84fb64bf8f | |
Ingo Schommer | 9e7108420b | |
Sam Minnee | cf5115624d | |
Daniel Hensby | 76b59e7464 | |
Daniel Hensby | 108869841c | |
Saophalkun Ponlu | ab0b3d8a2f | |
Damian Mooyman | 1e386c83f6 | |
Ingo Schommer | a58416b6f6 | |
Ingo Schommer | 19fa5355ce | |
Robbie Averill | 83dae0fbac | |
Ingo Schommer | 179310e457 | |
Damian Mooyman | 7bab717c8d | |
Damian Mooyman | 8dbdc98cc2 | |
Simon Erkelens | 03125b8c6c | |
Daniel Hensby | 2aa6433357 | |
Daniel Hensby | 2e29bf27fe | |
Daniel Hensby | c249173e48 | |
Daniel Hensby | be44c5e7df | |
Daniel Hensby | 63bbc0bae4 | |
Daniel Hensby | d1764ffc08 | |
Daniel Hensby | a0fc3aa86f | |
Daniel Hensby | c2e804ad72 | |
Daniel Hensby | c927870432 | |
Daniel Hensby | 3f112ecfcd | |
Ingo Schommer | c37c69097d | |
Garion Herman | 19974fe1f2 | |
Aaron Carlino | 664fa75a02 | |
Ingo Schommer | 24afd61131 | |
Sam Minnee | 4332d2fa8e | |
Daniel Hensby | 7172eecd08 | |
Loz Calver | c1d2696514 | |
Sam Minnee | e5f51b14a3 | |
Damian Mooyman | 27b2037c68 | |
Sam Minnee | 59a9ff8459 | |
Damian Mooyman | 5dace88830 | |
Ingo Schommer | 02446a47a5 | |
Ingo Schommer | 376d2bfbc1 | |
Damian Mooyman | 4599b2b52c | |
Damian Mooyman | 240723b378 | |
Sam Minnee | c9e2c249c2 | |
Ingo Schommer | 015b400fb7 | |
Damian Mooyman | b4e047b39f | |
Daniel Hensby | 37a7e68f2d | |
Damian Mooyman | 3c8a56f904 | |
Damian Mooyman | 3cadebf595 | |
Paul Clarke | d67aee56fe | |
Paul Clarke | 417fb81ae4 | |
Daniel Hensby | cb06593a65 | |
Daniel Hensby | b10955d985 | |
Daniel Hensby | dcb8024158 | |
Daniel Hensby | 9c5974a13b | |
Daniel Hensby | da4aeda90e | |
Daniel Hensby | 65e0b6ef35 | |
Daniel Hensby | 8e5462794d | |
Damian Mooyman | a0eda1edd1 | |
Daniel Hensby | 804ff7c247 | |
Daniel Hensby | 537f4da88f | |
Damian Mooyman | 43462f1afa | |
Damian Mooyman | 8dd40b3c26 | |
Paul Clarke | 97eac2eb09 | |
Loz Calver | fa7e0825c5 | |
Sam Minnee | c813488412 | |
Daniel Hensby | 2c7846cb72 | |
Sam Minnee | fe0ca63c7a | |
Paul Clarke | 3d144b3afc | |
Will Rossiter | 0008f474b8 | |
Will Rossiter | 3b32047f28 | |
Loz Calver | c4ba67c1fb | |
Paul Clarke | 9cee3c25fb | |
Ingo Schommer | 1e8d94afe1 | |
Damian Mooyman | b2f3902996 | |
Ingo Schommer | c6b6c5fb2a | |
Damian Mooyman | bd17cca223 | |
Daniel Hensby | f759a7c6db | |
Damian Mooyman | a96ab15af3 | |
Damian Mooyman | 8f14b94433 | |
Damian Mooyman | 20f929ba8b | |
Damian Mooyman | 8395a55442 | |
Damian Mooyman | ac0d621d6f | |
Damian Mooyman | f92b83fc45 | |
Damian Mooyman | eb096e869f | |
Daniel Hensby | c1525c8ba6 | |
Daniel Hensby | efa20d2da0 | |
Daniel Hensby | cff2ea9a98 | |
Damian Mooyman | a325bfb222 | |
Hamish Friedlander | 83c7e9955b | |
Damian Mooyman | 5953957881 | |
Damian Mooyman | a446714e5e | |
Damian Mooyman | f17fad179f | |
Hamish Friedlander | 77a696ecdd | |
Damian Mooyman | c54b8b5a6c | |
Damian Mooyman | d2142f252e | |
Paul Clarke | f04975c64e | |
Paul Clarke | 9fdf236611 | |
Hamish Friedlander | 2502118a21 | |
Damian Mooyman | 9d31bb0542 | |
Damian Mooyman | 6c1f17da91 | |
Daniel Hensby | c6fdf440a6 | |
Damian Mooyman | 87477a1e01 | |
Daniel Hensby | 8a5741aa00 | |
Daniel Hensby | 761cbf0dc2 | |
Daniel Hensby | c876a21669 | |
Daniel Hensby | ca526b08c3 | |
Damian Mooyman | 2d47fed75d | |
Damian Mooyman | 39b6d12969 | |
Ingo Schommer | c4cdeda7dc | |
Will Rossiter | 6c31b23b2d | |
Mike Cochrane | 0a2c248e6b | |
Damian Mooyman | ab81961115 | |
Will Rossiter | 9c124f1d53 | |
Daniel Hensby | 3bb32eb013 | |
Daniel Hensby | 68c4040299 | |
Daniel Hensby | 1890c208e3 | |
Sam Minnée | 36298ee8c1 | |
Damian Mooyman | 68d9533542 | |
Daniel Hensby | 1d8f3b0c7c | |
Ingo Schommer | b63600c392 | |
Daniel Hensby | a39515357d | |
Daniel Hensby | fe17073355 | |
Daniel Hensby | b985ce0eba | |
Damian Mooyman | 7eb6b21494 | |
Daniel Hensby | b1566f3e79 | |
Damian Mooyman | dfb7b4d29f | |
Daniel Hensby | 57cfe3c66a | |
Damian Mooyman | 94faf34266 | |
Ingo Schommer | 3c6c4b33db | |
Damian Mooyman | d031f53e73 | |
Damian Mooyman | f777f36c68 | |
Damian Mooyman | e7ed9b2a77 | |
helpfulrobot | 32b39916d2 | |
Damian Mooyman | 59cff00bb7 | |
Damian Mooyman | fb3083688e | |
Daniel Hensby | ccb72a10c5 | |
Damian Mooyman | 36241d52a0 | |
Damian Mooyman | fc81b1f582 | |
Christopher Darling | 5d0f833a39 | |
Damian Mooyman | af2d51125c | |
Damian Mooyman | 7b245c6df9 | |
helpfulrobot | 648b6bac40 | |
helpfulrobot | 9f780b3eeb | |
Damian Mooyman | 49e1631066 | |
Damian Mooyman | ff0598f827 | |
Damian Mooyman | 8691a7d9b6 | |
Damian Mooyman | c6ce02bd33 | |
Damian Mooyman | 1024c16ffe | |
Damian Mooyman | 891cb40654 | |
helpfulrobot | b16e1c6788 | |
helpfulrobot | 75ece1710c | |
helpfulrobot | 3014b3dbe3 | |
helpfulrobot | 26fb8d965a | |
helpfulrobot | 1e2e65d279 | |
helpfulrobot | 93fced94f1 | |
Damian Mooyman | a09795b3a2 | |
Damian Mooyman | 1e665a59b1 | |
Daniel Hensby | 0759392980 | |
Daniel Hensby | 324da5fff4 | |
Daniel Hensby | 740dbbfb99 | |
Daniel Hensby | c56b92a4c3 | |
Damian Mooyman | bcc7f358a8 | |
Damian Mooyman | 46281d7d33 | |
Jeremy Shipman | 6b17c8fb42 | |
Nicolaas | 78cebe8723 | |
Nicolaas | c736906016 |
|
@ -0,0 +1,21 @@
|
|||
# For more information about the properties used in this file,
|
||||
# please see the EditorConfig documentation:
|
||||
# http://editorconfig.org
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{yml,js,json,css,scss,eslintrc}]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
[composer.json]
|
||||
indent_size = 4
|
||||
|
||||
# The indent size used in the package.json file cannot be changed:
|
||||
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
|
|
@ -0,0 +1,7 @@
|
|||
/tests export-ignore
|
||||
/.tx export-ignore
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
/.travis.yml export-ignore
|
||||
/.scrutinizer.yml export-ignore
|
||||
/*.dist export-ignore
|
|
@ -0,0 +1,11 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: CI
|
||||
uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1
|
|
@ -0,0 +1,16 @@
|
|||
name: Dispatch CI
|
||||
|
||||
on:
|
||||
# At 11:00 AM UTC, only on Monday and Tuesday
|
||||
schedule:
|
||||
- cron: '0 11 * * 1,2'
|
||||
|
||||
jobs:
|
||||
dispatch-ci:
|
||||
name: Dispatch CI
|
||||
# Only run cron on the silverstripe account
|
||||
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Dispatch CI
|
||||
uses: silverstripe/gha-dispatch-ci@v1
|
|
@ -0,0 +1,17 @@
|
|||
name: Keepalive
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# The 4th of every month at 10:50am UTC
|
||||
schedule:
|
||||
- cron: '50 10 4 * *'
|
||||
|
||||
jobs:
|
||||
keepalive:
|
||||
name: Keepalive
|
||||
# Only run cron on the silverstripe account
|
||||
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Keepalive
|
||||
uses: silverstripe/gha-keepalive@v1
|
27
.travis.yml
27
.travis.yml
|
@ -1,27 +0,0 @@
|
|||
# See https://github.com/silverstripe-labs/silverstripe-travis-support for setup details
|
||||
|
||||
language: php
|
||||
php:
|
||||
- 5.6
|
||||
|
||||
sudo: false
|
||||
|
||||
env:
|
||||
- DB=MYSQL CORE_RELEASE=3.2
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.4
|
||||
env: DB=PGSQL CORE_RELEASE=3.2
|
||||
- php: 5.3
|
||||
env: DB=MYSQL CORE_RELEASE=3.2
|
||||
- php: 5.5
|
||||
env: DB=MYSQL CORE_RELEASE=3
|
||||
|
||||
before_script:
|
||||
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support
|
||||
- php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss
|
||||
- cd ~/builds/ss
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit reports/tests/
|
|
@ -0,0 +1,9 @@
|
|||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[o:silverstripe:p:silverstripe-reports:r:master]
|
||||
file_filter = lang/<lang>.yml
|
||||
source_file = lang/en.yml
|
||||
source_lang = en
|
||||
type = YML
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
mappings:
|
||||
SS_Report: SilverStripe\Reports\Report
|
||||
SilverStripe\Reports\SS_Report: SilverStripe\Reports\Report
|
||||
SS_ReportWrapper: SilverStripe\Reports\ReportWrapper
|
||||
SilverStripe\Reports\SS_ReportWrapper: SilverStripe\Reports\ReportWrapper
|
||||
ReportAdmin: SilverStripe\Reports\ReportAdmin
|
||||
SideReportView: SilverStripe\Reports\SideReportView
|
||||
SideReportWrapper: SilverStripe\Reports\SideReportWrapper
|
||||
SideReport: SilverStripe\Reports\SideReportView
|
|
@ -0,0 +1,15 @@
|
|||
# Contributing
|
||||
|
||||
Any open source product is only as good as the community behind it. You can participate by sharing code, ideas, or simply helping others. No matter what your skill level is, every contribution counts.
|
||||
|
||||
See our [high level overview](http://silverstripe.org/contributing-to-silverstripe) on silverstripe.org on how you can help out.
|
||||
|
||||
## Copyright
|
||||
|
||||
**IMPORTANT: By supplying code to the SilverStripe core team in patches, tickets and pull requests, you agree to assign copyright of that code to SilverStripe Limited, on the condition that SilverStripe Limited releases that code under the BSD license.**
|
||||
|
||||
We ask for this so that the ownership in the license is clear and unambiguous, and so that community involvement doesn't stop us from being able to continue supporting these projects. By releasing this code under a permissive license, this copyright assignment won't prevent you from using the code in any way you see fit.
|
||||
|
||||
## Contributing code
|
||||
|
||||
See [contributing code](docs/en/05_Contributing/01_Code.md)
|
19
LICENSE
19
LICENSE
|
@ -1,17 +1,12 @@
|
|||
Copyright (c) 2007-2013, SilverStripe Limited - silverstripe.com
|
||||
Copyright (c) 2017, SilverStripe Limited
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of SilverStripe nor the names of its contributors may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGE.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
30
README.md
30
README.md
|
@ -1,13 +1,35 @@
|
|||
# Reports
|
||||
|
||||
[![Build Status](https://secure.travis-ci.org/silverstripe-labs/silverstripe-reports.png?branch=master)](http://travis-ci.org/silverstripe-labs/silverstripe-reports)
|
||||
[![CI](https://github.com/silverstripe/silverstripe-reports/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-reports/actions/workflows/ci.yml)
|
||||
[![Silverstripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)
|
||||
|
||||
## Introduction
|
||||
|
||||
This module contains the API's for building Reports that are displayed in the
|
||||
SilverStripe backend. This module replaces the built-in reports API from earlier
|
||||
versions of SilverStripe (2.4 and 3.0).
|
||||
Silverstripe backend. This module replaces the built-in reports API from earlier
|
||||
versions of Silverstripe (2.4 and 3.0).
|
||||
|
||||
## Requirements
|
||||
|
||||
* SilverStripe 3.2
|
||||
* Silverstripe 4.0
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
The reports section will not show up in the CMS if:
|
||||
|
||||
* There are no reports to show
|
||||
* The logged in user does not have permission to view any reports
|
||||
|
||||
For large datasets, the reports section may take a long time to load, since each report is getting a count of the items it contains to display next to the title.
|
||||
|
||||
To mitigate this issue, there is a cap on the number of items that will be counted per report. This is set at 10,000 items by default, but can be configured using the `limit_count_in_overview` configuration variable. Setting this to `null` will result in showing the actual count regardless of how many items there are.
|
||||
|
||||
```yml
|
||||
SilverStripe\Reports\Report:
|
||||
limit_count_in_overview: 500
|
||||
```
|
||||
Note that some reports may have overridden the `getCount` method, and for those reports this may not apply.
|
||||
|
||||
## Links ##
|
||||
|
||||
* [License](./LICENSE)
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
|
||||
$path = explode(DIRECTORY_SEPARATOR, dirname(__FILE__));
|
||||
$dir = $path[count($path) - 1];
|
||||
|
||||
define('REPORTS_DIR', $dir);
|
|
@ -0,0 +1 @@
|
|||
When having discussions about this module in issues or pull request please adhere to the [SilverStripe Community Code of Conduct](https://docs.silverstripe.org/en/contributing/code_of_conduct).
|
889
code/Report.php
889
code/Report.php
|
@ -1,439 +1,544 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports;
|
||||
|
||||
use ReflectionClass;
|
||||
use SilverStripe\CMS\Model\SiteTree;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\FormAction;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\Forms\GridField\GridField;
|
||||
use SilverStripe\Forms\GridField\GridFieldButtonRow;
|
||||
use SilverStripe\Forms\GridField\GridFieldConfig;
|
||||
use SilverStripe\Forms\GridField\GridFieldDataColumns;
|
||||
use SilverStripe\Forms\GridField\GridFieldExportButton;
|
||||
use SilverStripe\Forms\GridField\GridFieldPaginator;
|
||||
use SilverStripe\Forms\GridField\GridFieldPrintButton;
|
||||
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
|
||||
use SilverStripe\Forms\LiteralField;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\ORM\CMSPreviewable;
|
||||
use SilverStripe\ORM\DataList;
|
||||
use SilverStripe\ORM\DataQuery;
|
||||
use SilverStripe\ORM\Limitable;
|
||||
use SilverStripe\ORM\SS_List;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\View\ArrayData;
|
||||
use SilverStripe\View\ViewableData;
|
||||
|
||||
/**
|
||||
* Base "abstract" class creating reports on your data.
|
||||
*
|
||||
*
|
||||
* Creating reports
|
||||
* ================
|
||||
*
|
||||
*
|
||||
* Creating a new report is a matter overloading a few key methods
|
||||
*
|
||||
*
|
||||
* {@link title()}: Return the title - i18n is your responsibility
|
||||
* {@link description()}: Return the description - i18n is your responsibility
|
||||
* {@link sourceQuery()}: Return a SS_List of the search results
|
||||
* {@link columns()}: Return information about the columns in this report.
|
||||
* {@link parameterFields()}: Return a FieldList of the fields that can be used to filter this
|
||||
* report.
|
||||
*
|
||||
*
|
||||
* If you wish to modify the report in more extreme ways, you could overload these methods instead.
|
||||
*
|
||||
*
|
||||
* {@link getReportField()}: Return a FormField in the place where your report's TableListField
|
||||
* usually appears.
|
||||
* {@link getCMSFields()}: Return the FieldList representing the complete right-hand area of the
|
||||
* {@link getCMSFields()}: Return the FieldList representing the complete right-hand area of the
|
||||
* report, including the title, description, parameter fields, and results.
|
||||
*
|
||||
*
|
||||
* Showing reports to the user
|
||||
* ===========================
|
||||
*
|
||||
*
|
||||
* Right now, all subclasses of SS_Report will be shown in the ReportAdmin. In SS3 there is only
|
||||
* one place where reports can go, so this class is greatly simplifed from its version in SS2.
|
||||
*
|
||||
* @package reports
|
||||
*
|
||||
* @method SS_List|DataList sourceRecords($params = [], $sort = null, $limit = null)
|
||||
* List of records to show for this report
|
||||
*/
|
||||
class SS_Report extends ViewableData {
|
||||
/**
|
||||
* This is the title of the report,
|
||||
* used by the ReportAdmin templates.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title = '';
|
||||
class Report extends ViewableData
|
||||
{
|
||||
/**
|
||||
* This is the title of the report,
|
||||
* used by the ReportAdmin templates.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title = '';
|
||||
|
||||
/**
|
||||
* This is a description about what this
|
||||
* report does. Used by the ReportAdmin
|
||||
* templates.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* The class of object being managed by this report.
|
||||
* Set by overriding in your subclass.
|
||||
*/
|
||||
protected $dataClass = 'SiteTree';
|
||||
/**
|
||||
* This is a description about what this
|
||||
* report does. Used by the ReportAdmin
|
||||
* templates.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '';
|
||||
|
||||
/**
|
||||
* A field that specifies the sort order of this report
|
||||
* @var int
|
||||
*/
|
||||
protected $sort = 0;
|
||||
/**
|
||||
* The class of object being managed by this report.
|
||||
* Set by overriding in your subclass.
|
||||
*/
|
||||
protected $dataClass = SiteTree::class;
|
||||
|
||||
/**
|
||||
* Reports which should not be collected and returned in get_reports
|
||||
* @var array
|
||||
*/
|
||||
public static $excluded_reports = array(
|
||||
'SS_Report',
|
||||
'SS_ReportWrapper',
|
||||
'SideReportWrapper',
|
||||
'SideReport_RecentlyEdited', // @deprecated 3.2..4.0
|
||||
'SideReport_EmptyPages', // @deprecated 3.2..4.0
|
||||
'SideReport_BrokenVirtualPages', // @deprecated 3.2..4.0
|
||||
'SideReport_BrokenRedirectorPages', // @deprecated 3.2..4.0
|
||||
'SideReport_BrokenLinks', // @deprecated 3.2..4.0
|
||||
'SideReport_BrokenFiles' // @deprecated 3.2..4.0
|
||||
);
|
||||
/**
|
||||
* A field that specifies the sort order of this report
|
||||
* @var int
|
||||
*/
|
||||
protected $sort = 0;
|
||||
|
||||
/**
|
||||
* Return the title of this report.
|
||||
*
|
||||
* You have two ways of specifying the description:
|
||||
* - overriding description(), which lets you support i18n
|
||||
* - defining the $description property
|
||||
*/
|
||||
public function title() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows access to title as a property
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle() {
|
||||
return $this->title();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description of this report.
|
||||
*
|
||||
* You have two ways of specifying the description:
|
||||
* - overriding description(), which lets you support i18n
|
||||
* - defining the $description property
|
||||
*/
|
||||
public function description() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link SQLQuery} that provides your report data.
|
||||
*/
|
||||
public function sourceQuery($params) {
|
||||
if($this->hasMethod('sourceRecords')) {
|
||||
return $this->sourceRecords($params, null, null)->dataQuery();
|
||||
} else {
|
||||
user_error("Please override sourceQuery()/sourceRecords() and columns() or, if necessary, override getReportField()", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a SS_List records for this report.
|
||||
*/
|
||||
public function records($params) {
|
||||
if($this->hasMethod('sourceRecords')) {
|
||||
return $this->sourceRecords($params, null, null);
|
||||
} else {
|
||||
$query = $this->sourceQuery();
|
||||
$results = new ArrayList();
|
||||
foreach($query->execute() as $data) {
|
||||
$class = $this->dataClass();
|
||||
$result = new $class($data);
|
||||
$results->push($result);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Reports which should not be collected and returned in get_reports
|
||||
*
|
||||
* @config
|
||||
* @var array
|
||||
*/
|
||||
private static $excluded_reports = [
|
||||
self::class,
|
||||
ReportWrapper::class,
|
||||
SideReportWrapper::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Return the data class for this report
|
||||
*/
|
||||
public function dataClass() {
|
||||
return $this->dataClass;
|
||||
}
|
||||
/**
|
||||
* The maximum number of items to include in the count in the reports overview
|
||||
*
|
||||
* @config
|
||||
* @var int|null
|
||||
*/
|
||||
private static $limit_count_in_overview = 10000;
|
||||
|
||||
public function getLink($action = null) {
|
||||
return Controller::join_links(
|
||||
'admin/reports/',
|
||||
"$this->class",
|
||||
'/', // trailing slash needed if $action is null!
|
||||
"$action"
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Return the title of this report.
|
||||
*
|
||||
* You have two ways of specifying the description:
|
||||
* - overriding description(), which lets you support i18n
|
||||
* - defining the $description property
|
||||
*/
|
||||
public function title()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude certain reports classes from the list of Reports in the CMS
|
||||
* @param $reportClass Can be either a string with the report classname or an array of reports classnames
|
||||
*/
|
||||
static public function add_excluded_reports($reportClass) {
|
||||
if (is_array($reportClass)) {
|
||||
self::$excluded_reports = array_merge(self::$excluded_reports, $reportClass);
|
||||
} else {
|
||||
if (is_string($reportClass)) {
|
||||
//add to the excluded reports, so this report doesn't get used
|
||||
self::$excluded_reports[] = $reportClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Allows access to title as a property
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of excluded reports. That is, reports that will not be included in
|
||||
* the list of reports in report admin in the CMS.
|
||||
* @return array
|
||||
*/
|
||||
static public function get_excluded_reports() {
|
||||
return self::$excluded_reports;
|
||||
}
|
||||
/**
|
||||
* Return the description of this report.
|
||||
*
|
||||
* You have two ways of specifying the description:
|
||||
* - overriding description(), which lets you support i18n
|
||||
* - defining the $description property
|
||||
*/
|
||||
public function description()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SS_Report objects making up the given list.
|
||||
* @return Array of SS_Report objects
|
||||
*/
|
||||
static public function get_reports() {
|
||||
$reports = ClassInfo::subclassesFor(get_called_class());
|
||||
/**
|
||||
* Return the {@link DataQuery} that provides your report data.
|
||||
*
|
||||
* @param array $params
|
||||
* @return DataQuery
|
||||
*/
|
||||
public function sourceQuery($params)
|
||||
{
|
||||
if (!$this->hasMethod('sourceRecords')) {
|
||||
throw new \RuntimeException(
|
||||
'Please override sourceQuery()/sourceRecords() and columns() or, '
|
||||
. 'if necessary, override getReportField()'
|
||||
);
|
||||
}
|
||||
|
||||
$reportsArray = array();
|
||||
if ($reports && count($reports) > 0) {
|
||||
//collect reports into array with an attribute for 'sort'
|
||||
foreach($reports as $report) {
|
||||
if (in_array($report, self::$excluded_reports)) continue; //don't use the SS_Report superclass
|
||||
$reflectionClass = new ReflectionClass($report);
|
||||
if ($reflectionClass->isAbstract()) continue; //don't use abstract classes
|
||||
return $this->sourceRecords($params, null, null)->dataQuery();
|
||||
}
|
||||
|
||||
$reportObj = new $report;
|
||||
if (method_exists($reportObj,'sort')) $reportObj->sort = $reportObj->sort(); //use the sort method to specify the sort field
|
||||
$reportsArray[$report] = $reportObj;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return a SS_List records for this report.
|
||||
*
|
||||
* @param array $params
|
||||
* @return SS_List
|
||||
*/
|
||||
public function records($params)
|
||||
{
|
||||
if ($this->hasMethod('sourceRecords')) {
|
||||
return $this->sourceRecords($params, null, null);
|
||||
} else {
|
||||
$query = $this->sourceQuery($params);
|
||||
$results = ArrayList::create();
|
||||
foreach ($query->execute() as $data) {
|
||||
$class = $this->dataClass();
|
||||
$result = Injector::inst()->create($class, $data);
|
||||
$results->push($result);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
||||
uasort($reportsArray, function($a, $b) {
|
||||
if($a->sort == $b->sort) return 0;
|
||||
else return ($a->sort < $b->sort) ? -1 : 1;
|
||||
});
|
||||
public function columns()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return $reportsArray;
|
||||
}
|
||||
|
||||
/////////////////////// UI METHODS ///////////////////////
|
||||
/**
|
||||
* Return the data class for this report
|
||||
*/
|
||||
public function dataClass()
|
||||
{
|
||||
return $this->dataClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a FieldList with which to create the CMS editing form.
|
||||
* You can use the extend() method of FieldList to create customised forms for your other
|
||||
* data objects.
|
||||
*
|
||||
* @uses getReportField() to render a table, or similar field for the report. This
|
||||
* method should be defined on the SS_Report subclasses.
|
||||
*
|
||||
* @return FieldList
|
||||
*/
|
||||
public function getCMSFields() {
|
||||
$fields = new FieldList();
|
||||
public function getLink($action = null)
|
||||
{
|
||||
return Controller::join_links(
|
||||
ReportAdmin::singleton()->Link('show'),
|
||||
$this->sanitiseClassName(static::class),
|
||||
$action
|
||||
);
|
||||
}
|
||||
|
||||
if($title = $this->title()) {
|
||||
$fields->push(new LiteralField('ReportTitle', "<h3>{$title}</h3>"));
|
||||
}
|
||||
|
||||
if($description = $this->description()) {
|
||||
$fields->push(new LiteralField('ReportDescription', "<p>" . $description . "</p>"));
|
||||
}
|
||||
|
||||
// Add search fields is available
|
||||
if($this->hasMethod('parameterFields') && $parameterFields = $this->parameterFields()) {
|
||||
foreach($parameterFields as $field) {
|
||||
// Namespace fields for easier handling in form submissions
|
||||
$field->setName(sprintf('filters[%s]', $field->getName()));
|
||||
$field->addExtraClass('no-change-track'); // ignore in changetracker
|
||||
$fields->push($field);
|
||||
}
|
||||
/**
|
||||
* Sanitise a model class' name for inclusion in a link
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
protected function sanitiseClassName($class)
|
||||
{
|
||||
return str_replace('\\', '-', $class ?? '');
|
||||
}
|
||||
|
||||
// Add a search button
|
||||
$fields->push(new FormAction('updatereport', _t('GridField.Filter')));
|
||||
}
|
||||
|
||||
$fields->push($this->getReportField());
|
||||
|
||||
$this->extend('updateCMSFields', $fields);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
public function getCMSActions() {
|
||||
// getCMSActions() can be extended with updateCMSActions() on a extension
|
||||
$actions = new FieldList();
|
||||
$this->extend('updateCMSActions', $actions);
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a field, such as a {@link GridField} that is
|
||||
* used to show and manipulate data relating to this report.
|
||||
*
|
||||
* Generally, you should override {@link columns()} and {@link records()} to make your report,
|
||||
* but if they aren't sufficiently flexible, then you can override this method.
|
||||
*
|
||||
* @return FormField subclass
|
||||
*/
|
||||
public function getReportField() {
|
||||
// TODO Remove coupling with global state
|
||||
$params = isset($_REQUEST['filters']) ? $_REQUEST['filters'] : array();
|
||||
$items = $this->sourceRecords($params, null, null);
|
||||
/**
|
||||
* counts the number of objects returned
|
||||
* @param array $params - any parameters for the sourceRecords
|
||||
* @param int|null $limit - the maximum number of records to count
|
||||
* @return int
|
||||
*/
|
||||
public function getCount($params = array(), $limit = null)
|
||||
{
|
||||
$sourceRecords = $this->sourceRecords($params, null, $limit);
|
||||
if (!$sourceRecords instanceof SS_List) {
|
||||
user_error(static::class . "::sourceRecords does not return an SS_List", E_USER_NOTICE);
|
||||
return "-1";
|
||||
}
|
||||
// Some reports may not use the $limit parameter in sourceRecords since it isn't actually
|
||||
// used anywhere else - so make sure we limit record counts if possible.
|
||||
if ($sourceRecords instanceof Limitable) {
|
||||
$sourceRecords = $sourceRecords->limit($limit);
|
||||
}
|
||||
return $sourceRecords->count();
|
||||
}
|
||||
|
||||
$gridFieldConfig = GridFieldConfig::create()->addComponents(
|
||||
new GridFieldToolbarHeader(),
|
||||
new GridFieldSortableHeader(),
|
||||
new GridFieldDataColumns(),
|
||||
new GridFieldPaginator(),
|
||||
new GridFieldButtonRow('after'),
|
||||
new GridFieldPrintButton('buttons-after-left'),
|
||||
new GridFieldExportButton('buttons-after-left')
|
||||
);
|
||||
$gridField = new GridField('Report',$this->title(), $items, $gridFieldConfig);
|
||||
$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
|
||||
$displayFields = array();
|
||||
$fieldCasting = array();
|
||||
$fieldFormatting = array();
|
||||
/**
|
||||
* Counts the number of objects returned up to a configurable limit.
|
||||
*
|
||||
* Large datasets can cause performance issues for some reports if allowed to count all records.
|
||||
* To mitigate this, you can set the limit_count_in_overview config variable to the maximum number
|
||||
* of items you wish to count to. Counts will be limited to this value, and any counts that hit
|
||||
* this limit will be displayed with a plus, e.g. "500+"
|
||||
*
|
||||
* The default is to have no limit.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCountForOverview(): string
|
||||
{
|
||||
$limit = $this->config()->get('limit_count_in_overview');
|
||||
$count = $this->getCount([], $limit);
|
||||
if ($limit && $count == $limit) {
|
||||
$count = "$count+";
|
||||
}
|
||||
return "$count";
|
||||
}
|
||||
|
||||
// Parse the column information
|
||||
foreach($this->columns() as $source => $info) {
|
||||
if(is_string($info)) $info = array('title' => $info);
|
||||
|
||||
if(isset($info['formatting'])) $fieldFormatting[$source] = $info['formatting'];
|
||||
if(isset($info['csvFormatting'])) $csvFieldFormatting[$source] = $info['csvFormatting'];
|
||||
if(isset($info['casting'])) $fieldCasting[$source] = $info['casting'];
|
||||
/**
|
||||
* Return an array of excluded reports. That is, reports that will not be included in
|
||||
* the list of reports in report admin in the CMS.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_excluded_reports()
|
||||
{
|
||||
return (array) self::config()->get('excluded_reports');
|
||||
}
|
||||
|
||||
if(isset($info['link']) && $info['link']) {
|
||||
$link = singleton('CMSPageEditController')->Link('show');
|
||||
$fieldFormatting[$source] = '<a href=\"' . $link . '/$ID\">$value</a>';
|
||||
}
|
||||
/**
|
||||
* Return the SS_Report objects making up the given list.
|
||||
*
|
||||
* @return Report[] Array of Report objects
|
||||
*/
|
||||
public static function get_reports()
|
||||
{
|
||||
$reports = ClassInfo::subclassesFor(get_called_class());
|
||||
|
||||
$displayFields[$source] = isset($info['title']) ? $info['title'] : $source;
|
||||
}
|
||||
$columns->setDisplayFields($displayFields);
|
||||
$columns->setFieldCasting($fieldCasting);
|
||||
$columns->setFieldFormatting($fieldFormatting);
|
||||
$reportsArray = [];
|
||||
if ($reports && count($reports ?? []) > 0) {
|
||||
$excludedReports = static::get_excluded_reports();
|
||||
// Collect reports into array with an attribute for 'sort'
|
||||
foreach ($reports as $report) {
|
||||
// Don't use the Report superclass, or any excluded report classes
|
||||
if (in_array($report, $excludedReports ?? [])) {
|
||||
continue;
|
||||
}
|
||||
$reflectionClass = new ReflectionClass($report);
|
||||
// Don't use abstract classes
|
||||
if ($reflectionClass->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $gridField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Member $member
|
||||
* @return boolean
|
||||
*/
|
||||
public function canView($member = null) {
|
||||
if(!$member && $member !== FALSE) {
|
||||
$member = Member::currentUser();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var Report $reportObj */
|
||||
$reportObj = $report::create();
|
||||
if ($reportObj->hasMethod('sort')) {
|
||||
// Use the sort method to specify the sort field
|
||||
$reportObj->sort = $reportObj->sort();
|
||||
}
|
||||
$reportsArray[$report] = $reportObj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of this report, which
|
||||
* is used by the templates to render the
|
||||
* name of the report in the report tree,
|
||||
* the left hand pane inside ReportAdmin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function TreeTitle() {
|
||||
return $this->title();
|
||||
}
|
||||
uasort($reportsArray, function ($a, $b) {
|
||||
if ($a->sort == $b->sort) {
|
||||
return 0;
|
||||
} else {
|
||||
return ($a->sort < $b->sort) ? -1 : 1;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* SS_ReportWrapper is a base class for creating report wappers.
|
||||
*
|
||||
* Wrappers encapsulate an existing report to alter their behaviour - they are implementations of
|
||||
* the standard GoF decorator pattern.
|
||||
*
|
||||
* This base class ensure that, by default, wrappers behave in the same way as the report that is
|
||||
* being wrapped. You should override any methods that need to behave differently in your subclass
|
||||
* of SS_ReportWrapper.
|
||||
*
|
||||
* It also makes calls to 2 empty methods that you can override {@link beforeQuery()} and
|
||||
* {@link afterQuery()}
|
||||
*
|
||||
* @package reports
|
||||
*/
|
||||
abstract class SS_ReportWrapper extends SS_Report {
|
||||
protected $baseReport;
|
||||
|
||||
public function __construct($baseReport) {
|
||||
$this->baseReport = is_string($baseReport) ? new $baseReport : $baseReport;
|
||||
$this->dataClass = $this->baseReport->dataClass();
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function ID() {
|
||||
return get_class($this->baseReport) . '_' . get_class($this);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Filtering
|
||||
|
||||
public function parameterFields() {
|
||||
return $this->baseReport->parameterFields();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Columns
|
||||
|
||||
public function columns() {
|
||||
return $this->baseReport->columns();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Querying
|
||||
|
||||
/**
|
||||
* Override this method to perform some actions prior to querying.
|
||||
*/
|
||||
public function beforeQuery($params) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to perform some actions after querying.
|
||||
*/
|
||||
public function afterQuery() {}
|
||||
|
||||
public function sourceQuery($params) {
|
||||
if($this->baseReport->hasMethod('sourceRecords')) {
|
||||
// The default implementation will create a fake query from our sourceRecords() method
|
||||
return parent::sourceQuery($params);
|
||||
|
||||
} else if($this->baseReport->hasMethod('sourceQuery')) {
|
||||
$this->beforeQuery($params);
|
||||
$query = $this->baseReport->sourceQuery($params);
|
||||
$this->afterQuery();
|
||||
return $query;
|
||||
|
||||
} else {
|
||||
user_error("Please override sourceQuery()/sourceRecords() and columns() in your base report", E_USER_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function sourceRecords($params = array(), $sort = null, $limit = null) {
|
||||
$this->beforeQuery($params);
|
||||
$records = $this->baseReport->sourceRecords($params, $sort, $limit);
|
||||
$this->afterQuery();
|
||||
return $records;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Pass-through
|
||||
|
||||
public function title() {
|
||||
return $this->baseReport->title();
|
||||
}
|
||||
|
||||
public function group() {
|
||||
return $this->baseReport->hasMethod('group') ? $this->baseReport->group() : 'Group';
|
||||
}
|
||||
|
||||
public function sort() {
|
||||
return $this->baseReport->hasMethod('sort') ? $this->baseReport->sort() : 0;
|
||||
}
|
||||
|
||||
public function description() {
|
||||
return $this->baseReport->description();
|
||||
}
|
||||
|
||||
public function canView($member = null) {
|
||||
return $this->baseReport->canView($member);
|
||||
}
|
||||
return $reportsArray;
|
||||
}
|
||||
|
||||
/////////////////////// UI METHODS ///////////////////////
|
||||
|
||||
|
||||
/**
|
||||
* Returns a FieldList with which to create the CMS editing form.
|
||||
* You can use the extend() method of FieldList to create customised forms for your other
|
||||
* data objects.
|
||||
*
|
||||
* @uses getReportField() to render a table, or similar field for the report. This
|
||||
* method should be defined on the SS_Report subclasses.
|
||||
*
|
||||
* @return FieldList
|
||||
*/
|
||||
public function getCMSFields()
|
||||
{
|
||||
$fields = new FieldList();
|
||||
|
||||
if ($description = $this->description()) {
|
||||
$fields->push(new LiteralField('ReportDescription', "<p>" . $description . "</p>"));
|
||||
}
|
||||
|
||||
// Add search fields is available
|
||||
if ($this->hasMethod('parameterFields') && $parameterFields = $this->parameterFields()) {
|
||||
/** @var FormField $field */
|
||||
foreach ($parameterFields as $field) {
|
||||
// Namespace fields for easier handling in form submissions
|
||||
$field->setName(sprintf('filters[%s]', $field->getName()));
|
||||
$field->addExtraClass('no-change-track'); // ignore in changetracker
|
||||
$fields->push($field);
|
||||
}
|
||||
|
||||
// Add a search button
|
||||
$formAction = FormAction::create(
|
||||
'updatereport',
|
||||
_t('SilverStripe\\Forms\\GridField\\GridField.Filter', 'Filter')
|
||||
);
|
||||
$formAction->addExtraClass('btn-primary mb-4');
|
||||
|
||||
$fields->push($formAction);
|
||||
}
|
||||
|
||||
$fields->push($this->getReportField());
|
||||
|
||||
$this->extend('updateCMSFields', $fields);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
public function getCMSActions()
|
||||
{
|
||||
// getCMSActions() can be extended with updateCMSActions() on a extension
|
||||
$actions = new FieldList();
|
||||
$this->extend('updateCMSActions', $actions);
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a field, such as a {@link GridField} that is
|
||||
* used to show and manipulate data relating to this report.
|
||||
*
|
||||
* Generally, you should override {@link columns()} and {@link records()} to make your report,
|
||||
* but if they aren't sufficiently flexible, then you can override this method.
|
||||
*
|
||||
* @return \SilverStripe\Forms\FormField subclass
|
||||
*/
|
||||
public function getReportField()
|
||||
{
|
||||
$params = $this->getSourceParams();
|
||||
$items = $this->sourceRecords($params, null, null);
|
||||
|
||||
$gridFieldConfig = GridFieldConfig::create()->addComponents(
|
||||
GridFieldButtonRow::create('before'),
|
||||
GridFieldPrintButton::create('buttons-before-left'),
|
||||
GridFieldExportButton::create('buttons-before-left'),
|
||||
GridFieldSortableHeader::create(),
|
||||
GridFieldDataColumns::create(),
|
||||
GridFieldPaginator::create()
|
||||
);
|
||||
/** @var GridField $gridField */
|
||||
$gridField = GridField::create('Report', null, $items, $gridFieldConfig);
|
||||
/** @var GridFieldDataColumns $columns */
|
||||
$columns = $gridField->getConfig()->getComponentByType(GridFieldDataColumns::class);
|
||||
$displayFields = [];
|
||||
$fieldCasting = [];
|
||||
$fieldFormatting = [];
|
||||
|
||||
// Parse the column information
|
||||
foreach ($this->columns() as $source => $info) {
|
||||
if (is_string($info)) {
|
||||
$info = ['title' => $info];
|
||||
}
|
||||
|
||||
if (isset($info['formatting'])) {
|
||||
$fieldFormatting[$source] = $info['formatting'];
|
||||
}
|
||||
if (isset($info['csvFormatting'])) {
|
||||
$csvFieldFormatting[$source] = $info['csvFormatting'];
|
||||
}
|
||||
if (isset($info['casting'])) {
|
||||
$fieldCasting[$source] = $info['casting'];
|
||||
}
|
||||
|
||||
if (isset($info['link']) && $info['link']) {
|
||||
if (is_callable($info['link'])) {
|
||||
$fieldFormatting[$source] = $info['link'];
|
||||
} else {
|
||||
$fieldFormatting[$source] = function ($value, $item) {
|
||||
if ($item instanceof CMSPreviewable) {
|
||||
/** @var CMSPreviewable $item */
|
||||
return sprintf(
|
||||
'<a class="grid-field__link-block" href="%s" title="%s">%s</a>',
|
||||
Convert::raw2att($item->CMSEditLink()),
|
||||
Convert::raw2att($value),
|
||||
Convert::raw2xml($value)
|
||||
);
|
||||
}
|
||||
return $value;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
$displayFields[$source] = isset($info['title']) ? $info['title'] : $source;
|
||||
}
|
||||
$columns->setDisplayFields($displayFields);
|
||||
$columns->setFieldCasting($fieldCasting);
|
||||
$columns->setFieldFormatting($fieldFormatting);
|
||||
|
||||
return $gridField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Member $member
|
||||
* @return boolean
|
||||
*/
|
||||
public function canView($member = null)
|
||||
{
|
||||
if (!$member && $member !== false) {
|
||||
$member = Security::getCurrentUser();
|
||||
}
|
||||
|
||||
$extended = $this->extendedCan('canView', $member);
|
||||
if ($extended !== null) {
|
||||
return $extended;
|
||||
}
|
||||
|
||||
if ($member && Permission::checkMember($member, array('CMS_ACCESS_LeftAndMain', 'CMS_ACCESS_ReportAdmin'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to assist with permission extension
|
||||
*
|
||||
* {@see DataObject::extendedCan()}
|
||||
*
|
||||
* @param string $methodName Method on the same object, e.g. {@link canEdit()}
|
||||
* @param Member|int $member
|
||||
* @return boolean|null
|
||||
*/
|
||||
public function extendedCan($methodName, $member)
|
||||
{
|
||||
$results = $this->extend($methodName, $member);
|
||||
if ($results && is_array($results)) {
|
||||
// Remove NULLs
|
||||
$results = array_filter($results ?? [], function ($v) {
|
||||
return !is_null($v);
|
||||
});
|
||||
// If there are any non-NULL responses, then return the lowest one of them.
|
||||
// If any explicitly deny the permission, then we don't get access
|
||||
if ($results) {
|
||||
return min($results);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the name of this report, which is used by the templates to render the name of the report in the report
|
||||
* tree, the left hand pane inside ReportAdmin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function TreeTitle()
|
||||
{
|
||||
return $this->title();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return additional breadcrumbs for this report. Useful when this report is a child of another.
|
||||
*
|
||||
* @return ArrayData[]
|
||||
*/
|
||||
public function getBreadcrumbs()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get source params for the report to filter by
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getSourceParams()
|
||||
{
|
||||
$params = [];
|
||||
if (Injector::inst()->has(HTTPRequest::class)) {
|
||||
/** @var HTTPRequest $request */
|
||||
$request = Injector::inst()->get(HTTPRequest::class);
|
||||
$params = $request->param('filters') ?: $request->requestVar('filters') ?: [];
|
||||
}
|
||||
|
||||
$this->extend('updateSourceParams', $params);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,189 +1,263 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports;
|
||||
|
||||
use SilverStripe\Admin\LeftAndMain;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\Forms\GridField\GridField;
|
||||
use SilverStripe\Forms\GridField\GridFieldConfig;
|
||||
use SilverStripe\Forms\GridField\GridFieldDataColumns;
|
||||
use SilverStripe\Forms\GridField\GridFieldFooter;
|
||||
use SilverStripe\Forms\GridField\GridFieldSortableHeader;
|
||||
use SilverStripe\Forms\HTMLEditor\HTMLEditorConfig;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\ORM\SS_List;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\PermissionProvider;
|
||||
use SilverStripe\Security\Security;
|
||||
use SilverStripe\View\ArrayData;
|
||||
use SilverStripe\View\Requirements;
|
||||
|
||||
/**
|
||||
* Reports section of the CMS.
|
||||
*
|
||||
*
|
||||
* All reports that should show in the ReportAdmin section
|
||||
* of the CMS need to subclass {@link SS_Report}, and implement
|
||||
* of the CMS need to subclass {@link SilverStripe\Reports\Report}, and implement
|
||||
* the appropriate methods and variables that are required.
|
||||
*
|
||||
* @see SS_Report
|
||||
*
|
||||
* @package reports
|
||||
*/
|
||||
class ReportAdmin extends LeftAndMain implements PermissionProvider {
|
||||
|
||||
private static $url_segment = 'reports';
|
||||
|
||||
private static $url_rule = '/$ReportClass/$Action';
|
||||
|
||||
private static $menu_title = 'Reports';
|
||||
|
||||
private static $template_path = null; // defaults to (project)/templates/email
|
||||
|
||||
private static $tree_class = 'SS_Report';
|
||||
class ReportAdmin extends LeftAndMain implements PermissionProvider
|
||||
{
|
||||
private static $url_segment = 'reports';
|
||||
|
||||
private static $url_handlers = array(
|
||||
'$ReportClass/$Action' => 'handleAction'
|
||||
);
|
||||
private static $menu_title = 'Reports';
|
||||
|
||||
/**
|
||||
* Variable that describes which report we are currently viewing based on
|
||||
* the URL (gets set in init method).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reportClass;
|
||||
private static $menu_icon_class = 'font-icon-chart-line';
|
||||
|
||||
protected $reportObject;
|
||||
|
||||
public function init() {
|
||||
parent::init();
|
||||
private static $template_path = null; // defaults to (project)/templates/email
|
||||
|
||||
//set the report we are currently viewing from the URL
|
||||
$this->reportClass = (isset($this->urlParams['ReportClass']) && $this->urlParams['ReportClass'] !== 'index')
|
||||
? $this->urlParams['ReportClass']
|
||||
: null;
|
||||
$allReports = SS_Report::get_reports();
|
||||
$this->reportObject = (isset($allReports[$this->reportClass])) ? $allReports[$this->reportClass] : null;
|
||||
private static $tree_class = Report::class;
|
||||
|
||||
// Set custom options for TinyMCE specific to ReportAdmin
|
||||
HtmlEditorConfig::get('cms')->setOption('content_css', project() . '/css/editor.css');
|
||||
HtmlEditorConfig::get('cms')->setOption('Lang', i18n::get_tinymce_lang());
|
||||
private static $url_handlers = array(
|
||||
'show/$ReportClass/$Action' => 'handleAction'
|
||||
);
|
||||
|
||||
// Always block the HtmlEditorField.js otherwise it will be sent with an ajax request
|
||||
Requirements::block(FRAMEWORK_DIR . '/javascript/HtmlEditorField.js');
|
||||
Requirements::javascript(REPORTS_DIR . '/javascript/ReportAdmin.js');
|
||||
}
|
||||
/**
|
||||
* Variable that describes which report we are currently viewing based on
|
||||
* the URL (gets set in init method).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reportClass;
|
||||
|
||||
/**
|
||||
* Does the parent permission checks, but also
|
||||
* makes sure that instantiatable subclasses of
|
||||
* {@link Report} exist. By default, the CMS doesn't
|
||||
* include any Reports, so there's no point in showing
|
||||
*
|
||||
* @param Member $member
|
||||
* @return boolean
|
||||
*/
|
||||
public function canView($member = null) {
|
||||
if(!$member && $member !== FALSE) $member = Member::currentUser();
|
||||
/**
|
||||
* @var Report
|
||||
*/
|
||||
protected $reportObject;
|
||||
|
||||
if(!parent::canView($member)) return false;
|
||||
private static $required_permission_codes = 'CMS_ACCESS_ReportAdmin';
|
||||
|
||||
$hasViewableSubclasses = false;
|
||||
foreach($this->Reports() as $report) {
|
||||
if($report->canView($member)) return true;
|
||||
}
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
return false;
|
||||
}
|
||||
// Set custom options for TinyMCE specific to ReportAdmin
|
||||
HTMLEditorConfig::get('cms')->setOption('content_css', project() . '/css/editor.css');
|
||||
|
||||
/**
|
||||
* Return a SS_List of SS_Report subclasses
|
||||
* that are available for use.
|
||||
*
|
||||
* @return SS_List
|
||||
*/
|
||||
public function Reports() {
|
||||
$output = new ArrayList();
|
||||
foreach(SS_Report::get_reports() as $report) {
|
||||
if($report->canView()) $output->push($report);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
Requirements::javascript('silverstripe/reports: javascript/ReportAdmin.js');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have reports and need
|
||||
* to display the "Reports" main menu item
|
||||
* in the CMS.
|
||||
*
|
||||
* The test for an existance of a report
|
||||
* is done by checking for a subclass of
|
||||
* "SS_Report" that exists.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_reports() {
|
||||
return sizeof(SS_Report::get_reports()) > 0;
|
||||
}
|
||||
/**
|
||||
* Does the parent permission checks, but also
|
||||
* makes sure that instantiatable subclasses of
|
||||
* {@link SilverStripe\Reports\Report} exist. By default, the CMS doesn't
|
||||
* include any Reports, so there's no point in showing
|
||||
*
|
||||
* @param Member $member
|
||||
* @return boolean
|
||||
*/
|
||||
public function canView($member = null)
|
||||
{
|
||||
if (!$member && $member !== false) {
|
||||
$member = Security::getCurrentUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Breadcrumbs for the ReportAdmin
|
||||
* @return ArrayList
|
||||
*/
|
||||
public function Breadcrumbs($unlinked = false) {
|
||||
$items = parent::Breadcrumbs($unlinked);
|
||||
|
||||
// The root element should explicitly point to the root node.
|
||||
// Uses session state for current record otherwise.
|
||||
$items[0]->Link = singleton('ReportAdmin')->Link();
|
||||
if (!parent::canView($member)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->reportObject) {
|
||||
//build breadcrumb trail to the current report
|
||||
$items->push(new ArrayData(array(
|
||||
'Title' => $this->reportObject->title(),
|
||||
'Link' => Controller::join_links($this->Link(), '?' . http_build_query(array('q' => $this->request->requestVar('q'))))
|
||||
)));
|
||||
}
|
||||
foreach ($this->Reports() as $report) {
|
||||
if ($report->canView($member)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link to the report admin section, or the specific report that is currently displayed
|
||||
* @return String
|
||||
*/
|
||||
public function Link($action = null) {
|
||||
if ($this->reportObject) {
|
||||
$link = $this->reportObject->getLink($action);
|
||||
} else {
|
||||
$link = self::join_links(parent::Link('index'), $action);
|
||||
}
|
||||
return $link;
|
||||
}
|
||||
/**
|
||||
* Return a SS_List of SS_Report subclasses
|
||||
* that are available for use.
|
||||
*
|
||||
* @return SS_List
|
||||
*/
|
||||
public function Reports()
|
||||
{
|
||||
$output = new ArrayList();
|
||||
/** @var Report $report */
|
||||
foreach (Report::get_reports() as $report) {
|
||||
if ($report->canView()) {
|
||||
$output->push($report);
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function providePermissions() {
|
||||
$title = _t("ReportAdmin.MENUTITLE", LeftAndMain::menu_title_for_class($this->class));
|
||||
return array(
|
||||
"CMS_ACCESS_ReportAdmin" => array(
|
||||
'name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array('title' => $title)),
|
||||
'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
|
||||
)
|
||||
);
|
||||
}
|
||||
public function handleAction($request, $action)
|
||||
{
|
||||
$this->reportClass = $this->unsanitiseClassName($request->param('ReportClass'));
|
||||
|
||||
public function getEditForm($id = null, $fields = null) {
|
||||
$report = $this->reportObject;
|
||||
if($report) {
|
||||
$fields = $report->getCMSFields();
|
||||
} else {
|
||||
// List all reports
|
||||
$fields = new FieldList();
|
||||
$gridFieldConfig = GridFieldConfig::create()->addComponents(
|
||||
new GridFieldToolbarHeader(),
|
||||
new GridFieldSortableHeader(),
|
||||
new GridFieldDataColumns(),
|
||||
new GridFieldFooter()
|
||||
);
|
||||
$gridField = new GridField('Reports',false, $this->Reports(), $gridFieldConfig);
|
||||
$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
|
||||
$columns->setDisplayFields(array(
|
||||
'title' => _t('ReportAdmin.ReportTitle', 'Title'),
|
||||
));
|
||||
// Check report
|
||||
if ($this->reportClass) {
|
||||
$allReports = Report::get_reports();
|
||||
if (empty($allReports[$this->reportClass])) {
|
||||
return $this->httpError(404);
|
||||
}
|
||||
$this->reportObject = $allReports[$this->reportClass];
|
||||
}
|
||||
|
||||
$columns->setFieldFormatting(array(
|
||||
'title' => '<a href=\"$Link\" class=\"cms-panel-link\">$value</a>'
|
||||
));
|
||||
$gridField->addExtraClass('all-reports-gridfield');
|
||||
$fields->push($gridField);
|
||||
}
|
||||
// Delegate to sub-form
|
||||
return parent::handleAction($request, $action);
|
||||
}
|
||||
|
||||
$actions = new FieldList();
|
||||
$form = new Form($this, "EditForm", $fields, $actions);
|
||||
$form->addExtraClass('cms-edit-form cms-panel-padded center ' . $this->BaseCSSClasses());
|
||||
$form->loadDataFrom($this->request->getVars());
|
||||
/**
|
||||
* Unsanitise a model class' name from a URL param
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
protected function unsanitiseClassName($class)
|
||||
{
|
||||
return str_replace('-', '\\', $class ?? '');
|
||||
}
|
||||
|
||||
$this->extend('updateEditForm', $form);
|
||||
/**
|
||||
* Determine if we have reports and need
|
||||
* to display the "Reports" main menu item
|
||||
* in the CMS.
|
||||
*
|
||||
* The test for an existance of a report
|
||||
* is done by checking for a subclass of
|
||||
* "SS_Report" that exists.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_reports()
|
||||
{
|
||||
return sizeof(Report::get_reports() ?? []) > 0;
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
/**
|
||||
* Returns the Breadcrumbs for the ReportAdmin
|
||||
*
|
||||
* @param bool $unlinked
|
||||
* @return ArrayList
|
||||
*/
|
||||
public function Breadcrumbs($unlinked = false)
|
||||
{
|
||||
$items = parent::Breadcrumbs($unlinked);
|
||||
|
||||
// The root element should explicitly point to the root node.
|
||||
// Uses session state for current record otherwise.
|
||||
$items[0]->Link = singleton('SilverStripe\\Reports\\ReportAdmin')->Link();
|
||||
|
||||
if ($report = $this->reportObject) {
|
||||
$breadcrumbs = $report->getBreadcrumbs();
|
||||
if (!empty($breadcrumbs)) {
|
||||
foreach ($breadcrumbs as $crumb) {
|
||||
$items->push($crumb);
|
||||
}
|
||||
}
|
||||
|
||||
//build breadcrumb trail to the current report
|
||||
$items->push(ArrayData::create([
|
||||
'Title' => $report->title(),
|
||||
'Link' => Controller::join_links(
|
||||
$this->Link(),
|
||||
'?' . http_build_query(['q' => $this->request->requestVar('q')])
|
||||
)
|
||||
]));
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link to the report admin section, or the specific report that is currently displayed
|
||||
*
|
||||
* @param string $action
|
||||
* @return string
|
||||
*/
|
||||
public function Link($action = null)
|
||||
{
|
||||
if ($this->reportObject) {
|
||||
return $this->reportObject->getLink($action);
|
||||
}
|
||||
|
||||
// Basic link to this cms section
|
||||
return parent::Link($action);
|
||||
}
|
||||
|
||||
public function providePermissions()
|
||||
{
|
||||
return array(
|
||||
"CMS_ACCESS_ReportAdmin" => array(
|
||||
'name' => _t('SilverStripe\\CMS\\Controllers\\CMSMain.ACCESS', "Access to '{title}' section", array(
|
||||
'title' => static::menu_title()
|
||||
)),
|
||||
'category' => _t('SilverStripe\\Security\\Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function getEditForm($id = null, $fields = null)
|
||||
{
|
||||
$report = $this->reportObject;
|
||||
if ($report) {
|
||||
$fields = $report->getCMSFields();
|
||||
} else {
|
||||
// List all reports
|
||||
$fields = new FieldList();
|
||||
$gridFieldConfig = GridFieldConfig::create()->addComponents(
|
||||
GridFieldSortableHeader::create(),
|
||||
GridFieldDataColumns::create(),
|
||||
GridFieldFooter::create()
|
||||
);
|
||||
$gridField = GridField::create('Reports', false, $this->Reports(), $gridFieldConfig);
|
||||
/** @var GridFieldDataColumns $columns */
|
||||
$columns = $gridField->getConfig()
|
||||
->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDataColumns');
|
||||
$columns->setDisplayFields(array(
|
||||
'title' => _t('SilverStripe\\Reports\\ReportAdmin.ReportTitle', 'Title'),
|
||||
));
|
||||
|
||||
$columns->setFieldFormatting(array(
|
||||
'title' => '<a href=\"$Link\" class=\"grid-field__link-block\">$value ($CountForOverview)</a>'
|
||||
));
|
||||
$gridField->addExtraClass('all-reports-gridfield');
|
||||
$fields->push($gridField);
|
||||
}
|
||||
|
||||
$actions = new FieldList();
|
||||
$form = new Form($this, "EditForm", $fields, $actions);
|
||||
$form->addExtraClass(
|
||||
'panel panel--padded panel--scrollable cms-edit-form cms-panel-padded' . $this->BaseCSSClasses()
|
||||
);
|
||||
$form->loadDataFrom($this->request->getVars());
|
||||
|
||||
$this->extend('updateEditForm', $form);
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports;
|
||||
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
|
||||
/**
|
||||
* SS_ReportWrapper is a base class for creating report wappers.
|
||||
*
|
||||
* Wrappers encapsulate an existing report to alter their behaviour - they are implementations of
|
||||
* the standard GoF decorator pattern.
|
||||
*
|
||||
* This base class ensure that, by default, wrappers behave in the same way as the report that is
|
||||
* being wrapped. You should override any methods that need to behave differently in your subclass
|
||||
* of SS_ReportWrapper.
|
||||
*
|
||||
* It also makes calls to 2 empty methods that you can override {@link beforeQuery()} and
|
||||
* {@link afterQuery()}
|
||||
*/
|
||||
abstract class ReportWrapper extends Report
|
||||
{
|
||||
protected $baseReport;
|
||||
|
||||
public function __construct($baseReport)
|
||||
{
|
||||
$this->baseReport = is_string($baseReport) ? Injector::inst()->create($baseReport) : $baseReport;
|
||||
$this->dataClass = $this->baseReport->dataClass();
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function ID()
|
||||
{
|
||||
return get_class($this->baseReport) . '_' . static::class;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Filtering
|
||||
|
||||
public function parameterFields()
|
||||
{
|
||||
return $this->baseReport->parameterFields();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Columns
|
||||
|
||||
public function columns()
|
||||
{
|
||||
return $this->baseReport->columns();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Querying
|
||||
|
||||
/**
|
||||
* Override this method to perform some actions prior to querying.
|
||||
*/
|
||||
public function beforeQuery($params)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to perform some actions after querying.
|
||||
*/
|
||||
public function afterQuery()
|
||||
{
|
||||
}
|
||||
|
||||
public function sourceQuery($params)
|
||||
{
|
||||
if ($this->baseReport->hasMethod('sourceRecords')) {
|
||||
// The default implementation will create a fake query from our sourceRecords() method
|
||||
return parent::sourceQuery($params);
|
||||
} elseif ($this->baseReport->hasMethod('sourceQuery')) {
|
||||
$this->beforeQuery($params);
|
||||
$query = $this->baseReport->sourceQuery($params);
|
||||
$this->afterQuery();
|
||||
return $query;
|
||||
} else {
|
||||
throw new \RuntimeException(
|
||||
"Please override sourceQuery()/sourceRecords() and columns() in your base report"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function sourceRecords($params = array(), $sort = null, $limit = null)
|
||||
{
|
||||
$this->beforeQuery($params);
|
||||
$records = $this->baseReport->sourceRecords($params, $sort, $limit);
|
||||
$this->afterQuery();
|
||||
return $records;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Pass-through
|
||||
|
||||
public function title()
|
||||
{
|
||||
return $this->baseReport->title();
|
||||
}
|
||||
|
||||
public function group()
|
||||
{
|
||||
/** @skipUpgrade */
|
||||
return $this->baseReport->hasMethod('group') ? $this->baseReport->group() : 'Group';
|
||||
}
|
||||
|
||||
public function sort()
|
||||
{
|
||||
return $this->baseReport->hasMethod('sort') ? $this->baseReport->sort() : 0;
|
||||
}
|
||||
|
||||
public function description()
|
||||
{
|
||||
return $this->baseReport->description();
|
||||
}
|
||||
|
||||
public function canView($member = null)
|
||||
{
|
||||
return $this->baseReport->canView($member);
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Renderer for showing SideReports in CMSMain
|
||||
*
|
||||
* @package reports
|
||||
*/
|
||||
class SideReportView extends ViewableData {
|
||||
|
||||
protected $controller, $report;
|
||||
protected $parameters;
|
||||
|
||||
public function __construct($controller, $report) {
|
||||
$this->controller = $controller;
|
||||
$this->report = $report;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function group() {
|
||||
return _t('SideReport.OtherGroupTitle', "Other");
|
||||
}
|
||||
|
||||
public function sort() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function setParameters($parameters) {
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
public function forTemplate() {
|
||||
$records = $this->report->records($this->parameters);
|
||||
$columns = $this->report->columns();
|
||||
|
||||
if($records && $records->Count()) {
|
||||
$result = "<ul class=\"$this->class\">\n";
|
||||
|
||||
foreach($records as $record) {
|
||||
$result .= "<li>\n";
|
||||
foreach($columns as $source => $info) {
|
||||
if(is_string($info)) $info = array('title' => $info);
|
||||
$result .= $this->formatValue($record, $source, $info);
|
||||
}
|
||||
$result .= "\n</li>\n";
|
||||
}
|
||||
$result .= "</ul>\n";
|
||||
} else {
|
||||
$result = "<p class=\"message notice\">" .
|
||||
_t(
|
||||
'SideReport.REPEMPTY',
|
||||
'The {title} report is empty.',
|
||||
array('title' => $this->report->title())
|
||||
)
|
||||
. "</p>";
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function formatValue($record, $source, $info) {
|
||||
// Field sources
|
||||
//if(is_string($source)) {
|
||||
$val = Convert::raw2xml($record->$source);
|
||||
//} else {
|
||||
// $val = $record->val($source[0], $source[1]);
|
||||
//}
|
||||
|
||||
// Casting, a la TableListField. We're deep-calling a helper method on TableListField that
|
||||
// should probably be pushed elsewhere...
|
||||
if(!empty($info['casting'])) {
|
||||
$val = TableListField::getCastedValue($val, $info['casting']);
|
||||
}
|
||||
|
||||
// Formatting, a la TableListField
|
||||
if(!empty($info['formatting'])) {
|
||||
$format = str_replace('$value', "__VAL__", $info['formatting']);
|
||||
$format = preg_replace('/\$([A-Za-z0-9-_]+)/','$record->$1', $format);
|
||||
$format = str_replace('__VAL__', '$val', $format);
|
||||
$val = eval('return "' . $format . '";');
|
||||
}
|
||||
|
||||
$prefix = empty($info['newline']) ? "" : "<br>";
|
||||
|
||||
|
||||
$classClause = "";
|
||||
if(isset($info['title'])) {
|
||||
$cssClass = preg_replace('/[^A-Za-z0-9]+/', '', $info['title']);
|
||||
$classClause = "class=\"$cssClass\"";
|
||||
}
|
||||
|
||||
if(isset($info['link']) && $info['link']) {
|
||||
$linkBase = singleton('CMSPageEditController')->Link('show') . '/';
|
||||
$link = ($info['link'] === true) ? $linkBase . $record->ID : $info['link'];
|
||||
return $prefix . "<a $classClause href=\"$link\">$val</a>";
|
||||
} else {
|
||||
return $prefix . "<span $classClause>$val</span>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A report wrapper that makes it easier to define slightly different behaviour for side-reports.
|
||||
*
|
||||
* This report wrapper will use sideReportColumns() for the report columns, instead of columns().
|
||||
*
|
||||
* @package reports
|
||||
*/
|
||||
class SideReportWrapper extends SS_ReportWrapper {
|
||||
public function columns() {
|
||||
if($this->baseReport->hasMethod('sideReportColumns')) {
|
||||
return $this->baseReport->sideReportColumns();
|
||||
} else {
|
||||
return parent::columns();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports;
|
||||
|
||||
use SilverStripe\Core\Convert;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\View\ViewableData;
|
||||
|
||||
/**
|
||||
* Renderer for showing SideReports in CMSMain
|
||||
*/
|
||||
class SideReportView extends ViewableData
|
||||
{
|
||||
protected $controller;
|
||||
protected $report;
|
||||
protected $parameters;
|
||||
|
||||
public function __construct($controller, $report)
|
||||
{
|
||||
$this->controller = $controller;
|
||||
$this->report = $report;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function group()
|
||||
{
|
||||
return _t('SilverStripe\\Reports\\SideReport.OtherGroupTitle', "Other");
|
||||
}
|
||||
|
||||
public function sort()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function setParameters($parameters)
|
||||
{
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
public function forTemplate()
|
||||
{
|
||||
$records = $this->report->records($this->parameters);
|
||||
$columns = $this->report->columns();
|
||||
|
||||
if ($records && $records->Count()) {
|
||||
$result = "<ul class=\"" . static::class . "\">\n";
|
||||
|
||||
foreach ($records as $record) {
|
||||
$result .= "<li>\n";
|
||||
foreach ($columns as $source => $info) {
|
||||
if (is_string($info)) {
|
||||
$info = array('title' => $info);
|
||||
}
|
||||
$result .= $this->formatValue($record, $source, $info);
|
||||
}
|
||||
$result .= "\n</li>\n";
|
||||
}
|
||||
$result .= "</ul>\n";
|
||||
} else {
|
||||
$result = "<p class=\"message notice\">" .
|
||||
_t(
|
||||
'SilverStripe\\Reports\\SideReport.REPEMPTY',
|
||||
'The {title} report is empty.',
|
||||
array('title' => $this->report->title())
|
||||
)
|
||||
. "</p>";
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function formatValue($record, $source, $info)
|
||||
{
|
||||
// Cast value
|
||||
if (!empty($info['casting'])) {
|
||||
$val = DBField::create_field($info['casting'], $record->source)->forTemplate();
|
||||
} else {
|
||||
$val = Convert::raw2xml($record->$source);
|
||||
}
|
||||
|
||||
// Formatting, a la TableListField
|
||||
if (!empty($info['formatting'])) {
|
||||
$format = str_replace('$value', "__VAL__", $info['formatting'] ?? '');
|
||||
$format = preg_replace('/\$([A-Za-z0-9-_]+)/', '$record->$1', $format ?? '');
|
||||
$format = str_replace('__VAL__', '$val', $format ?? '');
|
||||
$val = eval('return "' . $format . '";');
|
||||
}
|
||||
|
||||
$prefix = empty($info['newline']) ? "" : "<br>";
|
||||
|
||||
|
||||
$classClause = "";
|
||||
if (isset($info['title'])) {
|
||||
$cssClass = preg_replace('/[^A-Za-z0-9]+/', '', $info['title'] ?? '');
|
||||
$classClause = "class=\"$cssClass\"";
|
||||
}
|
||||
|
||||
if (isset($info['link']) && $info['link']) {
|
||||
$link = ($info['link'] === true && $record->hasMethod('CMSEditLink'))
|
||||
? $record->CMSEditLink()
|
||||
: $info['link'];
|
||||
return $prefix . "<a $classClause href=\"$link\">$val</a>";
|
||||
} else {
|
||||
return $prefix . "<span $classClause>$val</span>";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports;
|
||||
|
||||
/**
|
||||
* A report wrapper that makes it easier to define slightly different behaviour for side-reports.
|
||||
*
|
||||
* This report wrapper will use sideReportColumns() for the report columns, instead of columns().
|
||||
*/
|
||||
class SideReportWrapper extends ReportWrapper
|
||||
{
|
||||
public function columns()
|
||||
{
|
||||
if ($this->baseReport->hasMethod('sideReportColumns')) {
|
||||
return $this->baseReport->sideReportColumns();
|
||||
} else {
|
||||
return parent::columns();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,48 @@
|
|||
{
|
||||
"name": "silverstripe/reports",
|
||||
"type": "silverstripe-module",
|
||||
"homepage": "http://silverstripe.org",
|
||||
"license": "BSD-3-Clause",
|
||||
"keywords": ["silverstripe", "cms", "reports"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "SilverStripe",
|
||||
"homepage": "http://silverstripe.com"
|
||||
},
|
||||
{
|
||||
"name": "The SilverStripe Community",
|
||||
"homepage": "http://silverstripe.org"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.3",
|
||||
"silverstripe/framework": "~3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/PHPUnit": "~3.7"
|
||||
}
|
||||
}
|
||||
"name": "silverstripe/reports",
|
||||
"type": "silverstripe-vendormodule",
|
||||
"description": "Reports module for SilverStripe CMS",
|
||||
"homepage": "http://silverstripe.org",
|
||||
"license": "BSD-3-Clause",
|
||||
"keywords": [
|
||||
"silverstripe",
|
||||
"cms",
|
||||
"reports"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "SilverStripe",
|
||||
"homepage": "http://silverstripe.com"
|
||||
},
|
||||
{
|
||||
"name": "The SilverStripe Community",
|
||||
"homepage": "http://silverstripe.org"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"silverstripe/framework": "^4.11",
|
||||
"silverstripe/admin": "^1.6@dev",
|
||||
"silverstripe/versioned": "^1.6@dev",
|
||||
"silverstripe/config": "^1.0@dev",
|
||||
"silverstripe/assets": "^1.6@dev",
|
||||
"silverstripe/vendor-plugin": "^1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"squizlabs/php_codesniffer": "^3.0"
|
||||
},
|
||||
"extra": {
|
||||
"expose": [
|
||||
"javascript"
|
||||
]
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SilverStripe\\Reports\\": "code/",
|
||||
"SilverStripe\\Reports\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
|
@ -2,22 +2,32 @@
|
|||
* File: ReportAdmin.js
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
$.entwine('ss', function($){
|
||||
$('.ReportAdmin .cms-edit-form').entwine({
|
||||
onsubmit: function(e) {
|
||||
var url = $.path.parseUrl(document.location.href).hrefNoSearch,
|
||||
params = this.find(':input[name^=filters]').serializeArray();
|
||||
params = $.grep(params, function(param) {return (param.value);}); // filter out empty
|
||||
if(params) url = $.path.addSearchParams(url, $.param(params));
|
||||
$('.cms-container').loadPanel(url);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$('.ReportAdmin').css({
|
||||
'overflow-y': 'scroll',
|
||||
'padding-top': 0
|
||||
});
|
||||
});
|
||||
(function ($) {
|
||||
$.entwine("ss", function ($) {
|
||||
$(".ReportAdmin .cms-edit-form").entwine({
|
||||
onsubmit: function (e) {
|
||||
let url = $.path.parseUrl(document.location.href).hrefNoSearch;
|
||||
let params = this.find(":input[name^=filters]").serializeArray();
|
||||
|
||||
try {
|
||||
params = $.grep(params, function (param) {
|
||||
// filter out empty
|
||||
return param.value;
|
||||
});
|
||||
|
||||
// convert params to a query string
|
||||
params = $.param(params);
|
||||
|
||||
// append query string to url
|
||||
url += "?" + params;
|
||||
|
||||
$(".cms-container").loadPanel(url);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
bg:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Отчети
|
||||
ReportTitle: Заглавие
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Други
|
||||
REPEMPTY: 'Отчетът за {title} е празен.'
|
|
@ -0,0 +1,7 @@
|
|||
cs:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Výkazy
|
||||
ReportTitle: Titulek
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Jiné
|
||||
REPEMPTY: 'Výkaz {title} je prázdný.'
|
|
@ -0,0 +1,7 @@
|
|||
da:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Rapporter
|
||||
ReportTitle: Titel
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Andre
|
||||
REPEMPTY: 'Rapporten {title} er tom.'
|
|
@ -0,0 +1,7 @@
|
|||
de:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Berichte
|
||||
ReportTitle: Titel
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Andere
|
||||
REPEMPTY: 'Der Bericht {title} ist leer.'
|
|
@ -0,0 +1,11 @@
|
|||
en:
|
||||
SilverStripe\CMS\Controllers\CMSPageHistoryController:
|
||||
PREVIEW: 'Website preview'
|
||||
SilverStripe\Forms\GridField\GridField:
|
||||
Filter: Filter
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Reports
|
||||
ReportTitle: Title
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Other
|
||||
REPEMPTY: 'The {title} report is empty.'
|
|
@ -0,0 +1,11 @@
|
|||
eo:
|
||||
SilverStripe\CMS\Controllers\CMSPageHistoryController:
|
||||
PREVIEW: 'Retejon antaŭvidi'
|
||||
SilverStripe\Forms\GridField\GridField:
|
||||
Filter: Filtrilo
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Raportoj
|
||||
ReportTitle: Titoloj
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Aliaj
|
||||
REPEMPTY: 'La raporto {title} estas malplena.'
|
|
@ -0,0 +1,7 @@
|
|||
fi:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Raportit
|
||||
ReportTitle: Otsikko
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Muu
|
||||
REPEMPTY: '{title} raportti on tyhjä.'
|
|
@ -0,0 +1,7 @@
|
|||
fr:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Rapports
|
||||
ReportTitle: Titre
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Autre
|
||||
REPEMPTY: 'Le rapport {title} est vide.'
|
|
@ -0,0 +1,6 @@
|
|||
hr:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Izvještaji
|
||||
ReportTitle: Naslov
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Drugi
|
|
@ -0,0 +1,7 @@
|
|||
it:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Rapporti
|
||||
ReportTitle: Titolo
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Altro
|
||||
REPEMPTY: 'Il rapporto {title} è vuoto.'
|
|
@ -0,0 +1,7 @@
|
|||
nl:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Rapporten
|
||||
ReportTitle: Naam
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Overig
|
||||
REPEMPTY: 'Het {title} rapport is leeg.'
|
|
@ -0,0 +1,7 @@
|
|||
pl:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Raporty
|
||||
ReportTitle: Tytuł
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Inne
|
||||
REPEMPTY: 'Raport {title} jest pusty'
|
|
@ -0,0 +1,7 @@
|
|||
ru:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Отчёты
|
||||
ReportTitle: Заголовок
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Другое
|
||||
REPEMPTY: 'Отчёт {title} пустой.'
|
|
@ -0,0 +1,9 @@
|
|||
sk:
|
||||
SilverStripe\CMS\Controllers\CMSPageHistoryController:
|
||||
PREVIEW: 'Náhľad webovej stránky'
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Správy
|
||||
ReportTitle: Názov
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Iné
|
||||
REPEMPTY: 'Správa {title} je prázdna.'
|
|
@ -0,0 +1,7 @@
|
|||
sl:
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Poročila
|
||||
ReportTitle: Naslov
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Drugo
|
||||
REPEMPTY: 'Poročilo ''{title}'' je prazno.'
|
|
@ -0,0 +1,9 @@
|
|||
sv:
|
||||
SilverStripe\CMS\Controllers\CMSPageHistoryController:
|
||||
PREVIEW: 'Webbplats förhandsvisning'
|
||||
SilverStripe\Reports\ReportAdmin:
|
||||
MENUTITLE: Rapporter
|
||||
ReportTitle: Titel
|
||||
SilverStripe\Reports\SideReport:
|
||||
OtherGroupTitle: Annat
|
||||
REPEMPTY: 'Rapporten {title} är tom.'
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ruleset name="SilverStripe">
|
||||
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
|
||||
|
||||
<file>code</file>
|
||||
<file>tests</file>
|
||||
|
||||
<!-- base rules are PSR-12 -->
|
||||
<rule ref="PSR12" >
|
||||
<!-- Current exclusions -->
|
||||
<exclude name="PSR1.Methods.CamelCapsMethodName.NotCamelCaps" />
|
||||
</rule>
|
||||
</ruleset>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit bootstrap="vendor/silverstripe/framework/tests/bootstrap.php" colors="true">
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Default">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist addUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">.</directory>
|
||||
<exclude>
|
||||
<directory suffix=".php">tests/</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
</phpunit>
|
|
@ -1,20 +0,0 @@
|
|||
<div id="reportadmin-cms-content" class="cms-content center cms-tabset $BaseCSSClasses" data-layout-type="border" data-pjax-fragment="Content">
|
||||
|
||||
<div class="cms-content-header north">
|
||||
<% with $EditForm %>
|
||||
<div class="cms-content-header-info">
|
||||
<% include BackLink_Button %>
|
||||
<% with $Controller %>
|
||||
<% include CMSBreadcrumbs %>
|
||||
<% end_with %>
|
||||
</div>
|
||||
<% end_with %>
|
||||
</div>
|
||||
|
||||
<div class="cms-content-fields center ui-widget-content" data-layout-type="border">
|
||||
|
||||
$EditForm
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -1,41 +0,0 @@
|
|||
<form $FormAttributes>
|
||||
|
||||
|
||||
<p style="display: none;" class="message " id="Form_EditForm_error"/>
|
||||
|
||||
|
||||
<div id="ScrollPanel">
|
||||
<fieldset>
|
||||
$FieldMap.ReportTitle.FieldHolder
|
||||
$FieldMap.ReportDescription.FieldHolder
|
||||
|
||||
<% if $FieldMap.Filters.Children %>
|
||||
<h4><% _t('ReportAdminForm.FILTERBY', 'Filter by') %></h4>
|
||||
|
||||
<div class="filters">
|
||||
<% loop $FieldMap.Filters %>
|
||||
<% loop $Children %>
|
||||
$FieldHolder
|
||||
<% end_loop %>
|
||||
<% end_loop %>
|
||||
</div>
|
||||
|
||||
<div id="action_updatereport">
|
||||
<% if $FieldMap.action_updatereport %>
|
||||
$FieldMap.action_updatereport.Field
|
||||
<% end_if %>
|
||||
</div>
|
||||
|
||||
<div style="clear: both"> </div>
|
||||
<% end_if %>
|
||||
|
||||
$FieldMap.ReportContent.FieldHolder
|
||||
|
||||
<% loop $HiddenFields %>$Field<% end_loop %>
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="clear"><!-- --></div>
|
||||
</form>
|
|
@ -0,0 +1,20 @@
|
|||
<div id="reportadmin-cms-content" class="flexbox-area-grow fill-height cms-content cms-tabset $BaseCSSClasses" data-layout-type="border" data-pjax-fragment="Content">
|
||||
|
||||
<div class="cms-content-header vertical-align-items">
|
||||
<% with $EditForm %>
|
||||
<div class="cms-content-header-info flexbox-area-grow vertical-align-items">
|
||||
<% include SilverStripe\\Admin\\BackLink_Button %>
|
||||
<% with $Controller %>
|
||||
<% include SilverStripe\\Admin\\CMSBreadcrumbs %>
|
||||
<% end_with %>
|
||||
</div>
|
||||
<% end_with %>
|
||||
</div>
|
||||
|
||||
<div class="flexbox-area-grow cms-content-fields ui-widget-content" data-layout-type="border">
|
||||
|
||||
$EditForm
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,14 @@
|
|||
<div class="cms-preview fill-height flexbox-area-grow" data-layout-type="border">
|
||||
<div class="panel flexbox-area-grow fill-height">
|
||||
<div class="preview-note"><span><!-- --></span><%t SilverStripe\CMS\Controllers\CMSPageHistoryController.PREVIEW 'Website preview' %></div>
|
||||
<div class="preview__device">
|
||||
<div class="preview-device-outer">
|
||||
<div class="preview-device-inner">
|
||||
<iframe src="about:blank" class="center" name="cms-preview-iframe"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar toolbar--south cms-content-controls cms-preview-controls"></div>
|
||||
<div class="cms-preview-overlay ui-widget-overlay-light"></div>
|
||||
</div>
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports\Tests;
|
||||
|
||||
use ReflectionClass;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Reports\Report;
|
||||
use SilverStripe\Reports\ReportAdmin;
|
||||
use SilverStripe\Reports\Tests\ReportAdminTest\FakeReport;
|
||||
use SilverStripe\Reports\Tests\ReportAdminTest\FakeReport2;
|
||||
|
||||
class ReportAdminTest extends SapphireTest
|
||||
{
|
||||
public function testBreadcrumbsAreGenerated()
|
||||
{
|
||||
$noExtraCrumbs = FakeReport::create();
|
||||
|
||||
$controller = $this->mockController($noExtraCrumbs);
|
||||
$breadcrumbs = $controller->BreadCrumbs();
|
||||
|
||||
$this->assertCount(2, $breadcrumbs);
|
||||
$map = $breadcrumbs[0]->toMap();
|
||||
$this->assertSame('Reports', $map['Title']);
|
||||
$this->assertSame('admin/reports/', $map['Link']);
|
||||
|
||||
$map = $breadcrumbs[1]->toMap();
|
||||
$this->assertSame('Fake report', $map['Title']);
|
||||
|
||||
$extraCrumbs = FakeReport2::create();
|
||||
$controller = $this->mockController($extraCrumbs);
|
||||
$breadcrumbs = $controller->Breadcrumbs();
|
||||
|
||||
$this->assertCount(3, $breadcrumbs);
|
||||
|
||||
$map = $breadcrumbs[0]->toMap();
|
||||
$this->assertSame('Reports', $map['Title']);
|
||||
$this->assertSame('admin/reports/', $map['Link']);
|
||||
|
||||
$map = $breadcrumbs[1]->toMap();
|
||||
$this->assertSame('Fake report title', $map['Title']);
|
||||
$this->assertSame('admin/reports/show/SilverStripe-Reports-Tests-ReportAdminTest-FakeReport', $map['Link']);
|
||||
|
||||
$map = $breadcrumbs[2]->toMap();
|
||||
$this->assertSame('Fake report two', $map['Title']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Report $report
|
||||
* @return ReportAdmin
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function mockController(Report $report)
|
||||
{
|
||||
$reflector = new ReflectionClass($controller = ReportAdmin::create());
|
||||
|
||||
$reportClass = $reflector->getProperty('reportClass');
|
||||
$reportClass->setAccessible(true);
|
||||
$reportClass->setValue($controller, get_class($report));
|
||||
|
||||
$reportObject = $reflector->getProperty('reportObject');
|
||||
$reportObject->setAccessible(true);
|
||||
$reportObject->setValue($controller, $report);
|
||||
|
||||
$controller->setRequest(Controller::curr()->getRequest());
|
||||
|
||||
return $controller;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports\Tests\ReportAdminTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\Reports\Report;
|
||||
|
||||
class FakeReport extends Report implements TestOnly
|
||||
{
|
||||
public function title()
|
||||
{
|
||||
return 'Fake report';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports\Tests\ReportAdminTest;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\Reports\Report;
|
||||
use SilverStripe\Reports\ReportAdmin;
|
||||
use SilverStripe\View\ArrayData;
|
||||
|
||||
class FakeReport2 extends Report implements TestOnly
|
||||
{
|
||||
public function title()
|
||||
{
|
||||
return 'Fake report two';
|
||||
}
|
||||
|
||||
public function getBreadcrumbs()
|
||||
{
|
||||
return [ArrayData::create([
|
||||
'Title' => 'Fake report title',
|
||||
'Link' => FakeReport::singleton()->getLink()
|
||||
])];
|
||||
}
|
||||
}
|
|
@ -1,134 +1,133 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package reports
|
||||
* @subpackage tests
|
||||
*/
|
||||
class ReportTest extends SapphireTest {
|
||||
namespace SilverStripe\Reports\Tests;
|
||||
|
||||
public function testGetReports() {
|
||||
$reports = SS_Report::get_reports();
|
||||
$this->assertNotNull($reports, "Reports returned");
|
||||
$previousSort = 0;
|
||||
foreach($reports as $report) {
|
||||
$this->assertGreaterThanOrEqual($previousSort, $report->sort, "Reports are in correct sort order");
|
||||
$previousSort = $report->sort;
|
||||
}
|
||||
}
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Forms\GridField\GridFieldDataColumns;
|
||||
use SilverStripe\Reports\Tests\ReportTest\FakeObject;
|
||||
use SilverStripe\Reports\Tests\ReportTest\FakeTest;
|
||||
use SilverStripe\Reports\Tests\ReportTest\FakeTest2;
|
||||
use SilverStripe\Reports\Report;
|
||||
|
||||
public function testExcludeReport() {
|
||||
$reports = SS_Report::get_reports();
|
||||
$reportNames = array();
|
||||
foreach($reports as $report) {
|
||||
$reportNames[] = $report->class;
|
||||
}
|
||||
$this->assertContains('ReportTest_FakeTest',$reportNames,'ReportTest_FakeTest is in reports list');
|
||||
class ReportTest extends SapphireTest
|
||||
{
|
||||
protected static $extra_dataobjects = [
|
||||
FakeObject::class,
|
||||
];
|
||||
|
||||
//exclude one report
|
||||
SS_Report::add_excluded_reports('ReportTest_FakeTest');
|
||||
public function testGetReports()
|
||||
{
|
||||
$reports = Report::get_reports();
|
||||
$this->assertNotNull($reports, "Reports returned");
|
||||
$previousSort = 0;
|
||||
foreach ($reports as $report) {
|
||||
$this->assertGreaterThanOrEqual($previousSort, $report->sort, "Reports are in correct sort order");
|
||||
$previousSort = $report->sort;
|
||||
}
|
||||
}
|
||||
|
||||
$reports = SS_Report::get_reports();
|
||||
$reportNames = array();
|
||||
foreach($reports as $report) {
|
||||
$reportNames[] = $report->class;
|
||||
}
|
||||
$this->assertNotContains('ReportTest_FakeTest',$reportNames,'ReportTest_FakeTest is NOT in reports list');
|
||||
public function testExcludeReport()
|
||||
{
|
||||
$reports = Report::get_reports();
|
||||
$reportNames = [];
|
||||
foreach ($reports as $report) {
|
||||
$reportNames[] = get_class($report);
|
||||
}
|
||||
$this->assertContains(FakeTest::class, $reportNames, 'ReportTest_FakeTest is in reports list');
|
||||
|
||||
//exclude two reports
|
||||
SS_Report::add_excluded_reports(array('ReportTest_FakeTest','ReportTest_FakeTest2'));
|
||||
// Exclude one report
|
||||
Config::modify()->merge(Report::class, 'excluded_reports', [FakeTest::class]);
|
||||
|
||||
$reports = SS_Report::get_reports();
|
||||
$reportNames = array();
|
||||
foreach($reports as $report) {
|
||||
$reportNames[] = $report->class;
|
||||
}
|
||||
$this->assertNotContains('ReportTest_FakeTest',$reportNames,'ReportTest_FakeTest is NOT in reports list');
|
||||
$this->assertNotContains('ReportTest_FakeTest2',$reportNames,'ReportTest_FakeTest2 is NOT in reports list');
|
||||
}
|
||||
$reports = Report::get_reports();
|
||||
$reportNames = array();
|
||||
foreach ($reports as $report) {
|
||||
$reportNames[] = get_class($report);
|
||||
}
|
||||
$this->assertNotContains(FakeTest::class, $reportNames, 'ReportTest_FakeTest is NOT in reports list');
|
||||
|
||||
public function testAbstractClassesAreExcluded() {
|
||||
$reports = SS_Report::get_reports();
|
||||
$reportNames = array();
|
||||
foreach($reports as $report) {
|
||||
$reportNames[] = $report->class;
|
||||
}
|
||||
$this->assertNotContains('ReportTest_FakeTest_Abstract',
|
||||
$reportNames,
|
||||
'ReportTest_FakeTest_Abstract is NOT in reports list as it is abstract');
|
||||
}
|
||||
// Exclude two reports
|
||||
Config::modify()->merge(Report::class, 'excluded_reports', [
|
||||
FakeTest::class,
|
||||
FakeTest2::class
|
||||
]);
|
||||
|
||||
$reports = Report::get_reports();
|
||||
$reportNames = [];
|
||||
foreach ($reports as $report) {
|
||||
$reportNames[] = get_class($report);
|
||||
}
|
||||
$this->assertNotContains(FakeTest::class, $reportNames, 'ReportTest_FakeTest is NOT in reports list');
|
||||
$this->assertNotContains(FakeTest2::class, $reportNames, 'ReportTest_FakeTest2 is NOT in reports list');
|
||||
}
|
||||
|
||||
public function testAbstractClassesAreExcluded()
|
||||
{
|
||||
$reports = Report::get_reports();
|
||||
$reportNames = array();
|
||||
foreach ($reports as $report) {
|
||||
$reportNames[] = get_class($report);
|
||||
}
|
||||
$this->assertNotContains(
|
||||
'ReportTest_FakeTest_Abstract',
|
||||
$reportNames,
|
||||
'ReportTest_FakeTest_Abstract is NOT in reports list as it is abstract'
|
||||
);
|
||||
}
|
||||
|
||||
public function testPermissions()
|
||||
{
|
||||
$report = new ReportTest\FakeTest2();
|
||||
|
||||
// Visitor cannot view
|
||||
$this->logOut();
|
||||
$this->assertFalse($report->canView());
|
||||
|
||||
// Logged in user that cannot view reports
|
||||
$this->logInWithPermission('SITETREE_REORGANISE');
|
||||
$this->assertFalse($report->canView());
|
||||
|
||||
// Logged in with report permissions
|
||||
$this->logInWithPermission('CMS_ACCESS_ReportAdmin');
|
||||
$this->assertTrue($report->canView());
|
||||
|
||||
// Admin can view
|
||||
$this->logInWithPermission('ADMIN');
|
||||
$this->assertTrue($report->canView());
|
||||
}
|
||||
|
||||
public function testColumnLink()
|
||||
{
|
||||
$report = new ReportTest\FakeTest();
|
||||
/** @var GridField $gridField */
|
||||
$gridField = $report->getReportField();
|
||||
/** @var GridFieldDataColumns $columns */
|
||||
$columns = $gridField->getConfig()->getComponentByType(GridFieldDataColumns::class);
|
||||
|
||||
$page = new ReportTest\FakeObject();
|
||||
$page->Title = 'My Object';
|
||||
$page->ID = 959547;
|
||||
|
||||
$titleContent = $columns->getColumnContent($gridField, $page, 'Title');
|
||||
$this->assertEquals(
|
||||
'<a class="grid-field__link-block" href="dummy-edit-link/959547" title="My Object">My Object</a>',
|
||||
$titleContent
|
||||
);
|
||||
}
|
||||
|
||||
public function testCountForOverview()
|
||||
{
|
||||
$report = new ReportTest\FakeTest3();
|
||||
|
||||
// Count is limited to 10000 by default
|
||||
$this->assertEquals('10000+', $report->getCountForOverview());
|
||||
|
||||
// Count is limited as per configuration
|
||||
Config::modify()->set(ReportTest\FakeTest3::class, 'limit_count_in_overview', 15);
|
||||
$this->assertEquals('15+', $report->getCountForOverview());
|
||||
|
||||
// A null limit displays the full count
|
||||
Config::modify()->set(ReportTest\FakeTest3::class, 'limit_count_in_overview', null);
|
||||
$this->assertEquals('15000', $report->getCountForOverview());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @package reports
|
||||
* @subpackage tests
|
||||
*/
|
||||
class ReportTest_FakeTest extends SS_Report implements TestOnly {
|
||||
public function title() {
|
||||
return 'Report title';
|
||||
}
|
||||
public function columns() {
|
||||
return array(
|
||||
"Title" => array(
|
||||
"title" => "Page Title"
|
||||
)
|
||||
);
|
||||
}
|
||||
public function sourceRecords($params, $sort, $limit) {
|
||||
return new ArrayList();
|
||||
}
|
||||
|
||||
public function sort() {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @package reports
|
||||
* @subpackage tests
|
||||
*/
|
||||
class ReportTest_FakeTest2 extends SS_Report implements TestOnly {
|
||||
public function title() {
|
||||
return 'Report title 2';
|
||||
}
|
||||
public function columns() {
|
||||
return array(
|
||||
"Title" => array(
|
||||
"title" => "Page Title 2"
|
||||
)
|
||||
);
|
||||
}
|
||||
public function sourceRecords($params, $sort, $limit) {
|
||||
return new ArrayList();
|
||||
}
|
||||
|
||||
public function sort() {
|
||||
return 98;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @package reports
|
||||
* @subpackage tests
|
||||
*/
|
||||
abstract class ReportTest_FakeTest_Abstract extends SS_Report implements TestOnly {
|
||||
|
||||
public function title() {
|
||||
return 'Report title Abstract';
|
||||
}
|
||||
|
||||
public function columns() {
|
||||
return array(
|
||||
"Title" => array(
|
||||
"title" => "Page Title Abstract"
|
||||
)
|
||||
);
|
||||
}
|
||||
public function sourceRecords($params, $sort, $limit) {
|
||||
return new ArrayList();
|
||||
}
|
||||
|
||||
public function sort() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports\Tests\ReportTest;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\CMSPreviewable;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class FakeObject extends DataObject implements CMSPreviewable, TestOnly
|
||||
{
|
||||
private static $table_name = 'ReportTest_FakeObject';
|
||||
|
||||
private static $db = array(
|
||||
'Title' => 'Varchar'
|
||||
);
|
||||
|
||||
/**
|
||||
* @return String Absolute URL to the end-user view for this record.
|
||||
* Example: http://mysite.com/my-record
|
||||
*/
|
||||
public function Link()
|
||||
{
|
||||
return Controller::join_links('dummy-link', $this->ID);
|
||||
}
|
||||
|
||||
public function CMSEditLink()
|
||||
{
|
||||
return Controller::join_links('dummy-edit-link', $this->ID);
|
||||
}
|
||||
|
||||
public function PreviewLink($action = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getMimeType()
|
||||
{
|
||||
return 'text/html';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports\Tests\ReportTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\Reports\Report;
|
||||
|
||||
class FakeTest extends Report implements TestOnly
|
||||
{
|
||||
public function title()
|
||||
{
|
||||
return 'Report title';
|
||||
}
|
||||
|
||||
public function columns()
|
||||
{
|
||||
return array(
|
||||
"Title" => array(
|
||||
"title" => "Page Title",
|
||||
"link" => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function sourceRecords($params, $sort, $limit)
|
||||
{
|
||||
return new ArrayList();
|
||||
}
|
||||
|
||||
public function sort()
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports\Tests\ReportTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\Reports\Report;
|
||||
|
||||
class FakeTest2 extends Report implements TestOnly
|
||||
{
|
||||
public function title()
|
||||
{
|
||||
return 'Report title 2';
|
||||
}
|
||||
|
||||
public function columns()
|
||||
{
|
||||
return array(
|
||||
"Title" => array(
|
||||
"title" => "Page Title 2"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function sourceRecords($params, $sort, $limit)
|
||||
{
|
||||
return new ArrayList();
|
||||
}
|
||||
|
||||
public function sort()
|
||||
{
|
||||
return 98;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports\Tests\ReportTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\Reports\Report;
|
||||
|
||||
class FakeTest3 extends Report implements TestOnly
|
||||
{
|
||||
public function sourceRecords($params, $sort, $limit)
|
||||
{
|
||||
return new ArrayList(range(1, 15000));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\Reports\Tests\ReportTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\Reports\Report;
|
||||
|
||||
abstract class FakeTestAbstract extends Report implements TestOnly
|
||||
{
|
||||
public function title()
|
||||
{
|
||||
return 'Report title Abstract';
|
||||
}
|
||||
|
||||
public function columns()
|
||||
{
|
||||
return array(
|
||||
"Title" => array(
|
||||
"title" => "Page Title Abstract"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function sourceRecords($params, $sort, $limit)
|
||||
{
|
||||
return new ArrayList();
|
||||
}
|
||||
|
||||
public function sort()
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue