Compare commits

...

152 Commits
1.1.2 ... 2

Author SHA1 Message Date
Michal Kleiner 9881c03c22 Merge branch '2.7' into 2 2023-05-19 22:22:54 +12:00
Guy Sartorelli e8517ebfb0
Merge pull request #90 from silverstripe/85-implode-args-order
Fix arguments order in SolrIndexCheck
2023-05-19 20:43:40 +12:00
Michal Kleiner 28ca036345
Fix arguments order in SolrIndexCheck 2023-05-19 14:53:43 +12:00
Guy Sartorelli 16caad30b2
Merge branch '2.7' into 2 2023-04-26 12:48:24 +12:00
Maxime Rainville 6f6c30bdcc
Merge pull request #87 from creative-commoners/pulls/2.7/fix-constraints
MNT Revert erroneous dependency changes
2023-03-28 17:15:30 +13:00
Guy Sartorelli f918209e35
MNT Revert erroneous dependency changes 2023-03-28 14:55:27 +13:00
Maxime Rainville 714de64252
Merge pull request #86 from creative-commoners/pulls/2/dispatch-ci
MNT Use gha-dispatch-ci
2023-03-23 14:10:44 +13:00
Steve Boyd d9d245a0da MNT Use gha-dispatch-ci 2023-03-21 12:24:56 +13:00
Guy Sartorelli 9d5f8fce7d
MNT Update development dependencies 2023-03-10 16:36:51 +13:00
Guy Sartorelli f12698f47f
MNT Update release dependencies 2023-03-10 16:36:47 +13:00
Guy Sartorelli 6f0d1c84f2
MNT Update development dependencies 2023-03-10 12:21:31 +13:00
Guy Sartorelli 15a8bca241
Merge pull request #81 from creative-commoners/pulls/2/depr-messages
API Update deprecations
2022-11-21 09:50:03 +13:00
Steve Boyd 94ff5b621c API Update deprecations 2022-11-16 11:31:42 +13:00
Will Rossiter 0e4867f736
DOC add note about `FileAccessibilityAndValidationCheck` 2022-10-07 09:09:09 +13:00
Steve Boyd 7e59b7e88b Merge branch '2.5' into 2 2022-08-02 18:49:26 +12:00
Steve Boyd e4b8934148 Merge branch '2.4' into 2.5 2022-08-02 18:49:22 +12:00
Guy Sartorelli 737ac0d8ed
Merge pull request #80 from creative-commoners/pulls/2.4/standardise-modules
MNT Standardise modules
2022-08-02 15:09:01 +12:00
Steve Boyd 3378dc7a99 MNT Standardise modules 2022-08-01 16:21:54 +12:00
Steve Boyd 58caa67c96 Merge branch '2.5' into 2 2022-07-25 11:25:08 +12:00
Steve Boyd 7be4c0dbf3 Merge branch '2.4' into 2.5 2022-07-25 11:25:04 +12:00
Guy Sartorelli 7ba4acc4b2
Merge pull request #79 from creative-commoners/pulls/2.4/module-standards
MNT Use GitHub Actions CI
2022-07-15 17:30:58 +12:00
Steve Boyd a76c3b89ea MNT Use GitHub Actions CI 2022-07-05 18:55:00 +12:00
Guy Sartorelli 7f6c03b0cb
Merge pull request #78 from wilr/pr/public-privacy
fix: public health/check should not include full details
2022-07-05 17:10:32 +12:00
Will Rossiter 3e3907d14c
fix: public health/check should not include full details 2022-07-05 09:15:03 +12:00
Guy Sartorelli d3d20e5539
Merge pull request #76 from creative-commoners/pulls/2/php81
ENH PHP 8.1 compatibility
2022-04-26 17:58:17 +12:00
Steve Boyd b7554a5fc9 ENH PHP 8.1 compatibility 2022-04-13 10:29:17 +12:00
Daniel Hensby cb13f4ec12
Merge pull request #75 from creative-commoners/pulls/2/php74
DEP Set PHP 7.4 as the minimum version
2022-02-10 11:58:45 +00:00
Steve Boyd 3902a1d688 DEP Set PHP 7.4 as the minimum version 2022-02-10 16:16:07 +13:00
Steve Boyd aa1311fa01
Merge pull request #72 from creative-commoners/pulls/2/bumpguzzle-requirements
MNT Bump guzzle requirements
2021-11-09 13:13:10 +13:00
Steve Boyd 47b184df5d MNT Fix unit test 2021-11-09 11:35:24 +13:00
Maxime Rainville 94fa4ee1f0 MNT Bump guzzle requirements 2021-11-08 10:32:34 +13:00
Maxime Rainville 3f2fbdaea0
Merge pull request #71 from creative-commoners/pulls/2/sapphire-test-nine
API phpunit 9 support
2021-11-01 21:25:32 +13:00
Steve Boyd 05dd7bc0a7 API phpunit 9 support 2021-10-27 18:06:29 +13:00
Steve Boyd f6e001bf55 Merge branch '2.3' into 2 2021-05-21 13:50:33 +12:00
Maxime Rainville e5b77ff325 MNT Remove obsolete branch-alias 2021-05-05 11:17:25 +12:00
Steve Boyd 029078d157
Update build status badge 2021-01-21 16:34:03 +13:00
Steve Boyd f14fe7c40d Merge branch '2.2' into 2 2021-01-02 20:06:19 +13:00
Maxime Rainville 585b6564b2
Merge pull request #70 from creative-commoners/pulls/2.2/travis-shared
MNT Travis shared config
2020-12-21 15:52:49 +13:00
Maxime Rainville dd037c8f50
DEP Remove explicit dev requirement for sminnee/phpunit-mock-objects 2020-12-21 14:51:00 +13:00
Steve Boyd d29a062562 MNT Travis shared config, use sminnee/phpunit 2020-12-01 16:29:13 +13:00
Steve Boyd 2c4d32af6b Merge branch '2.2' into 2 2020-11-12 14:45:10 +13:00
Robbie Averill 7c0b7b0ca7
Merge pull request #68 from creative-commoners/pulls/2.2/travis
Travis 2.2
2020-06-23 10:15:38 -07:00
Steve Boyd b765ec87c1 Travis 2.2 2020-06-23 12:54:42 +12:00
Steve Boyd 56dab108c9 Require phpunit-mock-objects, bump recipe versions 2020-06-18 13:54:59 +12:00
Steve Boyd b01567767c Merge branch '2.2' into 2 2020-06-18 11:06:23 +12:00
Maxime Rainville e19e476cf4
Merge pull request #66 from creative-commoners/2.2
Update for 2.2
2020-06-15 18:32:48 +12:00
Maxime Rainville dfd9c15d56 Add PHP 7.4 test 2020-06-15 15:52:29 +12:00
Maxime Rainville 23104b77aa Add PHP 7.4 test 2020-06-15 15:48:00 +12:00
Steve Boyd 1aa18e7ced Update for 2.2 2020-06-15 14:53:33 +12:00
Maxime Rainville 08409bb9ca Merge remote-tracking branch 'origin/2.1' into 2 2020-06-12 14:12:14 +12:00
Damian Mooyman b2f6e06795
Merge pull request #65 from creative-commoners/pulls/2.1/missing-envtypecheck-alias
FIX Add missing aliases for EnvironmentChecks
2020-01-13 11:49:46 +13:00
Garion Herman 0685dfad75 FIX Add missing aliases for EnvironmentChecks 2020-01-06 10:08:08 +13:00
Garion Herman 5f2591a5c1 Merge branch '2.1' into 2 2019-11-18 20:42:46 +13:00
Garion Herman 2bbebc321e Merge branch '2.0' into 2.1 2019-11-18 20:42:15 +13:00
Sam Minnée 9829476dae
Merge pull request #63 from creative-commoners/pulls/2.0/xenial
FIX Update Travis config to Xenial
2019-11-18 19:16:38 +13:00
Garion Herman 49d7d5f2a3 FIX Update Travis config to Xenial 2019-11-18 18:01:51 +13:00
Robbie Averill f347d1d5c0 Merge branch '2.1' 2019-06-12 10:19:45 +12:00
Serge Latyntcev c490c4e5a5 ADD ConfirmationMiddleware exceptions for the dev routes 2019-06-12 09:37:05 +12:00
Aaron Carlino d8452c681a
Merge pull request #1 from silverstripe-security/pulls/master/ss-2018-022
[SS-2018-022] ADD ConfirmationMiddleware exceptions for the dev routes
2019-06-12 09:25:59 +12:00
Serge Latyntcev e757c9477c ADD ConfirmationMiddleware exceptions for the dev routes 2019-06-12 09:24:52 +12:00
Damian Mooyman 6fe0b1889e
Update master alias to 2.2 2019-04-05 16:02:38 +13:00
Damian Mooyman 40050e9ab4
Update changelog 2019-04-05 16:01:54 +13:00
Robbie Averill efd996d618
Merge pull request #61 from tractorcow/pulls/2.1/basicauth
BUG Only present basic auth challenge if configured to use it
2019-03-29 11:29:29 +13:00
Robbie Averill 922d810a3a Reduce line length for PSR-2 compatibility 2019-03-29 10:03:02 +13:00
Damian Mooyman a54400681f
BUG Only present basic auth challenge if configured to use it 2019-03-28 17:21:16 +13:00
Dylan Wagstaff c5783a2450
Merge pull request #60 from silverstripe-terraformers/feature/extra-checks
Extra checks; cache headers, and session cookies
2019-03-20 11:51:11 +13:00
Frank Mullenger da9eddc0e1
Merge branch 'master' into feature/extra-checks 2019-03-20 10:23:03 +13:00
Frank Mullenger 8f7f8570f2 NEW: Refactor to provide unit tests for cache headers check. 2019-03-20 10:21:00 +13:00
Frank Mullenger 578c73e6ce NEW: Refactor to provide unit tests for session check. 2019-03-20 09:38:58 +13:00
Robbie Averill 128ddb56ed
Merge pull request #58 from silverstripe-terraformers/feature/env-type-check
NEW: Environment type check.
2019-03-11 13:20:52 +13:00
Frank Mullenger ace6c51aae FIX: Missed a reference to project code. 2019-03-11 13:03:48 +13:00
Frank Mullenger 540710ab0f NEW: Adding unit test. 2019-03-11 12:44:24 +13:00
Frank Mullenger 2674beb94a FIX: Line length. 2019-02-15 13:58:08 +13:00
Frank Mullenger 90f0556ca4 FIX: Removal of optional param. 2019-02-15 13:52:58 +13:00
Frank Mullenger 27994e51fa FIX: Removing return type declarations. 2019-02-15 13:42:41 +13:00
Guy Marriott 986bc370b9
Update scrutinizer config 2019-02-15 13:10:32 +13:00
Frank Mullenger c1bfd5640d NEW: Cache headers check. 2019-02-15 11:57:14 +13:00
Frank Mullenger df59195634 NEW: Session check. 2019-02-15 11:56:32 +13:00
Guy Marriott f74c371e16
Merge branch '2.0' 2019-02-15 11:43:42 +13:00
Guy Marriott 41763c6042
Merge pull request #59 from creative-commoners/pulls/2.0/update-travis
Update travis configuration
2019-02-15 11:43:12 +13:00
Guy Marriott f8d35639a0
Update travis configuration 2019-02-15 11:37:55 +13:00
Frank Mullenger e323674adf NEW: Adding Guzzle dependency. 2019-02-15 11:30:51 +13:00
Frank Mullenger da6a8a8ee4 MINOR: Adding new check to documentation. 2019-02-15 11:02:13 +13:00
Frank Mullenger 9940c5e8ab FIX: Removing PHP7 return type declaration. 2019-02-15 10:32:13 +13:00
Frank Mullenger 1771a6ef11 NEW: Environment type check. 2019-02-15 10:07:05 +13:00
Robbie Averill 05ac221c2f
Merge pull request #56 from creative-commoners/pulls/master/add-supported-module-badge
Add supported module badge to readme
2018-06-19 09:23:42 +12:00
Dylan Wagstaff ed61c6bb3a Add supported module badge to readme 2018-06-15 17:34:56 +12:00
Robbie Averill 014c23804f Merge branch '2.0' 2018-05-25 14:55:32 +12:00
Robbie Averill f6652c2b37 Remove obsolete branch alias 2018-05-25 14:55:17 +12:00
Robbie Averill 161f0aefaf Merge branch '1' 2018-05-03 16:01:24 +12:00
Robbie Averill a788b37de5 Merge branch '1.2' into 1 2018-05-03 16:00:34 +12:00
Robbie Averill 449a265301 Merge branch '1.1' into 1.2 2018-05-03 16:00:19 +12:00
Dylan Wagstaff 7ad2334f49
Merge pull request #55 from creative-commoners/pulls/1.1/update-travis
Update Travis matrix, Scrutinizer config and readme badges. Use codecov.io
2018-05-03 14:53:59 +12:00
Robbie Averill 250c8d2e00 Use test database in database check test 2018-05-03 11:01:18 +12:00
Robbie Averill e5651c429c Update Travis matrix, Scrutinizer config and readme badges. Use codecov.io. 2018-05-03 11:01:18 +12:00
Robbie Averill 564936ee01
Remove PHP 5.3 from Travis build matrix 2018-05-03 10:37:28 +12:00
Dylan Wagstaff b89aefaa20
Merge pull request #54 from creative-commoners/pulls/2.0/fix-solr-check
FIX Implement Solr namespaces into Solr check, remove index filter
2018-01-26 09:51:31 +13:00
Daniel Hensby 152c11638b
Merge branch '1' 2018-01-15 16:31:52 +00:00
Robbie Averill e6e5ac96df FIX Implement Solr namespaces into Solr check, remove index filter 2017-12-22 15:31:55 +13:00
Dylan Wagstaff 1ce832f859
Merge pull request #53 from creative-commoners/pulls/2.0/fix-basic-auth
FIX Update BasicAuth call signature, remove deprecated code and update getenv
2017-12-15 10:57:16 +13:00
Robbie Averill b1f5ef184b Add PHP 7.2 to Travis, ensure phpcs passes stricter standard 2017-12-13 21:08:59 +13:00
Robbie Averill bf5076f2df FIX Update BasicAuth call signature, remove deprecated code and update getenv 2017-12-13 20:51:37 +13:00
Dylan Wagstaff 227b64cbcc
Merge pull request #50 from creative-commoners/pulls/2.0/vendorize
Convert to vendor module
2017-11-10 10:52:08 +13:00
Robbie Averill 0e84848c3d Convert to vendor module 2017-11-10 09:25:17 +13:00
Daniel Hensby ab610bb55e
Update travis install settings 2017-10-11 18:02:11 +01:00
Dylan Wagstaff fb0bddec2c FIX convert CI bootstrap references to new their new locations in vendor (#49)
* FIX convert CI bootstrap references to new their new locations in vendor

* FIX Use core recipe instead of framework directly
2017-10-05 15:28:41 +13:00
Daniel Hensby 1fe68e7d4a Merge pull request #48 from robbieaverill/pulls/2.0/ss4-updates
NEW Remove Phockito test dependency, update config API use, update Travis config
2017-09-13 09:11:40 +01:00
Robbie Averill a68532cef0 Replace Travis configuration with standalone, add phpcs dependency and phpunit config 2017-08-28 09:44:50 +12:00
Robbie Averill 2131840da4 NEW Remove Phockito test dependency, update config API use 2017-08-25 15:19:45 +12:00
Andrew Aitken-Fincham 9dcfc4f337 BUGFIX move allowed_actions to private static (#47) 2017-08-22 19:06:24 +12:00
Daniel Hensby 7204c4792d Merge pull request #46 from andrewandante/FIX/add_object_traits
add object traits
2017-05-26 17:50:12 +01:00
Andrew Aitken-Fincham 5b4adb286e add object traits 2017-05-26 17:40:25 +01:00
Daniel Hensby 04b7cb2ff1 Merge pull request #45 from andrewandante/FIX/remove_object
update to not include Object
2017-05-26 17:19:54 +01:00
Andrew Aitken-Fincham 1e727bf8c6 update to not include Object 2017-05-26 17:11:35 +01:00
Sam Minnee 5897559cd2 FIX: Simpler matrix, polish test. 2017-05-04 18:00:25 +12:00
Robbie Averill b9af2d0734 FIX Update config API, Logger alias, dotenv config, remove PHP 5.5, other fixes
Remove PHP 5.5 from Travis configuration. Dotenv used for environment management now,
examples and tests updated to use putenv instead of define. Logger alias update to
LoggerInterface. Update mutable config API calls. Replace array declaration with
short version: []. Update license year.
2017-05-04 16:11:25 +12:00
Robbie Averill 745a9f93ef FIX Remove after from YAML to prevent circular reference (#39) 2017-03-16 14:10:33 +13:00
Robbie Averill 254ed4801f SilverStripe 4.x compatibility (#38)
* Update composer constraint for 4.x compat

* 4.x compatibility: Rename "code" to "src" and add PSR-4 autoloading

* Restructure code and tests for PSR-4

* Implement namespaces

* Implement namespaced throughout

* PSR-2 formatting updates, separate Result from CheckSuite, implement PSR-3 logging, fix tests

* FIX Routes and template location

* Update readme. Allow check classes to be namespaced or not.

* Add entry to changelog

* FIX Ensure DatabaseCheckTest always has a member. Allow strings or objects for $check in the suite

* Update readme to be clearer about passing checks with or without namespaces

* Revert namespace checking and implement Injector aliases instead. Update readme.
2017-01-05 11:16:12 +13:00
Daniel Hensby 1f6ba594a6
Updating composer alias 2016-12-28 11:21:23 +00:00
Daniel Hensby b78c2af4aa
Updating composer alias 2016-12-28 11:20:58 +00:00
Will Rossiter ad54d788d4 Update check URL (Fixes #37) 2016-11-08 07:02:13 +13:00
Daniel Hensby 582c4173e1 Merge pull request #36 from anotheredward/patch-1
add license to composer.json
2016-08-31 01:31:14 +01:00
Ed c1ac12193e add license to composer.json 2016-08-30 13:07:47 +12:00
Damian Mooyman a8c0963c69 Merge pull request #34 from silverstripe-labs/public-access
Allow public access to the dev/health url and change response to 500
2016-07-12 14:23:16 +12:00
Stig Lindqvist f48efcf963 Change dev/health route to health/check 2016-07-12 14:13:41 +12:00
Stig Lindqvist c24c52e77f Allow public access to the dev/health url and change response to 500 2016-07-12 14:13:41 +12:00
Stig Lindqvist a19fcb42e6 Merge pull request #35 from tractorcow/pulls/fix-tests
BUG ensure tests have database enabled
2016-07-12 14:12:44 +12:00
Damian Mooyman 3135c1eec7
BUG ensure tests have database enabled 2016-07-12 14:08:44 +12:00
Damian Mooyman b084f5433c Add changelog for 1.2.0 2016-02-04 13:31:33 +13:00
Scott Hutchinson 26817742d2 Merge pull request #33 from tractorcow/pulls/icons
Fix broken badges
2016-02-04 13:26:07 +13:00
Damian Mooyman 580a2deafe Fix broken badges 2016-02-04 13:21:10 +13:00
Damian Mooyman c814d653f9 Merge pull request #32 from helpfulrobot/update-license-year
Updated license year
2016-01-05 11:19:35 +13:00
helpfulrobot f2d0a39ff7 Updated license year 2016-01-01 06:44:20 +13:00
Damian Mooyman 18d79e1b12 Merge pull request #22 from helpfulrobot/convert-to-psr-2
Converted to PSR-2
2015-11-23 10:28:12 +13:00
Daniel Hensby e090e4adb7 Merge pull request #31 from helpfulrobot/add-standard-code-of-conduct
Added standard code of conduct
2015-11-21 10:32:14 +00:00
helpfulrobot b62632d6ab Added standard code of conduct 2015-11-21 20:13:19 +13:00
helpfulrobot 5f1f1fcd53 Converted to PSR-2 2015-11-21 19:18:35 +13:00
Damian Mooyman d6a65e1614 Merge pull request #25 from helpfulrobot/add-standard-editor-config
Added standard editor config
2015-11-20 14:22:00 +13:00
Damian Mooyman c9c6076156 Merge pull request #23 from helpfulrobot/add-standard-travis-config
Added standard Travis config
2015-11-20 14:21:50 +13:00
Damian Mooyman 9223736d30 Merge pull request #21 from helpfulrobot/add-standard-scrutinizer-config
Added standard Scrutinizer config
2015-11-20 14:16:56 +13:00
Sam Minnée 1ef045d1b7 Merge pull request #29 from chillu/pulls/log-check-failures
Log check failures
2015-11-20 13:49:11 +13:00
Ingo Schommer ea047df63d Use config API 2015-11-20 12:08:29 +13:00
Ingo Schommer 81c7f2ba27 Optionally log check failures
Gives more granular control over monitoring failures
2015-11-20 12:06:26 +13:00
Daniel Hensby b9bfacf223 Merge pull request #26 from helpfulrobot/add-standard-license
Added standard license
2015-11-19 17:25:03 +00:00
Daniel Hensby e45a9019a1 Merge pull request #27 from helpfulrobot/add-license-to-composer
Added license to composer.json
2015-11-19 13:31:54 +00:00
Daniel Hensby 79e75aede9 Merge pull request #28 from helpfulrobot/add-standard-git-attributes
Added standard git attributes
2015-11-19 10:40:33 +00:00
helpfulrobot 3a3dd59964 Added standard git attributes 2015-11-19 19:11:39 +13:00
helpfulrobot 6c92cf3a8d Added license to composer.json 2015-11-19 18:52:02 +13:00
helpfulrobot a24b6f23ba Added standard license 2015-11-19 18:30:18 +13:00
helpfulrobot e2fdefab59 Added standard Travis config 2015-11-19 14:19:06 +13:00
helpfulrobot 8ccafb5cd4 Added standard editor config 2015-11-19 13:25:13 +13:00
helpfulrobot 0f67667ce5 Added standard Scrutinizer config 2015-11-18 15:08:21 +13:00
78 changed files with 3261 additions and 1696 deletions

17
.editorconfig Normal file
View File

@ -0,0 +1,17 @@
# 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,package.json}]
indent_size = 2
# The indent size used in the package.json file cannot be changed:
# https://github.com/npm/npm/pull/3180#issuecomment-16336516

12
.gitattributes vendored
View File

@ -1,7 +1,7 @@
/tests export-ignore
/docs export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/tests export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/.scrutinizer.yml export-ignore
/phpunit.xml export-ignore
/codecov.yml export-ignore
/phpunit.xml.dist export-ignore

11
.github/workflows/ci.yml vendored Normal file
View File

@ -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

16
.github/workflows/dispatch-ci.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Dispatch CI
on:
# At 1:20 PM UTC, only on Wednesday and Thursday
schedule:
- cron: '20 13 * * 3,4'
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

17
.github/workflows/keepalive.yml vendored Normal file
View File

@ -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

View File

@ -1,13 +0,0 @@
inherit: true
tools:
external_code_coverage:
timeout: 1800 # 30 minute delay to allow for coverage reporting taking ages!
checks:
php:
code_rating: true
duplication: true
filter:
paths: [code/*, tests/*]

View File

@ -1,35 +0,0 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
sudo: false
env:
- DB=MYSQL CORE_RELEASE=3.1
before_script:
- composer self-update || true
- 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
- composer install
script:
- vendor/bin/phpunit --coverage-clover coverage.clover environmentcheck/tests
- wget https://scrutinizer-ci.com/ocular.phar
- git remote rm origin
- git remote add origin git@github.com:silverstripe-labs/silverstripe-environmentcheck.git
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
branches:
only:
- master
matrix:
allow_failures:
- php: 7.0

29
.upgrade.yml Normal file
View File

@ -0,0 +1,29 @@
mappings:
EnvironmentCheck: SilverStripe\EnvironmentCheck\EnvironmentCheck
EnvironmentChecker: SilverStripe\EnvironmentCheck\EnvironmentChecker
EnvironmentCheckSuite: SilverStripe\EnvironmentCheck\EnvironmentCheckSuite
EnvironmentCheckSuiteResult: SilverStripe\EnvironmentCheck\EnvironmentCheckSuiteResult
DatabaseCheck: SilverStripe\EnvironmentCheck\Checks\DatabaseCheck
ExternalURLCheck: SilverStripe\EnvironmentCheck\Checks\ExternalURLCheck
FileAccessibilityAndValidationCheck: SilverStripe\EnvironmentCheck\Checks\FileAccessibilityAndValidationCheck
FileAgeCheck: SilverStripe\EnvironmentCheck\Checks\FileAgeCheck
FileWriteableCheck: SilverStripe\EnvironmentCheck\Checks\FileWriteableCheck
HasClassCheck: SilverStripe\EnvironmentCheck\Checks\HasClassCheck
HasFunctionCheck: SilverStripe\EnvironmentCheck\Checks\HasFunctionCheck
SMTPConnectCheck: SilverStripe\EnvironmentCheck\Checks\SMTPConnectCheck
SolrIndexCheck: SilverStripe\EnvironmentCheck\Checks\SolrIndexCheck
URLCheck: SilverStripe\EnvironmentCheck\Checks\URLCheck
DevCheckController: SilverStripe\EnvironmentCheck\Controllers\DevCheckController
DevHealthController: SilverStripe\EnvironmentCheck\Controllers\DevHealthController
EnvironmentCheckerTest: SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest
EnvironmentCheckerTest_CheckNoErrors: SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest_CheckNoErrors
EnvironmentCheckerTest_CheckWarnings: SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest_CheckWarnings
EnvironmentCheckerTest_CheckErrors: SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest_CheckErrors
DatabaseCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\DatabaseCheckTest
ExternalURLCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\ExternalURLCheckTest
FileWritableCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\FileWritableCheckTest
HasClassCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\HasClassCheckTest
HasFunctionCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\HasFunctionCheckTest
URLCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\URLCheckTest
DevCheckControllerTest: SilverStripe\EnvironmentCheck\Tests\Controllers\DevCheckControllerTest
DevHealthControllerTest: SilverStripe\EnvironmentCheck\Tests\Controllers\DevHealthControllerTest

View File

@ -1,17 +1,21 @@
# SilverStripe Environment Checker Module
# Silverstripe Environment Checker Module
[![Build Status](http://img.shields.io/travis/silverstripe-labs/silverstripe-environmentalcheck.svg?style=flat-square)](https://travis-ci.org/silverstripe-labs/silverstripe-environmentalcheck)
[![Code Quality](http://img.shields.io/scrutinizer/g/silverstripe-labs/silverstripe-environmentalcheck.svg?style=flat-square)](https://scrutinizer-ci.com/g/silverstripe-labs/silverstripe-environmentalcheck)
[![Code Coverage](http://img.shields.io/scrutinizer/coverage/g/silverstripe-labs/silverstripe-environmentalcheck.svg?style=flat-square)](https://scrutinizer-ci.com/g/silverstripe-labs/silverstripe-environmentalcheck)
[![Version](http://img.shields.io/packagist/v/silverstripe/environmentalcheck.svg?style=flat-square)](https://packagist.org/packages/silverstripe/silverstripe-environmentalcheck)
[![License](http://img.shields.io/packagist/l/silverstripe/environmentalcheck.svg?style=flat-square)](LICENSE.md)
[![CI](https://github.com/silverstripe/silverstripe-environmentcheck/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-environmentcheck/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/)
This module adds an API for running environment checks to your API.
* `dev/health` - A public URL that performs a quick check that this environment is functioning. This could be tied to a load balancer, for example.
* `health/check` - A public URL that performs a quick check that this environment is functioning. This could be tied to a load balancer, for example.
* `dev/check` - An admin-only URL that performs a more comprehensive set of checks. This could be tied to a deployment system, for example.
* `dev/check/<suite>` - Check a specific suite (admin only)
## Requirements
* Silverstripe 4.x
* PHP 7.3
For Silverstripe 3.x support, please use a `1.x` tagged release.
## Aren't these just unit tests?
Almost, but not really. Environment checks differ from unit tests in two important ways:
@ -22,18 +26,20 @@ Almost, but not really. Environment checks differ from unit tests in two importa
## Installation
```sh
$ composer require silverstripe/environmentalcheck
$ composer require silverstripe/environmentcheck
```
You'll also need to run `/dev/build`.
### Activating Directly
Register checks in your own `_config.php` - see the `_config.php` in this module for some defaults.
Register checks in your own `_config.php` - see the `_config.php` in this module for some defaults. Don't forget to
either use a fully-qualified (namespaced) class name for `EnvironmentCheckSuite`, or `use` (import) the namespaced class
first.
```php
EnvironmentCheckSuite::register('health', 'DatabaseCheck', "Can we connect to the database?");
EnvironmentCheckSuite::register('check', 'URLCheck("")', "Is the homepage accessible?");
EnvironmentCheckSuite::register('health', 'DatabaseCheck', 'Can we connect to the database?');
EnvironmentCheckSuite::register('check', 'URLCheck("")', 'Is the homepage accessible?');
```
### Activating Via Config
@ -41,7 +47,7 @@ EnvironmentCheckSuite::register('check', 'URLCheck("")', "Is the homepage access
Register your checks on the `EnvironmentCheckSuite`. The same named check may be used multiple times.
```yaml
EnvironmentCheckSuite:
SilverStripe\EnvironmentCheck\EnvironmentCheckSuite:
registered_checks:
db:
definition: 'DatabaseCheck("Page")'
@ -61,7 +67,7 @@ You can also disable checks configured this way. This is handy if you want to ov
by some other module. Just set the "state" property of the check to "disabled" like this:
```yaml
EnvironmentCheckSuite:
SilverStripe\EnvironmentCheck\EnvironmentCheckSuite:
registered_checks:
db:
state: disabled
@ -76,12 +82,41 @@ EnvironmentCheckSuite:
* `HasClassCheck`: Check that the given class exists.
This can be used to check that PHP modules or features are installed.
* `FileWriteableCheck`: Check that the given file is writeable.
* `FileAccessibilityAndValidationCheck`: Check that a given file is accessible and optionally matches a given format.
* `FileAgeCheck`: Checks for the maximum age of one or more files or folders.
Useful for files which should be frequently auto-generated,
Useful for files which should be frequently auto-generated,
like static caches, as well as for backup files and folders.
* `ExternalURLCheck`: Checks that one or more URLs are reachable via HTTP.
* `SMTPConnectCheck`: Checks if the SMTP connection configured through PHP.ini works as expected.
* `SolrIndexCheck`: Checks if the Solr cores of given class are available.
* `SessionCheck`: Checks that a given URL does not generate a session.
* `CacheHeadersCheck`: Check cache headers in response for directives that must either be included or excluded as well
checking for existence of ETag.
* `EnvTypeCheck`: Checks environment type, dev and test should not be used on production environments.
## Monitoring Checks
Checks will return an appropriate HTTP status code, so are easy to hook into common uptime montoring
solutions like pingdom.com.
You can also have the environment checker email results with the following configuration:
```yaml
SilverStripe\EnvironmentCheck\EnvironmentChecker:
email_results: true
to_email_address: support@test.com
from_email_address: admin@test.com
```
Errors can be logged via the standard Silverstripe PSR-3 compatible logging. Each check will cause an individual log
entry. You can choose to enable logging separately for warnings and errors, identified through the
result of `EnvironmentCheck->check()`.
```yaml
SilverStripe\EnvironmentCheck\EnvironmentChecker:
log_results_warning: true
log_results_error: true
```
## Authentication
@ -90,73 +125,90 @@ trying to access it on a live or test environment, it will respond with a 403 HT
an administrator on the site.
You may wish to have an automated service check `dev/check` periodically, but not want to open it up for public access.
You can enable basic authentication by defining the following in your environment:
You can enable basic authentication by defining the following in your environment (`.env` file):
define('ENVCHECK_BASICAUTH_USERNAME', 'test');
define('ENVCHECK_BASICAUTH_PASSWORD', 'password');
```
ENVCHECK_BASICAUTH_USERNAME="test"
ENVCHECK_BASICAUTH_PASSWORD="password"
```
Now if you access `dev/check` in a browser it will pop up a basic auth popup, and if the submitted username and password
match the ones defined the username and password defined in the environment, access will be granted to the page.
## Adding more checks
To add more checks, you should put additional `EnvironmentCheckSuite::register` calls into your `_config.php`. See the `_config.php` file of this module for examples.
To add more checks, you should put additional `EnvironmentCheckSuite::register` calls into your `_config.php`.
See the `_config.php` file of this module for examples.
:::php
EnvironmentCheckSuite::register('check', 'HasFunctionCheck("curl_init")', "Does PHP have CURL support?");
EnvironmentCheckSuite::register('check', 'HasFunctionCheck("imagecreatetruecolor")', "Does PHP have GD2 support?");
The first argument is the name of the check suite. There are two built-in check suites, "health", and "check", corresponding to the `dev/health` and `dev/check` URLs. If you wish, you can create your own check suites and execute them on other URLs. You can also add a check to more than one suite by passing the first argument as an array.
```php
EnvironmentCheckSuite::register('check', 'HasFunctionCheck("curl_init")', "Does PHP have CURL support?");
EnvironmentCheckSuite::register('check', 'HasFunctionCheck("imagecreatetruecolor")', "Does PHP have GD2 support?");
```
The module comes bundled with a few checks in `DefaultHealthChecks.php`. However, to test your own application, you probably want to write custom checks.
The first argument is the name of the check suite. There are two built-in check suites, "health", and "check", corresponding to the `health/check` and `dev/check` URLs. If you wish, you can create your own check suites and execute them on other URLs. You can also add a check to more than one suite by passing the first argument as an array.
* Implement the `EnvironmentCheck` interface
To test your own application, you probably want to write custom checks:
* Implement the `SilverStripe\EnvironmentCheck\EnvironmentCheck` interface
* Define the `check()` function, which returns a 2 element array:
* The first element is one of `EnvironmentCheck::OK`, `EnvironmentCheck::WARNING`, `EnvironmentCheck::ERROR`, depending on the status of the check
* The second element is a string describing the response.
Here is a simple example of how you might create a check to test your own code. In this example, we are checking that an instance of the `MyGateway` class will return "foo" when `call()` is called on it. Testing interfaces with 3rd party systems is a common use case for custom environment checks.
:::php
class MyGatewayCheck implements EnvironmentCheck {
protected $checkTable;
```php
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
class MyGatewayCheck implements EnvironmentCheck
{
protected $checkTable;
function check()
{
$g = new \MyGateway;
$response = $g->call();
$expectedResponse = 'foo';
if($response == null) {
return array(EnvironmentCheck::ERROR, "MyGateway didn't return a response");
} else if($response != $expectedResponse) {
return array(EnvironmentCheck::WARNING, "MyGateway returned unexpected response $response");
}
return array(EnvironmentCheck::OK, '');
}
}
```
function check() {
$g = new MyGateway;
$response = $g->call();
$expectedResponse = "foo";
if($response == null) {
return array(EnvironmentCheck::ERROR, "MyGateway didn't return a response");
} else if($response != $expectedResponse) {
return array(EnvironmentCheck::WARNING, "MyGateway returned unexpected response $response");
} else {
return array(EnvironmentCheck::OK, "");
}
}
}
Once you have created your custom check class, don't forget to register it in a check suite
:::php
EnvironmentCheckSuite::register('check', 'MyGatewayCheck', "Can I connect to the gateway?");
```php
EnvironmentCheckSuite::register('check', 'MyGatewayCheck', 'Can I connect to the gateway?');
```
### Using other environment check suites
If you want to use the same UI as dev/health and dev/check, you can create an `EnvironmentChecker` object. This class is a `RequestHandler` and so can be returned from an action handler. The first argument to the `EnvironmentChecker` constructor is the suite name. For example:
If you want to use the same UI as `health/check` and `dev/check`, you can create an `EnvironmentChecker` object. This class is a `RequestHandler` and so can be returned from an action handler. The first argument to the `EnvironmentChecker` constructor is the suite name. For example:
class DevHealth extends Controller {
function index() {
$e = new EnvironmentChecker('health', 'Site health');
return $e;
}
}
If you wish to embed an environment check suite in another, you can use the following call.
```php
use SilverStripe\Control\Controller;
class DevHealth extends Controller
{
function index()
{
$e = new EnvironmentChecker('health', 'Site health');
return $e;
}
}
```
If you wish to embed an environment check suite in another, you can use the following call:
```php
$result = EnvironmentCheckSuite::inst('health')->run();
```
$result = EnvironmentCheckSuite::inst("health")->run();
`$result` will contain a `EnvironmentCheckSuiteResult` object
* `$result->ShouldPass()`: Return a boolean of whether or not the tests passed.
@ -173,4 +225,4 @@ All methods, with `public` visibility, are part of the public API. All other met
## Reporting Issues
Please [create an issue](http://github.com/silverstripe-labs/silverstripe-environmentalcheck/issues) for any bugs you've found, or features you're missing.
Please [create an issue](http://github.com/silverstripe/silverstripe-environmentcheck/issues) for any bugs you've found, or features you're missing.

View File

@ -1,21 +1,70 @@
<?php
// // These power dev/health, which can be used by load balancers and other such systems
// EnvironmentCheckSuite::register('health', 'DatabaseCheck');
// // These power dev/check, which is used for diagnostics and for deployment
// EnvironmentCheckSuite::register('check', 'DatabaseCheck("Member")', "Is the database accessible?");
// EnvironmentCheckSuite::register('check', 'URLCheck("")', "Is the homepage accessible?");
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("curl_init")', "Does PHP have CURL support?");
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("imagecreatetruecolor")', "Does PHP have GD2 support?");
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("xml_set_object")', "Does PHP have XML support?");
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("token_get_all")', "Does PHP have tokenizer support?");
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("iconv")', "Does PHP have iconv support?");
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("hash")', "Does PHP have hash support?");
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("session_start")', "Does PHP have session support?");
// EnvironmentCheckSuite::register('check', 'HasClassCheck("DOMDocument")', "Does PHP have DOMDocument support?");
// EnvironmentCheckSuite::register('check', 'FileWriteableCheck("assets")', "Is assets/ writeable?");
// EnvironmentCheckSuite::register('check', 'FileWriteableCheck("' . TEMP_FOLDER . '")', "Is the temp folder writeable?");
//
//use SilverStripe\EnvironmentCheck\EnvironmentCheckSuite;
//
//// These power dev/health, which can be used by load balancers and other such systems
//EnvironmentCheckSuite::register('health', 'DatabaseCheck');
//
//// These power dev/check, which is used for diagnostics and for deployment
//EnvironmentCheckSuite::register('check', 'DatabaseCheck("Member")', "Is the database accessible?");
//EnvironmentCheckSuite::register('check', 'URLCheck("")', "Is the homepage accessible?");
//
//EnvironmentCheckSuite::register(
// 'check',
// 'HasFunctionCheck("curl_init")',
// "Does PHP have CURL support?"
//);
//
//EnvironmentCheckSuite::register(
// 'check',
// 'HasFunctionCheck("imagecreatetruecolor")',
// "Does PHP have GD2 support?"
//);
//
//EnvironmentCheckSuite::register(
// 'check',
// 'HasFunctionCheck("xml_set_object")',
// "Does PHP have XML support?"
//);
//
//EnvironmentCheckSuite::register(
// 'check',
// 'HasFunctionCheck("token_get_all")',
// "Does PHP have tokenizer support?"
//);
//
//EnvironmentCheckSuite::register(
// 'check',
// 'HasFunctionCheck("iconv")',
// "Does PHP have iconv support?"
//);
//
//EnvironmentCheckSuite::register(
// 'check',
// 'HasFunctionCheck("hash")',
// "Does PHP have hash support?"
//);
//
//EnvironmentCheckSuite::register(
// 'check',
// 'HasFunctionCheck("session_start")',
// "Does PHP have session support?"
//);
//
//EnvironmentCheckSuite::register(
// 'check',
// 'HasClassCheck("DOMDocument")',
// "Does PHP have DOMDocument support?"
//);
//
//EnvironmentCheckSuite::register(
// 'check',
// 'FileWriteableCheck("assets")',
// "Is assets/ writeable?"
//);
//
//EnvironmentCheckSuite::register(
// 'check',
// 'FileWriteableCheck("' . TEMP_FOLDER . '")',
// "Is the temp folder writeable?"
//);

39
_config/injector.yml Normal file
View File

@ -0,0 +1,39 @@
---
Name: environmentcheckinjector
---
SilverStripe\Core\Injector\Injector:
CacheHeadersCheck:
class: SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck
DatabaseCheck:
class: SilverStripe\EnvironmentCheck\Checks\DatabaseCheck
EnvTypeCheck:
class: SilverStripe\EnvironmentCheck\Checks\EnvTypeCheck
ExternalURLCheck:
class: SilverStripe\EnvironmentCheck\Checks\ExternalURLCheck
FileAccessibilityAndValidationCheck:
class: SilverStripe\EnvironmentCheck\Checks\FileAccessibilityAndValidationCheck
FileAgeCheck:
class: SilverStripe\EnvironmentCheck\Checks\FileAgeCheck
FileWriteableCheck:
class: SilverStripe\EnvironmentCheck\Checks\FileWriteableCheck
HasClassCheck:
class: SilverStripe\EnvironmentCheck\Checks\HasClassCheck
HasFunctionCheck:
class: SilverStripe\EnvironmentCheck\Checks\HasFunctionCheck
SMTPConnectCheck:
class: SilverStripe\EnvironmentCheck\Checks\SMTPConnectCheck
SolrIndexCheck:
class: SilverStripe\EnvironmentCheck\Checks\SolrIndexCheck
URLCheck:
class: SilverStripe\EnvironmentCheck\Checks\URLCheck
EnvCheckClient:
factory: 'SilverStripe\EnvironmentCheck\Services\ClientFactory'
constructor:
timeout: 10.0
SilverStripe\EnvironmentCheck\Checks\SessionCheck:
dependencies:
client: '%$EnvCheckClient'
SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck:
dependencies:
client: '%$EnvCheckClient'

View File

@ -1,9 +1,25 @@
---
Name: environmentcheckroutes
After: 'framework/*','cms/*'
---
Director:
SilverStripe\Control\Director:
rules:
'dev/health': 'DevHealthController'
'dev/check/$Suite': 'DevCheckController'
'health/check': 'Silverstripe\EnvironmentCheck\Controllers\DevHealthController'
'dev/check/$Suite': 'Silverstripe\EnvironmentCheck\Controllers\DevCheckController'
SilverStripe\Dev\DevelopmentAdmin:
registered_controllers:
check:
controller: Silverstripe\EnvironmentCheck\Controllers\DevCheckController
links:
check: 'Run registered environment checks and display their status'
---
Name: environmentcheckroutes-dev_urls-confirmation-exceptions
After:
- 'dev_urls-confirmation-middleware'
---
SilverStripe\Core\Injector\Injector:
DevUrlsConfirmationMiddleware:
properties:
Bypasses:
- '%$SilverStripe\Control\Middleware\ConfirmationMiddleware\UrlPathStartsWith("dev/check")'

View File

@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [2.1.0]
* Add new checks:
* Environment type
* Session
* Cache Headers
* Fix basic auth
* Add unit tests
* PSR-2 Conformance
## [2.0.0]
* Minimum versions required:
* SilverStripe 4.x
* PHP 5.6+
* PHP 7 supported
## [1.2.0]
* Fix broken badges
* Converted to PSR-2
* Use Config API
* Optionally log check failures
* Update documentation and configuration to supported module standard
## [1.1.2]
* Bring up to supported module standard.

View File

@ -1,3 +1 @@
# Code of Conduct
https://docs.silverstripe.org/en/3.1/contributing/code_of_conduct/
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).

View File

@ -1,37 +0,0 @@
<?php
class DevCheckController extends Controller {
/**
* @var array
*/
public static $allowed_actions = array(
'index'
);
/**
* Permission code to check for access to this controller.
*
* @var string
*/
private static $permission = 'ADMIN';
/**
* @param SS_HTTPRequest $request
*
* @return EnvironmentChecker
*
* @throws SS_HTTPResponse_Exception
*/
function index($request) {
$suite = 'check';
if ($name = $request->param('Suite')) {
$suite = $name;
}
$checker = new EnvironmentChecker($suite, 'Environment status');
$checker->init($this->config()->permission);
return $checker;
}
}

View File

@ -1,25 +0,0 @@
<?php
class DevHealthController extends Controller {
/**
* @var array
*/
public static $allowed_actions = array(
'index'
);
/**
* @return EnvironmentChecker
*
* @throws SS_HTTPResponse_Exception
*/
function index() {
// health check does not require permission to run
$checker = new EnvironmentChecker('health', 'Site health');
$checker->init('');
$checker->setErrorCode(404);
return $checker;
}
}

View File

@ -1,270 +0,0 @@
<?php
/**
* Represents a suite of environment checks.
* Specific checks can be registered against a named instance of EnvironmentCheckSuite.
*
* Usage #1 - _config.php
* EnvironmentCheckSuite::register('health', 'MyHealthCheck("my param")', 'Title of my health check');
*
* Usage #2 - config.yml
* EnvironmentCheckSuite:
* registered_checks:
* mycheck:
* definition: 'MyHealthCheck("my param")'
* title: 'Title of my health check'
* registered_suites:
* health:
* - mycheck
*
* $result = EnvironmentCheckSuite::inst('health')->run();
*/
class EnvironmentCheckSuite extends Object {
/**
* Name of this suite.
*
* @var string
*/
protected $name;
/**
* @var array
*/
protected $checks = array();
/**
* Associative array of named checks registered via the config system. Each check should specify:
* - definition (e.g. 'MyHealthCheck("my param")')
* - title (e.g. 'Is my feature working?')
* - state (setting this to 'disabled' will cause suites to skip this check entirely.
*
* @var array
*/
private static $registered_checks = array();
/**
* Associative array of named suites registered via the config system. Each suite should enumerate
* named checks that have been configured in 'registered_checks'.
*
* @var array
*/
private static $registered_suites = array();
/**
* Load checks for this suite from the configuration system. This is an alternative to the
* EnvironmentCheckSuite::register - both can be used, checks will be appended to the suite.
*
* @param string $suiteName The name of this suite.
*/
public function __construct($suiteName) {
parent::__construct();
if (empty($this->config()->registered_suites[$suiteName])) {
// Not registered via config system, but it still may be configured later via self::register.
return;
}
foreach ($this->config()->registered_suites[$suiteName] as $checkName) {
if (empty($this->config()->registered_checks[$checkName])) {
throw new InvalidArgumentException(
"Bad EnvironmentCheck: '$checkName' - the named check has not been registered."
);
}
$check = $this->config()->registered_checks[$checkName];
// Existing named checks can be disabled by setting their 'state' to 'disabled'.
// This is handy for disabling checks mandated by modules.
if (!empty($check['state']) && $check['state']==='disabled') continue;
// Add the check to this suite.
$this->push($check['definition'], $check['title']);
}
}
/**
* Run this test suite and return the result code of the worst result.
*
* @return int
*/
public function run() {
$result = new EnvironmentCheckSuiteResult();
foreach($this->checkInstances() as $check) {
list($checkClass, $checkTitle) = $check;
try {
list($status, $message) = $checkClass->check();
// If the check fails, register that as an error
} catch(Exception $e) {
$status = EnvironmentCheck::ERROR;
$message = $e->getMessage();
}
$result->addResult($status, $message, $checkTitle);
}
return $result;
}
/**
* Get instances of all the environment checks.
*
* @return array
*/
protected function checkInstances() {
$output = array();
foreach($this->checks as $check) {
list($checkClass, $checkTitle) = $check;
if(is_string($checkClass)) {
$checkInst = Object::create_from_string($checkClass);
if($checkInst instanceof EnvironmentCheck) {
$output[] = array($checkInst, $checkTitle);
} else {
throw new InvalidArgumentException("Bad EnvironmentCheck: '$checkClass' - the named class doesn't implement EnvironmentCheck");
}
} else if($checkClass instanceof EnvironmentCheck) {
$output[] = array($checkClass, $checkTitle);
} else {
throw new InvalidArgumentException("Bad EnvironmentCheck: " . var_export($check, true));
}
}
return $output;
}
/**
* Add a check to this suite.
*
* @param mixed $check
* @param string $title
*/
public function push($check, $title = null) {
if(!$title) {
$title = is_string($check) ? $check : get_class($check);
}
$this->checks[] = array($check, $title);
}
/////////////////////////////////////////////////////////////////////////////////////////////
/**
* @var array
*/
protected static $instances = array();
/**
* Return a named instance of EnvironmentCheckSuite.
*
* @param string $name
*
* @return EnvironmentCheckSuite
*/
static function inst($name) {
if(!isset(self::$instances[$name])) self::$instances[$name] = new EnvironmentCheckSuite($name);
return self::$instances[$name];
}
/**
* Register a check against the named check suite.
*
* @param string|array $names
* @param EnvironmentCheck $check
* @param string|array
*/
static function register($names, $check, $title = null) {
if(!is_array($names)) $names = array($names);
foreach($names as $name) self::inst($name)->push($check, $title);
}
}
/**
* A single set of results from running an EnvironmentCheckSuite
*/
class EnvironmentCheckSuiteResult extends ViewableData {
/**
* @var ArrayList
*/
protected $details;
/**
* @var int
*/
protected $worst = 0;
function __construct() {
parent::__construct();
$this->details = new ArrayList();
}
/**
* @param int $status
* @param string $message
* @param string $checkIdentifier
*/
function addResult($status, $message, $checkIdentifier) {
$this->details->push(new ArrayData(array(
'Check' => $checkIdentifier,
'Status' => $this->statusText($status),
'Message' => $message,
)));
$this->worst = max($this->worst, $status);
}
/**
* Returns true if there are no errors.
*
* @return bool
*/
public function ShouldPass() {
return $this->worst <= EnvironmentCheck::WARNING;
}
/**
* Returns overall (i.e. worst) status as a string.
*
* @return string
*/
function Status() {
return $this->statusText($this->worst);
}
/**
* Returns detailed status information about each check.
*
* @return ArrayList
*/
function Details() {
return $this->details;
}
/**
* Convert the final result status and details to JSON.
*
* @return string
*/
function toJSON() {
$result = array(
'Status' => $this->Status(),
'ShouldPass' => $this->ShouldPass(),
'Checks' => array()
);
foreach($this->details as $detail) {
$result['Checks'][] = $detail->toMap();
}
return json_encode($result);
}
/**
* Return a text version of a status code.
*
* @return string
*/
protected function statusText($status) {
switch($status) {
case EnvironmentCheck::ERROR: return "ERROR";
case EnvironmentCheck::WARNING: return "WARNING";
case EnvironmentCheck::OK: return "OK";
case 0: return "NO CHECKS";
default: throw new InvalidArgumentException("Bad environment check status '$status'");
}
}
}

View File

@ -1,219 +0,0 @@
<?php
/**
* Provides an interface for checking the given EnvironmentCheckSuite.
*/
class EnvironmentChecker extends RequestHandler {
/**
* @var array
*/
private static $url_handlers = array(
'' => 'index',
);
/**
* @var string
*/
protected $checkSuiteName;
/**
* @var string
*/
protected $title;
/**
* @var int
*/
protected $errorCode = 500;
/**
* @var null|string
*/
public static $to_email_address = null;
/**
* @var null|string
*/
public static $from_email_address = null;
/**
* @var bool
*/
public static $email_results = false;
/**
* @param string $checkSuiteName
* @param string $title
*/
function __construct($checkSuiteName, $title) {
parent::__construct();
$this->checkSuiteName = $checkSuiteName;
$this->title = $title;
}
/**
* @param string $permission
*
* @throws SS_HTTPResponse_Exception
*/
function init($permission = 'ADMIN') {
// if the environment supports it, provide a basic auth challenge and see if it matches configured credentials
if(defined('ENVCHECK_BASICAUTH_USERNAME') && defined('ENVCHECK_BASICAUTH_PASSWORD')) {
if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
// authenticate the input user/pass with the configured credentials
if(
!(
$_SERVER['PHP_AUTH_USER'] == ENVCHECK_BASICAUTH_USERNAME
&& $_SERVER['PHP_AUTH_PW'] == ENVCHECK_BASICAUTH_PASSWORD
)
) {
$response = new SS_HTTPResponse(null, 401);
$response->addHeader('WWW-Authenticate', "Basic realm=\"Environment check\"");
// Exception is caught by RequestHandler->handleRequest() and will halt further execution
$e = new SS_HTTPResponse_Exception(null, 401);
$e->setResponse($response);
throw $e;
}
} else {
$response = new SS_HTTPResponse(null, 401);
$response->addHeader('WWW-Authenticate', "Basic realm=\"Environment check\"");
// Exception is caught by RequestHandler->handleRequest() and will halt further execution
$e = new SS_HTTPResponse_Exception(null, 401);
$e->setResponse($response);
throw $e;
}
} else {
if(!$this->canAccess(null, $permission)) return $this->httpError(403);
}
}
/**
* @param null|int|Member $member
* @param string $permission
*
* @return bool
*
* @throws SS_HTTPResponse_Exception
*/
function canAccess($member = null, $permission = "ADMIN") {
if(!$member) {
$member = Member::currentUser();
}
if(!$member) {
$member = BasicAuth::requireLogin('Environment Checker', $permission, false);
}
// We allow access to this controller regardless of live-status or ADMIN permission only
// if on CLI. Access to this controller is always allowed in "dev-mode", or of the user is ADMIN.
if(
Director::isDev()
|| Director::is_cli()
|| empty($permission)
|| Permission::checkMember($member, $permission)
) {
return true;
}
// Extended access checks.
// "Veto" style, return NULL to abstain vote.
$canExtended = null;
$results = $this->extend('canAccess', $member);
if($results && is_array($results)) {
if(!min($results)) return false;
else return true;
}
return false;
}
/**
* @return SS_HTTPResponse
*/
function index() {
$response = new SS_HTTPResponse;
$result = EnvironmentCheckSuite::inst($this->checkSuiteName)->run();
if(!$result->ShouldPass()) {
$response->setStatusCode($this->errorCode);
}
$resultText = $result->customise(array(
"URL" => Director::absoluteBaseURL(),
"Title" => $this->title,
"Name" => $this->checkSuiteName,
"ErrorCode" => $this->errorCode,
))->renderWith("EnvironmentChecker");
if (self::$email_results && !$result->ShouldPass()) {
$email = new Email(self::$from_email_address, self::$to_email_address, $this->title, $resultText);
$email->send();
}
// output the result as JSON if requested
if(
$this->getRequest()->getExtension() == 'json'
|| strpos($this->getRequest()->getHeader('Accept'), 'application/json') !== false
) {
$response->setBody($result->toJSON());
$response->addHeader('Content-Type', 'application/json');
return $response;
}
$response->setBody($resultText);
return $response;
}
/**
* Set the HTTP status code that should be returned when there's an error.
*
* @param int $errorCode
*/
function setErrorCode($errorCode) {
$this->errorCode = $errorCode;
}
/**
* @param string $from
*/
public static function set_from_email_address($from) {
self::$from_email_address = $from;
}
/**
* @return null|string
*/
public static function get_from_email_address() {
return self::$from_email_address;
}
/**
* @param string $to
*/
public static function set_to_email_address($to) {
self::$to_email_address = $to;
}
/**
* @return null|string
*/
public static function get_to_email_address() {
return self::$to_email_address;
}
/**
* @param bool $results
*/
public static function set_email_results($results) {
self::$email_results = $results;
}
/**
* @return bool
*/
public static function get_email_results() {
return self::$email_results;
}
}

View File

@ -1,37 +0,0 @@
<?php
/**
* Check that the connection to the database is working, by ensuring that the table exists and that
* the table contains some records.
*/
class DatabaseCheck implements EnvironmentCheck {
protected $checkTable;
/**
* By default, Member will be checked.
*
* @param string $checkTable
*/
function __construct($checkTable = "Member") {
$this->checkTable = $checkTable;
}
/**
* @inheritdoc
*
* @return array
*/
function check() {
if(!DB::getConn()->hasTable($this->checkTable)) {
return array(EnvironmentCheck::ERROR, "$this->checkTable not present in the database");
}
$count = DB::query("SELECT COUNT(*) FROM \"$this->checkTable\"")->value();
if($count > 0) {
return array(EnvironmentCheck::OK, "");
} else {
return array(EnvironmentCheck::WARNING, "$this->checkTable queried ok but has no records");
}
}
}

View File

@ -1,116 +0,0 @@
<?php
/**
* Checks that one or more URLs are reachable via HTTP.
* Note that the HTTP connectivity can just be verified from the server to the remote URL,
* it can still fail if the URL in question is requested by the client, e.g. through an iframe.
*
* Requires curl to present, so ensure to check it before with the following:
* <code>EnvironmentCheckSuite::register('check', 'HasFunctionCheck("curl_init")', "Does PHP have CURL support?");</code>
*/
class ExternalURLCheck implements EnvironmentCheck {
/**
* @var array
*/
protected $urls = array();
/**
* @var Int Timeout in seconds.
*/
protected $timeout;
/**
* @param string $urls Space-separated list of absolute URLs.
* @param int $timeout
*/
function __construct($urls, $timeout = 15) {
if($urls) $this->urls = explode(' ', $urls);
$this->timeout = $timeout;
}
/**
* @inheritdoc
*
* @return array
*/
function check() {
$urls = $this->getURLs();
$chs = array();
foreach($urls as $url) {
$ch = curl_init();
$chs[] = $ch;
curl_setopt_array($ch, $this->getCurlOpts($url));
}
// Parallel execution for faster performance
$mh = curl_multi_init();
foreach($chs as $ch) curl_multi_add_handle($mh,$ch);
$active = null;
// Execute the handles
do {
$mrc = curl_multi_exec($mh, $active);
curl_multi_select($mh);
} while ($active > 0);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
$hasError = false;
$msgs = array();
foreach($chs as $ch) {
$url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if(curl_errno($ch) || $code >= 400) {
$hasError = true;
$msgs[] = sprintf(
'Error retrieving "%s": %s (Code: %s)',
$url,
curl_error($ch),
$code
);
} else {
$msgs[] = sprintf(
'Success retrieving "%s" (Code: %s)',
$url,
$code
);
}
}
// Close the handles
foreach($chs as $ch) curl_multi_remove_handle($mh, $ch);
curl_multi_close($mh);
if($hasError) {
return array(EnvironmentCheck::ERROR, implode(', ', $msgs));
} else {
return array(EnvironmentCheck::OK, implode(', ', $msgs));
}
}
/**
* @return array
*/
protected function getCurlOpts($url) {
return array(
CURLOPT_URL => $url,
CURLOPT_HEADER => 0,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FAILONERROR => 1,
CURLOPT_TIMEOUT => $this->timeout,
);
}
/**
* @return array
*/
protected function getURLs() {
return $this->urls;
}
}

View File

@ -1,178 +0,0 @@
<?php
/**
* Checks for the accessibility and file type validation of one or more files or folders.
*
* Examples:
* // Checks /assets/calculator_files has .json files and all files are valid json files.
* EnvironmentCheckSuite::register('check', 'FileAccessibilityAndValidationCheck("' . BASE_PATH . '/assets/calculator_files/*.json",
* "jsonValidate", '.FileAccessibilityAndValidationCheck::CHECK_ALL.')', 'Check a json file exist and are all valid json files'
* );
*
* // Checks /assets/calculator_files/calculator.json exists and is valid json file.
* EnvironmentCheckSuite::register('check', 'FileAccessibilityAndValidationCheck("' . BASE_PATH . '/assets/calculator_files/calculator.json",
* "jsonValidate", '.FileAccessibilityAndValidationCheck::CHECK_SINGLE.')', 'Check a calculator.json exists and is valid json file'
* );
*
* // Check only existence
* EnvironmentCheckSuite::register('check', 'FileAccessibilityAndValidationCheck("' . BASE_PATH . '/assets/calculator_files/calculator.json")',
* 'Check a calculator.json exists only'
* );
*/
class FileAccessibilityAndValidationCheck implements EnvironmentCheck {
/**
* @var int
*/
const CHECK_SINGLE = 1;
/**
* @var int
*/
const CHECK_ALL = 2;
/**
* Absolute path to a file or folder, compatible with glob().
*
* @var string
*/
protected $path;
/**
* Constant, check for a single file to match age criteria, or all of them.
*
* @var int
*/
protected $fileTypeValidateFunc;
/**
* Constant, check for a single file to match age criteria, or all of them.
*
* @var int
*/
protected $checkType;
/**
* @param string $path
* @param string $fileTypeValidateFunc
* @param null|int $checkType
*/
function __construct($path, $fileTypeValidateFunc = 'noVidation', $checkType = null) {
$this->path = $path;
$this->fileTypeValidateFunc = ($fileTypeValidateFunc)? $fileTypeValidateFunc:'noVidation';
$this->checkType = ($checkType) ? $checkType : self::CHECK_SINGLE;
}
/**
* @inheritdoc
*
* @return array
*/
function check() {
$origStage = Versioned::get_reading_mode();
Versioned::set_reading_mode('Live');
$files = $this->getFiles();
if($files){
$fileTypeValidateFunc = $this->fileTypeValidateFunc;
if(method_exists ($this, $fileTypeValidateFunc)){
$invalidFiles = array();
$validFiles = array();
foreach($files as $file){
if($this->$fileTypeValidateFunc($file)){
$validFiles[] = $file;
}else{
$invalidFiles[] = $file;
}
}
// If at least one file was valid, count as passed
if($this->checkType == self::CHECK_SINGLE && count($invalidFiles) < count($files)) {
$validFileList = "\n";
foreach($validFiles as $vf){
$validFileList .= $vf."\n";
}
if($fileTypeValidateFunc == 'noVidation') {
$checkReturn = array(
EnvironmentCheck::OK,
sprintf('At least these file(s) accessible: %s', $validFileList)
);
}else{
$checkReturn = array(
EnvironmentCheck::OK,
sprintf('At least these file(s) passed file type validate function "%s": %s', $fileTypeValidateFunc, $validFileList)
);
}
} else {
if (count($invalidFiles) == 0) $checkReturn = array(EnvironmentCheck::OK, 'All files valideted');
else {
$invalidFileList = "\n";
foreach($invalidFiles as $vf){
$invalidFileList .= $vf."\n";
}
if($fileTypeValidateFunc == 'noVidation'){
$checkReturn = array(
EnvironmentCheck::ERROR,
sprintf('File(s) not accessible: %s', $invalidFileList)
);
}else{
$checkReturn = array(
EnvironmentCheck::ERROR,
sprintf('File(s) not passing the file type validate function "%s": %s', $fileTypeValidateFunc, $invalidFileList)
);
}
}
}
}else{
$checkReturn = array(
EnvironmentCheck::ERROR,
sprintf("Invalid file type validation method name passed: %s ", $fileTypeValidateFunc)
);
}
}else{
$checkReturn = array(
EnvironmentCheck::ERROR,
sprintf("No files accessible at path %s", $this->path)
);
}
Versioned::set_reading_mode($origStage);
return $checkReturn;
}
/**
* @param string $file
*
* @return bool
*/
private function jsonValidate($file){
$json = json_decode(file_get_contents($file));
if(!$json) {
return false;
}else{
return true;
}
}
/**
* @param string $file
*
* @return bool
*/
protected function noVidation($file) {
return true;
}
/**
* Gets a list of absolute file paths.
*
* @return array
*/
protected function getFiles() {
return glob($this->path);
}
}

View File

@ -1,135 +0,0 @@
<?php
/**
* Checks for the maximum age of one or more files or folders.
* Useful for files which should be frequently auto-generated,
* like static caches, as well as for backup files and folders.
* Does NOT check for existence of a file (will silently fail).
*
* Examples:
* // Checks that Requirements::combine_files() has regenerated files in the last 24h
* EnvironmentCheckSuite::register(
* 'check',
* 'FileAgeCheck("' . ASSETS_PATH . '/_combined_files/*.js' . '", "-1 day", '>', " . FileAgeCheck::CHECK_ALL) . "'
* );
*
* // Checks that at least one backup folder has been created in the last 24h
* EnvironmentCheckSuite::register(
* 'check',
* 'FileAgeCheck("' . BASE_PATH . '/../backups/*' . '", "-1 day", '>', " . FileAgeCheck::CHECK_SINGLE) . "'
* );
*/
class FileAgeCheck implements EnvironmentCheck {
/**
* @var int
*/
const CHECK_SINGLE = 1;
/**
* @var int
*/
const CHECK_ALL = 2;
/**
* Absolute path to a file or folder, compatible with glob().
*
* @var string
*/
protected $path;
/**
* Relative date specification, compatible with strtotime().
*
* @var string
*/
protected $relativeAge;
/**
* The function to use for checking file age: so filemtime(), filectime(), or fileatime().
*
* @var string
*/
protected $checkFn;
/**
* Constant, check for a single file to match age criteria, or all of them.
*
* @var int
*/
protected $checkType;
/**
* Type of comparison (either > or <).
*
* @var string
*/
protected $compareOperand;
/**
* @param string $path
* @param string $relativeAge
* @param string $compareOperand
* @param null|int $checkType
* @param string $checkFn
*/
function __construct($path, $relativeAge, $compareOperand = '>', $checkType = null, $checkFn = 'filemtime') {
$this->path = $path;
$this->relativeAge = $relativeAge;
$this->checkFn = $checkFn;
$this->checkType = ($checkType) ? $checkType : self::CHECK_SINGLE;
$this->compareOperand = $compareOperand;
}
/**
* @inheritdoc
*
* @return array
*/
function check() {
$cutoffTime = strtotime($this->relativeAge, SS_Datetime::now()->Format('U'));
$files = $this->getFiles();
$invalidFiles = array();
$validFiles = array();
$checkFn = $this->checkFn;
$allValid = true;
if($files) foreach($files as $file) {
$fileTime = $checkFn($file);
$valid = ($this->compareOperand == '>') ? ($fileTime >= $cutoffTime) : ($fileTime <= $cutoffTime);
if($valid) {
$validFiles[] = $file;
} else {
$invalidFiles[] = $file;
if($this->checkType == self::CHECK_ALL) {
return array(
EnvironmentCheck::ERROR,
sprintf(
'File "%s" doesn\'t match age check (compare %s: %s, actual: %s)',
$file, $this->compareOperand, date('c', $cutoffTime), date('c', $fileTime)
)
);
}
}
}
// If at least one file was valid, count as passed
if($this->checkType == self::CHECK_SINGLE && count($invalidFiles) < count($files)) {
return array(EnvironmentCheck::OK, '');
} else {
if (count($invalidFiles) == 0) return array(EnvironmentCheck::OK, '');
else return array(
EnvironmentCheck::ERROR,
sprintf('No files matched criteria (%s %s)', $this->compareOperand, date('c', $cutoffTime))
);
}
}
/**
* Gets a list of absolute file paths.
*
* @return array
*/
protected function getFiles() {
return glob($this->path);
}
}

View File

@ -1,68 +0,0 @@
<?php
/**
* Check that the given file is writable.
*/
class FileWriteableCheck implements EnvironmentCheck {
/**
* @var string
*/
protected $path;
/**
* @param string $path The full path. If a relative path, it will relative to the BASE_PATH.
*/
function __construct($path) {
$this->path = $path;
}
/**
* @inheritdoc
*
* @return array
*/
function check() {
if($this->path[0] == '/') $filename = $this->path;
else $filename = BASE_PATH . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $this->path);
if(file_exists($filename)) $isWriteable = is_writeable($filename);
else $isWriteable = is_writeable(dirname($filename));
if(!$isWriteable) {
if(function_exists('posix_getgroups')) {
$userID = posix_geteuid();
$user = posix_getpwuid($userID);
$currentOwnerID = fileowner(file_exists($filename) ? $filename : dirname($filename) );
$currentOwner = posix_getpwuid($currentOwnerID);
$message = "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe file is currently owned by '$currentOwner[name]'. ";
if($user['name'] == $currentOwner['name']) {
$message .= "We recommend that you make the file writeable.";
} else {
$groups = posix_getgroups();
$groupList = array();
foreach($groups as $group) {
$groupInfo = posix_getgrgid($group);
if(in_array($currentOwner['name'], $groupInfo['members'])) $groupList[] = $groupInfo['name'];
}
if($groupList) {
$message .= " We recommend that you make the file group-writeable and change the group to one of these groups:\n - ". implode("\n - ", $groupList)
. "\n\nFor example:\nchmod g+w $filename\nchgrp " . $groupList[0] . " $filename";
} else {
$message .= " There is no user-group that contains both the web-server user and the owner of this file. Change the ownership of the file, create a new group, or temporarily make the file writeable by everyone during the install process.";
}
}
} else {
$message = "The webserver user needs to be able to write to this file:\n$filename";
}
return array(EnvironmentCheck::ERROR, $message);
}
return array(EnvironmentCheck::OK,'');
}
}

View File

@ -1,28 +0,0 @@
<?php
/**
* Check that the given class exists.
*/
class HasClassCheck implements EnvironmentCheck {
/**
* @var string
*/
protected $className;
/**
* @param string $className The name of the class to look for.
*/
function __construct($className) {
$this->className = $className;
}
/**
* @inheritdoc
*
* @return array
*/
function check() {
if(class_exists($this->className)) return array(EnvironmentCheck::OK, 'Class ' . $this->className.' exists');
else return array(EnvironmentCheck::ERROR, 'Class ' . $this->className.' doesn\'t exist');
}
}

View File

@ -1,28 +0,0 @@
<?php
/**
* Check that the given function exists.
*/
class HasFunctionCheck implements EnvironmentCheck {
/**
* @var string
*/
protected $functionName;
/**
* @param string $functionName The name of the function to look for.
*/
function __construct($functionName) {
$this->functionName = $functionName;
}
/**
* @inheritdoc
*
* @return array
*/
function check() {
if(function_exists($this->functionName)) return array(EnvironmentCheck::OK, $this->functionName.'() exists');
else return array(EnvironmentCheck::ERROR, $this->functionName.'() doesn\'t exist');
}
}

View File

@ -1,66 +0,0 @@
<?php
/**
* Checks if the SMTP connection configured through PHP.ini works as expected.
*
* Only checks socket connection with HELO command, not actually sending the email.
*/
class SMTPConnectCheck implements EnvironmentCheck {
/**
* @var string
*/
protected $host;
/**
* @var int
*/
protected $port;
/**
* Timeout (in seconds).
*
* @var int
*/
protected $timeout;
/**
* @param null|string $host
* @param null|int $port
* @param int $timeout
*/
function __construct($host = null, $port = null, $timeout = 15) {
$this->host = ($host) ? $host : ini_get('SMTP');
if(!$this->host) $this->host = 'localhost';
$this->port = ($port) ? $port : ini_get('smtp_port');
if(!$this->port) $this->port = 25;
$this->timeout = $timeout;
}
/**
* @inheritdoc
*
* @return array
*/
function check() {
$f = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
if(!$f) {
return array(
EnvironmentCheck::ERROR,
sprintf("Couldn't connect to SMTP on %s:%s (Error: %s %s)", $this->host, $this->port, $errno, $errstr)
);
}
fwrite($f, "HELO its_me\r\n");
$response = fread($f, 26);
if(substr($response, 0, 3) != '220') {
return array(
EnvironmentCheck::ERROR,
sprintf("Invalid mail server response: %s", $response)
);
}
return array(EnvironmentCheck::OK, '');
}
}

View File

@ -1,53 +0,0 @@
<?php
/**
* Check the availability of all Solr indexes of given class.
*
* If there are no indexes of given class found, the returned status will still be "OK".
*/
class SolrIndexCheck implements EnvironmentCheck {
/**
* @var null|string
*/
protected $indexClass;
/**
* @param string $indexClass Limit the index checks to the specified class and all its subclasses.
*/
function __construct($indexClass = null) {
$this->indexClass = $indexClass;
}
/**
* @inheritdoc
*
* @return array
*/
function check() {
$brokenCores = array();
if (!class_exists('Solr')) {
return array(
EnvironmentCheck::ERROR,
'Class `Solr` not found. Is the fulltextsearch module installed?'
);
}
$service = Solr::service();
foreach (Solr::get_indexes($this->indexClass) as $index) {
$core = $index->getIndexName();
if (!$service->coreIsActive($core)) {
$brokenCores[] = $core;
}
}
if (!empty($brokenCores)) {
return array(
EnvironmentCheck::ERROR,
'The following indexes are unavailable: ' . implode($brokenCores, ', ')
);
}
return array(EnvironmentCheck::OK, 'Expected indexes are available.');
}
}

View File

@ -1,57 +0,0 @@
<?php
/**
* Check that a given URL is functioning, by default, the homepage.
*
* Note that Director::test() will be used rather than a CURL check.
*/
class URLCheck implements EnvironmentCheck {
/**
* @var string
*/
protected $url;
/**
* @var string
*/
protected $testString;
/*
* @param string $url The URL to check, relative to the site (homepage is '').
* @param string $testString An optional piece of text to search for on the homepage.
*/
function __construct($url = '', $testString = '') {
$this->url = $url;
$this->testString = $testString;
}
/**
* @inheritdoc
*
* @return array
*
* @throws SS_HTTPResponse_Exception
*/
function check() {
$response = Director::test($this->url);
if($response->getStatusCode() != 200) {
return array(
EnvironmentCheck::ERROR,
sprintf('Error retrieving "%s" (Code: %d)', $this->url, $response->getStatusCode())
);
} else if($this->testString && (strpos($response->getBody(), $this->testString) === false)) {
return array(
EnvironmentCheck::WARNING,
sprintf('Success retrieving "%s", but string "%s" not found', $this->url, $this->testString)
);
} else {
return array(
EnvironmentCheck::OK,
sprintf('Success retrieving "%s"', $this->url)
);
}
}
}

1
codecov.yml Normal file
View File

@ -0,0 +1 @@
comment: false

View File

@ -1,25 +1,41 @@
{
"name": "silverstripe/environmentcheck",
"description": "Provides an API for building environment tests",
"type": "silverstripe-module",
"keywords": ["silverstripe", "testing", "environment", "check"],
"authors": [
{
"name": "Will Rossiter",
"email": "will@fullscreen.io"
},
{
"name": "Sam Minnee",
"email": "sam@silverstripe.com"
}
],
"require":
{
"silverstripe/framework": "~3.1"
},
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
}
}
"name": "silverstripe/environmentcheck",
"description": "Provides an API for building environment tests",
"license": "BSD-3-Clause",
"type": "silverstripe-vendormodule",
"keywords": [
"silverstripe",
"testing",
"environment",
"check"
],
"authors": [
{
"name": "Will Rossiter",
"email": "will@fullscreen.io"
},
{
"name": "Sam Minnee",
"email": "sam@silverstripe.com"
}
],
"require": {
"php": "^7.4 || ^8.0",
"silverstripe/framework": "^4.10",
"silverstripe/versioned": "^1.0",
"guzzlehttp/guzzle": "^6.3.3 || ^7"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "^3.0"
},
"extra": [],
"autoload": {
"psr-4": {
"SilverStripe\\EnvironmentCheck\\": "src/",
"SilverStripe\\EnvironmentCheck\\Tests\\": "tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@ -1,16 +1,12 @@
Copyright (c) 2015, SilverStripe Limited
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.
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* 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.
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.
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.
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.

12
phpcs.xml.dist Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="SilverStripe">
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
<file>src</file>
<file>tests</file>
<rule ref="PSR2" >
<!-- Current exclusions -->
<exclude name="PSR1.Methods.CamelCapsMethodName" />
</rule>
</ruleset>

19
phpunit.xml.dist Normal file
View File

@ -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">src/.</directory>
<exclude>
<directory suffix=".php">tests/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,207 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\Control\Director;
use SilverStripe\Control\Controller;
use SilverStripe\ORM\ValidationResult;
use Psr\Http\Message\ResponseInterface;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\EnvironmentCheck\Traits\Fetcher;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
/**
* Check cache headers for any response, can specify directives that must be included and
* also must be excluded from Cache-Control headers in response. Also checks for
* existence of ETag.
*
* @example SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck("/",["must-revalidate", "max-age=120"],["no-store"])
* @package environmentcheck
*/
class CacheHeadersCheck implements EnvironmentCheck
{
use Fetcher;
/**
* Settings that must be included in the Cache-Control header
*
* @var array
*/
protected $mustInclude = [];
/**
* Settings that must be excluded in the Cache-Control header
*
* @var array
*/
protected $mustExclude = [];
/**
* Result to keep track of status and messages for all checks, reuses
* ValidationResult for convenience.
*
* @var ValidationResult
*/
protected $result;
/**
* Set up with URL, arrays of header settings to check.
*
* @param string $url
* @param array $mustInclude Settings that must be included in Cache-Control
* @param array $mustExclude Settings that must be excluded in Cache-Control
*/
public function __construct($url = '', $mustInclude = [], $mustExclude = [])
{
$this->setURL($url);
$this->mustInclude = $mustInclude;
$this->mustExclude = $mustExclude;
}
/**
* Check that correct caching headers are present.
*
* @return void
*/
public function check()
{
// Using a validation result to capture messages
$this->result = new ValidationResult();
$response = $this->client->get($this->getURL());
$fullURL = $this->getURL();
if ($response === null) {
return [
EnvironmentCheck::ERROR,
"Cache headers check request failed for $fullURL",
];
}
//Check that Etag exists
$this->checkEtag($response);
// Check Cache-Control settings
$this->checkCacheControl($response);
if ($this->result->isValid()) {
return [
EnvironmentCheck::OK,
$this->getMessage(),
];
} else {
// @todo Ability to return a warning
return [
EnvironmentCheck::ERROR,
$this->getMessage(),
];
}
}
/**
* Collate messages from ValidationResult so that it is clear which parts
* of the check passed and which failed.
*
* @return string
*/
private function getMessage()
{
$ret = '';
// Filter good messages
$goodTypes = [ValidationResult::TYPE_GOOD, ValidationResult::TYPE_INFO];
$good = array_filter(
$this->result->getMessages() ?? [],
function ($val, $key) use ($goodTypes) {
if (in_array($val['messageType'], $goodTypes ?? [])) {
return true;
}
return false;
},
ARRAY_FILTER_USE_BOTH
);
if (!empty($good)) {
$ret .= "GOOD: " . implode('; ', array_column($good ?? [], 'message')) . " ";
}
// Filter bad messages
$badTypes = [ValidationResult::TYPE_ERROR, ValidationResult::TYPE_WARNING];
$bad = array_filter(
$this->result->getMessages() ?? [],
function ($val, $key) use ($badTypes) {
if (in_array($val['messageType'], $badTypes ?? [])) {
return true;
}
return false;
},
ARRAY_FILTER_USE_BOTH
);
if (!empty($bad)) {
$ret .= "BAD: " . implode('; ', array_column($bad ?? [], 'message'));
}
return $ret;
}
/**
* Check that ETag header exists
*
* @param ResponseInterface $response
* @return void
*/
private function checkEtag(ResponseInterface $response)
{
$eTag = $response->getHeaderLine('ETag');
$fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url);
if ($eTag) {
$this->result->addMessage(
"$fullURL includes an Etag header in response",
ValidationResult::TYPE_GOOD
);
return;
}
$this->result->addError(
"$fullURL is missing an Etag header",
ValidationResult::TYPE_WARNING
);
}
/**
* Check that the correct header settings are either included or excluded.
*
* @param ResponseInterface $response
* @return void
*/
private function checkCacheControl(ResponseInterface $response)
{
$cacheControl = $response->getHeaderLine('Cache-Control');
$vals = array_map('trim', explode(',', $cacheControl ?? ''));
$fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url);
// All entries from must contain should be present
if ($this->mustInclude == array_intersect($this->mustInclude ?? [], $vals)) {
$matched = implode(",", $this->mustInclude);
$this->result->addMessage(
"$fullURL includes all settings: {$matched}",
ValidationResult::TYPE_GOOD
);
} else {
$missing = implode(",", array_diff($this->mustInclude ?? [], $vals));
$this->result->addError(
"$fullURL is excluding some settings: {$missing}"
);
}
// All entries from must exclude should not be present
if (empty(array_intersect($this->mustExclude ?? [], $vals))) {
$missing = implode(",", $this->mustExclude);
$this->result->addMessage(
"$fullURL excludes all settings: {$missing}",
ValidationResult::TYPE_GOOD
);
} else {
$matched = implode(",", array_intersect($this->mustExclude ?? [], $vals));
$this->result->addError(
"$fullURL is including some settings: {$matched}"
);
}
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
use SilverStripe\ORM\DB;
/**
* Check that the connection to the database is working, by ensuring that the table exists and that
* the table contains some records.
*
* @package environmentcheck
*/
class DatabaseCheck implements EnvironmentCheck
{
/**
* @var string
*/
protected $checkTable;
/**
* By default, Member will be checked.
*
* @param string $checkTable
*/
public function __construct($checkTable = 'Member')
{
$this->checkTable = $checkTable;
}
/**
* {@inheritDoc}
*
* @return array
*/
public function check()
{
if (!DB::get_schema()->hasTable($this->checkTable)) {
return [EnvironmentCheck::ERROR, "$this->checkTable not present in the database"];
}
$count = DB::query("SELECT COUNT(*) FROM \"$this->checkTable\"")->value();
if ($count > 0) {
return [EnvironmentCheck::OK, ''];
}
return [EnvironmentCheck::WARNING, "$this->checkTable queried ok but has no records"];
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\Control\Director;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
/**
* Check whether the environment setting is safe. Useful for live sites where a
* non "Live" setting might disclose sensitive information.
*
* @package environmentcheck
*/
class EnvTypeCheck implements EnvironmentCheck
{
/**
* Check the environment setting.
*
* @return array
*/
public function check()
{
$envSetting = Director::get_environment_type();
switch ($envSetting) {
case 'live':
return [
EnvironmentCheck::OK,
"Env setting is 'live'",
];
// Fallthrough
default:
case 'dev':
case 'test':
return [
EnvironmentCheck::ERROR,
"Env setting is '{$envSetting}' and may disclose information",
];
}
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
/**
* Checks that one or more URLs are reachable via HTTP.
* Note that the HTTP connectivity can just be verified from the server to the remote URL,
* it can still fail if the URL in question is requested by the client, e.g. through an iframe.
*
* Requires curl to present, so ensure to check it before with the following:
* <code>
* EnvironmentCheckSuite::register(
* 'check',
* 'HasFunctionCheck("curl_init")',
* "Does PHP have CURL support?"
* );
* </code>
*/
class ExternalURLCheck implements EnvironmentCheck
{
/**
* @var array
*/
protected $urls = [];
/**
* @var Int Timeout in seconds.
*/
protected $timeout;
/**
* @param string $urls Space-separated list of absolute URLs.
* @param int $timeout
*/
public function __construct($urls, $timeout = 15)
{
if ($urls) {
$this->urls = explode(' ', $urls ?? '');
}
$this->timeout = $timeout;
}
/**
* {@inheritDoc}
*
* @return array
*/
public function check()
{
$urls = $this->getURLs();
$chs = [];
foreach ($urls as $url) {
$ch = curl_init();
$chs[] = $ch;
curl_setopt_array($ch, $this->getCurlOpts($url) ?? []);
}
// Parallel execution for faster performance
$mh = curl_multi_init();
foreach ($chs as $ch) {
curl_multi_add_handle($mh, $ch);
}
$active = null;
// Execute the handles
do {
$mrc = curl_multi_exec($mh, $active);
curl_multi_select($mh);
} while ($active > 0);
while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
$hasError = false;
$msgs = [];
foreach ($chs as $ch) {
$url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch) || $code >= 400) {
$hasError = true;
$msgs[] = sprintf(
'Error retrieving "%s": %s (Code: %s)',
$url,
curl_error($ch),
$code
);
} else {
$msgs[] = sprintf(
'Success retrieving "%s" (Code: %s)',
$url,
$code
);
}
}
// Close the handles
foreach ($chs as $ch) {
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
if ($hasError) {
return [EnvironmentCheck::ERROR, implode(', ', $msgs)];
}
return [EnvironmentCheck::OK, implode(', ', $msgs)];
}
/**
* @return array
*/
protected function getCurlOpts($url)
{
return [
CURLOPT_URL => $url,
CURLOPT_HEADER => 0,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FAILONERROR => 1,
CURLOPT_TIMEOUT => $this->timeout,
];
}
/**
* @return array
*/
protected function getURLs()
{
return $this->urls;
}
}

View File

@ -0,0 +1,209 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
use SilverStripe\Versioned\Versioned;
/**
* Checks for the accessibility and file type validation of one or more files or folders.
*
* Examples:
* // Checks /assets/calculator_files has .json files and all files are valid json files.
* EnvironmentCheckSuite::register(
* 'check',
* 'FileAccessibilityAndValidationCheck(
* "' . BASE_PATH . '/assets/calculator_files/*.json",
* "jsonValidate",
* '.FileAccessibilityAndValidationCheck::CHECK_ALL.'
* )',
* 'Check a json file exist and are all valid json files'
* );
*
* // Checks /assets/calculator_files/calculator.json exists and is valid json file.
* EnvironmentCheckSuite::register(
* 'check',
* 'FileAccessibilityAndValidationCheck(
* "' . BASE_PATH . '/assets/calculator_files/calculator.json",
* "jsonValidate",
* '.FileAccessibilityAndValidationCheck::CHECK_SINGLE.'
* )',
* 'Check a calculator.json exists and is valid json file'
* );
*
* // Check only existence
* EnvironmentCheckSuite::register(
* 'check',
* 'FileAccessibilityAndValidationCheck("' . BASE_PATH . '/assets/calculator_files/calculator.json")',
* 'Check a calculator.json exists only'
* );
*/
class FileAccessibilityAndValidationCheck implements EnvironmentCheck
{
/**
* @var int
*/
const CHECK_SINGLE = 1;
/**
* @var int
*/
const CHECK_ALL = 2;
/**
* Absolute path to a file or folder, compatible with glob().
*
* @var string
*/
protected $path;
/**
* Constant, check for a single file to match age criteria, or all of them.
*
* @var int
*/
protected $fileTypeValidateFunc;
/**
* Constant, check for a single file to match age criteria, or all of them.
*
* @var int
*/
protected $checkType;
/**
* @param string $path
* @param string $fileTypeValidateFunc
* @param null|int $checkType
*/
public function __construct($path, $fileTypeValidateFunc = 'noVidation', $checkType = null)
{
$this->path = $path;
$this->fileTypeValidateFunc = ($fileTypeValidateFunc)? $fileTypeValidateFunc : 'noVidation';
$this->checkType = ($checkType) ? $checkType : self::CHECK_SINGLE;
}
/**
* {@inheritDoc}
*
* @return array
*/
public function check()
{
$origStage = Versioned::get_reading_mode();
Versioned::set_reading_mode(Versioned::LIVE);
$files = $this->getFiles();
if ($files) {
$fileTypeValidateFunc = $this->fileTypeValidateFunc;
if (method_exists($this, $fileTypeValidateFunc ?? '')) {
$invalidFiles = [];
$validFiles = [];
foreach ($files as $file) {
if ($this->$fileTypeValidateFunc($file)) {
$validFiles[] = $file;
} else {
$invalidFiles[] = $file;
}
}
// If at least one file was valid, count as passed
if ($this->checkType == self::CHECK_SINGLE && count($invalidFiles ?? []) < count($files ?? [])) {
$validFileList = PHP_EOL;
foreach ($validFiles as $vf) {
$validFileList .= $vf . PHP_EOL;
}
if ($fileTypeValidateFunc == 'noVidation') {
$checkReturn = [
EnvironmentCheck::OK,
sprintf('At least these file(s) accessible: %s', $validFileList)
];
} else {
$checkReturn = [
EnvironmentCheck::OK,
sprintf(
'At least these file(s) passed file type validate function "%s": %s',
$fileTypeValidateFunc,
$validFileList
)
];
}
} else {
if (count($invalidFiles ?? []) == 0) {
$checkReturn = [EnvironmentCheck::OK, 'All files valideted'];
} else {
$invalidFileList = PHP_EOL;
foreach ($invalidFiles as $vf) {
$invalidFileList .= $vf . PHP_EOL;
}
if ($fileTypeValidateFunc == 'noVidation') {
$checkReturn = [
EnvironmentCheck::ERROR,
sprintf('File(s) not accessible: %s', $invalidFileList)
];
} else {
$checkReturn = [
EnvironmentCheck::ERROR,
sprintf(
'File(s) not passing the file type validate function "%s": %s',
$fileTypeValidateFunc,
$invalidFileList
)
];
}
}
}
} else {
$checkReturn = array(
EnvironmentCheck::ERROR,
sprintf("Invalid file type validation method name passed: %s ", $fileTypeValidateFunc)
);
}
} else {
$checkReturn = array(
EnvironmentCheck::ERROR,
sprintf("No files accessible at path %s", $this->path)
);
}
Versioned::set_reading_mode($origStage);
return $checkReturn;
}
/**
* @param string $file
*
* @return bool
*/
private function jsonValidate($file)
{
$json = json_decode(file_get_contents($file ?? '') ?? '');
if (!$json) {
return false;
}
return true;
}
/**
* @param string $file
*
* @return bool
*/
protected function noVidation($file)
{
return true;
}
/**
* Gets a list of absolute file paths.
*
* @return array
*/
protected function getFiles()
{
return glob($this->path ?? '');
}
}

151
src/Checks/FileAgeCheck.php Normal file
View File

@ -0,0 +1,151 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
use SilverStripe\ORM\FieldType\DBDatetime;
/**
* Checks for the maximum age of one or more files or folders.
* Useful for files which should be frequently auto-generated,
* like static caches, as well as for backup files and folders.
* Does NOT check for existence of a file (will silently fail).
*
* Examples:
* // Checks that Requirements::combine_files() has regenerated files in the last 24h
* EnvironmentCheckSuite::register(
* 'check',
* 'FileAgeCheck("' . ASSETS_PATH . '/_combined_files/*.js' . '", "-1 day", '>', " . FileAgeCheck::CHECK_ALL) . "'
* );
*
* // Checks that at least one backup folder has been created in the last 24h
* EnvironmentCheckSuite::register(
* 'check',
* 'FileAgeCheck("' . BASE_PATH . '/../backups/*' . '", "-1 day", '>', " . FileAgeCheck::CHECK_SINGLE) . "'
* );
*
* @package environmentcheck
*/
class FileAgeCheck implements EnvironmentCheck
{
/**
* @var int
*/
const CHECK_SINGLE = 1;
/**
* @var int
*/
const CHECK_ALL = 2;
/**
* Absolute path to a file or folder, compatible with glob().
*
* @var string
*/
protected $path;
/**
* Relative date specification, compatible with strtotime().
*
* @var string
*/
protected $relativeAge;
/**
* The function to use for checking file age: so filemtime(), filectime(), or fileatime().
*
* @var string
*/
protected $checkFn;
/**
* Constant, check for a single file to match age criteria, or all of them.
*
* @var int
*/
protected $checkType;
/**
* Type of comparison (either > or <).
*
* @var string
*/
protected $compareOperand;
/**
* @param string $path
* @param string $relativeAge
* @param string $compareOperand
* @param null|int $checkType
* @param string $checkFn
*/
public function __construct($path, $relativeAge, $compareOperand = '>', $checkType = null, $checkFn = 'filemtime')
{
$this->path = $path;
$this->relativeAge = $relativeAge;
$this->checkFn = $checkFn;
$this->checkType = ($checkType) ? $checkType : self::CHECK_SINGLE;
$this->compareOperand = $compareOperand;
}
/**
* {@inheritDoc}
*
* @return array
*/
public function check()
{
$cutoffTime = strtotime($this->relativeAge ?? '', DBDatetime::now()->Format('U'));
$files = $this->getFiles();
$invalidFiles = [];
$validFiles = [];
$checkFn = $this->checkFn;
$allValid = true;
if ($files) {
foreach ($files as $file) {
$fileTime = $checkFn($file);
$valid = ($this->compareOperand == '>') ? ($fileTime >= $cutoffTime) : ($fileTime <= $cutoffTime);
if ($valid) {
$validFiles[] = $file;
} else {
$invalidFiles[] = $file;
if ($this->checkType == self::CHECK_ALL) {
return [
EnvironmentCheck::ERROR,
sprintf(
'File "%s" doesn\'t match age check (compare %s: %s, actual: %s)',
$file,
$this->compareOperand,
date('c', $cutoffTime),
date('c', $fileTime)
)
];
}
}
}
}
// If at least one file was valid, count as passed
if ($this->checkType == self::CHECK_SINGLE && count($invalidFiles ?? []) < count($files ?? [])) {
return [EnvironmentCheck::OK, ''];
}
if (count($invalidFiles ?? []) == 0) {
return [EnvironmentCheck::OK, ''];
}
return [
EnvironmentCheck::ERROR,
sprintf('No files matched criteria (%s %s)', $this->compareOperand, date('c', $cutoffTime))
];
}
/**
* Gets a list of absolute file paths.
*
* @return array
*/
protected function getFiles()
{
return glob($this->path ?? '');
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
/**
* Check that the given file is writable.
*
* @package environmentcheck
*/
class FileWriteableCheck implements EnvironmentCheck
{
/**
* @var string
*/
protected $path;
/**
* @param string $path The full path. If a relative path, it will relative to the BASE_PATH.
*/
public function __construct($path)
{
$this->path = $path;
}
/**
* {@inheritDoc}
*
* @return array
*/
public function check()
{
if ($this->path[0] == '/') {
$filename = $this->path;
} else {
$filename = BASE_PATH . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $this->path ?? '');
}
if (file_exists($filename ?? '')) {
$isWriteable = is_writeable($filename ?? '');
} else {
$isWriteable = is_writeable(dirname($filename ?? ''));
}
if (!$isWriteable) {
if (function_exists('posix_getgroups')) {
$userID = posix_geteuid();
$user = posix_getpwuid($userID ?? 0);
$currentOwnerID = fileowner(file_exists($filename ?? '') ? $filename : dirname($filename ?? ''));
$currentOwner = posix_getpwuid($currentOwnerID ?? 0);
$message = "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe file is "
. "currently owned by '$currentOwner[name]'. ";
if ($user['name'] == $currentOwner['name']) {
$message .= 'We recommend that you make the file writeable.';
} else {
$groups = posix_getgroups();
$groupList = [];
foreach ($groups as $group) {
$groupInfo = posix_getgrgid($group ?? 0);
if (in_array($currentOwner['name'], $groupInfo['members'] ?? [])) {
$groupList[] = $groupInfo['name'];
}
}
if ($groupList) {
$message .= " We recommend that you make the file group-writeable and change the group to "
. "one of these groups:\n - " . implode("\n - ", $groupList)
. "\n\nFor example:\nchmod g+w $filename\nchgrp " . $groupList[0] . " $filename";
} else {
$message .= " There is no user-group that contains both the web-server user and the owner "
. "of this file. Change the ownership of the file, create a new group, or temporarily "
. "make the file writeable by everyone during the install process.";
}
}
} else {
$message = "The webserver user needs to be able to write to this file:\n$filename";
}
return [EnvironmentCheck::ERROR, $message];
}
return [EnvironmentCheck::OK, ''];
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
/**
* Check that the given class exists.
*
* @package environmentcheck
*/
class HasClassCheck implements EnvironmentCheck
{
/**
* @var string
*/
protected $className;
/**
* @param string $className The name of the class to look for.
*/
public function __construct($className)
{
$this->className = $className;
}
/**
* {@inheritDoc}
*
* @return array
*/
public function check()
{
if (class_exists($this->className ?? '')) {
return [EnvironmentCheck::OK, 'Class ' . $this->className.' exists'];
}
return [EnvironmentCheck::ERROR, 'Class ' . $this->className.' doesn\'t exist'];
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
/**
* Check that the given function exists.
*
* @package environmentcheck
*/
class HasFunctionCheck implements EnvironmentCheck
{
/**
* @var string
*/
protected $functionName;
/**
* @param string $functionName The name of the function to look for.
*/
public function __construct($functionName)
{
$this->functionName = $functionName;
}
/**
* {@inheritDoc}
*
* @return array
*/
public function check()
{
if (function_exists($this->functionName ?? '')) {
return [EnvironmentCheck::OK, $this->functionName . '() exists'];
}
return [EnvironmentCheck::ERROR, $this->functionName . '() doesn\'t exist'];
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
/**
* Checks if the SMTP connection configured through PHP.ini works as expected.
*
* Only checks socket connection with HELO command, not actually sending the email.
*
* @package environmentcheck
*/
class SMTPConnectCheck implements EnvironmentCheck
{
/**
* @var string
*/
protected $host;
/**
* @var int
*/
protected $port;
/**
* Timeout (in seconds).
*
* @var int
*/
protected $timeout;
/**
* @param null|string $host
* @param null|int $port
* @param int $timeout
*/
public function __construct($host = null, $port = null, $timeout = 15)
{
$this->host = ($host) ? $host : ini_get('SMTP');
if (!$this->host) {
$this->host = 'localhost';
}
$this->port = ($port) ? $port : ini_get('smtp_port');
if (!$this->port) {
$this->port = 25;
}
$this->timeout = $timeout;
}
/**
* {@inheritDoc}
*
* @return array
*/
public function check()
{
$f = @fsockopen($this->host ?? '', $this->port ?? 0, $errno, $errstr, $this->timeout);
if (!$f) {
return [
EnvironmentCheck::ERROR,
sprintf("Couldn't connect to SMTP on %s:%s (Error: %s %s)", $this->host, $this->port, $errno, $errstr)
];
}
fwrite($f, "HELO its_me\r\n");
$response = fread($f, 26);
if (substr($response ?? '', 0, 3) != '220') {
return [
EnvironmentCheck::ERROR,
sprintf('Invalid mail server response: %s', $response)
];
}
return [EnvironmentCheck::OK, ''];
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use Psr\Http\Message\ResponseInterface;
use SilverStripe\EnvironmentCheck\Traits\Fetcher;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
/**
* Check that a given URL does not generate a session.
*
* @author Adrian Humphreys
* @package environmentcheck
*/
class SessionCheck implements EnvironmentCheck
{
use Fetcher;
/**
* Set up check with URL
*
* @param string $url The route, excluding the domain
* @inheritdoc
*/
public function __construct($url = '')
{
$this->setURL($url);
}
/**
* Check that the response for URL does not create a session
*
* @return array
*/
public function check()
{
$response = $this->client->get($this->getURL());
$cookie = $this->getCookie($response);
$fullURL = $this->getURL();
if ($cookie) {
return [
EnvironmentCheck::ERROR,
"Sessions are being set for {$fullURL} : Set-Cookie => " . $cookie,
];
}
return [
EnvironmentCheck::OK,
"Sessions are not being created for {$fullURL} 👍",
];
}
/**
* Get PHPSESSID or SECSESSID cookie set from the response if it exists.
*
* @param ResponseInterface $response
* @return string|null Cookie contents or null if it doesn't exist
*/
public function getCookie(ResponseInterface $response)
{
$result = null;
$cookies = $response->getHeader('Set-Cookie');
foreach ($cookies as $cookie) {
if (strpos($cookie ?? '', 'SESSID') !== false) {
$result = $cookie;
}
}
return $result;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
use SilverStripe\FullTextSearch\Solr\Solr;
use SilverStripe\FullTextSearch\Solr\SolrIndex;
/**
* Check the availability of all Solr indexes
*
* If there are no indexes of given class found, the returned status will still be "OK".
*
* @package environmentcheck
*/
class SolrIndexCheck implements EnvironmentCheck
{
/**
* {@inheritDoc}
*
* @return array
*/
public function check()
{
$brokenCores = [];
if (!class_exists(Solr::class)) {
return [
EnvironmentCheck::ERROR,
'Class `' . Solr::class . '` not found. Is the fulltextsearch module installed?'
];
}
$service = Solr::service();
foreach (Solr::get_indexes() as $index) {
/** @var SolrIndex $core */
$core = $index->getIndexName();
if (!$service->coreIsActive($core)) {
$brokenCores[] = $core;
}
}
if (!empty($brokenCores)) {
return [
EnvironmentCheck::ERROR,
'The following indexes are unavailable: ' . implode(', ', $brokenCores)
];
}
return [EnvironmentCheck::OK, 'Expected indexes are available.'];
}
}

63
src/Checks/URLCheck.php Normal file
View File

@ -0,0 +1,63 @@
<?php
namespace SilverStripe\EnvironmentCheck\Checks;
use SilverStripe\Control\Director;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
/**
* Check that a given URL is functioning, by default, the homepage.
*
* Note that Director::test() will be used rather than a CURL check.
*
* @package environmentcheck
*/
class URLCheck implements EnvironmentCheck
{
/**
* @var string
*/
protected $url;
/**
* @var string
*/
protected $testString;
/**
* @param string $url The URL to check, relative to the site (homepage is '').
* @param string $testString An optional piece of text to search for on the homepage.
*/
public function __construct($url = '', $testString = '')
{
$this->url = $url;
$this->testString = $testString;
}
/**
* {@inheritDoc}
*
* @return array
* @throws HTTPResponse_Exception
*/
public function check()
{
$response = Director::test($this->url);
if ($response->getStatusCode() != 200) {
return [
EnvironmentCheck::ERROR,
sprintf('Error retrieving "%s" (Code: %d)', $this->url, $response->getStatusCode())
];
} elseif ($this->testString && (strpos($response->getBody() ?? '', $this->testString ?? '') === false)) {
return [
EnvironmentCheck::WARNING,
sprintf('Success retrieving "%s", but string "%s" not found', $this->url, $this->testString)
];
}
return [
EnvironmentCheck::OK,
sprintf('Success retrieving "%s"', $this->url)
];
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace SilverStripe\EnvironmentCheck\Controllers;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\EnvironmentCheck\EnvironmentChecker;
/**
* Class DevCheckController
*
* @package environmentcheck
*/
class DevCheckController extends Controller
{
/**
* @var array
*/
private static $allowed_actions = [
'index'
];
/**
* Permission code to check for access to this controller.
*
* @var string
*/
private static $permission = 'ADMIN';
/**
* @param HTTPRequest $request
*
* @return EnvironmentChecker
*
* @throws HTTPResponse_Exception
*/
public function index($request)
{
$suite = 'check';
if ($name = $request->param('Suite')) {
$suite = $name;
}
/** @var EnvironmentChecker */
$checker = EnvironmentChecker::create($suite, 'Environment status')
->setRequest($request)
->setIncludeDetails(true);
$checker->init($this->config()->permission);
return $checker;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace SilverStripe\EnvironmentCheck\Controllers;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\EnvironmentCheck\EnvironmentChecker;
/**
* Class DevHealthController
*
* @package environmentcheck
*/
class DevHealthController extends Controller
{
/**
* @var array
*/
private static $allowed_actions = [
'index'
];
/**
* @return EnvironmentChecker
*
* @throws HTTPResponse_Exception
*/
public function index()
{
// health check does not require permission to run
$checker = new EnvironmentChecker('health', 'Site health');
$checker->setErrorCode(500);
return $checker;
}
}

View File

@ -1,39 +1,44 @@
<?php
namespace SilverStripe\EnvironmentCheck;
/**
* Interface for environment checks
*
*
* An environment check is a test that can be performed on a live environment. They differ from
* unit tests in that they are designed to check the state of the environment/server, rather than
* the code.
*
*
* Environment checks should *never* alter production data.
*
*
* Some things you might make environment checks for:
* - Can I access the database?
* - Are the right PHP modules installed?
* - Are the file permissions correct?
*
* @package environmentcheck
*/
interface EnvironmentCheck {
/**
* @var int
*/
const ERROR = 3;
interface EnvironmentCheck
{
/**
* @var int
*/
const ERROR = 3;
/**
* @var int
*/
const WARNING = 2;
/**
* @var int
*/
const WARNING = 2;
/**
* @var int
*/
const OK = 1;
/**
* @var int
*/
const OK = 1;
/**
* @return array Result with 'status' and 'message' keys.
*
* Status is EnvironmentCheck::ERROR, EnvironmentCheck::WARNING, or EnvironmentCheck::OK.
*/
function check();
}
/**
* @return array Result with 'status' and 'message' keys.
*
* Status is EnvironmentCheck::ERROR, EnvironmentCheck::WARNING, or EnvironmentCheck::OK.
*/
public function check();
}

View File

@ -0,0 +1,215 @@
<?php
namespace SilverStripe\EnvironmentCheck;
use Exception;
use InvalidArgumentException;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Extensible;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
/**
* Represents a suite of environment checks.
* Specific checks can be registered against a named instance of EnvironmentCheckSuite.
*
* Usage #1 - _config.php
* EnvironmentCheckSuite::register('health', 'MyHealthCheck("my param")', 'Title of my health check');
*
* Usage #2 - config.yml
* EnvironmentCheckSuite:
* registered_checks:
* mycheck:
* definition: 'MyHealthCheck("my param")'
* title: 'Title of my health check'
* registered_suites:
* health:
* - mycheck
*
* $result = EnvironmentCheckSuite::inst('health')->run();
*
* @package environmentcheck
*/
class EnvironmentCheckSuite
{
use Configurable;
use Injectable;
use Extensible;
/**
* Name of this suite.
*
* @var string
*/
protected $name;
/**
* @var array
*/
protected $checks = [];
/**
* Associative array of named checks registered via the config system. Each check should specify:
* - definition (e.g. 'MyHealthCheck("my param")')
* - title (e.g. 'Is my feature working?')
* - state (setting this to 'disabled' will cause suites to skip this check entirely.
*
* @var array
*/
private static $registered_checks = [];
/**
* Associative array of named suites registered via the config system. Each suite should enumerate
* named checks that have been configured in 'registered_checks'.
*
* @var array
*/
private static $registered_suites = [];
/**
* Load checks for this suite from the configuration system. This is an alternative to the
* EnvironmentCheckSuite::register - both can be used, checks will be appended to the suite.
*
* @param string $suiteName The name of this suite.
*/
public function __construct($suiteName)
{
if (empty($this->config()->registered_suites[$suiteName])) {
// Not registered via config system, but it still may be configured later via self::register.
return;
}
foreach ($this->config()->registered_suites[$suiteName] as $checkName) {
if (empty($this->config()->registered_checks[$checkName])) {
throw new InvalidArgumentException(
"Bad EnvironmentCheck: '$checkName' - the named check has not been registered."
);
}
$check = $this->config()->registered_checks[$checkName];
// Existing named checks can be disabled by setting their 'state' to 'disabled'.
// This is handy for disabling checks mandated by modules.
if (!empty($check['state']) && $check['state'] === 'disabled') {
continue;
}
// Add the check to this suite.
$this->push($check['definition'], $check['title']);
}
}
/**
* Run this test suite and return the result code of the worst result.
*
* @return EnvironmentCheckSuiteResult
*/
public function run()
{
$result = new EnvironmentCheckSuiteResult();
foreach ($this->checkInstances() as $check) {
list($checkClass, $checkTitle) = $check;
try {
list($status, $message) = $checkClass->check();
// If the check fails, register that as an error
} catch (Exception $e) {
$status = EnvironmentCheck::ERROR;
$message = $e->getMessage();
}
$result->addResult($status, $message, $checkTitle);
}
return $result;
}
/**
* Get instances of all the environment checks.
*
* @return EnvironmentChecker[]
* @throws InvalidArgumentException
*/
protected function checkInstances()
{
$output = [];
foreach ($this->checks as $check) {
list($checkClass, $checkTitle) = $check;
if (is_string($checkClass)) {
$checkInst = Injector::inst()->create($checkClass);
if ($checkInst instanceof EnvironmentCheck) {
$output[] = [$checkInst, $checkTitle];
} else {
throw new InvalidArgumentException(
"Bad EnvironmentCheck: '$checkClass' - the named class doesn't implement EnvironmentCheck"
);
}
} elseif ($checkClass instanceof EnvironmentCheck) {
$output[] = [$checkClass, $checkTitle];
} else {
throw new InvalidArgumentException("Bad EnvironmentCheck: " . var_export($check, true));
}
}
return $output;
}
/**
* Add a check to this suite.
*
* @param mixed $check
* @param string $title
*/
public function push($check, $title = null)
{
if (!$title) {
$title = is_string($check) ? $check : get_class($check);
}
$this->checks[] = [$check, $title];
}
/////////////////////////////////////////////////////////////////////////////////////////////
/**
* @var array
*/
protected static $instances = [];
/**
* Return a named instance of EnvironmentCheckSuite.
*
* @param string $name
*
* @return EnvironmentCheckSuite
*/
public static function inst($name)
{
if (!isset(self::$instances[$name])) {
self::$instances[$name] = new EnvironmentCheckSuite($name);
}
return self::$instances[$name];
}
/**
* Register a check against the named check suite.
*
* @param string|array $names
* @param EnvironmentCheck $check
* @param string|array
*/
public static function register($names, $check, $title = null)
{
if (!is_array($names)) {
$names = [$names];
}
foreach ($names as $name) {
self::inst($name)->push($check, $title);
}
}
/**
* Unregisters all checks.
*/
public static function reset()
{
self::$instances = [];
}
}

View File

@ -0,0 +1,125 @@
<?php
namespace SilverStripe\EnvironmentCheck;
use InvalidArgumentException;
use SilverStripe\ORM\ArrayList;
use SilverStripe\View\ArrayData;
use SilverStripe\View\ViewableData;
/**
* A single set of results from running an EnvironmentCheckSuite
*
* @package environmentcheck
*/
class EnvironmentCheckSuiteResult extends ViewableData
{
/**
* @var ArrayList
*/
protected $details;
/**
* @var int
*/
protected $worst = 0;
public function __construct()
{
parent::__construct();
$this->details = new ArrayList();
}
/**
* @param int $status
* @param string $message
* @param string $checkIdentifier
*/
public function addResult($status, $message, $checkIdentifier)
{
$this->details->push(new ArrayData([
'Check' => $checkIdentifier,
'Status' => $this->statusText($status),
'StatusCode' => $status,
'Message' => $message,
]));
$this->worst = max($this->worst, $status);
}
/**
* Returns true if there are no errors.
*
* @return bool
*/
public function ShouldPass()
{
return $this->worst <= EnvironmentCheck::WARNING;
}
/**
* Returns overall (i.e. worst) status as a string.
*
* @return string
*/
public function Status()
{
return $this->statusText($this->worst);
}
/**
* Returns detailed status information about each check.
*
* @return ArrayList
*/
public function Details()
{
return $this->details;
}
/**
* Convert the final result status and details to JSON.
*
* @return string
*/
public function toJSON()
{
$result = [
'Status' => $this->Status(),
'ShouldPass' => $this->ShouldPass(),
'Checks' => []
];
foreach ($this->details as $detail) {
$result['Checks'][] = $detail->toMap();
}
return json_encode($result);
}
/**
* Return a text version of a status code.
*
* @param int $status
* @return string
* @throws InvalidArgumentException
*/
protected function statusText($status)
{
switch ($status) {
case EnvironmentCheck::ERROR:
return 'ERROR';
break;
case EnvironmentCheck::WARNING:
return 'WARNING';
break;
case EnvironmentCheck::OK:
return 'OK';
break;
case 0:
return 'NO CHECKS';
break;
default:
throw new InvalidArgumentException("Bad environment check status '$status'");
break;
}
}
}

337
src/EnvironmentChecker.php Normal file
View File

@ -0,0 +1,337 @@
<?php
namespace SilverStripe\EnvironmentCheck;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use SilverStripe\Control\Director;
use SilverStripe\Control\Email\Email;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Control\RequestHandler;
use SilverStripe\Core\Environment;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Deprecation;
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
use SilverStripe\Security\Security;
/**
* Provides an interface for checking the given EnvironmentCheckSuite.
*
* @package environmentcheck
*/
class EnvironmentChecker extends RequestHandler
{
/**
* @var array
*/
private static $url_handlers = [
'' => 'index',
];
/**
* @var string
*/
protected $checkSuiteName;
/**
* @var string
*/
protected $title;
/**
* @var bool
*/
protected $includeDetails = false;
/**
* @var int
*/
protected $errorCode = 500;
/**
* @var null|string
*/
private static $to_email_address = null;
/**
* @var null|string
*/
private static $from_email_address = null;
/**
* @var bool
*/
private static $email_results = false;
/**
* @var bool Log results via {@link \Psr\Log\LoggerInterface}
*/
private static $log_results_warning = false;
/**
* @var string Maps to {@link \Psr\Log\LogLevel} levels. Defaults to LogLevel::WARNING
*/
private static $log_results_warning_level = LogLevel::WARNING;
/**
* @var bool Log results via a {@link \Psr\Log\LoggerInterface}
*/
private static $log_results_error = false;
/**
* @var int Maps to {@link \Psr\Log\LogLevel} levels. Defaults to LogLevel::ALERT
*/
private static $log_results_error_level = LogLevel::ALERT;
/**
* @param string $checkSuiteName
* @param string $title
*/
public function __construct($checkSuiteName, $title)
{
parent::__construct();
$this->checkSuiteName = $checkSuiteName;
$this->title = $title;
}
/**
* @param string $permission
*
* @throws HTTPResponse_Exception
*/
public function init($permission = 'ADMIN')
{
// if the environment supports it, provide a basic auth challenge and see if it matches configured credentials
if (Environment::getEnv('ENVCHECK_BASICAUTH_USERNAME')
&& Environment::getEnv('ENVCHECK_BASICAUTH_PASSWORD')
) {
// Check that details are both provided, and match
if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW'])
|| $_SERVER['PHP_AUTH_USER'] != Environment::getEnv('ENVCHECK_BASICAUTH_USERNAME')
|| $_SERVER['PHP_AUTH_PW'] != Environment::getEnv('ENVCHECK_BASICAUTH_PASSWORD')
) {
// Fail check with basic auth challenge
$response = new HTTPResponse(null, 401);
$response->addHeader('WWW-Authenticate', "Basic realm=\"Environment check\"");
throw new HTTPResponse_Exception($response);
}
} elseif (!$this->canAccess(null, $permission)) {
// Fail check with silverstripe login challenge
$result = Security::permissionFailure(
$this,
"You must have the {$permission} permission to access this check"
);
throw new HTTPResponse_Exception($result);
}
}
/**
* Determine if the current member can access the environment checker
*
* @param null|int|Member $member
* @param string $permission
* @return bool
*/
public function canAccess($member = null, $permission = 'ADMIN')
{
if (!$member) {
$member = Security::getCurrentUser();
}
// We allow access to this controller regardless of live-status or ADMIN permission only
// if on CLI. Access to this controller is always allowed in "dev-mode", or of the user is ADMIN.
if (Director::isDev()
|| Director::is_cli()
|| empty($permission)
|| Permission::checkMember($member, $permission)
) {
return true;
}
// Extended access checks.
// "Veto" style, return NULL to abstain vote.
$canExtended = null;
$results = $this->extend('canAccess', $member);
if ($results && is_array($results)) {
if (!min($results)) {
return false;
}
return true;
}
return false;
}
/**
* @return HTTPResponse
*/
public function index()
{
$response = new HTTPResponse;
$result = EnvironmentCheckSuite::inst($this->checkSuiteName)->run();
if (!$result->ShouldPass()) {
$response->setStatusCode($this->errorCode);
}
$data = [
'URL' => Director::absoluteBaseURL(),
'Title' => $this->title,
'Name' => $this->checkSuiteName,
'ErrorCode' => $this->errorCode
];
$emailContent = $result->customise(array_merge($data, [
'IncludeDetails' => true
]))->renderWith(__CLASS__);
if (!$this->includeDetails) {
$webContent = $result->customise(array_merge($data, [
'IncludeDetails' => false
]))->renderWith(__CLASS__);
} else {
$webContent = $emailContent;
}
if ($this->config()->get('email_results') && !$result->ShouldPass()) {
$email = new Email(
$this->config()->get('from_email_address'),
$this->config()->get('to_email_address'),
$this->title,
$emailContent
);
$email->send();
}
// Optionally log errors and warnings individually
foreach ($result->Details() as $detail) {
if ($this->config()->get('log_results_warning') && $detail->StatusCode == EnvironmentCheck::WARNING) {
$this->log(
sprintf('EnvironmentChecker warning at "%s" check. Message: %s', $detail->Check, $detail->Message),
$this->config()->get('log_results_warning_level')
);
} elseif ($this->config()->get('log_results_error') && $detail->StatusCode == EnvironmentCheck::ERROR) {
$this->log(
sprintf('EnvironmentChecker error at "%s" check. Message: %s', $detail->Check, $detail->Message),
$this->config()->get('log_results_error_level')
);
}
}
// output the result as JSON if requested
if ($this->getRequest()->getExtension() == 'json'
|| strpos($this->getRequest()->getHeader('Accept') ?? '', 'application/json') !== false
) {
$response->setBody($result->toJSON());
$response->addHeader('Content-Type', 'application/json');
return $response;
}
$response->setBody($webContent);
return $response;
}
/**
* Sends a log entry to the configured PSR-3 LoggerInterface
*
* @param string $message
* @param int $level
*/
public function log($message, $level)
{
Injector::inst()->get(LoggerInterface::class)->log($level, $message);
}
/**
* Set the HTTP status code that should be returned when there's an error.
*
* @param int $errorCode
*
* @return $this
*/
public function setErrorCode($errorCode)
{
$this->errorCode = $errorCode;
return $this;
}
/**
* Set whether to include the full breakdown of services
*
* @param bool $includeDetails
*
* @return $this
*/
public function setIncludeDetails($includeDetails)
{
$this->includeDetails = $includeDetails;
return $this;
}
/**
* @deprecated 2.0.0 Use config API instead
* @param string $from
*/
public static function set_from_email_address($from)
{
Deprecation::notice('2.0.0', 'Use config API instead');
static::config()->set('from_email_address', $from);
}
/**
* @deprecated 2.0.0 Use config API instead
* @return null|string
*/
public static function get_from_email_address()
{
Deprecation::notice('2.0.0', 'Use config API instead');
return static::config()->get('from_email_address');
}
/**
* @deprecated 2.0.0 Use config API instead
* @param string $to
*/
public static function set_to_email_address($to)
{
Deprecation::notice('2.0.0', 'Use config API instead');
static::config()->set('to_email_address', $to);
}
/**
* @deprecated 2.0.0 Use config API instead
* @return null|string
*/
public static function get_to_email_address()
{
Deprecation::notice('2.0.0', 'Use config API instead');
return static::config()->get('to_email_address');
}
/**
* @deprecated 2.0.0 Use config API instead
* @param bool $results
*/
public static function set_email_results($results)
{
Deprecation::notice('2.0.0', 'Use config API instead');
static::config()->set('email_results', $results);
}
/**
* @deprecated 2.0.0 Use config API instead
* @return bool
*/
public static function get_email_results()
{
Deprecation::notice('2.0.0', 'Use config API instead');
return static::config()->get('email_results');
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace SilverStripe\EnvironmentCheck\Services;
use GuzzleHttp\Client as GuzzleClient;
use SilverStripe\Core\Injector\Factory;
use SilverStripe\Core\Config\Configurable;
/**
* Factory class for creating HTTP client which are injected into some env check classes. Inject via YAML,
* arguments for Guzzle client can be supplied using "constructor" property or set as default_config.
*
* @see SilverStripe\EnvironmentCheck\Traits\Fetcher
*/
class ClientFactory implements Factory
{
use Configurable;
/**
* Default config for Guzzle client.
*
* @var array
*/
private static $default_config = [];
/**
* Wrapper to create a Guzzle client.
*
* {@inheritdoc}
*/
public function create($service, array $params = [])
{
return new GuzzleClient($this->getConfig($params));
}
/**
* Merge config provided from yaml with default config
*
* @param array $overrides
* @return array
*/
public function getConfig(array $overrides)
{
return array_merge(
$this->config()->get('default_config'),
$overrides
);
}
}

51
src/Traits/Fetcher.php Normal file
View File

@ -0,0 +1,51 @@
<?php
namespace SilverStripe\EnvironmentCheck\Traits;
use SilverStripe\Control\Director;
/**
* Simple helper for env checks which require HTTP clients.
*
* @package environmentcheck
*/
trait Fetcher
{
/**
* Client for making requests, set vi Injector.
*
* @see SilverStripe\EnvironmentCheck\Services
*
* @var GuzzleHttp\Client
*/
public $client = null;
/**
* Absolute URL for requests.
*
* @var string
*/
protected $url;
/**
* Set URL for requests.
*
* @param string $url Relative URL
* @return self
*/
public function setURL($url)
{
$this->url = Director::absoluteURL($url);
return $this;
}
/**
* Getter for URL
*
* @return string
*/
public function getURL()
{
return $this->url;
}
}

View File

@ -10,7 +10,7 @@
margin-top: -10px;
font-size: 10px;
}
h1 {
font-size: 30px;
margin-bottom: 3px;
@ -21,11 +21,11 @@
font-size: 16px;
margin: 2px 0 10px 8px;
}
p {
margin-left: 10px;
}
table {
border-collapse: collapse;
}
@ -44,7 +44,7 @@
table tr:nth-child(odd) td {
background-color: #ddd;
}
.OK {
color: green;
}
@ -59,23 +59,25 @@
</style>
</head>
<body>
<h1 class="$Status">$Title: $Status</h1>
<h2 class="website">Site: $URL</h2>
<% if $IncludeDetails %>
<table>
<tr><th>Check</th> <th>Status</th> <th>Message</th></tr>
<% loop Details %>
<% loop $Details %>
<tr><td>$Check</td> <td class="$Status">$Status</td> <td>$Message.XML</td></tr>
<% end_loop %>
</table>
<% end_if %>
<% if ShouldPass %>
<p>Site is available</p>
<p class="subtext">(you may check for the presence of the text 'Site is available' rather than an HTTP $ErrorCode error on this page, if you prefer.)</p>
<% if $ShouldPass %>
<p>Site is available</p>
<p class="subtext">(you may check for the presence of the text 'Site is available' rather than an HTTP $ErrorCode error on this page, if you prefer.<% if not $IncludeDetails %> Full details are available for logged in users at <a href="{$AbsoluteBaseURL}dev/check/">dev/check</a><% end_if %>)</p>
<% else %>
<% if Name == check %>
<% if $Name == "check" %>
<p><b>A subsystem of the site is unavailable, but the site remains operational</b></p>
<% else %>
<p><b>Site is not available</b></p>

View File

@ -0,0 +1,101 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use SilverStripe\Dev\SapphireTest;
use GuzzleHttp\Handler\MockHandler;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
use SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck;
/**
* Test session checks.
*/
class CacheHeadersCheckTest extends SapphireTest
{
/**
* Test that directives that must be included, are.
*
* @return void
*/
public function testMustInclude()
{
// Create a mock and queue responses
$mock = new MockHandler([
new Response(200, ['Cache-Control' => 'must-revalidate', 'ETag' => '123']),
new Response(200, ['Cache-Control' =>'no-cache', 'ETag' => '123']),
new Response(200, ['ETag' => '123']),
new Response(200, ['Cache-Control' => 'must-revalidate, private', 'ETag' => '123']),
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$cacheHeadersCheck = new CacheHeadersCheck('/', ['must-revalidate']);
$cacheHeadersCheck->client = $client;
// Run checks for each response above
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
}
/**
* Test that directives that must be excluded, are.
*
* @return void
*/
public function testMustExclude()
{
// Create a mock and queue responses
$mock = new MockHandler([
new Response(200, ['Cache-Control' => 'must-revalidate', 'ETag' => '123']),
new Response(200, ['Cache-Control' =>'no-cache', 'ETag' => '123']),
new Response(200, ['ETag' => '123']),
new Response(200, ['Cache-Control' =>'private, no-store', 'ETag' => '123']),
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$cacheHeadersCheck = new CacheHeadersCheck('/', [], ["no-store", "no-cache", "private"]);
$cacheHeadersCheck->client = $client;
// Run checks for each response above
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
}
/**
* Test that Etag header must exist in response.
*
* @return void
*/
public function testEtag()
{
// Create a mock and queue responses
$mock = new MockHandler([
new Response(200, ['Cache-Control' => 'must-revalidate', 'ETag' => '123']),
new Response(200, ['Cache-Control' =>'no-cache']),
new Response(200, ['ETag' => '123']),
new Response(200, []),
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$cacheHeadersCheck = new CacheHeadersCheck('/');
$cacheHeadersCheck->client = $client;
// Run checks for each response above
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\EnvironmentCheck\Checks\DatabaseCheck;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
use SilverStripe\Security\Member;
/**
* Class DatabaseCheckTest
*
* @package environmentcheck
*/
class DatabaseCheckTest extends SapphireTest
{
/**
* {@inheritDoc}
* @var bool
*/
protected $usesDatabase = true;
public function testCheckReportsValidConnection()
{
$member = new Member;
$member->FirstName = 'Bob';
$member->write();
$check = new DatabaseCheck();
$expected = [
EnvironmentCheck::OK,
''
];
$this->assertEquals($expected, $check->check());
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
use SilverStripe\Core\Kernel;
use SilverStripe\Control\Director;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
use SilverStripe\EnvironmentCheck\Checks\EnvTypeCheck;
/**
* Test the env setting check.
*/
class EnvTypeCheckTest extends SapphireTest
{
/**
* Check is OK when in live mode
*
* @return void
*/
public function testEnvSettingLive()
{
/** @var Kernel $kernel */
$kernel = Injector::inst()->get(Kernel::class);
$kernel->setEnvironment('live');
$this->assertTrue(Director::isLive());
$checker = Injector::inst()->get(EnvTypeCheck::class);
$result = $checker->check();
$this->assertSame($result[0], EnvironmentCheck::OK);
}
/**
* Check is ERROR when in test mode
*
* @return void
*/
public function testEnvSettingTest()
{
/** @var Kernel $kernel */
$kernel = Injector::inst()->get(Kernel::class);
$kernel->setEnvironment('test');
$this->assertTrue(Director::isTest());
$checker = Injector::inst()->get(EnvTypeCheck::class);
$result = $checker->check();
$this->assertSame($result[0], EnvironmentCheck::ERROR);
}
/**
* Check is ERROR when in dev mode
*
* @return void
*/
public function testEnvSettingDev()
{
/** @var Kernel $kernel */
$kernel = Injector::inst()->get(Kernel::class);
$kernel->setEnvironment('dev');
$this->assertTrue(Director::isDev());
$checker = Injector::inst()->get(EnvTypeCheck::class);
$result = $checker->check();
$this->assertSame($result[0], EnvironmentCheck::ERROR);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\EnvironmentCheck\Checks\ExternalURLCheck;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
/**
* Class ExternalURLCheckTest
*
* @package environmentcheck
*/
class ExternalURLCheckTest extends SapphireTest
{
public function testCheckReportsMissingPages()
{
$this->markTestSkipped('ExternalURLCheck seems faulty on some systems');
$check = new ExternalURLCheck('http://missing-site/');
$expected = [
EnvironmentCheck::ERROR,
'Success retrieving "http://missing-site/" (Code: 404)'
];
$this->assertEquals($expected, $check->check());
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
use SilverStripe\EnvironmentCheck\Checks\FileWriteableCheck;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
use SilverStripe\Dev\SapphireTest;
/**
* Class FileWritableCheckTest
*
* @package environmentcheck
*/
class FileWritableCheckTest extends SapphireTest
{
public function testCheckReportsWritablePaths()
{
$check = new FileWriteableCheck(TEMP_FOLDER);
$expected = [
EnvironmentCheck::OK,
''
];
$this->assertEquals($expected, $check->check());
}
public function testCheckReportsNonWritablePaths()
{
$check = new FileWriteableCheck('/var');
$result = $check->check();
$this->assertEquals(EnvironmentCheck::ERROR, $result[0]);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\EnvironmentCheck\Checks\HasClassCheck;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
/**
* Class HasClassCheckTest
*
* @package environmentcheck
*/
class HasClassCheckTest extends SapphireTest
{
public function testCheckReportsMissingClasses()
{
$check = new HasClassCheck('foo');
$expected = [
EnvironmentCheck::ERROR,
'Class foo doesn\'t exist'
];
$this->assertEquals($expected, $check->check());
}
public function testCheckReportsFoundClasses()
{
$check = new HasClassCheck('stdClass');
$expected = [
EnvironmentCheck::OK,
'Class stdClass exists',
];
$this->assertEquals($expected, $check->check());
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
use SilverStripe\EnvironmentCheck\Checks\HasFunctionCheck;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
use SilverStripe\Dev\SapphireTest;
/**
* Class HasFunctionCheckTest
*
* @package environmentcheck
*/
class HasFunctionCheckTest extends SapphireTest
{
public function testCheckReportsMissingFunctions()
{
$check = new HasFunctionCheck('foo');
$expected = [
EnvironmentCheck::ERROR,
'foo() doesn\'t exist'
];
$this->assertEquals($expected, $check->check());
}
public function testCheckReportsFoundFunctions()
{
$check = new HasFunctionCheck('class_exists');
$expected = [
EnvironmentCheck::OK,
'class_exists() exists'
];
$this->assertEquals($expected, $check->check());
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use SilverStripe\Dev\SapphireTest;
use GuzzleHttp\Handler\MockHandler;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
use SilverStripe\EnvironmentCheck\Checks\SessionCheck;
/**
* Test session checks.
*/
class SessionCheckTest extends SapphireTest
{
/**
* @var SilverStripe\EnvironmentCheck\Checks\SessionCheck
*/
public $sessionCheck = null;
/**
* Create a session check for use by tests.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->sessionCheck = new SessionCheck('/');
}
/**
* Env check reports error when session cookies are being set.
*
* @return void
*/
public function testSessionSet()
{
// Create a mock and queue two responses.
$mock = new MockHandler([
new Response(200, ['Set-Cookie' => 'PHPSESSID:foo']),
new Response(200, ['Set-Cookie' => 'SECSESSID:bar'])
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$this->sessionCheck->client = $client;
// Check for PHPSESSID
$this->assertContains(EnvironmentCheck::ERROR, $this->sessionCheck->check());
// Check for SECSESSID
$this->assertContains(EnvironmentCheck::ERROR, $this->sessionCheck->check());
}
/**
* Env check responds OK when no session cookies are set in response.
*
* @return void
*/
public function testSessionNotSet()
{
// Create a mock and queue two responses.
$mock = new MockHandler([
new Response(200)
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$this->sessionCheck->client = $client;
$this->assertContains(EnvironmentCheck::OK, $this->sessionCheck->check());
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
use SilverStripe\EnvironmentCheck\Checks\URLCheck;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
use SilverStripe\Dev\SapphireTest;
/**
* Class URLCheckTest
*
* @package environmentcheck
*/
class URLCheckTest extends SapphireTest
{
public function testCheckReportsMissingPages()
{
$check = new URLCheck('foo', 'bar');
$expected = [
EnvironmentCheck::ERROR,
'Error retrieving "foo" (Code: 404)'
];
$this->assertEquals($expected, $check->check());
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\Controllers;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\EnvironmentCheck\Controllers\DevCheckController;
use SilverStripe\EnvironmentCheck\EnvironmentChecker;
/**
* Class DevCheckControllerTest
*
* @package environmentcheck
*/
class DevCheckControllerTest extends SapphireTest
{
/**
* {@inheritDoc}
* @var array
*/
protected $usesDatabase = true;
public function testIndexCreatesChecker()
{
$controller = new DevCheckController();
$request = new HTTPRequest('GET', 'example.com');
$this->assertInstanceOf(EnvironmentChecker::class, $controller->index($request));
}
public function testCheckIncludesDetails()
{
$controller = new DevCheckController();
$request = new HTTPRequest('GET', 'example.com');
$response = $controller->index($request)->index();
$this->assertStringContainsString('<table>', $response->getBody());
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\Controllers;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\EnvironmentCheck\Controllers\DevHealthController;
use SilverStripe\EnvironmentCheck\EnvironmentChecker;
/**
* Class DevHealthControllerTest
*
* @package environmentcheck
*/
class DevHealthControllerTest extends SapphireTest
{
/**
* {@inheritDoc}
* @var array
*/
protected $usesDatabase = true;
public function testIndexCreatesChecker()
{
$controller = new DevHealthController();
$request = new HTTPRequest('GET', 'example.com');
// we need to fake authenticated access as BasicAuth::requireLogin doesn't like empty
// permission type strings, which is what health check uses.
putenv('ENVCHECK_BASICAUTH_USERNAME="foo"');
putenv('ENVCHECK_BASICAUTH_PASSWORD="bar"');
$_SERVER['PHP_AUTH_USER'] = 'foo';
$_SERVER['PHP_AUTH_PW'] = 'bar';
$this->assertInstanceOf(EnvironmentChecker::class, $controller->index($request));
}
public function testHealthDoesNotIncludeDetails()
{
$controller = new DevHealthController();
$request = new HTTPRequest('GET', 'example.com');
$response = $controller->index($request)->index();
$this->assertFalse(strpos($response->getBody(), '<table>'));
}
}

View File

@ -1,14 +0,0 @@
<?php
/**
* @mixin PHPUnit_Framework_TestCase
*/
class DevCheckControllerTest extends SapphireTest {
public function testIndexCreatesChecker() {
$controller = new DevCheckController();
$request = new SS_HTTPRequest('GET', 'example.com');
$this->assertInstanceOf('EnvironmentChecker', $controller->index($request));
}
}

View File

@ -1,23 +0,0 @@
<?php
/**
* @mixin PHPUnit_Framework_TestCase
*/
class DevHealthControllerTest extends SapphireTest {
public function testIndexCreatesChecker() {
$controller = new DevHealthController();
$request = new SS_HTTPRequest('GET', 'example.com');
// we need to fake authenticated access as BasicAuth::requireLogin doesn't like empty
// permission type strings, which is what health check uses.
define('ENVCHECK_BASICAUTH_USERNAME', 'foo');
define('ENVCHECK_BASICAUTH_PASSWORD', 'bar');
$_SERVER['PHP_AUTH_USER'] = 'foo';
$_SERVER['PHP_AUTH_PW'] = 'bar';
$this->assertInstanceOf('EnvironmentChecker', $controller->index($request));
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests;
use Monolog\Logger;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\EnvironmentCheck\EnvironmentChecker;
use SilverStripe\EnvironmentCheck\EnvironmentCheckSuite;
/**
* Class EnvironmentCheckerTest
*
* @package environmentcheck
*/
class EnvironmentCheckerTest extends SapphireTest
{
protected $usesDatabase = true;
protected function tearDown(): void
{
EnvironmentCheckSuite::reset();
parent::tearDown();
}
public function testOnlyLogsWithErrors()
{
Config::modify()->set(EnvironmentChecker::class, 'log_results_warning', true);
Config::modify()->set(EnvironmentChecker::class, 'log_results_error', true);
EnvironmentCheckSuite::register('test suite', new EnvironmentCheckerTest\CheckNoErrors());
$logger = $this->getMockBuilder(Logger::class)
->disableOriginalConstructor()
->setMethods(['log'])
->getMock();
$logger->expects($this->never())->method('log');
Injector::inst()->registerService($logger, LoggerInterface::class);
(new EnvironmentChecker('test suite', 'test'))->index();
}
public function testLogsWithWarnings()
{
Config::modify()->set(EnvironmentChecker::class, 'log_results_warning', true);
Config::modify()->set(EnvironmentChecker::class, 'log_results_error', false);
EnvironmentCheckSuite::register('test suite', new EnvironmentCheckerTest\CheckWarnings());
EnvironmentCheckSuite::register('test suite', new EnvironmentCheckerTest\CheckErrors());
$logger = $this->getMockBuilder(Logger::class)
->disableOriginalConstructor()
->setMethods(['log'])
->getMock();
$logger->expects($this->once())
->method('log')
->withConsecutive(
[$this->equalTo(LogLevel::WARNING)],
[$this->anything()]
);
Injector::inst()->registerService($logger, LoggerInterface::class);
(new EnvironmentChecker('test suite', 'test'))->index();
}
public function testLogsWithErrors()
{
Config::modify()->set(EnvironmentChecker::class, 'log_results_error', false);
Config::modify()->set(EnvironmentChecker::class, 'log_results_error', true);
EnvironmentCheckSuite::register('test suite', new EnvironmentCheckerTest\CheckWarnings());
EnvironmentCheckSuite::register('test suite', new EnvironmentCheckerTest\CheckErrors());
$logger = $this->getMockBuilder(Logger::class)
->disableOriginalConstructor()
->setMethods(['log'])
->getMock();
$logger->expects($this->once())
->method('log')
->withConsecutive(
[$this->equalTo(LogLevel::ALERT), $this->anything()],
[$this->equalTo(LogLevel::WARNING), $this->anything()]
);
Injector::inst()->registerService($logger, LoggerInterface::class);
(new EnvironmentChecker('test suite', 'test'))->index();
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
class CheckErrors implements EnvironmentCheck, TestOnly
{
public function check()
{
return [EnvironmentCheck::ERROR, 'test error'];
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
class CheckNoErrors implements EnvironmentCheck, TestOnly
{
public function check()
{
return [EnvironmentCheck::OK, ''];
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
class CheckWarnings implements EnvironmentCheck, TestOnly
{
public function check()
{
return [EnvironmentCheck::WARNING, 'test warning'];
}
}

View File

@ -1,17 +0,0 @@
<?php
/**
* @mixin PHPUnit_Framework_TestCase
*/
class DatabaseCheckTest extends SapphireTest {
public function testCheckReportsValidConnection() {
$check = new DatabaseCheck();
$expected = array(
EnvironmentCheck::OK,
'',
);
$this->assertEquals($expected, $check->check());
}
}

View File

@ -1,19 +0,0 @@
<?php
/**
* @mixin PHPUnit_Framework_TestCase
*/
class ExternalURLCheckTest extends SapphireTest {
public function testCheckReportsMissingPages() {
$this->markTestSkipped('ExternalURLCheck seems faulty on some systems');
$check = new ExternalURLCheck('http://missing-site/');
$expected = array(
EnvironmentCheck::ERROR,
'Success retrieving "http://missing-site/" (Code: 404)',
);
$this->assertEquals($expected, $check->check());
}
}

View File

@ -1,25 +0,0 @@
<?php
/**
* @mixin PHPUnit_Framework_TestCase
*/
class FileWritableCheckTest extends SapphireTest {
public function testCheckReportsWritablePaths() {
$check = new FileWriteableCheck(TEMP_FOLDER);
$expected = array(
EnvironmentCheck::OK,
'',
);
$this->assertEquals($expected, $check->check());
}
public function testCheckReportsNonWritablePaths() {
$check = new FileWriteableCheck('/var');
$result = $check->check();
$this->assertEquals(EnvironmentCheck::ERROR, $result[0]);
}
}

View File

@ -1,28 +0,0 @@
<?php
/**
* @mixin PHPUnit_Framework_TestCase
*/
class HasClassCheckTest extends SapphireTest {
public function testCheckReportsMissingClasses() {
$check = new HasClassCheck('foo');
$expected = array(
EnvironmentCheck::ERROR,
'Class foo doesn\'t exist',
);
$this->assertEquals($expected, $check->check());
}
public function testCheckReportsFoundClasses() {
$check = new HasClassCheck('stdClass');
$expected = array(
EnvironmentCheck::OK,
'Class stdClass exists',
);
$this->assertEquals($expected, $check->check());
}
}

View File

@ -1,28 +0,0 @@
<?php
/**
* @mixin PHPUnit_Framework_TestCase
*/
class HasFunctionCheckTest extends SapphireTest {
public function testCheckReportsMissingFunctions() {
$check = new HasFunctionCheck('foo');
$expected = array(
EnvironmentCheck::ERROR,
'foo() doesn\'t exist',
);
$this->assertEquals($expected, $check->check());
}
public function testCheckReportsFoundFunctions() {
$check = new HasFunctionCheck('class_exists');
$expected = array(
EnvironmentCheck::OK,
'class_exists() exists',
);
$this->assertEquals($expected, $check->check());
}
}

View File

@ -1,17 +0,0 @@
<?php
/**
* @mixin PHPUnit_Framework_TestCase
*/
class URLCheckTest extends SapphireTest {
public function testCheckReportsMissingPages() {
$check = new URLCheck('foo', 'bar');
$expected = array(
EnvironmentCheck::ERROR,
'Error retrieving "foo" (Code: 404)',
);
$this->assertEquals($expected, $check->check());
}
}