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).
|
481
code/Report.php
481
code/Report.php
|
@ -1,4 +1,38 @@
|
|||
<?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.
|
||||
*
|
||||
|
@ -27,9 +61,11 @@
|
|||
* 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 {
|
||||
class Report extends ViewableData
|
||||
{
|
||||
/**
|
||||
* This is the title of the report,
|
||||
* used by the ReportAdmin templates.
|
||||
|
@ -51,7 +87,7 @@ class SS_Report extends ViewableData {
|
|||
* The class of object being managed by this report.
|
||||
* Set by overriding in your subclass.
|
||||
*/
|
||||
protected $dataClass = 'SiteTree';
|
||||
protected $dataClass = SiteTree::class;
|
||||
|
||||
/**
|
||||
* A field that specifies the sort order of this report
|
||||
|
@ -61,19 +97,23 @@ class SS_Report extends ViewableData {
|
|||
|
||||
/**
|
||||
* Reports which should not be collected and returned in get_reports
|
||||
*
|
||||
* @config
|
||||
* @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
|
||||
);
|
||||
private static $excluded_reports = [
|
||||
self::class,
|
||||
ReportWrapper::class,
|
||||
SideReportWrapper::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Return the title of this report.
|
||||
|
@ -82,7 +122,8 @@ class SS_Report extends ViewableData {
|
|||
* - overriding description(), which lets you support i18n
|
||||
* - defining the $description property
|
||||
*/
|
||||
public function title() {
|
||||
public function title()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
|
@ -91,7 +132,8 @@ class SS_Report extends ViewableData {
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle() {
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title();
|
||||
}
|
||||
|
||||
|
@ -102,103 +144,180 @@ class SS_Report extends ViewableData {
|
|||
* - overriding description(), which lets you support i18n
|
||||
* - defining the $description property
|
||||
*/
|
||||
public function description() {
|
||||
public function description()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link SQLQuery} that provides your report data.
|
||||
* Return the {@link DataQuery} that provides your report data.
|
||||
*
|
||||
* @param array $params
|
||||
* @return DataQuery
|
||||
*/
|
||||
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);
|
||||
public function sourceQuery($params)
|
||||
{
|
||||
if (!$this->hasMethod('sourceRecords')) {
|
||||
throw new \RuntimeException(
|
||||
'Please override sourceQuery()/sourceRecords() and columns() or, '
|
||||
. 'if necessary, override getReportField()'
|
||||
);
|
||||
}
|
||||
|
||||
return $this->sourceRecords($params, null, null)->dataQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a SS_List records for this report.
|
||||
*
|
||||
* @param array $params
|
||||
* @return SS_List
|
||||
*/
|
||||
public function records($params) {
|
||||
public function records($params)
|
||||
{
|
||||
if ($this->hasMethod('sourceRecords')) {
|
||||
return $this->sourceRecords($params, null, null);
|
||||
} else {
|
||||
$query = $this->sourceQuery();
|
||||
$results = new ArrayList();
|
||||
$query = $this->sourceQuery($params);
|
||||
$results = ArrayList::create();
|
||||
foreach ($query->execute() as $data) {
|
||||
$class = $this->dataClass();
|
||||
$result = new $class($data);
|
||||
$result = Injector::inst()->create($class, $data);
|
||||
$results->push($result);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
||||
public function columns()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data class for this report
|
||||
*/
|
||||
public function dataClass() {
|
||||
public function dataClass()
|
||||
{
|
||||
return $this->dataClass;
|
||||
}
|
||||
|
||||
public function getLink($action = null) {
|
||||
|
||||
public function getLink($action = null)
|
||||
{
|
||||
return Controller::join_links(
|
||||
'admin/reports/',
|
||||
"$this->class",
|
||||
'/', // trailing slash needed if $action is null!
|
||||
"$action"
|
||||
ReportAdmin::singleton()->Link('show'),
|
||||
$this->sanitiseClassName(static::class),
|
||||
$action
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Sanitise a model class' name for inclusion in a link
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
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;
|
||||
protected function sanitiseClassName($class)
|
||||
{
|
||||
return str_replace('\\', '-', $class ?? '');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
public static function get_excluded_reports()
|
||||
{
|
||||
return (array) self::config()->get('excluded_reports');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SS_Report objects making up the given list.
|
||||
* @return Array of SS_Report objects
|
||||
*
|
||||
* @return Report[] Array of Report objects
|
||||
*/
|
||||
static public function get_reports() {
|
||||
public static function get_reports()
|
||||
{
|
||||
$reports = ClassInfo::subclassesFor(get_called_class());
|
||||
|
||||
$reportsArray = array();
|
||||
if ($reports && count($reports) > 0) {
|
||||
//collect reports into array with an attribute for 'sort'
|
||||
$reportsArray = [];
|
||||
if ($reports && count($reports ?? []) > 0) {
|
||||
$excludedReports = static::get_excluded_reports();
|
||||
// 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
|
||||
// Don't use the Report superclass, or any excluded report classes
|
||||
if (in_array($report, $excludedReports ?? [])) {
|
||||
continue;
|
||||
}
|
||||
$reflectionClass = new ReflectionClass($report);
|
||||
if ($reflectionClass->isAbstract()) continue; //don't use abstract classes
|
||||
// Don't use abstract classes
|
||||
if ($reflectionClass->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$reportObj = new $report;
|
||||
if (method_exists($reportObj,'sort')) $reportObj->sort = $reportObj->sort(); //use the sort method to specify the sort field
|
||||
/** @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;
|
||||
}
|
||||
}
|
||||
|
||||
uasort($reportsArray, function ($a, $b) {
|
||||
if($a->sort == $b->sort) return 0;
|
||||
else return ($a->sort < $b->sort) ? -1 : 1;
|
||||
if ($a->sort == $b->sort) {
|
||||
return 0;
|
||||
} else {
|
||||
return ($a->sort < $b->sort) ? -1 : 1;
|
||||
}
|
||||
});
|
||||
|
||||
return $reportsArray;
|
||||
|
@ -217,19 +336,17 @@ class SS_Report extends ViewableData {
|
|||
*
|
||||
* @return FieldList
|
||||
*/
|
||||
public function getCMSFields() {
|
||||
public function getCMSFields()
|
||||
{
|
||||
$fields = new FieldList();
|
||||
|
||||
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()) {
|
||||
/** @var FormField $field */
|
||||
foreach ($parameterFields as $field) {
|
||||
// Namespace fields for easier handling in form submissions
|
||||
$field->setName(sprintf('filters[%s]', $field->getName()));
|
||||
|
@ -238,7 +355,13 @@ class SS_Report extends ViewableData {
|
|||
}
|
||||
|
||||
// Add a search button
|
||||
$fields->push(new FormAction('updatereport', _t('GridField.Filter')));
|
||||
$formAction = FormAction::create(
|
||||
'updatereport',
|
||||
_t('SilverStripe\\Forms\\GridField\\GridField.Filter', 'Filter')
|
||||
);
|
||||
$formAction->addExtraClass('btn-primary mb-4');
|
||||
|
||||
$fields->push($formAction);
|
||||
}
|
||||
|
||||
$fields->push($this->getReportField());
|
||||
|
@ -248,7 +371,8 @@ class SS_Report extends ViewableData {
|
|||
return $fields;
|
||||
}
|
||||
|
||||
public function getCMSActions() {
|
||||
public function getCMSActions()
|
||||
{
|
||||
// getCMSActions() can be extended with updateCMSActions() on a extension
|
||||
$actions = new FieldList();
|
||||
$this->extend('updateCMSActions', $actions);
|
||||
|
@ -262,39 +386,62 @@ class SS_Report extends ViewableData {
|
|||
* 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
|
||||
* @return \SilverStripe\Forms\FormField subclass
|
||||
*/
|
||||
public function getReportField() {
|
||||
// TODO Remove coupling with global state
|
||||
$params = isset($_REQUEST['filters']) ? $_REQUEST['filters'] : array();
|
||||
public function getReportField()
|
||||
{
|
||||
$params = $this->getSourceParams();
|
||||
$items = $this->sourceRecords($params, null, null);
|
||||
|
||||
$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')
|
||||
GridFieldButtonRow::create('before'),
|
||||
GridFieldPrintButton::create('buttons-before-left'),
|
||||
GridFieldExportButton::create('buttons-before-left'),
|
||||
GridFieldSortableHeader::create(),
|
||||
GridFieldDataColumns::create(),
|
||||
GridFieldPaginator::create()
|
||||
);
|
||||
$gridField = new GridField('Report',$this->title(), $items, $gridFieldConfig);
|
||||
$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
|
||||
$displayFields = array();
|
||||
$fieldCasting = array();
|
||||
$fieldFormatting = array();
|
||||
/** @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 = array('title' => $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['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']) {
|
||||
$link = singleton('CMSPageEditController')->Link('show');
|
||||
$fieldFormatting[$source] = '<a href=\"' . $link . '/$ID\">$value</a>';
|
||||
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;
|
||||
|
@ -310,130 +457,88 @@ class SS_Report extends ViewableData {
|
|||
* @param Member $member
|
||||
* @return boolean
|
||||
*/
|
||||
public function canView($member = null) {
|
||||
if(!$member && $member !== FALSE) {
|
||||
$member = Member::currentUser();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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() {
|
||||
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 [];
|
||||
}
|
||||
|
||||
/**
|
||||
* SS_ReportWrapper is a base class for creating report wappers.
|
||||
* Get source params for the report to filter by
|
||||
*
|
||||
* 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
|
||||
* @return array
|
||||
*/
|
||||
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();
|
||||
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') ?: [];
|
||||
}
|
||||
|
||||
public function ID() {
|
||||
return get_class($this->baseReport) . '_' . get_class($this);
|
||||
}
|
||||
$this->extend('updateSourceParams', $params);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 $params;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,46 @@
|
|||
<?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 {
|
||||
|
||||
class ReportAdmin extends LeftAndMain implements PermissionProvider
|
||||
{
|
||||
private static $url_segment = 'reports';
|
||||
|
||||
private static $url_rule = '/$ReportClass/$Action';
|
||||
|
||||
private static $menu_title = 'Reports';
|
||||
|
||||
private static $menu_icon_class = 'font-icon-chart-line';
|
||||
|
||||
private static $template_path = null; // defaults to (project)/templates/email
|
||||
|
||||
private static $tree_class = 'SS_Report';
|
||||
private static $tree_class = Report::class;
|
||||
|
||||
private static $url_handlers = array(
|
||||
'$ReportClass/$Action' => 'handleAction'
|
||||
'show/$ReportClass/$Action' => 'handleAction'
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -34,44 +51,46 @@ class ReportAdmin extends LeftAndMain implements PermissionProvider {
|
|||
*/
|
||||
protected $reportClass;
|
||||
|
||||
/**
|
||||
* @var Report
|
||||
*/
|
||||
protected $reportObject;
|
||||
|
||||
public function init() {
|
||||
private static $required_permission_codes = 'CMS_ACCESS_ReportAdmin';
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
//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;
|
||||
|
||||
// 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());
|
||||
HTMLEditorConfig::get('cms')->setOption('content_css', project() . '/css/editor.css');
|
||||
|
||||
// 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');
|
||||
Requirements::javascript('silverstripe/reports: javascript/ReportAdmin.js');
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the parent permission checks, but also
|
||||
* makes sure that instantiatable subclasses of
|
||||
* {@link Report} exist. By default, the CMS doesn't
|
||||
* {@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 = Member::currentUser();
|
||||
public function canView($member = null)
|
||||
{
|
||||
if (!$member && $member !== false) {
|
||||
$member = Security::getCurrentUser();
|
||||
}
|
||||
|
||||
if(!parent::canView($member)) return false;
|
||||
if (!parent::canView($member)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasViewableSubclasses = false;
|
||||
foreach ($this->Reports() as $report) {
|
||||
if($report->canView($member)) return true;
|
||||
if ($report->canView($member)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -83,14 +102,46 @@ class ReportAdmin extends LeftAndMain implements PermissionProvider {
|
|||
*
|
||||
* @return SS_List
|
||||
*/
|
||||
public function Reports() {
|
||||
public function Reports()
|
||||
{
|
||||
$output = new ArrayList();
|
||||
foreach(SS_Report::get_reports() as $report) {
|
||||
if($report->canView()) $output->push($report);
|
||||
/** @var Report $report */
|
||||
foreach (Report::get_reports() as $report) {
|
||||
if ($report->canView()) {
|
||||
$output->push($report);
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function handleAction($request, $action)
|
||||
{
|
||||
$this->reportClass = $this->unsanitiseClassName($request->param('ReportClass'));
|
||||
|
||||
// Check report
|
||||
if ($this->reportClass) {
|
||||
$allReports = Report::get_reports();
|
||||
if (empty($allReports[$this->reportClass])) {
|
||||
return $this->httpError(404);
|
||||
}
|
||||
$this->reportObject = $allReports[$this->reportClass];
|
||||
}
|
||||
|
||||
// Delegate to sub-form
|
||||
return parent::handleAction($request, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsanitise a model class' name from a URL param
|
||||
*
|
||||
* @param string $class
|
||||
* @return string
|
||||
*/
|
||||
protected function unsanitiseClassName($class)
|
||||
{
|
||||
return str_replace('-', '\\', $class ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have reports and need
|
||||
* to display the "Reports" main menu item
|
||||
|
@ -102,27 +153,41 @@ class ReportAdmin extends LeftAndMain implements PermissionProvider {
|
|||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function has_reports() {
|
||||
return sizeof(SS_Report::get_reports()) > 0;
|
||||
public static function has_reports()
|
||||
{
|
||||
return sizeof(Report::get_reports() ?? []) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Breadcrumbs for the ReportAdmin
|
||||
*
|
||||
* @param bool $unlinked
|
||||
* @return ArrayList
|
||||
*/
|
||||
public function Breadcrumbs($unlinked = false) {
|
||||
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();
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
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'))))
|
||||
)));
|
||||
$items->push(ArrayData::create([
|
||||
'Title' => $report->title(),
|
||||
'Link' => Controller::join_links(
|
||||
$this->Link(),
|
||||
'?' . http_build_query(['q' => $this->request->requestVar('q')])
|
||||
)
|
||||
]));
|
||||
}
|
||||
|
||||
return $items;
|
||||
|
@ -130,28 +195,34 @@ class ReportAdmin extends LeftAndMain implements PermissionProvider {
|
|||
|
||||
/**
|
||||
* Returns the link to the report admin section, or the specific report that is currently displayed
|
||||
* @return String
|
||||
*
|
||||
* @param string $action
|
||||
* @return string
|
||||
*/
|
||||
public function Link($action = null) {
|
||||
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 $this->reportObject->getLink($action);
|
||||
}
|
||||
|
||||
public function providePermissions() {
|
||||
$title = _t("ReportAdmin.MENUTITLE", LeftAndMain::menu_title_for_class($this->class));
|
||||
// Basic link to this cms section
|
||||
return parent::Link($action);
|
||||
}
|
||||
|
||||
public function providePermissions()
|
||||
{
|
||||
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')
|
||||
'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) {
|
||||
public function getEditForm($id = null, $fields = null)
|
||||
{
|
||||
$report = $this->reportObject;
|
||||
if ($report) {
|
||||
$fields = $report->getCMSFields();
|
||||
|
@ -159,19 +230,20 @@ class ReportAdmin extends LeftAndMain implements PermissionProvider {
|
|||
// List all reports
|
||||
$fields = new FieldList();
|
||||
$gridFieldConfig = GridFieldConfig::create()->addComponents(
|
||||
new GridFieldToolbarHeader(),
|
||||
new GridFieldSortableHeader(),
|
||||
new GridFieldDataColumns(),
|
||||
new GridFieldFooter()
|
||||
GridFieldSortableHeader::create(),
|
||||
GridFieldDataColumns::create(),
|
||||
GridFieldFooter::create()
|
||||
);
|
||||
$gridField = new GridField('Reports',false, $this->Reports(), $gridFieldConfig);
|
||||
$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
|
||||
$gridField = GridField::create('Reports', false, $this->Reports(), $gridFieldConfig);
|
||||
/** @var GridFieldDataColumns $columns */
|
||||
$columns = $gridField->getConfig()
|
||||
->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDataColumns');
|
||||
$columns->setDisplayFields(array(
|
||||
'title' => _t('ReportAdmin.ReportTitle', 'Title'),
|
||||
'title' => _t('SilverStripe\\Reports\\ReportAdmin.ReportTitle', 'Title'),
|
||||
));
|
||||
|
||||
$columns->setFieldFormatting(array(
|
||||
'title' => '<a href=\"$Link\" class=\"cms-panel-link\">$value</a>'
|
||||
'title' => '<a href=\"$Link\" class=\"grid-field__link-block\">$value ($CountForOverview)</a>'
|
||||
));
|
||||
$gridField->addExtraClass('all-reports-gridfield');
|
||||
$fields->push($gridField);
|
||||
|
@ -179,7 +251,9 @@ class ReportAdmin extends LeftAndMain implements PermissionProvider {
|
|||
|
||||
$actions = new FieldList();
|
||||
$form = new Form($this, "EditForm", $fields, $actions);
|
||||
$form->addExtraClass('cms-edit-form cms-panel-padded center ' . $this->BaseCSSClasses());
|
||||
$form->addExtraClass(
|
||||
'panel panel--padded panel--scrollable cms-edit-form cms-panel-padded' . $this->BaseCSSClasses()
|
||||
);
|
||||
$form->loadDataFrom($this->request->getVars());
|
||||
|
||||
$this->extend('updateEditForm', $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,9 +1,14 @@
|
|||
{
|
||||
"name": "silverstripe/reports",
|
||||
"type": "silverstripe-module",
|
||||
"type": "silverstripe-vendormodule",
|
||||
"description": "Reports module for SilverStripe CMS",
|
||||
"homepage": "http://silverstripe.org",
|
||||
"license": "BSD-3-Clause",
|
||||
"keywords": ["silverstripe", "cms", "reports"],
|
||||
"keywords": [
|
||||
"silverstripe",
|
||||
"cms",
|
||||
"reports"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "SilverStripe",
|
||||
|
@ -15,10 +20,29 @@
|
|||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.3",
|
||||
"silverstripe/framework": "~3.2"
|
||||
"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": "~3.7"
|
||||
"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
|
||||
}
|
|
@ -3,21 +3,31 @@
|
|||
*/
|
||||
|
||||
(function ($) {
|
||||
$.entwine('ss', function($){
|
||||
$('.ReportAdmin .cms-edit-form').entwine({
|
||||
$.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;
|
||||
}
|
||||
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;
|
||||
});
|
||||
|
||||
$('.ReportAdmin').css({
|
||||
'overflow-y': 'scroll',
|
||||
'padding-top': 0
|
||||
// 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,13 +1,24 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @package reports
|
||||
* @subpackage tests
|
||||
*/
|
||||
class ReportTest extends SapphireTest {
|
||||
namespace SilverStripe\Reports\Tests;
|
||||
|
||||
public function testGetReports() {
|
||||
$reports = SS_Report::get_reports();
|
||||
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;
|
||||
|
||||
class ReportTest extends SapphireTest
|
||||
{
|
||||
protected static $extra_dataobjects = [
|
||||
FakeObject::class,
|
||||
];
|
||||
|
||||
public function testGetReports()
|
||||
{
|
||||
$reports = Report::get_reports();
|
||||
$this->assertNotNull($reports, "Reports returned");
|
||||
$previousSort = 0;
|
||||
foreach ($reports as $report) {
|
||||
|
@ -16,119 +27,107 @@ class ReportTest extends SapphireTest {
|
|||
}
|
||||
}
|
||||
|
||||
public function testExcludeReport() {
|
||||
$reports = SS_Report::get_reports();
|
||||
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 one report
|
||||
Config::modify()->merge(Report::class, 'excluded_reports', [FakeTest::class]);
|
||||
|
||||
$reports = Report::get_reports();
|
||||
$reportNames = array();
|
||||
foreach ($reports as $report) {
|
||||
$reportNames[] = $report->class;
|
||||
$reportNames[] = get_class($report);
|
||||
}
|
||||
$this->assertContains('ReportTest_FakeTest',$reportNames,'ReportTest_FakeTest is in reports list');
|
||||
$this->assertNotContains(FakeTest::class, $reportNames, 'ReportTest_FakeTest is NOT in reports list');
|
||||
|
||||
//exclude one report
|
||||
SS_Report::add_excluded_reports('ReportTest_FakeTest');
|
||||
// Exclude two reports
|
||||
Config::modify()->merge(Report::class, 'excluded_reports', [
|
||||
FakeTest::class,
|
||||
FakeTest2::class
|
||||
]);
|
||||
|
||||
$reports = SS_Report::get_reports();
|
||||
$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[] = $report->class;
|
||||
$reportNames[] = get_class($report);
|
||||
}
|
||||
$this->assertNotContains('ReportTest_FakeTest',$reportNames,'ReportTest_FakeTest is NOT in reports list');
|
||||
|
||||
//exclude two reports
|
||||
SS_Report::add_excluded_reports(array('ReportTest_FakeTest','ReportTest_FakeTest2'));
|
||||
|
||||
$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');
|
||||
}
|
||||
|
||||
public function testAbstractClassesAreExcluded() {
|
||||
$reports = SS_Report::get_reports();
|
||||
$reportNames = array();
|
||||
foreach($reports as $report) {
|
||||
$reportNames[] = $report->class;
|
||||
}
|
||||
$this->assertNotContains('ReportTest_FakeTest_Abstract',
|
||||
$this->assertNotContains(
|
||||
'ReportTest_FakeTest_Abstract',
|
||||
$reportNames,
|
||||
'ReportTest_FakeTest_Abstract is NOT in reports list as it is abstract');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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"
|
||||
)
|
||||
'ReportTest_FakeTest_Abstract is NOT in reports list as it is abstract'
|
||||
);
|
||||
}
|
||||
public function sourceRecords($params, $sort, $limit) {
|
||||
return new ArrayList();
|
||||
|
||||
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 sort() {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
public function testColumnLink()
|
||||
{
|
||||
$report = new ReportTest\FakeTest();
|
||||
/** @var GridField $gridField */
|
||||
$gridField = $report->getReportField();
|
||||
/** @var GridFieldDataColumns $columns */
|
||||
$columns = $gridField->getConfig()->getComponentByType(GridFieldDataColumns::class);
|
||||
|
||||
/**
|
||||
* @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"
|
||||
)
|
||||
$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 sourceRecords($params, $sort, $limit) {
|
||||
return new ArrayList();
|
||||
}
|
||||
|
||||
public function sort() {
|
||||
return 98;
|
||||
}
|
||||
}
|
||||
public function testCountForOverview()
|
||||
{
|
||||
$report = new ReportTest\FakeTest3();
|
||||
|
||||
/**
|
||||
* @package reports
|
||||
* @subpackage tests
|
||||
*/
|
||||
abstract class ReportTest_FakeTest_Abstract extends SS_Report implements TestOnly {
|
||||
// Count is limited to 10000 by default
|
||||
$this->assertEquals('10000+', $report->getCountForOverview());
|
||||
|
||||
public function title() {
|
||||
return 'Report title Abstract';
|
||||
}
|
||||
// Count is limited as per configuration
|
||||
Config::modify()->set(ReportTest\FakeTest3::class, 'limit_count_in_overview', 15);
|
||||
$this->assertEquals('15+', $report->getCountForOverview());
|
||||
|
||||
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;
|
||||
// A null limit displays the full count
|
||||
Config::modify()->set(ReportTest\FakeTest3::class, 'limit_count_in_overview', null);
|
||||
$this->assertEquals('15000', $report->getCountForOverview());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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