├── .codecov.yml ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .github_changelog_generator ├── .gitignore ├── .scrutinizer.yml ├── .stickler.yml ├── .styleci.yml ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── README.txt ├── assets ├── banner-1544x500.png ├── banner-772x250.png ├── icon-128x128.png ├── icon-256x256.png ├── screenshot-1.png ├── screenshot-2.png └── screenshot-3.png ├── codeception.dist.yml ├── codeception.example.yml ├── composer.json ├── composer.lock ├── lib ├── julien731 │ ├── wp-dismissible-notices-handler │ │ ├── LICENSE │ │ ├── assets │ │ │ └── js │ │ │ │ └── main.js │ │ ├── handler.php │ │ └── includes │ │ │ └── helper-functions.php │ └── wp-review-me │ │ └── review.php └── yoast │ └── i18n-module │ ├── LICENSE │ └── src │ ├── i18n-module-wordpressorg.php │ └── i18n-module.php ├── package.json ├── ruleset.xml ├── src ├── Admin.php ├── Ads │ ├── I18nPromoter.php │ └── ReviewNotice.php ├── BadLogin │ ├── Admin.php │ └── BadLogin.php ├── Blacklist │ ├── Event.php │ └── Handler.php ├── Cloudflare │ ├── AccessRules.php │ ├── Admin.php │ └── Helper.php ├── Container.php ├── I18n.php ├── LoadableInterface.php ├── OptionStore.php └── WPCFG.php ├── tests ├── _bootstrap.php ├── _data │ └── dump.sql ├── _output │ └── .gitignore ├── _support │ ├── AcceptanceTester.php │ ├── FunctionalTester.php │ ├── Helper │ │ ├── Acceptance.php │ │ ├── Functional.php │ │ ├── Integration.php │ │ └── Unit.php │ ├── IntegrationTester.php │ └── UnitTester.php ├── acceptance.suite.yml ├── acceptance │ ├── SettingPageTabLinkCept.php │ └── _bootstrap.php ├── functional.suite.yml ├── functional │ ├── ReviewNoticeShowsUpAfterTenDaysCept.php │ └── _bootstrap.php ├── integration.suite.yml ├── integration │ ├── Ads │ │ ├── I18nPromoterTest.php │ │ └── ReviewNoticeTest.php │ ├── BadLogin │ │ └── BadLoginTest.php │ ├── Blacklist │ │ └── HandlerTest.php │ ├── Cloudflare │ │ ├── AccessRulesTest.php │ │ └── HelperTest.php │ ├── OptionStoreTest.php │ └── _bootstrap.php ├── unit.suite.yml └── unit │ ├── Blacklist │ └── EventTest.php │ └── _bootstrap.php ├── uninstall.php ├── wp-cloudflare-guard.php └── yarn.lock /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: off 4 | patch: off 5 | comment: false 6 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/typisttech/wp-cloudflare-guard). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - Check the code style with ``$ composer check-style`` and fix it with ``$ composer fix-style``. 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 17 | 18 | - **Create feature branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 23 | 24 | 25 | ## Running Tests 26 | 27 | ``` bash 28 | $ composer test 29 | ``` 30 | 31 | 32 | **Happy coding**! 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Detailed description 4 | 5 | Provide a detailed description of the change or addition you are proposing. 6 | 7 | Make it clear if the issue is a bug, an enhancement or just a question. 8 | 9 | ## Context 10 | 11 | Why is this change important to you? How would you use it? 12 | 13 | How can it benefit other users? 14 | 15 | ## Possible implementation 16 | 17 | Not obligatory, but suggest an idea for implementing addition or change. 18 | 19 | ## Your environment 20 | 21 | Include as many relevant details about the environment you experienced the bug in and how to reproduce it. 22 | 23 | * Version used (e.g. PHP 5.6, HHVM 3): 24 | * Operating system and version (e.g. Ubuntu 16.04, Windows 7): 25 | * Link to your project: 26 | * ... 27 | * ... 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | Describe your changes in detail. 6 | 7 | ## Motivation and context 8 | 9 | Why is this change required? What problem does it solve? 10 | 11 | If it fixes an open issue, please link to the issue here (if you write `fixes #num` 12 | or `closes #num`, the issue will be automatically closed when the pull is accepted.) 13 | 14 | ## How has this been tested? 15 | 16 | Please describe in detail how you tested your changes. 17 | 18 | Include details of your testing environment, and the tests you ran to 19 | see how your change affects other areas of the code, etc. 20 | 21 | ## Screenshots (if appropriate) 22 | 23 | ## Types of changes 24 | 25 | What types of changes does your code introduce? Put an `x` in all the boxes that apply: 26 | - [ ] Bug fix (non-breaking change which fixes an issue) 27 | - [ ] New feature (non-breaking change which adds functionality) 28 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 29 | 30 | ## Checklist: 31 | 32 | Go over all the following points, and put an `x` in all the boxes that apply. 33 | 34 | Please, please, please, don't send your pull request until all of the boxes are ticked. Once your pull request is created, it will trigger a build on our [continuous integration](http://www.phptherightway.com/#continuous-integration) server to make sure your [tests and code style pass](https://help.github.com/articles/about-required-status-checks/). 35 | 36 | - [ ] I have read the **[CONTRIBUTING](../blob/master/.github/CONTRIBUTING.md)** document. 37 | - [ ] My pull request addresses exactly one patch/feature. 38 | - [ ] I have created a branch for this patch/feature. 39 | - [ ] Each individual commit in the pull request is meaningful. 40 | - [ ] I have added tests to cover my changes. 41 | - [ ] If my change requires a change to the documentation, I have updated it accordingly. 42 | 43 | If you're unsure about any of these, don't hesitate to ask. We're here to help! 44 | -------------------------------------------------------------------------------- /.github_changelog_generator: -------------------------------------------------------------------------------- 1 | unreleased=true 2 | future-release=0.2.0 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Build ### 2 | /build/ 3 | /release/ 4 | 5 | ### Codeception ### 6 | /codeception.yml 7 | /tests/_output/* 8 | /tests/_support/_generated/ 9 | 10 | ### Composer ### 11 | /vendor/ 12 | 13 | ### npm ### 14 | /node_modules/ 15 | 16 | ### PhpStorm ### 17 | /.idea/ 18 | 19 | ### i18n ### 20 | /languages/ 21 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | dependency_paths: 3 | - "lib/" 4 | excluded_paths: 5 | - "tests/" 6 | 7 | tools: 8 | external_code_coverage: 9 | enabled: true 10 | timeout: 900 11 | php_analyzer: true 12 | php_cpd: false 13 | php_code_coverage: true 14 | php_code_sniffer: 15 | config: { standard: psr2 } 16 | php_cs_fixer: 17 | config: { level: psr2 } 18 | php_loc: true 19 | php_mess_detector: true 20 | php_sim: true 21 | php_pdepend: true 22 | sensiolabs_security_checker: true 23 | 24 | checks: 25 | php: 26 | verify_property_names: true 27 | verify_argument_usable_as_reference: true 28 | verify_access_scope_valid: true 29 | variable_existence: true 30 | useless_calls: true 31 | use_statement_alias_conflict: true 32 | use_self_instead_of_fqcn: true 33 | uppercase_constants: true 34 | unused_variables: true 35 | unused_properties: true 36 | unused_parameters: true 37 | unused_methods: true 38 | unreachable_code: true 39 | too_many_arguments: true 40 | symfony_request_injection: true 41 | switch_fallthrough_commented: true 42 | sql_injection_vulnerabilities: true 43 | single_namespace_per_use: true 44 | simplify_boolean_return: true 45 | side_effects_or_types: true 46 | security_vulnerabilities: true 47 | return_in_constructor: true 48 | return_doc_comments: true 49 | return_doc_comment_if_not_inferrable: true 50 | require_scope_for_properties: true 51 | require_scope_for_methods: true 52 | require_php_tag_first: true 53 | remove_extra_empty_lines: true 54 | psr2_switch_declaration: true 55 | psr2_class_declaration: true 56 | property_assignments: true 57 | properties_in_camelcaps: true 58 | prefer_while_loop_over_for_loop: true 59 | precedence_mistakes: true 60 | precedence_in_conditions: true 61 | phpunit_assertions: true 62 | php5_style_constructor: true 63 | parse_doc_comments: true 64 | parameters_in_camelcaps: true 65 | parameter_non_unique: true 66 | parameter_doc_comments: true 67 | param_doc_comment_if_not_inferrable: true 68 | overriding_private_members: true 69 | overriding_parameter: true 70 | optional_parameters_at_the_end: true 71 | one_class_per_file: true 72 | non_commented_empty_catch_block: true 73 | no_unnecessary_if: true 74 | no_unnecessary_final_modifier: true 75 | no_underscore_prefix_in_properties: true 76 | no_underscore_prefix_in_methods: true 77 | no_trait_type_hints: true 78 | no_trailing_whitespace: true 79 | no_short_variable_names: 80 | minimum: '3' 81 | no_short_open_tag: true 82 | no_short_method_names: 83 | minimum: '3' 84 | no_property_on_interface: true 85 | no_non_implemented_abstract_methods: true 86 | no_long_variable_names: 87 | maximum: '20' 88 | no_goto: true 89 | no_global_keyword: true 90 | no_exit: true 91 | no_eval: true 92 | no_error_suppression: true 93 | no_empty_statements: true 94 | no_duplicate_arguments: true 95 | no_debug_code: true 96 | no_commented_out_code: true 97 | newline_at_end_of_file: true 98 | naming_conventions: 99 | local_variable: '^[a-z][a-zA-Z0-9]*$' 100 | abstract_class_name: ^Abstract|Factory$ 101 | utility_class_name: '(Factory|Utils?)$' 102 | constant_name: '^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$' 103 | property_name: '^[a-z][a-zA-Z0-9]*$' 104 | method_name: '^(?:[a-z]|__)[a-zA-Z0-9]*$' 105 | parameter_name: '^[a-z][a-zA-Z0-9]*$' 106 | interface_name: '^[A-Z][a-zA-Z0-9]*Interface$' 107 | type_name: '^[A-Z][a-zA-Z0-9]*$' 108 | exception_name: '^[A-Z][a-zA-Z0-9]*Exception$' 109 | isser_method_name: '^(?:is|has|should|may|supports)' 110 | more_specific_types_in_doc_comments: true 111 | missing_arguments: true 112 | method_calls_on_non_object: true 113 | line_length: 114 | max_length: '120' 115 | instanceof_class_exists: true 116 | function_in_camel_caps: true 117 | foreach_usable_as_reference: true 118 | foreach_traversable: true 119 | fix_use_statements: 120 | remove_unused: true 121 | preserve_multiple: false 122 | preserve_blanklines: false 123 | order_alphabetically: true 124 | fix_line_ending: true 125 | fix_doc_comments: true 126 | encourage_single_quotes: true 127 | encourage_shallow_comparison: true 128 | encourage_postdec_operator: true 129 | duplication: true 130 | deprecated_code_usage: true 131 | deadlock_detection_in_loops: true 132 | comparison_always_same_result: true 133 | code_rating: true 134 | closure_use_not_conflicting: true 135 | closure_use_modifiable: true 136 | classes_in_camel_caps: true 137 | check_method_contracts: 138 | verify_interface_like_constraints: true 139 | verify_documented_constraints: true 140 | verify_parent_constraints: true 141 | catch_class_exists: true 142 | call_to_parent_method: true 143 | blank_line_after_namespace_declaration: true 144 | avoid_useless_overridden_methods: true 145 | avoid_usage_of_logical_operators: true 146 | avoid_unnecessary_concatenation: true 147 | avoid_todo_comments: true 148 | avoid_superglobals: true 149 | avoid_perl_style_comments: true 150 | avoid_multiple_statements_on_same_line: true 151 | avoid_length_functions_in_loops: true 152 | avoid_fixme_comments: true 153 | avoid_entity_manager_injection: true 154 | avoid_duplicate_types: true 155 | avoid_corrupting_byteorder_marks: true 156 | avoid_conflicting_incrementers: true 157 | avoid_closing_tag: true 158 | avoid_aliased_php_functions: true 159 | assignment_of_null_return: true 160 | argument_type_checks: true 161 | -------------------------------------------------------------------------------- /.stickler.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | phpcs: 3 | standard: './ruleset.xml' 4 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: recommended 2 | 3 | finder: 4 | exclude: 5 | - "lib" 6 | 7 | disabled: 8 | - align_double_arrow 9 | - concat_without_spaces 10 | - new_with_braces 11 | - no_blank_lines_after_phpdoc 12 | - no_spaces_inside_offset 13 | - phpdoc_no_package 14 | - phpdoc_summary 15 | - simplified_null_return 16 | - trim_array_spaces 17 | 18 | enabled: 19 | - concat_with_spaces 20 | - declare_strict_types 21 | - dir_constant 22 | - is_null 23 | - linebreak_after_opening_tag 24 | - modernize_types_casting 25 | - no_empty_comment 26 | - no_php4_constructor 27 | - no_short_echo_tag 28 | - no_useless_else 29 | - not_operator_with_successor_space 30 | - php_unit_construct 31 | - php_unit_dedicate_assert 32 | - phpdoc_link_to_see 33 | - phpdoc_property 34 | - phpdoc_return_self_reference 35 | - pow_to_exponentiation 36 | - random_api_migration 37 | - return_type_declaration 38 | - semicolon_after_instruction 39 | - single_line_class_definition 40 | - strict_comparison 41 | - strict_param 42 | - ternary_to_null_coalescing 43 | - unalign_double_arrow 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | dist: trusty 4 | 5 | # Wait until travis bug fixes 6 | sudo: require 7 | 8 | branches: 9 | only: 10 | - master 11 | - /^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/ 12 | 13 | services: 14 | - mysql 15 | 16 | cache: 17 | apt: true 18 | yarn: true 19 | directories: 20 | - $HOME/.composer/cache/files 21 | 22 | addons: 23 | apt: 24 | packages: 25 | - nginx 26 | 27 | notifications: 28 | email: 29 | on_success: never 30 | on_failure: change 31 | 32 | php: 33 | - 7.0 34 | - 7.1 35 | 36 | env: 37 | global: 38 | - COMPOSER_NO_INTERACTION=1 39 | matrix: 40 | - WP_VERSION=nightly 41 | - WP_VERSION=latest WITH_COVERAGE=true 42 | - WP_VERSION=4.8 43 | - WP_VERSION=4.7.5 44 | 45 | matrix: 46 | allow_failures: 47 | - env: WP_VERSION=nightly 48 | fast_finish: true 49 | 50 | before_install: 51 | # Disable xDebug to speed up the build unless test coverage is needed 52 | - if [[ "$WITH_COVERAGE" != "true" ]]; then phpenv config-rm xdebug.ini; fi 53 | 54 | # Install helper scripts 55 | - travis_retry composer global require --prefer-dist --no-suggest typisttech/travis-nginx-wordpress 56 | - export PATH=$HOME/.composer/vendor/bin:$PATH 57 | - tnw-install-nginx 58 | - tnw-install-wordpress 59 | - tnw-prepare-codeception 60 | 61 | # Build the production plugin 62 | - travis_retry composer build 63 | # Activate the plugin 64 | - wp plugin install ./release/wp-cloudflare-guard.zip --force --activate --path=/tmp/wordpress 65 | # Export a dump of plugin-activated database to the _data folder 66 | - wp db export $TRAVIS_BUILD_DIR/tests/_data/dump.sql --path=/tmp/wordpress 67 | 68 | install: 69 | - travis_retry composer install --prefer-dist --no-suggest 70 | 71 | script: 72 | - if [[ "$WITH_COVERAGE" == "true" ]]; then vendor/bin/codecept run --coverage --coverage-xml; fi 73 | - if [[ "$WITH_COVERAGE" != "true" ]]; then vendor/bin/codecept run; fi 74 | 75 | after_script: 76 | - if [[ "$WITH_COVERAGE" == "true" ]]; then travis_retry tnw-upload-coverage-to-scrutinizer; fi 77 | - if [[ "$WITH_COVERAGE" == "true" ]]; then travis_retry tnw-upload-coverage-to-codecov; fi 78 | 79 | before_deploy: 80 | - unzip -qo release/wp-cloudflare-guard.zip -d build 81 | 82 | deploy: 83 | - provider: releases 84 | api_key: $GITHUB_ACCESS_TOKEN 85 | file: release/wp-cloudflare-guard.zip 86 | skip_cleanup: true 87 | on: 88 | condition: "$WP_VERSION = latest" 89 | php: 7.0 90 | tags: true 91 | repo: TypistTech/wp-cloudflare-guard 92 | - provider: wordpress-plugin 93 | edge: 94 | source: TypistTech/dpl 95 | branch: add-wordpress-plugin-deployment 96 | slug: wp-cloudflare-guard 97 | username: tangrufus 98 | build_dir: build 99 | assets_dir: assets 100 | skip_cleanup: true 101 | on: 102 | condition: "$WP_VERSION = latest" 103 | php: 7.1 104 | tags: true 105 | repo: TypistTech/wp-cloudflare-guard 106 | - provider: pages 107 | skip_cleanup: true 108 | github_token: $GITHUB_ACCESS_TOKEN 109 | local_dir: build 110 | target_branch: nightly 111 | project_name: "WP Cloudflare Guard" 112 | on: 113 | branch: master 114 | condition: "$WP_VERSION = latest" 115 | php: 7.0 116 | repo: TypistTech/wp-cloudflare-guard 117 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.2.0](https://github.com/TypistTech/wp-cloudflare-guard/tree/0.2.0) (2017-04-22) 4 | [Full Changelog](https://github.com/TypistTech/wp-cloudflare-guard/compare/0.1.3...0.2.0) 5 | 6 | **Closed issues:** 7 | 8 | - Reorganize tests [\#53](https://github.com/TypistTech/wp-cloudflare-guard/issues/53) 9 | - TravisCI: Deploy `master` branch production build to GitHub `nightly` branch [\#50](https://github.com/TypistTech/wp-cloudflare-guard/issues/50) 10 | - Update .styleci.yml to typist tech config [\#48](https://github.com/TypistTech/wp-cloudflare-guard/issues/48) 11 | - Change namespace to TypistTech\WPCFG [\#47](https://github.com/TypistTech/wp-cloudflare-guard/issues/47) 12 | - Disable codecov.io pull request commenting [\#44](https://github.com/TypistTech/wp-cloudflare-guard/issues/44) 13 | - Hide Cloudflare email, api key and zone id if set via constants [\#41](https://github.com/TypistTech/wp-cloudflare-guard/issues/41) 14 | - Remove imposter and imposter-plugin files from production build [\#36](https://github.com/TypistTech/wp-cloudflare-guard/issues/36) 15 | - Add review me notice [\#34](https://github.com/TypistTech/wp-cloudflare-guard/issues/34) 16 | - Extract loadable to separate package [\#29](https://github.com/TypistTech/wp-cloudflare-guard/issues/29) 17 | - Use TravisCI to deploy to wordpress.org [\#26](https://github.com/TypistTech/wp-cloudflare-guard/issues/26) 18 | - Refactor Loader [\#25](https://github.com/TypistTech/wp-cloudflare-guard/issues/25) 19 | - Remove disable bad login option [\#23](https://github.com/TypistTech/wp-cloudflare-guard/issues/23) 20 | - Expose container instance via WordPress hook [\#22](https://github.com/TypistTech/wp-cloudflare-guard/issues/22) 21 | - Remove unused jamesryanbell/cloudflare files [\#21](https://github.com/TypistTech/wp-cloudflare-guard/issues/21) 22 | 23 | **Merged pull requests:** 24 | 25 | - Fix: Assert that WP Review Me installed time is set within the past ten seconds [\#63](https://github.com/TypistTech/wp-cloudflare-guard/pull/63) ([TangRufus](https://github.com/TangRufus)) 26 | - TravisCI: Deploy `master` branch production build to GitHub `nightly` branch [\#62](https://github.com/TypistTech/wp-cloudflare-guard/pull/62) ([TangRufus](https://github.com/TangRufus)) 27 | - Refactor [\#60](https://github.com/TypistTech/wp-cloudflare-guard/pull/60) ([TangRufus](https://github.com/TangRufus)) 28 | - Remove imposter and imposter-plugin files from production build [\#59](https://github.com/TypistTech/wp-cloudflare-guard/pull/59) ([TangRufus](https://github.com/TangRufus)) 29 | - Add review me notice [\#57](https://github.com/TypistTech/wp-cloudflare-guard/pull/57) ([TangRufus](https://github.com/TangRufus)) 30 | - Remove Activator and Deactivator [\#56](https://github.com/TypistTech/wp-cloudflare-guard/pull/56) ([TangRufus](https://github.com/TangRufus)) 31 | - Use WP Contained Hook [\#55](https://github.com/TypistTech/wp-cloudflare-guard/pull/55) ([TangRufus](https://github.com/TangRufus)) 32 | - Change namespace to TypistTech\WPCFG; Reorganize tests [\#54](https://github.com/TypistTech/wp-cloudflare-guard/pull/54) ([TangRufus](https://github.com/TangRufus)) 33 | - Update dependencies [\#52](https://github.com/TypistTech/wp-cloudflare-guard/pull/52) ([TangRufus](https://github.com/TangRufus)) 34 | - Update StyleCI config [\#51](https://github.com/TypistTech/wp-cloudflare-guard/pull/51) ([TangRufus](https://github.com/TangRufus)) 35 | - Disable codecov.io pull request commenting [\#46](https://github.com/TypistTech/wp-cloudflare-guard/pull/46) ([TangRufus](https://github.com/TangRufus)) 36 | - Fix: BadLogin type hint [\#37](https://github.com/TypistTech/wp-cloudflare-guard/pull/37) ([TangRufus](https://github.com/TangRufus)) 37 | - Expose Container via WordPress action 'wpcfg\_get\_container' [\#33](https://github.com/TypistTech/wp-cloudflare-guard/pull/33) ([TangRufus](https://github.com/TangRufus)) 38 | - Apply scrutinizer fixes [\#32](https://github.com/TypistTech/wp-cloudflare-guard/pull/32) ([TangRufus](https://github.com/TangRufus)) 39 | - Deploy from TravisCI to wordpress.org [\#31](https://github.com/TypistTech/wp-cloudflare-guard/pull/31) ([TangRufus](https://github.com/TangRufus)) 40 | - Remove unused files [\#30](https://github.com/TypistTech/wp-cloudflare-guard/pull/30) ([TangRufus](https://github.com/TangRufus)) 41 | - Refactor [\#27](https://github.com/TypistTech/wp-cloudflare-guard/pull/27) ([TangRufus](https://github.com/TangRufus)) 42 | - Use league/container [\#20](https://github.com/TypistTech/wp-cloudflare-guard/pull/20) ([TangRufus](https://github.com/TangRufus)) 43 | - Apply PSR2, PSR4 and WP Coding Standards [\#19](https://github.com/TypistTech/wp-cloudflare-guard/pull/19) ([TangRufus](https://github.com/TangRufus)) 44 | - Use yarn instead of npm [\#18](https://github.com/TypistTech/wp-cloudflare-guard/pull/18) ([TangRufus](https://github.com/TangRufus)) 45 | - Use typisttech/imposter-plugin to manage vendor namespaces [\#17](https://github.com/TypistTech/wp-cloudflare-guard/pull/17) ([TangRufus](https://github.com/TangRufus)) 46 | - composer update [\#16](https://github.com/TypistTech/wp-cloudflare-guard/pull/16) ([TangRufus](https://github.com/TangRufus)) 47 | - composer scripts: Use composer archive to build [\#15](https://github.com/TypistTech/wp-cloudflare-guard/pull/15) ([TangRufus](https://github.com/TangRufus)) 48 | - Make composer validate happy [\#14](https://github.com/TypistTech/wp-cloudflare-guard/pull/14) ([TangRufus](https://github.com/TangRufus)) 49 | 50 | ## [0.1.3](https://github.com/TypistTech/wp-cloudflare-guard/tree/0.1.3) (2017-03-07) 51 | [Full Changelog](https://github.com/TypistTech/wp-cloudflare-guard/compare/0.1.2...0.1.3) 52 | 53 | **Merged pull requests:** 54 | 55 | - Version bump 0.1.3 [\#13](https://github.com/TypistTech/wp-cloudflare-guard/pull/13) ([TangRufus](https://github.com/TangRufus)) 56 | - Add Yoast i18n module [\#12](https://github.com/TypistTech/wp-cloudflare-guard/pull/12) ([TangRufus](https://github.com/TangRufus)) 57 | - composer update [\#11](https://github.com/TypistTech/wp-cloudflare-guard/pull/11) ([TangRufus](https://github.com/TangRufus)) 58 | 59 | ## [0.1.2](https://github.com/TypistTech/wp-cloudflare-guard/tree/0.1.2) (2017-03-05) 60 | [Full Changelog](https://github.com/TypistTech/wp-cloudflare-guard/compare/0.1.1...0.1.2) 61 | 62 | **Merged pull requests:** 63 | 64 | - Fix: grunt makepot not working and Version bump 0.1.2 [\#10](https://github.com/TypistTech/wp-cloudflare-guard/pull/10) ([TangRufus](https://github.com/TangRufus)) 65 | - Composer Script: build before deploy [\#9](https://github.com/TypistTech/wp-cloudflare-guard/pull/9) ([TangRufus](https://github.com/TangRufus)) 66 | 67 | ## [0.1.1](https://github.com/TypistTech/wp-cloudflare-guard/tree/0.1.1) (2017-03-05) 68 | [Full Changelog](https://github.com/TypistTech/wp-cloudflare-guard/compare/0.1.0...0.1.1) 69 | 70 | **Merged pull requests:** 71 | 72 | - Version bump 0.1.1 [\#8](https://github.com/TypistTech/wp-cloudflare-guard/pull/8) ([TangRufus](https://github.com/TangRufus)) 73 | - i18n: Translate vendor files [\#7](https://github.com/TypistTech/wp-cloudflare-guard/pull/7) ([TangRufus](https://github.com/TangRufus)) 74 | - Add grunt-wp-deploy [\#6](https://github.com/TypistTech/wp-cloudflare-guard/pull/6) ([TangRufus](https://github.com/TangRufus)) 75 | 76 | ## [0.1.0](https://github.com/TypistTech/wp-cloudflare-guard/tree/0.1.0) (2017-03-05) 77 | **Merged pull requests:** 78 | 79 | - Version bump 0.1.0 [\#5](https://github.com/TypistTech/wp-cloudflare-guard/pull/5) ([TangRufus](https://github.com/TangRufus)) 80 | - Add wordpress.org meta [\#4](https://github.com/TypistTech/wp-cloudflare-guard/pull/4) ([TangRufus](https://github.com/TangRufus)) 81 | - Add i18n translation [\#3](https://github.com/TypistTech/wp-cloudflare-guard/pull/3) ([TangRufus](https://github.com/TangRufus)) 82 | - Fix: TravisCI test coverage is always 0% [\#2](https://github.com/TypistTech/wp-cloudflare-guard/pull/2) ([TangRufus](https://github.com/TangRufus)) 83 | - Fix: TravisCI config [\#1](https://github.com/TypistTech/wp-cloudflare-guard/pull/1) ([TangRufus](https://github.com/TangRufus)) 84 | 85 | 86 | 87 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at `wp-cloudflare-guard@typist.tech`. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * WP CloudFlare Guard 3 | * 4 | * Connecting WordPress with Cloudflare firewall, 5 | * protect your WordPress site at DNS level. 6 | * Automatically create firewall rules to block dangerous IPs. 7 | * 8 | * @package WPCFG 9 | * 10 | * @author Typist Tech 11 | * @copyright 2017 Typist Tech 12 | * @license GPL-2.0+ 13 | * 14 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 15 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 16 | */ 17 | 18 | module.exports = function (grunt) { 19 | 20 | 'use strict'; 21 | 22 | // Project configuration 23 | grunt.initConfig({ 24 | 25 | pkg: grunt.file.readJSON('package.json'), 26 | 27 | addtextdomain: { 28 | options: { 29 | textdomain: '<%= pkg.name %>', 30 | updateDomains: true 31 | }, 32 | target: { 33 | files: { 34 | src: [ 35 | '*.php', 36 | 'lib/**/*.php', 37 | 'src/**/*.php', 38 | 'vendor/**/*.php' 39 | ] 40 | } 41 | } 42 | }, 43 | 44 | makepot: { 45 | target: { 46 | options: { 47 | include: [ 48 | '.*.php', 49 | 'lib/.*', 50 | 'src/.*', 51 | 'vendor/.*' 52 | ], 53 | mainFile: '<%= pkg.name %>.php', 54 | potHeaders: { 55 | poedit: true, 56 | 'Project-Id-Version': '<%= pkg.name %> <%= pkg.version %>', 57 | 'language-team': '<%= pkg.pot.languageteam %>', 58 | 'last-translator': '<%= pkg.pot.lasttranslator %>', 59 | 'report-msgid-bugs-to': '<%= pkg.pot.reportmsgidbugsto %>' 60 | }, 61 | type: 'wp-plugin', 62 | updateTimestamp: true 63 | } 64 | } 65 | }, 66 | 67 | // Bump version numbers 68 | version: { 69 | changelog: { 70 | options: { 71 | prefix: 'future-release=' 72 | }, 73 | src: ['.github_changelog_generator'] 74 | }, 75 | php: { 76 | options: { 77 | prefix: '\\* Version:\\s+' 78 | }, 79 | src: ['<%= pkg.name %>.php'] 80 | }, 81 | readme: { 82 | options: { 83 | prefix: 'Stable tag:\\s+' 84 | }, 85 | src: ['README.txt'] 86 | } 87 | }, 88 | 89 | clean: { 90 | cloudflare: [ 91 | 'vendor/jamesryanbell/cloudflare/src/**/*.*', 92 | '!vendor/jamesryanbell/cloudflare/src/CloudFlare/Exception/**', 93 | '!vendor/jamesryanbell/cloudflare/src/CloudFlare/*Api.php', 94 | '!vendor/jamesryanbell/cloudflare/src/CloudFlare/Zone/Firewall/AccessRules.php' 95 | ], 96 | "container-interop": [ 97 | 'vendor/container-interop/container-interop/docs/**' 98 | ], 99 | "cf-ip-rewrite": [ 100 | 'vendor/cloudflare/cf-ip-rewrite/tests/**' 101 | ], 102 | imposter: [ 103 | 'vendor/typisttech/imposter*' 104 | ], 105 | vendor: { 106 | nocase: true, 107 | src: [ 108 | 'vendor/**/.gitignore', 109 | 'vendor/**/.gitkeep', 110 | 'vendor/**/*.xml', 111 | 'vendor/**/*.yml', 112 | 'vendor/**/*.dist', 113 | 'vendor/**/*.md', 114 | 'vendor/**/*.lock', 115 | '!**/*license*', 116 | '!vendor/composer/**/*.*', 117 | '!vendor/squizlabs/php_codesniffer/**/*.*', 118 | '!vendor/wp-coding-standards/wpcs/**/*.*' 119 | ] 120 | } 121 | }, 122 | 123 | cleanempty: { 124 | options: { 125 | noJunk: true 126 | }, 127 | vendor: { 128 | src: ['vendor/**/*'] 129 | } 130 | } 131 | }); 132 | 133 | require('load-grunt-tasks')(grunt); 134 | 135 | grunt.registerTask('clean:install', ['clean:cloudflare', 'clean:cf-ip-rewrite', 'clean:container-interop', 'clean:vendor']); 136 | 137 | grunt.util.linefeed = '\n'; 138 | 139 | }; 140 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WP Cloudflare Guard 2 | 3 | [![WordPress plugin](https://img.shields.io/wordpress/plugin/v/wp-cloudflare-guard.svg)](https://wordpress.org/plugins/wp-cloudflare-guard/) 4 | [![WordPress](https://img.shields.io/wordpress/plugin/dt/wp-cloudflare-guard.svg)](https://wordpress.org/plugins/wp-cloudflare-guard/) 5 | [![WordPress rating](https://img.shields.io/wordpress/plugin/r/wp-cloudflare-guard.svg)](https://wordpress.org/plugins/wp-cloudflare-guard/) 6 | [![WordPress](https://img.shields.io/wordpress/v/wp-cloudflare-guard.svg)](https://wordpress.org/plugins/wp-cloudflare-guard/) 7 | [![Build Status](https://travis-ci.org/TypistTech/wp-cloudflare-guard.svg?branch=master)](https://travis-ci.org/TypistTech/wp-cloudflare-guard) 8 | [![codecov](https://codecov.io/gh/TypistTech/wp-cloudflare-guard/branch/master/graph/badge.svg)](https://codecov.io/gh/TypistTech/wp-cloudflare-guard) 9 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/TypistTech/wp-cloudflare-guard/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/TypistTech/wp-cloudflare-guard/?branch=master) 10 | [![PHP Versions Tested](http://php-eye.com/badge/typisttech/wp-cloudflare-guard/tested.svg)](https://travis-ci.org/TypistTech/wp-cloudflare-guard) 11 | [![StyleCI](https://styleci.io/repos/83855037/shield?branch=master)](https://styleci.io/repos/83855037) 12 | [![Dependency Status](https://gemnasium.com/badges/github.com/TypistTech/wp-cloudflare-guard.svg)](https://gemnasium.com/github.com/TypistTech/wp-cloudflare-guard) 13 | [![License](https://poser.pugx.org/typisttech/wp-cloudflare-guard/license)](https://packagist.org/packages/typisttech/wp-cloudflare-guard) 14 | [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://www.typist.tech/donate/wp-cloudflare-guard/) 15 | [![Hire Typist Tech](https://img.shields.io/badge/Hire-Typist%20Tech-ff69b4.svg)](https://www.typist.tech/contact/) 16 | 17 | Connecting WordPress with Cloudflare firewall, protect your WordPress site at DNS level. Automatically create firewall rules to block dangerous IPs 18 | 19 | 20 | 21 | 22 | 23 | - [Installation Instructions](#installation-instructions) 24 | - [Via Manually Upload](#via-manually-upload) 25 | - [Via WP CLI](#via-wp-cli) 26 | - [Developing](#developing) 27 | - [Build from Source](#build-from-source) 28 | - [Branches](#branches) 29 | - [Master](#master) 30 | - [Nightly](#nightly) 31 | - [Support!](#support) 32 | - [Donate via PayPal *](#donate-via-paypal-) 33 | - [Why don't you hire me?](#why-dont-you-hire-me) 34 | - [Want to help in other way? Want to be a sponsor?](#want-to-help-in-other-way-want-to-be-a-sponsor) 35 | - [Running the Tests](#running-the-tests) 36 | - [Feedback](#feedback) 37 | - [Change log](#change-log) 38 | - [Security](#security) 39 | - [Contributing](#contributing) 40 | - [Credits](#credits) 41 | - [License](#license) 42 | 43 | 44 | 45 | 46 | 47 | This repository is a development version of [WP Cloudflare Guard](https://wordpress.org/plugins/wp-cloudflare-guard/) intended to facilitate communication with developers. It is not stable and not intended for installation on production sites. 48 | 49 | Bug reports and pull requests are welcome. 50 | 51 | If you are not a developer or you'd like to receive the stable release version and automatic updates, install it via [WordPress.org](https://wordpress.org/plugins/wp-cloudflare-guard/) instead. 52 | 53 | 54 | 55 | ## Installation Instructions 56 | 57 | If you are not a developer or you'd like to receive the stable release version and automatic updates, install it via [WordPress.org](https://wordpress.org/plugins/wp-cloudflare-guard/) instead. 58 | 59 | 60 | 61 | The `master` branch is not installable. Use the `nightly` branch instead. See [branches](#branches). 62 | 63 | ### Via Manually Upload 64 | 65 | 1. Download the built archive from [nightly branch](https://github.com/TypistTech/wp-cloudflare-guard/archive/nightly.zip) 66 | 67 | 2. Unzip it 68 | 69 | 3. Upload it to `wp-content/plugins/` 70 | 71 | 4. Go to the WordPress plugin menu and activate it 72 | 73 | 74 | 75 | ### Via WP CLI 76 | 77 | 1. `$ wp plugin install https://github.com/TypistTech/wp-cloudflare-guard/archive/nightly.zip --activate` 78 | 79 | 80 | 81 | 82 | ## Developing 83 | 84 | Before start hacking, you need `composer ` and `yarn` installed. See: 85 | 86 | - [getcomposer.org](https://getcomposer.org/doc/00-intro.md) 87 | - [yarnpkg.com](https://yarnpkg.com/en/docs/install) 88 | 89 | 90 | 91 | To setup a developer workable version you should run these commands: 92 | 93 | ```bash 94 | $ composer create-project --keep-vcs --no-install typisttech/wp-cloudflare-guard:dev-master 95 | $ cd wp-cloudflare-guard 96 | $ composer install 97 | ``` 98 | 99 | 100 | 101 | ## Build from Source 102 | 103 | These commands build the plugin into `release/wp-cloudflare-guard.zip`. 104 | 105 | 1. `$ composer build` 106 | 2. `release/wp-cloudflare-guard.zip` 107 | 108 | 109 | 110 | 111 | ## Branches 112 | 113 | ### Master 114 | 115 | The `master` branch is the main branch where the source code of `HEAD` always reflects a state with the latest delivered development changes for the next release. This is where the `nightly` branch is built from. Since we built this plugin with `composer` and `grunt`, this branch is not installable. 116 | 117 | ### Nightly 118 | 119 | The `nightly` branch is built by TravisCI whenever the `master` branch is updated. Anything in the `nightly` branch is installable. See [installation instructions](#installation-instructions). 120 | 121 | 122 | 123 | ## Support! 124 | 125 | ### Donate via PayPal [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://www.typist.tech/donate/wp-cloudflare-guard/) 126 | 127 | Love WP Cloudflare Guard? Help me maintain WP Cloudflare Guard, a [donation here](https://www.typist.tech/donate/wp-cloudflare-guard/) can help with it. 128 | 129 | ### Why don't you hire me? 130 | 131 | Ready to take freelance WordPress jobs. Contact me via the contact form [here](https://www.typist.tech/contact/) or, via email [info@typist.tech](mailto:info@typist.tech) 132 | 133 | ### Want to help in other way? Want to be a sponsor? 134 | 135 | Contact: [Tang Rufus](mailto:tangrufus@gmail.com) 136 | 137 | 138 | 139 | ## Running the Tests 140 | 141 | [WP Cloudflare Guard](https://github.com/TypistTech/wp-cloudflare-guard) run tests on [Codeception](http://codeception.com/) and relies [wp-browser](https://github.com/lucatume/wp-browser) to provide WordPress integration. 142 | Before testing, you have to install WordPress locally and add a [codeception.yml](http://codeception.com/docs/reference/Configuration) file. 143 | 144 | See [codeception.example.yml](codeception.example.yml) for a [Varying Vagrant Vagrants](https://varyingvagrantvagrants.org/) configuration example. 145 | 146 | Actually run the tests: 147 | 148 | ``` bash 149 | $ composer test 150 | ``` 151 | 152 | We also test all PHP files against [PSR-2: Coding Style Guide](http://www.php-fig.org/psr/psr-2/) and part of the [WordPress coding standard](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards). 153 | 154 | Check the code style with ``$ composer check-style`` and fix it with ``$ composer fix-style``. 155 | 156 | 157 | 158 | ## Feedback 159 | 160 | **Please provide feedback!** We want to make this library useful in as many projects as possible. 161 | Please submit an [issue](https://github.com/TypistTech/wp-cloudflare-guard/issues/new) and point out what you do and don't like, or fork the project and make suggestions. 162 | **No issue is too small.** 163 | 164 | 165 | 166 | ## Change log 167 | 168 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 169 | 170 | 171 | 172 | ## Security 173 | 174 | If you discover any security related issues, please email wp-cloudflare-guard@typist.tech instead of using the issue tracker. 175 | 176 | 177 | 178 | ## Contributing 179 | 180 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) and [CODE_OF_CONDUCT](./CODE_OF_CONDUCT.md) for details. 181 | 182 | 183 | 184 | ## Credits 185 | 186 | [WP Cloudflare Guard](https://github.com/TypistTech/wp-cloudflare-guard) is a [Typist Tech](https://www.typist.tech) project and maintained by [Tang Rufus](https://twitter.com/Tangrufus), freelance developer for [hire](https://www.typist.tech/contact/). 187 | 188 | Full list of contributors can be found [here](https://github.com/TypistTech/wp-cloudflare-guard/graphs/contributors). 189 | 190 | 191 | 192 | ## License 193 | 194 | [WP Cloudflare Guard](https://github.com/TypistTech/wp-cloudflare-guard) is licensed under the GPLv2 (or later) from the [Free Software Foundation](http://www.fsf.org/). 195 | Please see [License File](LICENSE) for more information. 196 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | === WP Cloudflare Guard === 2 | Contributors: typisttech, tangrufus 3 | Donate link: https://www.typist.tech/donate/wp-cloudflare-guard/ 4 | Tags: cloudflare, firewall, security, spam 5 | Requires at least: 4.7 6 | Tested up to: 4.7.3 7 | Stable tag: 0.2.0 8 | License: GPLv2 or later 9 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 10 | 11 | Connecting WordPress with Cloudflare firewall, protect your WordPress site at DNS level. Automatically create firewall rules to block dangerous IPs. 12 | 13 | == Description == 14 | 15 | Connecting WordPress with Cloudflare firewall, protect your WordPress site at DNS level. Automatically create firewall rules to block dangerous IPs. 16 | 17 | = Features = 18 | 19 | * Blacklist IP if attempt to login with bad usernames 20 | 21 | = Integrations (Coming soon as add-ons) = 22 | 23 | * [iThemes Security](https://www.typist.tech/go/ithemes-security/) 24 | * [Contact Form 7](https://wordpress.org/plugins/contact-form-7/) 25 | * [WordPress Zero Spam](https://wordpress.org/plugins/zero-spam) 26 | 27 | = How does WP Cloudflare Guard different from Cloudflare's official plugin? = 28 | 29 | At the time of writing, Cloudflare's [official plugin](https://wordpress.org/plugins/cloudflare/) doesn't block any IP for WordPress when other plugins discover dangerous activities. Here comes WPCFG! WPCFG focus on integrating other plugins with Cloudflare. 30 | 31 | = Compatibility = 32 | 33 | * Works with Cloudflare's [official plugin](https://wordpress.org/plugins/cloudflare/) 34 | * Works with [Sunny (Purging CloudFlare caches for WordPress)](https://wordpress.org/plugins/sunny/) 35 | 36 | = Things You Need to Know = 37 | 38 | * You need PHP 7.0 or later 39 | * You need WordPress 4.7 or later 40 | * You need a Cloudflare account (free plan is okay) 41 | * This plugin was not built by [Cloudflare, Inc](https://www.cloudflare.com/) 42 | 43 | > If you like the plugin, feel free to [rate it](https://wordpress.org/support/plugin/wp-cloudflare-guard/reviews/#new-post) or [donate via PayPal](https://www.typist.tech/donate/wp-cloudflare-guard/). Thanks a lot! :) 44 | 45 | = For Bloggers = 46 | 47 | If you have written an article about `WP Cloudflare Guard`, do [let me know](https://www.typist.tech/contact/). For any questions, shoot me an email at [info@typist.tech](mailto:info@typist.tech) 48 | 49 | = For Developers = 50 | 51 | WP Cloudflare Guard is open source and hosted on [GitHub](https://github.com/TypistTech/wp-cloudflare-guard). Feel free to make [pull requests](https://github.com/Typisttech/wp-cloudflare-guard/pulls). 52 | 53 | = Who make this plugin? = 54 | 55 | [Tang Rufus](mailto:info@typist.tech), a freelance developer for hire. 56 | I make [Typist Tech](https://www.typist.tech/) also. 57 | 58 | = Support = 59 | 60 | To save time so that we can spend it on development, please read the plugin's [FAQs](https://wordpress.org/plugins/wp-cloudflare-guard/faq/) first. 61 | Before requesting support, and ensure that you have updated WP Cloudflare Guard and WordPress to the latest released version and installed PHP 7 or later. 62 | 63 | We hang out in the WordPress [support forum](https://wordpress.org/support/plugin/wp-cloudflare-guard) for this plugin. 64 | 65 | If you know what `GitHub` is, use [GitHub issues](https://github.com/Typisttech/wp-cloudflare-guard/issues) instead. 66 | 67 | == Installation == 68 | 69 | = Via WordPress admin dashboard = 70 | 71 | 1. Log in to your site’s Dashboard (e.g. www.your-domain.com/wp-admin) 72 | 1. Click on the `Plugins` tab in the left panel, then click “Add New” 73 | 1. Search for `WP Cloudflare Guard` and the latest version will appear at the top of the list of results 74 | 1. Install it by clicking the `Install Now` link 75 | 1. When installation finishes, click `Activate Plugin` 76 | 77 | = Via Manual Upload = 78 | 79 | 1. Download the plugin from [wordpress.org](https://downloads.wordpress.org/plugin/wp-cloudflare-guard.zip) 80 | 1. Unzip it 81 | 1. Upload it to `wp-content/plugins/` 82 | 1. Go to the WordPress plugin menu and activate it 83 | 84 | = Via WP CLI = 85 | 86 | 1. `$ wp plugin install wp-cloudflare-guard --activate` 87 | 88 | == Frequently Asked Questions == 89 | 90 | = What version of PHP do I need? = 91 | 92 | PHP 7 or later. 93 | 94 | = Is this plugin written by Cloudflare, Inc.? = 95 | 96 | No. 97 | This plugin is a [Typist Tech](https://www.typist.tech) project. 98 | 99 | = Can I install WP Cloudflare Guard, Sunny and Cloudflare's official plugin at the same time? = 100 | 101 | Yes, all of them work together without problems. 102 | 103 | * Install [WP Cloudflare Guard](https://wordpress.org/plugins/wp-cloudflare-guard/) if you want to protect your site from bad IPs 104 | * Install [Sunny](https://wordpress.org/plugins/sunny/) if you want to purge CloudFlare's cache automatically 105 | * Install the [official plugin](https://wordpress.org/plugins/cloudflare/) if you can't see the real IP from visitors 106 | 107 | = What if WP Cloudflare Guard blacklisted my IP? = 108 | 109 | 1. Login [CloudFlare](http://cloudflare.com) 110 | 1. Select your domain 111 | 1. Go `Firewall` 112 | 1. Release your IP under `Access Rules` 113 | 114 | = Does this plugin available in my language? = 115 | 116 | English works out of the box. 117 | 118 | Traditional Chinese language pack is available [here](https://translate.wordpress.org/projects/wp-plugins/wp-cloudflare-guard/language-packs). 119 | 120 | You can add your own translation at [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/wp-cloudflare-guard). 121 | 122 | = How to get support? = 123 | 124 | Use the WordPress support forum for this plugin at [https://wordpress.org/support/plugin/wp-cloudflare-guard](https://wordpress.org/support/plugin/wp-cloudflare-guard). 125 | 126 | Make sure you have read the plugin's FAQs at [https://wordpress.org/plugins/wp-cloudflare-guard/faq/](https://wordpress.org/plugins/wp-cloudflare-guard/faq/). And, updated WP Cloudflare Guard and WordPress to the latest released version before asking questions. 127 | 128 | If you know what `GitHub` is, use [GitHub issues](https://github.com/Typisttech/wp-cloudflare-guard/issues) instead. 129 | 130 | = How can I support this plugin? = 131 | 132 | If you like the plugin, feel free to: 133 | 134 | * Give us a 5-star review on [WordPress.org](https://wordpress.org/support/plugin/wp-cloudflare-guard/reviews/#new-post) 135 | * Donate via [PayPal](https://www.typist.tech/donate/wp-cloudflare-guard/). Thanks a lot! :) 136 | * Translate it at [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/wp-cloudflare-guard). 137 | 138 | Besides, `WP Cloudflare Guard` is open source and hosted on [GitHub](https://github.com/TypistTech/wp-cloudflare-guard). Feel free to make pull requests. 139 | 140 | Last but not least, you can hire me. Shoot me an email at [info@typist.tech](mailto:info@typist.tech) or use this [contact form](https://www.typist.tech/contact/). 141 | 142 | = What if I want more? = 143 | 144 | Hire me! 145 | 146 | Shoot me an email at [info@typist.tech](mailto:info@typist.tech) or use this [contact form](https://www.typist.tech/contact/). 147 | 148 | 149 | == Screenshots == 150 | 151 | 1. Cloudflare Settings 152 | 1. Bad Login Settings 153 | 1. Cloudflare Firewall Access Rules 154 | 155 | == Changelog == 156 | 157 | Full change log available at [GitHub](https://github.com/TangRufus/wp-cloudflare-guard/blob/master/CHANGELOG.md) 158 | 159 | = 0.2.0 = 160 | 161 | * Code refactor 162 | 163 | = 0.1.3 = 164 | 165 | * Add yoast i18n module 166 | * Fix PHP undefined notices 167 | 168 | = 0.1.2 = 169 | 170 | * Better translation support 171 | 172 | = 0.1.1 = 173 | 174 | * Better translation support 175 | 176 | = 0.1.0 = 177 | * First release 178 | 179 | == Upgrade Notice == 180 | 181 | = 0.2.0 = 182 | 183 | * You have to re-enter Cloudflare settings. 184 | -------------------------------------------------------------------------------- /assets/banner-1544x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typisttech/wp-cloudflare-guard/eac99666c6de4f13ccd010ff6f10b2abc24395c4/assets/banner-1544x500.png -------------------------------------------------------------------------------- /assets/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typisttech/wp-cloudflare-guard/eac99666c6de4f13ccd010ff6f10b2abc24395c4/assets/banner-772x250.png -------------------------------------------------------------------------------- /assets/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typisttech/wp-cloudflare-guard/eac99666c6de4f13ccd010ff6f10b2abc24395c4/assets/icon-128x128.png -------------------------------------------------------------------------------- /assets/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typisttech/wp-cloudflare-guard/eac99666c6de4f13ccd010ff6f10b2abc24395c4/assets/icon-256x256.png -------------------------------------------------------------------------------- /assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typisttech/wp-cloudflare-guard/eac99666c6de4f13ccd010ff6f10b2abc24395c4/assets/screenshot-1.png -------------------------------------------------------------------------------- /assets/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typisttech/wp-cloudflare-guard/eac99666c6de4f13ccd010ff6f10b2abc24395c4/assets/screenshot-2.png -------------------------------------------------------------------------------- /assets/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/typisttech/wp-cloudflare-guard/eac99666c6de4f13ccd010ff6f10b2abc24395c4/assets/screenshot-3.png -------------------------------------------------------------------------------- /codeception.dist.yml: -------------------------------------------------------------------------------- 1 | namespace: TypistTech\WPCFG 2 | actor: Tester 3 | paths: 4 | tests: tests 5 | log: tests/_output 6 | data: tests/_data 7 | helpers: tests/_support 8 | settings: 9 | backup_globals: false # See: https://core.trac.wordpress.org/ticket/39327 10 | bootstrap: _bootstrap.php 11 | shuffle: true 12 | colors: true 13 | memory_limit: 1024M 14 | coverage: 15 | include: 16 | - src/* 17 | modules: 18 | config: 19 | WPDb: 20 | dsn: 'mysql:host=127.0.0.1;dbname=wordpress' 21 | user: root 22 | password: '' 23 | dump: tests/_data/dump.sql 24 | populate: true 25 | cleanup: true 26 | url: 'http://wp.dev:8080' 27 | tablePrefix: wp_ 28 | WPBrowser: 29 | url: 'http://wp.dev:8080' 30 | adminUsername: 'admin' 31 | adminPassword: 'password' 32 | adminPath: /wp-admin 33 | WordPress: 34 | depends: WPDb 35 | wpRootFolder: /tmp/wordpress 36 | adminUsername: 'admin' 37 | adminPassword: 'password' 38 | WPLoader: 39 | wpRootFolder: /tmp/wordpress 40 | dbName: wordpress_int 41 | dbHost: 127.0.0.1 42 | dbUser: root 43 | dbPassword: '' 44 | tablePrefix: int_wp_ 45 | domain: wordpress.dev 46 | adminEmail: admin@wordpress.dev 47 | WPWebDriver: 48 | url: 'http://wp.dev:8080' 49 | port: 4444 50 | window_size: '1024x768' 51 | adminUsername: 'admin' 52 | adminPassword: 'password' 53 | adminPath: /wp-admin 54 | host: 'wp.dev' 55 | browser: phantomjs 56 | extensions: 57 | enabled: 58 | - Codeception\Extension\Phantoman 59 | config: 60 | Codeception\Extension\Phantoman: 61 | port: 4444 62 | suites: ['acceptance'] 63 | commands: [ 64 | 'Codeception\Command\GenerateWPUnit', 65 | 'Codeception\Command\GenerateWPRestApi', 66 | 'Codeception\Command\GenerateWPRestController', 67 | 'Codeception\Command\GenerateWPRestPostTypeController', 68 | 'Codeception\Command\GenerateWPAjax', 69 | 'Codeception\Command\GenerateWPCanonical', 70 | 'Codeception\Command\GenerateWPXMLRPC', 71 | 'Codeception\Command\DbSnapshot', 72 | 'tad\Codeception\Command\SearchReplace' 73 | ] 74 | -------------------------------------------------------------------------------- /codeception.example.yml: -------------------------------------------------------------------------------- 1 | modules: 2 | config: 3 | WPDb: 4 | dsn: 'mysql:host=vvv.dev;dbname=wpcfg-tester' 5 | user: external 6 | password: external 7 | url: 'http://wpcfg-tester.dev' 8 | WPBrowser: 9 | url: 'http://wpcfg-tester.dev' 10 | WordPress: 11 | wpRootFolder: /Users/developer/vagrant-local/www/wpcfg-tester/htdocs/ 12 | WPLoader: 13 | wpRootFolder: /Users/developer/vagrant-local/www/wpcfg-tester/htdocs/ 14 | dbHost: vvv.dev 15 | dbName: wpcfg-tester_int 16 | dbUser: external 17 | dbPassword: external 18 | WPWebDriver: 19 | url: 'http://wpcfg-tester.dev' 20 | host: '127.0.0.1' 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typisttech/wp-cloudflare-guard", 3 | "description": "Connecting WordPress with Cloudflare firewall, protect your WordPress site at DNS level. Automatically create firewall rules to block dangerous IPs", 4 | "keywords": [ 5 | "wordpress", 6 | "wp", 7 | "cloudflare", 8 | "firewall", 9 | "security" 10 | ], 11 | "homepage": "https://github.com/TypistTech/wp-cloudflare-guard", 12 | "license": "GPL-2.0+", 13 | "authors": [ 14 | { 15 | "name": "Typist Tech", 16 | "email": "wp-cloudflare-guard@typist.tech", 17 | "homepage": "https://www.typist.tech/" 18 | }, 19 | { 20 | "name": "Tang Rufus", 21 | "email": "tangrufus@gmail.com", 22 | "homepage": "https://www.typist.tech/", 23 | "role": "Developer" 24 | } 25 | ], 26 | "support": { 27 | "email": "wp-cloudflare-guard@typist.tech", 28 | "issues": "https://github.com/TypistTech/wp-cloudflare-guard/issues", 29 | "forum": "https://wordpress.org/support/plugin/wp-cloudflare-guard", 30 | "source": "https://github.com/TypistTech/wp-cloudflare-guard" 31 | }, 32 | "require": { 33 | "php": "^7.0", 34 | "cloudflare/cf-ip-rewrite": "^1.0", 35 | "league/container": "^2.4.1", 36 | "typisttech/cloudflare-wp-api": "^0.3.0", 37 | "typisttech/imposter-plugin": "^0.2.5", 38 | "typisttech/wp-better-settings": "^0.11.0", 39 | "typisttech/wp-contained-hook": "^0.1.1" 40 | }, 41 | "require-dev": { 42 | "codeception/aspect-mock": "^2.0", 43 | "doctrine/annotations": "<1.5.0", 44 | "doctrine/instantiator": "<1.1.0", 45 | "jakoch/phantomjs-installer": "^2.1", 46 | "lucatume/wp-browser": "^1.21.3", 47 | "neronmoon/scriptsdev": "^0.1.1", 48 | "site5/phantoman": "^1.1", 49 | "wp-coding-standards/wpcs": "^0.12.0" 50 | }, 51 | "autoload": { 52 | "classmap": [ 53 | "lib/" 54 | ], 55 | "psr-4": { 56 | "TypistTech\\WPCFG\\": "src/" 57 | } 58 | }, 59 | "scripts": { 60 | "build": [ 61 | "rm -fr vendor", 62 | "composer install --no-dev --prefer-dist --optimize-autoloader --no-suggest", 63 | "yarn grunt clean", 64 | "yarn grunt addtextdomain", 65 | "yarn grunt makepot", 66 | "yarn grunt cleanempty", 67 | "composer archive --format=zip --dir=release --file=wp-cloudflare-guard" 68 | ], 69 | "pre-tag": [ 70 | "composer update --no-suggest", 71 | "yarn upgrade", 72 | "yarn grunt version", 73 | "yarn doctoc README.md", 74 | "github_changelog_generator --no-verbose" 75 | ], 76 | "check-style": "phpcs --standard=ruleset.xml --colors -p -s .", 77 | "fix-style": "phpcbf --standard=ruleset.xml -p --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 .", 78 | "install-dev-extra": [ 79 | "PhantomInstaller\\Installer::installPhantomJS", 80 | "phpcs --config-set installed_paths vendor/wp-coding-standards/wpcs" 81 | ], 82 | "post-install-cmd": "@transform-vendor", 83 | "post-update-cmd": "@transform-vendor", 84 | "test": "codecept run", 85 | "test-acceptance": "codecept run acceptance", 86 | "test-functional": "codecept run functional", 87 | "test-integration": "codecept run integration", 88 | "test-unit": "codecept run unit", 89 | "test-with-coverage": "codecept run --coverage --coverage-xml --coverage-html", 90 | "transform-vendor": [ 91 | "yarn install", 92 | "yarn grunt clean:install", 93 | "cfwp build" 94 | ] 95 | }, 96 | "config": { 97 | "sort-packages": true 98 | }, 99 | "extra": { 100 | "imposter": { 101 | "namespace": "TypistTech\\WPCFG\\Vendor" 102 | }, 103 | "scripts-dev": { 104 | "post-install-cmd": "@install-dev-extra", 105 | "post-update-cmd": "@install-dev-extra" 106 | } 107 | }, 108 | "archive": { 109 | "exclude": [ 110 | "/*", 111 | ".*", 112 | "!/languages", 113 | "!/lib", 114 | "!/src", 115 | "!/vendor", 116 | "!/LICENSE", 117 | "!/README.txt", 118 | "!/*.php" 119 | ] 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/julien731/wp-dismissible-notices-handler/assets/js/main.js: -------------------------------------------------------------------------------- 1 | // This file is cloned from julien731/wp-dismissible-notices-handler@1.0.0 2 | // @see https://github.com/julien731/WP-Dismissible-Notices-Handler/commit/85ce1debbdfd4543e5b835dfe6670b9de99ddf6b 3 | // @see https://github.com/julien731/WP-Dismissible-Notices-Handler/ 4 | 5 | jQuery(document).ready(function($) { 6 | $( '.notice.is-dismissible' ).on('click', '.notice-dismiss', function ( event ) { 7 | event.preventDefault(); 8 | var $this = $(this); 9 | if( 'undefined' == $this.parent().attr('id') ){ 10 | return; 11 | } 12 | $.post( ajaxurl, { 13 | action: 'dnh_dismiss_notice', 14 | url: ajaxurl, 15 | id: $this.parent().attr('id') 16 | }); 17 | 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /lib/julien731/wp-dismissible-notices-handler/handler.php: -------------------------------------------------------------------------------- 1 | 23 | * 24 | * @package Dismissible Notices Handler 25 | * @author Julien Liabeuf 26 | * @version 1.0 27 | * @license GPL-2.0+ 28 | * @link https://julienliabeuf.com 29 | * @copyright 2016 Julien Liabeuf 30 | */ 31 | 32 | // If this file is called directly, abort. 33 | if ( ! defined( 'WPINC' ) ) { 34 | die; 35 | } 36 | 37 | final class Dismissible_Notices_Handler { 38 | 39 | /** 40 | * @var Dismissible_Notices_Handler Holds the unique instance of the handler 41 | * @since 1.0 42 | */ 43 | private static $instance; 44 | 45 | /** 46 | * Library version 47 | * 48 | * @since 1.0 49 | * @var string 50 | */ 51 | public $version = '1.0'; 52 | 53 | /** 54 | * Required version of PHP. 55 | * 56 | * @since 1.0 57 | * @var string 58 | */ 59 | public $php_version_required = '5.5'; 60 | 61 | /** 62 | * Minimum version of WordPress required to use the library 63 | * 64 | * @since 1.0 65 | * @var string 66 | */ 67 | public $wordpress_version_required = '4.2'; 68 | 69 | /** 70 | * @var array Holds all our registered notices 71 | * @since 1.0 72 | */ 73 | private $notices; 74 | 75 | /** 76 | * Instantiate and return the unique Dismissible_Notices_Handler object 77 | * 78 | * @since 1.0 79 | * @return object Dismissible_Notices_Handler Unique instance of the handler 80 | */ 81 | public static function instance() { 82 | 83 | if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Dismissible_Notices_Handler ) ) { 84 | self::$instance = new Dismissible_Notices_Handler; 85 | self::$instance->init(); 86 | } 87 | 88 | return self::$instance; 89 | 90 | } 91 | 92 | /** 93 | * Initialize the library 94 | * 95 | * @since 1.0 96 | * @return void 97 | */ 98 | private function init() { 99 | 100 | // Make sure WordPress is compatible 101 | if ( ! self::$instance->is_wp_compatible() ) { 102 | self::$instance->spit_error( 103 | sprintf( 104 | /* translators: %s: required wordpress version */ 105 | esc_html__( 'The library can not be used because your version of WordPress is too old. You need version %s at least.', 'wp-cloudflare-guard' ), 106 | self::$instance->wordpress_version_required 107 | ) 108 | ); 109 | 110 | return; 111 | } 112 | 113 | // Make sure PHP is compatible 114 | if ( ! self::$instance->is_php_compatible() ) { 115 | self::$instance->spit_error( 116 | sprintf( 117 | /* translators: %s: required php version */ 118 | esc_html__( 'The library can not be used because your version of PHP is too old. You need version %s at least.', 'wp-cloudflare-guard' ), 119 | self::$instance->php_version_required 120 | ) 121 | ); 122 | 123 | return; 124 | } 125 | 126 | self::$instance->includes(); 127 | 128 | add_action( 'admin_notices', array( self::$instance, 'display' ) ); 129 | add_action( 'admin_print_scripts', array( self::$instance, 'load_script' ) ); 130 | add_action( 'wp_ajax_dnh_dismiss_notice', array( self::$instance, 'dismiss_notice_ajax' ) ); 131 | 132 | } 133 | 134 | /** 135 | * Check if the current WordPress version fits the requirements 136 | * 137 | * @since 1.0 138 | * @return boolean 139 | */ 140 | private function is_wp_compatible() { 141 | 142 | if ( version_compare( get_bloginfo( 'version' ), self::$instance->wordpress_version_required, '<' ) ) { 143 | return false; 144 | } 145 | 146 | return true; 147 | 148 | } 149 | 150 | /** 151 | * Check if the version of PHP is compatible with this library 152 | * 153 | * @since 1.0 154 | * @return boolean 155 | */ 156 | private function is_php_compatible() { 157 | 158 | if ( version_compare( phpversion(), self::$instance->php_version_required, '<' ) ) { 159 | return false; 160 | } 161 | 162 | return true; 163 | 164 | } 165 | 166 | /** 167 | * Include all our files 168 | * 169 | * @since 1.0 170 | * @return void 171 | */ 172 | private function includes() { 173 | require( trailingslashit( plugin_dir_path( __FILE__ ) ) . 'includes/helper-functions.php' ); 174 | } 175 | 176 | /** 177 | * Load the script 178 | * 179 | * @since 1.0 180 | * @return void 181 | */ 182 | public function load_script() { 183 | wp_register_script( 'dnh', trailingslashit( plugin_dir_url( __FILE__ ) ) . 'assets/js/main.js', array( 'jquery' ), self::$instance->version, true ); 184 | wp_enqueue_script( 'dnh' ); 185 | } 186 | 187 | /** 188 | * Display all the registered notices 189 | * 190 | * @since 1.0 191 | * @return void 192 | */ 193 | public function display() { 194 | 195 | if ( is_null( self::$instance->notices ) || empty( self::$instance->notices ) ) { 196 | return; 197 | } 198 | 199 | foreach ( self::$instance->notices as $id => $notice ) { 200 | 201 | $id = self::$instance->get_id( $id ); 202 | 203 | // Check if the notice was dismissed 204 | if ( self::$instance->is_dismissed( $id ) ) { 205 | continue; 206 | } 207 | 208 | // Check if the current user has required capability 209 | if ( ! empty( $notice['cap'] ) && ! current_user_can( $notice['cap'] ) ) { 210 | continue; 211 | } 212 | 213 | $class = array( 214 | 'notice', 215 | $notice['type'], 216 | 'is-dismissible', 217 | $notice['class'], 218 | ); 219 | 220 | printf( '

%2$s

', trim( implode( ' ', $class ) ), $notice['content'], "dnh-$id" ); 221 | 222 | } 223 | 224 | } 225 | 226 | /** 227 | * Spits an error message at the top of the admin screen 228 | * 229 | * @since 1.0 230 | * 231 | * @param string $error Error message to spit 232 | * 233 | * @return void 234 | */ 235 | protected function spit_error( $error ) { 236 | printf( 237 | '
%1$s %2$s
', 238 | esc_html__( 'Dismissible Notices Handler Error:', 'wp-cloudflare-guard' ), 239 | wp_kses_post( $error ) 240 | ); 241 | } 242 | 243 | /** 244 | * Sanitize a notice ID and return it 245 | * 246 | * @since 1.0 247 | * 248 | * @param string $id 249 | * 250 | * @return string 251 | */ 252 | public function get_id( $id ) { 253 | return sanitize_key( $id ); 254 | } 255 | 256 | /** 257 | * Get available notice types 258 | * 259 | * @since 1.0 260 | * @return array 261 | */ 262 | public function get_types() { 263 | 264 | $types = array( 265 | 'error', 266 | 'updated', 267 | ); 268 | 269 | return apply_filters( 'dnh_notice_types', $types ); 270 | 271 | } 272 | 273 | /** 274 | * Get the default arguments for a notice 275 | * 276 | * @since 1.0 277 | * @return array 278 | */ 279 | private function default_args() { 280 | 281 | $args = array( 282 | 'screen' => '', // Coming soon 283 | 'scope' => 'user', // Scope of the dismissal. Either user or global 284 | 'cap' => '', // Required user capability 285 | 'class' => '', // Additional class to add to the notice 286 | ); 287 | 288 | return apply_filters( 'dnh_default_args', $args ); 289 | 290 | } 291 | 292 | /** 293 | * Register a new notice 294 | * 295 | * @since 1.0 296 | * 297 | * @param string $id Notice ID, used to identify it 298 | * @param string $type Type of notice to display 299 | * @param string $content Notice content 300 | * @param array $args Additional parameters 301 | * 302 | * @return bool 303 | */ 304 | public function register_notice( $id, $type, $content, $args = array() ) { 305 | 306 | if ( is_null( self::$instance->notices ) ) { 307 | self::$instance->notices = array(); 308 | } 309 | 310 | $id = self::$instance->get_id( $id ); 311 | $type = in_array( $t = sanitize_text_field( $type ), self::$instance->get_types() ) ? $t : 'updated'; 312 | $content = wp_kses_post( $content ); 313 | $args = wp_parse_args( $args, self::$instance->default_args() ); 314 | 315 | if ( array_key_exists( $id, self::$instance->notices ) ) { 316 | 317 | self::$instance->spit_error( 318 | sprintf( 319 | /* translators: %s: required php version */ 320 | esc_html__( 'A notice with the ID %s has already been registered.', 'wp-cloudflare-guard' ), 321 | "$id" 322 | ) 323 | ); 324 | 325 | return false; 326 | } 327 | 328 | $notice = array( 329 | 'type' => $type, 330 | 'content' => $content, 331 | ); 332 | 333 | $notice = array_merge( $notice, $args ); 334 | 335 | self::$instance->notices[ $id ] = $notice; 336 | 337 | return true; 338 | 339 | } 340 | 341 | /** 342 | * Notice dismissal triggered by Ajax 343 | * 344 | * @since 1.0 345 | * @return void 346 | */ 347 | public function dismiss_notice_ajax() { 348 | 349 | if ( ! isset( $_POST['id'] ) ) { 350 | echo 0; 351 | exit; 352 | } 353 | 354 | if ( empty( $_POST['id'] ) || false === strpos( $_POST['id'], 'dnh-' ) ) { 355 | echo 0; 356 | exit; 357 | } 358 | 359 | $id = self::$instance->get_id( str_replace( 'dnh-', '', $_POST['id'] ) ); 360 | 361 | echo self::$instance->dismiss_notice( $id ); 362 | exit; 363 | 364 | } 365 | 366 | /** 367 | * Dismiss a notice 368 | * 369 | * @since 1.0 370 | * 371 | * @param string $id ID of the notice to dismiss 372 | * 373 | * @return bool 374 | */ 375 | public function dismiss_notice( $id ) { 376 | 377 | $notice = self::$instance->get_notice( self::$instance->get_id( $id ) ); 378 | 379 | if ( false === $notice ) { 380 | return false; 381 | } 382 | 383 | if ( self::$instance->is_dismissed( $id ) ) { 384 | return false; 385 | } 386 | 387 | return 'user' === $notice['scope'] ? self::$instance->dismiss_user( $id ) : self::$instance->dismiss_global( $id ); 388 | 389 | } 390 | 391 | /** 392 | * Dismiss notice for the current user 393 | * 394 | * @since 1.0 395 | * 396 | * @param string $id Notice ID 397 | * 398 | * @return int|bool 399 | */ 400 | private function dismiss_user( $id ) { 401 | 402 | $dismissed = self::$instance->dismissed_user(); 403 | 404 | if ( in_array( $id, $dismissed ) ) { 405 | return false; 406 | } 407 | 408 | array_push( $dismissed, $id ); 409 | 410 | return update_user_meta( get_current_user_id(), 'dnh_dismissed_notices', $dismissed ); 411 | 412 | } 413 | 414 | /** 415 | * Dismiss notice globally on the site 416 | * 417 | * @since 1.0 418 | * 419 | * @param string $id Notice ID 420 | * 421 | * @return bool 422 | */ 423 | private function dismiss_global( $id ) { 424 | 425 | $dismissed = self::$instance->dismissed_global(); 426 | 427 | if ( in_array( $id, $dismissed ) ) { 428 | return false; 429 | } 430 | 431 | array_push( $dismissed, $id ); 432 | 433 | return update_option( 'dnh_dismissed_notices', $dismissed ); 434 | 435 | } 436 | 437 | /** 438 | * Restore a dismissed notice 439 | * 440 | * @since 1.0 441 | * 442 | * @param string $id ID of the notice to restore 443 | * 444 | * @return bool 445 | */ 446 | public function restore_notice( $id ) { 447 | 448 | $id = self::$instance->get_id( $id ); 449 | $notice = self::$instance->get_notice( $id ); 450 | 451 | if ( false === $notice ) { 452 | return false; 453 | } 454 | 455 | return 'user' === $notice['scope'] ? self::$instance->restore_user( $id ) : self::$instance->restore_global( $id ); 456 | 457 | } 458 | 459 | /** 460 | * Restore a notice dismissed by the current user 461 | * 462 | * @since 1.0 463 | * 464 | * @param string $id ID of the notice to restore 465 | * 466 | * @return bool 467 | */ 468 | private function restore_user( $id ) { 469 | 470 | $id = self::$instance->get_id( $id ); 471 | $notice = self::$instance->get_notice( $id ); 472 | 473 | if ( false === $notice ) { 474 | return false; 475 | } 476 | 477 | $dismissed = self::$instance->dismissed_user(); 478 | 479 | if ( ! in_array( $id, $dismissed ) ) { 480 | return false; 481 | } 482 | 483 | $flip = array_flip( $dismissed ); 484 | $key = $flip[ $id ]; 485 | 486 | unset( $dismissed[ $key ] ); 487 | 488 | return update_user_meta( get_current_user_id(), 'dnh_dismissed_notices', $dismissed ); 489 | 490 | } 491 | 492 | /** 493 | * Restore a notice dismissed globally 494 | * 495 | * @since 1.0 496 | * 497 | * @param string $id ID of the notice to restore 498 | * 499 | * @return bool 500 | */ 501 | private function restore_global( $id ) { 502 | 503 | $id = self::$instance->get_id( $id ); 504 | $notice = self::$instance->get_notice( $id ); 505 | 506 | if ( false === $notice ) { 507 | return false; 508 | } 509 | 510 | $dismissed = self::$instance->dismissed_global(); 511 | 512 | if ( ! in_array( $id, $dismissed ) ) { 513 | return false; 514 | } 515 | 516 | $flip = array_flip( $dismissed ); 517 | $key = $flip[ $id ]; 518 | 519 | unset( $dismissed[ $key ] ); 520 | 521 | return update_option( 'dnh_dismissed_notices', $dismissed ); 522 | 523 | } 524 | 525 | /** 526 | * Get all dismissed notices 527 | * 528 | * This includes notices dismissed globally or per user. 529 | * 530 | * @since 1.0 531 | * @return array 532 | */ 533 | public function dismissed_notices() { 534 | 535 | $user = self::$instance->dismissed_user(); 536 | $global = self::$instance->dismissed_global(); 537 | 538 | return array_merge( $user, $global ); 539 | 540 | } 541 | 542 | /** 543 | * Get user dismissed notices 544 | * 545 | * @since 1.0 546 | * @return array 547 | */ 548 | private function dismissed_user() { 549 | 550 | $dismissed = get_user_meta( get_current_user_id(), 'dnh_dismissed_notices', true ); 551 | 552 | if ( '' === $dismissed ) { 553 | $dismissed = array(); 554 | } 555 | 556 | return $dismissed; 557 | 558 | } 559 | 560 | /** 561 | * Get globally dismissed notices 562 | * 563 | * @since 1.0 564 | * @return array 565 | */ 566 | private function dismissed_global() { 567 | return get_option( 'dnh_dismissed_notices', array() ); 568 | } 569 | 570 | /** 571 | * Check if a notice has been dismissed 572 | * 573 | * @since 1.0 574 | * 575 | * @param string $id Notice ID 576 | * 577 | * @return bool 578 | */ 579 | public function is_dismissed( $id ) { 580 | 581 | $dismissed = self::$instance->dismissed_notices(); 582 | 583 | if ( ! in_array( self::$instance->get_id( $id ), $dismissed ) ) { 584 | return false; 585 | } 586 | 587 | return true; 588 | 589 | } 590 | 591 | /** 592 | * Get all the registered notices 593 | * 594 | * @since 1.0 595 | * @return array|null 596 | */ 597 | public function get_notices() { 598 | return self::$instance->notices; 599 | } 600 | 601 | /** 602 | * Return a specific notice 603 | * 604 | * @since 1.0 605 | * 606 | * @param string $id Notice ID 607 | * 608 | * @return array|false 609 | */ 610 | public function get_notice( $id ) { 611 | 612 | $id = self::$instance->get_id( $id ); 613 | 614 | if ( ! is_array( self::$instance->notices ) || ! array_key_exists( $id, self::$instance->notices ) ) { 615 | return false; 616 | } 617 | 618 | return self::$instance->notices[ $id ]; 619 | 620 | } 621 | 622 | } 623 | 624 | /** 625 | * The main function responsible for returning the unique Dismissible Notices Handler instance 626 | * 627 | * Use this function like you would a global variable, except without needing 628 | * to declare the global. 629 | * 630 | * @since 1.0 631 | * @return object Dismissible_Notices_Handler 632 | */ 633 | function DNH() { 634 | return Dismissible_Notices_Handler::instance(); 635 | } 636 | 637 | /** 638 | * Get the library running 639 | */ 640 | DNH(); 641 | -------------------------------------------------------------------------------- /lib/julien731/wp-dismissible-notices-handler/includes/helper-functions.php: -------------------------------------------------------------------------------- 1 | 21 | * 22 | * @package Dismissible Notices Handler/Helper Functions 23 | * @author Julien Liabeuf 24 | * @version 1.0 25 | * @license GPL-2.0+ 26 | * @link https://julienliabeuf.com 27 | * @copyright 2016 Julien Liabeuf 28 | */ 29 | 30 | // If this file is called directly, abort. 31 | if ( ! defined( 'WPINC' ) ) { 32 | die; 33 | } 34 | 35 | /** 36 | * Register a new notice 37 | * 38 | * @since 1.0 39 | * 40 | * @param string $id Notice ID, used to identify it 41 | * @param string $type Type of notice to display 42 | * @param string $content Notice content 43 | * @param array $args Additional parameters 44 | * 45 | * @return bool 46 | */ 47 | function dnh_register_notice( $id, $type, $content, $args = array() ) { 48 | 49 | if ( ! function_exists( __NAMESPACE__ . '\DNH' ) ) { 50 | return false; 51 | } 52 | 53 | return DNH()->register_notice( $id, $type, $content, $args ); 54 | 55 | } 56 | 57 | /** 58 | * Restore a previously dismissed notice 59 | * 60 | * @since 1.0 61 | * 62 | * @param string $id ID of the notice to restore 63 | * 64 | * @return bool 65 | */ 66 | function dnh_restore_notice( $id ) { 67 | 68 | if ( ! function_exists( __NAMESPACE__ . '\DNH' ) ) { 69 | return false; 70 | } 71 | 72 | return DNH()->restore_notice( $id ); 73 | 74 | } 75 | 76 | /** 77 | * Check if a notice has been dismissed 78 | * 79 | * @since 1.0 80 | * 81 | * @param string $id ID of the notice to check 82 | * 83 | * @return bool 84 | */ 85 | function dnh_is_dismissed( $id ) { 86 | 87 | if ( ! function_exists( __NAMESPACE__ . '\DNH' ) ) { 88 | return false; 89 | } 90 | 91 | return DNH()->is_dismissed( $id ); 92 | 93 | } 94 | -------------------------------------------------------------------------------- /lib/julien731/wp-review-me/review.php: -------------------------------------------------------------------------------- 1 | 23 | * 24 | * @package WP Review Me 25 | * @author Julien Liabeuf 26 | * @version 2.0.1 27 | * @license GPL-2.0+ 28 | * @link https://julienliabeuf.com 29 | * @copyright 2016 Julien Liabeuf 30 | */ 31 | 32 | class WP_Review_Me { 33 | 34 | /** 35 | * Library version 36 | * 37 | * @since 1.0 38 | * @var string 39 | */ 40 | public $version = '2.0.1'; 41 | 42 | /** 43 | * Required version of PHP. 44 | * 45 | * @since 1.0 46 | * @var string 47 | */ 48 | public $php_version_required = '5.5'; 49 | 50 | /** 51 | * Minimum version of WordPress required to use the library 52 | * 53 | * @since 1.0 54 | * @var string 55 | */ 56 | public $wordpress_version_required = '4.2'; 57 | 58 | /** 59 | * Holds the unique identifying key for this particular instance 60 | * 61 | * @since 1.0 62 | * @var string 63 | */ 64 | protected $key; 65 | 66 | /** 67 | * Link unique ID 68 | * 69 | * @since 1.0 70 | * @var string 71 | */ 72 | public $link_id; 73 | 74 | /** 75 | * WP_Review_Me constructor. 76 | * 77 | * @since 1.0 78 | * 79 | * @param array $args Object settings 80 | */ 81 | public function __construct( $args ) { 82 | 83 | $args = wp_parse_args( $args, $this->get_defaults() ); 84 | $this->days = $args['days_after']; 85 | $this->type = $args['type']; 86 | $this->slug = $args['slug']; 87 | $this->rating = $args['rating']; 88 | $this->message = $args['message']; 89 | $this->link_label = $args['link_label']; 90 | $this->cap = $args['cap']; 91 | $this->scope = $args['scope']; 92 | 93 | // Set the unique identifying key for this instance 94 | $this->key = 'wrm_' . substr( md5( plugin_basename( __FILE__ ) ), 0, 20 ); 95 | $this->link_id = 'wrm-review-link-' . $this->key; 96 | 97 | // Instantiate 98 | $this->init(); 99 | 100 | } 101 | 102 | /** 103 | * Get default object settings 104 | * 105 | * @since 1.0 106 | * @return array 107 | */ 108 | protected function get_defaults() { 109 | 110 | $defaults = array( 111 | 'days_after' => 10, 112 | 'type' => '', 113 | 'slug' => '', 114 | 'rating' => 5, 115 | 'message' => sprintf( esc_html__( 'Hey! It's been a little while that you've been using this product. You might not realize it, but user reviews are such a great help to us. We would be so grateful if you could take a minute to leave a review on WordPress.org. Many thanks in advance :)', 'wp-cloudflare-guard' ) ), 116 | 'link_label' => esc_html__( 'Click here to leave your review', 'wp-cloudflare-guard' ), 117 | // Parameters used in WP Dismissible Notices Handler 118 | 'cap' => 'administrator', 119 | 'scope' => 'global', 120 | ); 121 | 122 | return $defaults; 123 | 124 | } 125 | 126 | /** 127 | * Initialize the library 128 | * 129 | * @since 1.0 130 | * @return void 131 | */ 132 | private function init() { 133 | 134 | // Make sure WordPress is compatible 135 | if ( ! $this->is_wp_compatible() ) { 136 | $this->spit_error( 137 | sprintf( 138 | esc_html__( 'The library can not be used because your version of WordPress is too old. You need version %s at least.', 'wp-cloudflare-guard' ), 139 | $this->wordpress_version_required 140 | ) 141 | ); 142 | 143 | return; 144 | } 145 | 146 | // Make sure PHP is compatible 147 | if ( ! $this->is_php_compatible() ) { 148 | $this->spit_error( 149 | sprintf( 150 | esc_html__( 'The library can not be used because your version of PHP is too old. You need version %s at least.', 'wp-cloudflare-guard' ), 151 | $this->php_version_required 152 | ) 153 | ); 154 | 155 | return; 156 | } 157 | 158 | // Make sure the dependencies are loaded 159 | if ( ! function_exists( 'dnh_register_notice' ) ) { 160 | 161 | $dnh_file = trailingslashit( plugin_dir_path( __FILE__ ) ) . 'vendor/julien731/wp-dismissible-notices-handler/handler.php'; 162 | 163 | if ( file_exists( $dnh_file ) ) { 164 | require( $dnh_file ); 165 | } 166 | 167 | if ( ! function_exists( __NAMESPACE__ . '\dnh_register_notice' ) ) { 168 | $this->spit_error( 169 | sprintf( 170 | esc_html__( 'Dependencies are missing. Please run a %s.', 'wp-cloudflare-guard' ), 171 | 'composer install' 172 | ) 173 | ); 174 | 175 | return; 176 | } 177 | } 178 | 179 | add_action( 'admin_footer', array( $this, 'script' ) ); 180 | add_action( 'wp_ajax_wrm_clicked_review', array( $this, 'dismiss_notice' ) ); 181 | 182 | // And let's roll... maybe. 183 | $this->maybe_prompt(); 184 | 185 | } 186 | 187 | /** 188 | * Check if the current WordPress version fits the requirements 189 | * 190 | * @since 1.0 191 | * @return boolean 192 | */ 193 | private function is_wp_compatible() { 194 | 195 | if ( version_compare( get_bloginfo( 'version' ), $this->wordpress_version_required, '<' ) ) { 196 | return false; 197 | } 198 | 199 | return true; 200 | 201 | } 202 | 203 | /** 204 | * Check if the version of PHP is compatible with this library 205 | * 206 | * @since 1.0 207 | * @return boolean 208 | */ 209 | private function is_php_compatible() { 210 | 211 | if ( version_compare( phpversion(), $this->php_version_required, '<' ) ) { 212 | return false; 213 | } 214 | 215 | return true; 216 | 217 | } 218 | 219 | /** 220 | * Spits an error message at the top of the admin screen 221 | * 222 | * @since 1.0 223 | * 224 | * @param string $error Error message to spit 225 | * 226 | * @return void 227 | */ 228 | protected function spit_error( $error ) { 229 | printf( 230 | '
%1$s %2$s
', 231 | esc_html__( 'WP Review Me Error:', 'wp-cloudflare-guard' ), 232 | wp_kses_post( $error ) 233 | ); 234 | } 235 | 236 | /** 237 | * Check if it is time to ask for a review 238 | * 239 | * @since 1.0 240 | * @return bool 241 | */ 242 | public function is_time() { 243 | 244 | $installed = (int) get_option( $this->key, 0 ); 245 | 246 | if ( 0 === $installed ) { 247 | $this->setup_date(); 248 | $installed = time(); 249 | } 250 | 251 | if ( $installed + ( $this->days * 86400 ) > time() ) { 252 | return false; 253 | } 254 | 255 | return true; 256 | 257 | } 258 | 259 | /** 260 | * Save the current date as the installation date 261 | * 262 | * @since 1.0 263 | * @return void 264 | */ 265 | protected function setup_date() { 266 | update_option( $this->key, time() ); 267 | } 268 | 269 | /** 270 | * Get the review link 271 | * 272 | * @since 1.0 273 | * @return string 274 | */ 275 | protected function get_review_link() { 276 | 277 | $link = 'https://wordpress.org/support/'; 278 | 279 | switch ( $this->type ) { 280 | 281 | case 'theme': 282 | $link .= 'theme/'; 283 | break; 284 | 285 | case 'plugin': 286 | $link .= 'plugin/'; 287 | break; 288 | 289 | } 290 | 291 | $link .= $this->slug . '/reviews'; 292 | $link = add_query_arg( 'rate', $this->rating, $link ); 293 | $link = esc_url( $link . '#new-post' ); 294 | 295 | return $link; 296 | 297 | } 298 | 299 | /** 300 | * Get the complete link tag 301 | * 302 | * @since 1.0 303 | * @return string 304 | */ 305 | protected function get_review_link_tag() { 306 | 307 | $link = $this->get_review_link(); 308 | 309 | return "$this->link_label"; 310 | 311 | } 312 | 313 | /** 314 | * Trigger the notice if it is time to ask for a review 315 | * 316 | * @since 1.0 317 | * @return void 318 | */ 319 | protected function maybe_prompt() { 320 | 321 | if ( ! $this->is_time() ) { 322 | return; 323 | } 324 | 325 | dnh_register_notice( $this->key, 'updated', $this->get_message(), array( 326 | 'scope' => $this->scope, 327 | 'cap' => $this->cap 328 | ) ); 329 | 330 | } 331 | 332 | /** 333 | * Echo the JS script in the admin footer 334 | * 335 | * @since 1.0 336 | * @return void 337 | */ 338 | public function script() { ?> 339 | 340 | 362 | 363 | link_id ) { 386 | echo "not this instance's job"; 387 | die(); 388 | } 389 | 390 | // Get the DNH notice ID ready 391 | $notice_id = DNH()->get_id( str_replace( 'wrm-review-link-', '', $id ) ); 392 | $dismissed = DNH()->dismiss_notice( $notice_id ); 393 | 394 | echo $dismissed; 395 | 396 | /** 397 | * Fires right after the notice has been dismissed. This allows for various integrations to perform additional tasks. 398 | * 399 | * @since 1.0 400 | * 401 | * @param string $id The notice ID 402 | * @param string $notice_id The notice ID as defined by the DNH class 403 | */ 404 | do_action( 'wrm_after_notice_dismissed', $id, $notice_id ); 405 | 406 | // Stop execution here 407 | die(); 408 | 409 | } 410 | 411 | /** 412 | * Get the review prompt message 413 | * 414 | * @since 1.0 415 | * @return string 416 | */ 417 | protected function get_message() { 418 | 419 | $message = $this->message; 420 | $link = $this->get_review_link_tag(); 421 | $message = $message . ' ' . $link; 422 | 423 | return wp_kses_post( $message ); 424 | 425 | } 426 | 427 | } 428 | -------------------------------------------------------------------------------- /lib/yoast/i18n-module/src/i18n-module-wordpressorg.php: -------------------------------------------------------------------------------- 1 | set_defaults( $args ); 33 | 34 | $this->i18n = new Yoast_I18n_v2( $args ); 35 | $this->set_api_url( $args['textdomain'] ); 36 | } 37 | 38 | /** 39 | * Sets the default values for wordpress.org 40 | * 41 | * @param array $args The arguments to set defaults for. 42 | * 43 | * @return array The arguments with the arguments set. 44 | */ 45 | private function set_defaults( $args ) { 46 | 47 | if ( ! isset( $args['glotpress_logo'] ) ) { 48 | $args['glotpress_logo'] = 'https://plugins.svn.wordpress.org/' . $args['textdomain'] . '/assets/icon-128x128.png'; 49 | } 50 | 51 | if ( ! isset( $args['register_url'] ) ) { 52 | $args['register_url'] = 'https://translate.wordpress.org/projects/wp-plugins/' . $args['textdomain'] . '/'; 53 | } 54 | 55 | if ( ! isset( $args['glotpress_name'] ) ) { 56 | $args['glotpress_name'] = 'Translating WordPress'; 57 | } 58 | 59 | if ( ! isset( $args['project_slug'] ) ) { 60 | $args['project_slug'] = $args['textdomain']; 61 | } 62 | 63 | return $args; 64 | } 65 | 66 | /** 67 | * Set the API URL on the i18n object. 68 | * 69 | * @param string $textdomain The textdomain to use for the API URL. 70 | */ 71 | private function set_api_url( $textdomain ) { 72 | $this->i18n->set_api_url( 'https://translate.wordpress.org/api/projects/wp-plugins/' . $textdomain . '/stable/' ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/yoast/i18n-module/src/i18n-module.php: -------------------------------------------------------------------------------- 1 | locale = $this->get_admin_locale(); 133 | if ( 'en_US' === $this->locale ) { 134 | return; 135 | } 136 | 137 | $this->init( $args ); 138 | 139 | if ( ! $this->hide_promo() ) { 140 | add_action( $this->hook, array( $this, 'promo' ) ); 141 | } 142 | } 143 | 144 | /** 145 | * Returns the locale used in the admin. 146 | * 147 | * WordPress 4.7 introduced the ability for users to specify an Admin language 148 | * different from the language used on the front end. This checks if the feature 149 | * is available and returns the user's language, with a fallback to the site's language. 150 | * Can be removed when support for WordPress 4.6 will be dropped, in favor 151 | * of WordPress get_user_locale() that already fallbacks to the site’s locale. 152 | * 153 | * @returns string The locale. 154 | */ 155 | private function get_admin_locale() { 156 | if ( function_exists( 'get_user_locale' ) ) { 157 | return get_user_locale(); 158 | } 159 | 160 | return get_locale(); 161 | } 162 | 163 | /** 164 | * This is where you decide where to display the messages and where you set the plugin specific variables. 165 | * 166 | * @access private 167 | * 168 | * @param array $args 169 | */ 170 | private function init( $args ) { 171 | foreach ( $args as $key => $arg ) { 172 | $this->$key = $arg; 173 | } 174 | } 175 | 176 | /** 177 | * Check whether the promo should be hidden or not 178 | * 179 | * @access private 180 | * 181 | * @return bool 182 | */ 183 | private function hide_promo() { 184 | $hide_promo = get_transient( 'yoast_i18n_' . $this->project_slug . '_promo_hide' ); 185 | if ( ! $hide_promo ) { 186 | if ( filter_input( INPUT_GET, 'remove_i18n_promo', FILTER_VALIDATE_INT ) === 1 ) { 187 | // No expiration time, so this would normally not expire, but it wouldn't be copied to other sites etc. 188 | set_transient( 'yoast_i18n_' . $this->project_slug . '_promo_hide', true ); 189 | $hide_promo = true; 190 | } 191 | } 192 | 193 | return $hide_promo; 194 | } 195 | 196 | /** 197 | * Generates a promo message 198 | * 199 | * @access private 200 | * 201 | * @return bool|string $message 202 | */ 203 | private function promo_message() { 204 | $message = false; 205 | 206 | if ( $this->translation_exists && $this->translation_loaded && $this->percent_translated < 90 ) { 207 | $message = __( 'As you can see, there is a translation of this plugin in %1$s. This translation is currently %3$d%% complete. We need your help to make it complete and to fix any errors. Please register at %4$s to help complete the translation to %1$s!', $this->textdomain, 'wp-cloudflare-guard' ); 208 | } else if ( ! $this->translation_loaded && $this->translation_exists ) { 209 | $message = __( 'You\'re using WordPress in %1$s. While %2$s has been translated to %1$s for %3$d%%, it\'s not been shipped with the plugin yet. You can help! Register at %4$s to help complete the translation to %1$s!', $this->textdomain, 'wp-cloudflare-guard' ); 210 | } else if ( ! $this->translation_exists ) { 211 | $message = __( 'You\'re using WordPress in a language we don\'t support yet. We\'d love for %2$s to be translated in that language too, but unfortunately, it isn\'t right now. You can change that! Register at %4$s to help translate it!', $this->textdomain, 'wp-cloudflare-guard' ); 212 | } 213 | 214 | $registration_link = sprintf( '%2$s', esc_url( $this->register_url ), esc_html( $this->glotpress_name ) ); 215 | $message = sprintf( $message, esc_html( $this->locale_name ), esc_html( $this->plugin_name ), $this->percent_translated, $registration_link ); 216 | 217 | return $message; 218 | } 219 | 220 | /** 221 | * Outputs a promo box 222 | */ 223 | public function promo() { 224 | $this->translation_details(); 225 | 226 | $message = $this->promo_message(); 227 | 228 | if ( $message ) { 229 | echo '
'; 230 | echo 'X'; 231 | 232 | echo '
'; 233 | echo '

' . sprintf( __( 'Translation of %s', $this->textdomain, 'wp-cloudflare-guard' ), $this->plugin_name ) . '

'; 234 | if ( isset( $this->glotpress_logo ) && '' != $this->glotpress_logo ) { 235 | echo '' . esc_attr( $this->glotpress_name ) . ''; 236 | } 237 | echo '

' . $message . '

'; 238 | echo '

' . __( 'Register now »', $this->textdomain, 'wp-cloudflare-guard' ) . '

'; 239 | echo '
'; 240 | echo '
'; 241 | } 242 | } 243 | 244 | /** 245 | * Try to find the transient for the translation set or retrieve them. 246 | * 247 | * @access private 248 | * 249 | * @return object|null 250 | */ 251 | private function find_or_initialize_translation_details() { 252 | $set = get_transient( 'yoast_i18n_' . $this->project_slug . '_' . $this->locale ); 253 | 254 | if ( ! $set ) { 255 | $set = $this->retrieve_translation_details(); 256 | set_transient( 'yoast_i18n_' . $this->project_slug . '_' . $this->locale, $set, DAY_IN_SECONDS ); 257 | } 258 | 259 | return $set; 260 | } 261 | 262 | /** 263 | * Try to get translation details from cache, otherwise retrieve them, then parse them. 264 | * 265 | * @access private 266 | */ 267 | private function translation_details() { 268 | $set = $this->find_or_initialize_translation_details(); 269 | 270 | $this->translation_exists = ! is_null( $set ); 271 | $this->translation_loaded = is_textdomain_loaded( $this->textdomain ); 272 | 273 | $this->parse_translation_set( $set ); 274 | } 275 | 276 | /** 277 | * The API URL to use when requesting translation information. 278 | * 279 | * @param string $api_url The new API URL. 280 | */ 281 | public function set_api_url( $api_url ) { 282 | $this->api_url = $api_url; 283 | } 284 | 285 | /** 286 | * Returns the API URL to use when requesting translation information. 287 | * 288 | * @return string 289 | */ 290 | private function get_api_url() { 291 | if ( empty( $this->api_url ) ) { 292 | $this->api_url = trailingslashit( $this->glotpress_url ) . 'api/projects/' . $this->project_slug; 293 | } 294 | 295 | return $this->api_url; 296 | } 297 | 298 | /** 299 | * Retrieve the translation details from Yoast Translate 300 | * 301 | * @access private 302 | * 303 | * @return object|null 304 | */ 305 | private function retrieve_translation_details() { 306 | $api_url = $this->get_api_url(); 307 | 308 | $resp = wp_remote_get( $api_url ); 309 | if ( is_wp_error( $resp ) || wp_remote_retrieve_response_code( $resp ) !== 200 ) { 310 | return null; 311 | } 312 | $body = wp_remote_retrieve_body( $resp ); 313 | unset( $resp ); 314 | 315 | if ( $body ) { 316 | $body = json_decode( $body ); 317 | if ( empty( $body->translation_sets ) ) { 318 | return null; 319 | } 320 | foreach ( $body->translation_sets as $set ) { 321 | if ( ! property_exists( $set, 'wp_locale' ) ) { 322 | continue; 323 | } 324 | 325 | if ( $this->locale === $set->wp_locale ) { 326 | return $set; 327 | } 328 | } 329 | } 330 | 331 | return null; 332 | } 333 | 334 | /** 335 | * Set the needed private variables based on the results from Yoast Translate 336 | * 337 | * @param object $set The translation set 338 | * 339 | * @access private 340 | */ 341 | private function parse_translation_set( $set ) { 342 | if ( $this->translation_exists && is_object( $set ) ) { 343 | $this->locale_name = $set->name; 344 | $this->percent_translated = $set->percent_translated; 345 | } else { 346 | $this->locale_name = ''; 347 | $this->percent_translated = ''; 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-cloudflare-guard", 3 | "version": "0.2.0", 4 | "main": "Gruntfile.js", 5 | "private": true, 6 | "pot": { 7 | "languageteam": "Typist Tech ", 8 | "lasttranslator": "Typist Tech ", 9 | "reportmsgidbugsto": "https://wordpress.org/support/plugin/wp-cloudflare-guard" 10 | }, 11 | "devDependencies": { 12 | "doctoc": "^1.3.0", 13 | "grunt": "^1.0.1", 14 | "grunt-cleanempty": "^1.0.4", 15 | "grunt-contrib-clean": "^1.1.0", 16 | "grunt-version": "^1.1.1", 17 | "grunt-wp-i18n": "^1.0.0", 18 | "load-grunt-tasks": "^3.5.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | PSR 2 and WordPress Coding Standards for Plugins 22 | 23 | /build/* 24 | /lib/* 25 | /node_modules/* 26 | /release/* 27 | /tests/_data/* 28 | /tests/_output/* 29 | /vendor/* 30 | /Gruntfile.js 31 | 32 | 33 | 34 | 35 | /*.php 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | */_bootstrap.php 100 | 101 | 102 | 103 | */_bootstrap.php 104 | 105 | 106 | 107 | /tests/* 108 | 109 | 110 | 111 | /tests/* 112 | 113 | 114 | 115 | /tests/* 116 | 117 | 118 | 119 | /tests/* 120 | 121 | 122 | 123 | /tests/* 124 | 125 | 126 | 127 | /tests/* 128 | 129 | 130 | 131 | /tests/* 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /src/Admin.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG; 22 | 23 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\PageRegister; 24 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\Pages\MenuPage; 25 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\Pages\PageInterface; 26 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\Pages\SubmenuPage; 27 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\Section; 28 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\SettingRegister; 29 | use TypistTech\WPCFG\Vendor\TypistTech\WPContainedHook\Action; 30 | 31 | /** 32 | * Final class Admin. 33 | * 34 | * The admin-specific functionality of the plugin. 35 | */ 36 | final class Admin implements LoadableInterface 37 | { 38 | /** 39 | * Options store. 40 | * 41 | * @var OptionStore 42 | */ 43 | private $optionStore; 44 | 45 | /** 46 | * Menu and submenu pages. 47 | * 48 | * @var (MenuPage|SubmenuPage)[] 49 | */ 50 | private $pages = []; 51 | 52 | /** 53 | * Sections 54 | * 55 | * @var Section[] 56 | */ 57 | private $sections = []; 58 | 59 | /** 60 | * Admin constructor. 61 | * 62 | * @param OptionStore $optionStore The WPCFG option store. 63 | */ 64 | public function __construct(OptionStore $optionStore) 65 | { 66 | $this->optionStore = $optionStore; 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public static function getHooks(): array 73 | { 74 | return [ 75 | new Action('admin_menu', __CLASS__, 'registerPages'), 76 | new Action('admin_init', __CLASS__, 'registerSettings'), 77 | ]; 78 | } 79 | 80 | /** 81 | * Menu slugs getter. 82 | * 83 | * @return string[] 84 | */ 85 | public function getMenuSlugs(): array 86 | { 87 | return array_map( 88 | function (PageInterface $page) { 89 | return $page->getMenuSlug(); 90 | }, 91 | $this->getPages() 92 | ); 93 | } 94 | 95 | /** 96 | * Page getter. 97 | * 98 | * @return (MenuPage|SubmenuPage)[] 99 | */ 100 | private function getPages(): array 101 | { 102 | if (empty($this->pages)) { 103 | $wpcfgPages = apply_filters('wpcfg_pages', []); 104 | 105 | $typedPages = array_filter( 106 | $wpcfgPages, 107 | function ($page) { 108 | return $page instanceof MenuPage || $page instanceof SubmenuPage; 109 | } 110 | ); 111 | $this->pages = array_values($typedPages); 112 | } 113 | 114 | return $this->pages; 115 | } 116 | 117 | /** 118 | * Add menus and submenus. 119 | * 120 | * @return void 121 | */ 122 | public function registerPages() 123 | { 124 | $pageRegister = new PageRegister( 125 | $this->getPages() 126 | ); 127 | $pageRegister->run(); 128 | } 129 | 130 | /** 131 | * Register WPCFG settings. 132 | * 133 | * @return void 134 | */ 135 | public function registerSettings() 136 | { 137 | $settingRegister = new SettingRegister( 138 | $this->optionStore, 139 | ...$this->getSections() 140 | ); 141 | $settingRegister->run(); 142 | } 143 | 144 | /** 145 | * Section getter. 146 | * 147 | * @return Section[] 148 | */ 149 | private function getSections(): array 150 | { 151 | if (empty($this->sections)) { 152 | $wpcfgSettingsSections = apply_filters('wpcfg_settings_sections', []); 153 | 154 | $typedSections = array_filter( 155 | $wpcfgSettingsSections, 156 | function ($section) { 157 | return $section instanceof Section; 158 | } 159 | ); 160 | $this->sections = array_values($typedSections); 161 | } 162 | 163 | return $this->sections; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/Ads/I18nPromoter.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG\Ads; 22 | 23 | use TypistTech\WPCFG\Admin; 24 | use TypistTech\WPCFG\LoadableInterface; 25 | use TypistTech\WPCFG\Vendor\TypistTech\WPContainedHook\Action; 26 | use TypistTech\WPCFG\Vendor\Yoast_I18n_WordPressOrg_v2; 27 | 28 | /** 29 | * Final class I18nPromoter 30 | */ 31 | final class I18nPromoter implements LoadableInterface 32 | { 33 | /** 34 | * The WPCFG admin. 35 | * 36 | * @var Admin 37 | */ 38 | private $admin; 39 | 40 | /** 41 | * I18nPromoter constructor. 42 | * 43 | * @param Admin $admin The WPCFG admin. 44 | */ 45 | public function __construct(Admin $admin) 46 | { 47 | $this->admin = $admin; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public static function getHooks(): array 54 | { 55 | return [ 56 | new Action('admin_menu', __CLASS__, 'run', 20), 57 | ]; 58 | } 59 | 60 | /** 61 | * Initialize Yoast i18n module to all WPCFG menu pages. 62 | * 63 | * @return void 64 | */ 65 | public function run() 66 | { 67 | $hooks = array_map( 68 | function (string $menuSlug) { 69 | return str_replace('-', '_', $menuSlug . '_after_option_form'); 70 | }, 71 | $this->admin->getMenuSlugs() 72 | ); 73 | 74 | foreach ($hooks as $hook) { 75 | new Yoast_I18n_WordPressOrg_v2( 76 | [ 77 | 'textdomain' => 'wp-cloudflare-guard', 78 | 'plugin_name' => 'WP Cloudflare Guard', 79 | 'hook' => $hook, 80 | ] 81 | ); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Ads/ReviewNotice.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG\Ads; 22 | 23 | use TypistTech\WPCFG\LoadableInterface; 24 | use TypistTech\WPCFG\Vendor\TypistTech\WPContainedHook\Action; 25 | use TypistTech\WPCFG\Vendor\WP_Review_Me; 26 | 27 | /** 28 | * Final class ReviewNotice 29 | */ 30 | final class ReviewNotice implements LoadableInterface 31 | { 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public static function getHooks(): array 36 | { 37 | return [ 38 | new Action('admin_init', __CLASS__, 'run'), 39 | ]; 40 | } 41 | 42 | /** 43 | * Initialize WP_Review_Me 44 | * 45 | * @return void 46 | */ 47 | public function run() 48 | { 49 | // @codingStandardsIgnoreStart 50 | $message = __( 51 | 'Hey! WP Cloudflare Guard has been keeping your site safe for a while. You might not realize it, but user reviews are such a great help to us. We would be so grateful if you could take a minute to leave a review on WordPress.org. Many thanks in advance :)', 52 | 'wp-cloudflare-guard' 53 | ); 54 | // @codingStandardsIgnoreEnd 55 | 56 | new WP_Review_Me( 57 | [ 58 | 'type' => 'plugin', 59 | 'slug' => 'wp-cloudflare-guard', 60 | 'message' => $message, 61 | ] 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/BadLogin/Admin.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG\BadLogin; 22 | 23 | use TypistTech\WPCFG\LoadableInterface; 24 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\Fields\Textarea; 25 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\Pages\SubmenuPage; 26 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\Section; 27 | use TypistTech\WPCFG\Vendor\TypistTech\WPContainedHook\Filter; 28 | 29 | /** 30 | * Final class Admin. 31 | * 32 | * The admin-specific functionality of the Bad Login module. 33 | */ 34 | final class Admin implements LoadableInterface 35 | { 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public static function getHooks(): array 40 | { 41 | return [ 42 | new Filter('wpcfg_pages', __CLASS__, 'addPage'), 43 | new Filter('wpcfg_settings_sections', __CLASS__, 'addSettingsSection'), 44 | ]; 45 | } 46 | 47 | /** 48 | * Add the menu page config. 49 | * 50 | * @param (MenuPage|SubmenuPage)[] $pages Menu and submenu page configurations. 51 | * 52 | * @return (MenuPage|SubmenuPage)[] 53 | */ 54 | public function addPage(array $pages): array 55 | { 56 | $pages[] = new SubmenuPage( 57 | 'wpcfg-cloudflare', 58 | 'wpcfg-bad-login', 59 | __('Bad Login', 'wp-cloudflare-guard'), 60 | __('WP Cloudflare Guard - Bad Login', 'wp-cloudflare-guard') 61 | ); 62 | 63 | return $pages; 64 | } 65 | 66 | /** 67 | * Add settings section config. 68 | * 69 | * @param Section[] $sections Settings section configurations. 70 | * 71 | * @return Section[] 72 | */ 73 | public function addSettingsSection(array $sections): array 74 | { 75 | $badUsernames = new Textarea( 76 | 'wpcfg_bad_login_bad_usernames', 77 | __('Bad Usernames', 'wp-cloudflare-guard') 78 | ); 79 | $badUsernames->getDecorator() 80 | ->setDescription( 81 | __( 82 | 'You can define your own bad usernames here, separated by commas.', 83 | 'wp-cloudflare-guard' 84 | ) 85 | ); 86 | 87 | $sections[] = new Section( 88 | 'wpcfg-bad-login', 89 | __('Bad Login', 'wp-cloudflare-guard'), 90 | $badUsernames 91 | ); 92 | 93 | return $sections; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/BadLogin/BadLogin.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG\BadLogin; 22 | 23 | use TypistTech\WPCFG\Blacklist\Event; 24 | use TypistTech\WPCFG\Cloudflare\Helper; 25 | use TypistTech\WPCFG\LoadableInterface; 26 | use TypistTech\WPCFG\OptionStore; 27 | use TypistTech\WPCFG\Vendor\TypistTech\WPContainedHook\Action; 28 | 29 | /** 30 | * Final class BadLogin. 31 | * 32 | * This class blacklist login with bad username. 33 | */ 34 | final class BadLogin implements LoadableInterface 35 | { 36 | /** 37 | * Cloudflare helper 38 | * 39 | * @var Helper 40 | */ 41 | private $helper; 42 | 43 | /** 44 | * The WPCFG option store 45 | * 46 | * @var OptionStore 47 | */ 48 | private $optionStore; 49 | 50 | /** 51 | * BadLogin constructor. 52 | * 53 | * @param OptionStore $optionStore The WPCFG option store. 54 | * @param Helper $helper The WPCFG Cloudflare helper. 55 | */ 56 | public function __construct(OptionStore $optionStore, Helper $helper) 57 | { 58 | $this->optionStore = $optionStore; 59 | $this->helper = $helper; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public static function getHooks(): array 66 | { 67 | return [ 68 | new Action('wp_authenticate', __CLASS__, 'emitBlacklistEventIfBadUsername'), 69 | ]; 70 | } 71 | 72 | /** 73 | * Emit blacklist event if username is bad. 74 | * 75 | * @param string|null $inputUsername User input. 76 | * 77 | * @void 78 | */ 79 | public function emitBlacklistEventIfBadUsername($inputUsername) 80 | { 81 | if (! $this->shouldBlacklist($inputUsername)) { 82 | return; 83 | } 84 | 85 | do_action( 86 | 'wpcfg_blacklist', 87 | $this->getBlacklistEventForCurrentIp($inputUsername) 88 | ); 89 | } 90 | 91 | /** 92 | * Check whether blacklist should be performed. 93 | * 94 | * @param mixed $inputUsername User input. 95 | * 96 | * @return bool 97 | */ 98 | private function shouldBlacklist($inputUsername): bool 99 | { 100 | if (! is_string($inputUsername) || empty($inputUsername)) { 101 | return false; 102 | } 103 | 104 | $isBadUsername = apply_filters( 105 | 'wpcfg_is_bad_username', 106 | $this->isBadUsername($inputUsername), 107 | $inputUsername 108 | ); 109 | 110 | return true === $isBadUsername; 111 | } 112 | 113 | /** 114 | * Check whether username input is a bad username. 115 | * 116 | * @param string $inputUsername User input. 117 | * 118 | * @return bool 119 | */ 120 | private function isBadUsername(string $inputUsername): bool 121 | { 122 | return in_array( 123 | $this->normalize($inputUsername), 124 | $this->getNormalizedBadUsernames(), 125 | true 126 | ); 127 | } 128 | 129 | /** 130 | * Normalize username string. 131 | * 132 | * @param string $username Un-normalized username. 133 | * 134 | * @return string 135 | */ 136 | private function normalize(string $username): string 137 | { 138 | return strtolower( 139 | sanitize_user($username, true) 140 | ); 141 | } 142 | 143 | /** 144 | * Get normalized bad usernames from database. 145 | * 146 | * @return array 147 | */ 148 | private function getNormalizedBadUsernames(): array 149 | { 150 | $normalized = array_map( 151 | [ $this, 'normalize' ], 152 | $this->optionStore->getBadUsernames() 153 | ); 154 | 155 | return array_filter( 156 | $normalized, 157 | function ($username) { 158 | return ! empty($username); 159 | } 160 | ); 161 | } 162 | 163 | /** 164 | * Make blacklist event for current ip and the given username. 165 | * 166 | * @param string $username The input username which is bad. 167 | * 168 | * @return Event 169 | */ 170 | private function getBlacklistEventForCurrentIp(string $username): Event 171 | { 172 | $note = sprintf( 173 | // Translators: %1$s is the bad username. 174 | _x('WPCFG: Try to login with bad username: %1$s', '%1$s is the bad username', 'wp-cloudflare-guard'), 175 | $username 176 | ); 177 | 178 | return new Event( 179 | $this->helper->getCurrentIp(), 180 | $note 181 | ); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Blacklist/Event.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG\Blacklist; 22 | 23 | /** 24 | * Final class Event. 25 | * 26 | * Immutable data transfer object that holds necessary information about a blacklist action. 27 | */ 28 | final class Event 29 | { 30 | /** 31 | * IP address to be blacklisted. 32 | * 33 | * @var string 34 | */ 35 | private $ipAddress; 36 | 37 | /** 38 | * Note of this event. 39 | * 40 | * @var string 41 | */ 42 | private $note; 43 | 44 | /** 45 | * Event constructor. 46 | * 47 | * @param string $ipAddress IP address to be blacklisted. 48 | * @param string $note Note of this event. 49 | */ 50 | public function __construct(string $ipAddress, string $note) 51 | { 52 | $this->ipAddress = $ipAddress; 53 | $this->note = $note; 54 | } 55 | 56 | /** 57 | * Ip address getter. 58 | * 59 | * @return string 60 | */ 61 | public function getIpAddress(): string 62 | { 63 | return $this->ipAddress; 64 | } 65 | 66 | /** 67 | * Note getter. 68 | * 69 | * @return string 70 | */ 71 | public function getNote(): string 72 | { 73 | return $this->note; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Blacklist/Handler.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG\Blacklist; 22 | 23 | use TypistTech\WPCFG\Cloudflare\AccessRules; 24 | use TypistTech\WPCFG\LoadableInterface; 25 | use TypistTech\WPCFG\Vendor\TypistTech\WPContainedHook\Action; 26 | 27 | /** 28 | * Final class Handler. 29 | * 30 | * This class handle the blacklist event. 31 | */ 32 | final class Handler implements LoadableInterface 33 | { 34 | /** 35 | * The api client. 36 | * 37 | * @var AccessRules 38 | */ 39 | private $accessRules; 40 | 41 | /** 42 | * Handler constructor. 43 | * 44 | * @param AccessRules $accessRules The api client. 45 | */ 46 | public function __construct(AccessRules $accessRules) 47 | { 48 | $this->accessRules = $accessRules; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public static function getHooks(): array 55 | { 56 | return [ 57 | new Action('wpcfg_blacklist', __CLASS__, 'handleBlacklist'), 58 | ]; 59 | } 60 | 61 | /** 62 | * Handle blacklist events. 63 | * 64 | * @param Event $event Immutable data transfer object that holds necessary information about this blacklist action. 65 | * 66 | * @return void 67 | */ 68 | public function handleBlacklist(Event $event) 69 | { 70 | $this->accessRules->create( 71 | 'block', 72 | [ 73 | 'target' => 'ip', 74 | 'value' => $event->getIpAddress(), 75 | ], 76 | $event->getNote() 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Cloudflare/AccessRules.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG\Cloudflare; 22 | 23 | use TypistTech\WPCFG\OptionStore; 24 | use TypistTech\WPCFG\Vendor\Cloudflare\Zone\Firewall\AccessRules as CloudflareAccessRules; 25 | 26 | /** 27 | * Final class AccessRules. 28 | */ 29 | final class AccessRules 30 | { 31 | /** 32 | * The api client 33 | * 34 | * @var CloudflareAccessRules 35 | */ 36 | private $client; 37 | 38 | /** 39 | * The WPCFG option store 40 | * 41 | * @var OptionStore 42 | */ 43 | private $optionStore; 44 | 45 | /** 46 | * AccessRules constructor. 47 | * 48 | * @param OptionStore $optionStore The WPCFG option store. 49 | * @param CloudflareAccessRules|null $client Optional. The api client. 50 | */ 51 | public function __construct(OptionStore $optionStore, CloudflareAccessRules $client = null) 52 | { 53 | $this->optionStore = $optionStore; 54 | $this->client = $client ?? new CloudflareAccessRules(); 55 | } 56 | 57 | /** 58 | * Create access rule (permission needed: #zone:edit) 59 | * Make a new IP, IP range, or country access rule for the zone. 60 | * 61 | * @param string $mode The action to apply to a matched request. 62 | * @param array $configuration Rule configuration. 63 | * @param string $notes A personal note about the rule. Typically used as a reminder or explanation for the 64 | * rule. 65 | * 66 | * @return array|\WP_Error 67 | */ 68 | public function create(string $mode, array $configuration, string $notes) 69 | { 70 | $this->setUpClient(); 71 | 72 | return $this->client->create( 73 | $this->optionStore->getZoneId(), 74 | $mode, 75 | $configuration, 76 | $notes 77 | ); 78 | } 79 | 80 | /** 81 | * Set up client auth key and email. 82 | * 83 | * @return void 84 | */ 85 | private function setUpClient() 86 | { 87 | $this->client->setAuthKey( 88 | $this->optionStore->getApiKey() 89 | ); 90 | $this->client->setEmail( 91 | $this->optionStore->getEmail() 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Cloudflare/Admin.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG\Cloudflare; 22 | 23 | use TypistTech\WPCFG\LoadableInterface; 24 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\Fields\Email; 25 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\Fields\Text; 26 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\Pages\MenuPage; 27 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\Section; 28 | use TypistTech\WPCFG\Vendor\TypistTech\WPContainedHook\Filter; 29 | 30 | /** 31 | * Final class Admin. 32 | * 33 | * The admin-specific functionality of Cloudflare settings. 34 | */ 35 | final class Admin implements LoadableInterface 36 | { 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public static function getHooks(): array 41 | { 42 | return [ 43 | new Filter('wpcfg_pages', __CLASS__, 'addPage'), 44 | new Filter('wpcfg_settings_sections', __CLASS__, 'addSettingsSection'), 45 | ]; 46 | } 47 | 48 | /** 49 | * Add the menu page config. 50 | * 51 | * @param (MenuPage|SubmenuPage)[] $pages Menu and submenu page configurations. 52 | * 53 | * @return (MenuPage|SubmenuPage)[] 54 | */ 55 | public function addPage(array $pages): array 56 | { 57 | $pages[] = new MenuPage( 58 | 'wpcfg-cloudflare', 59 | __('WP Cloudflare Guard', 'wp-cloudflare-guard'), 60 | __('WP Cloudflare Guard - Cloudflare', 'wp-cloudflare-guard'), 61 | null, 62 | 'dashicons-shield' 63 | ); 64 | 65 | return $pages; 66 | } 67 | 68 | /** 69 | * Add settings section config. 70 | * 71 | * @param Section[] $sections Settings section configurations. 72 | * 73 | * @return Section[] 74 | */ 75 | public function addSettingsSection(array $sections): array 76 | { 77 | $email = new Email( 78 | 'wpcfg_cloudflare_email', 79 | __('Cloudflare Email', 'wp-cloudflare-guard') 80 | ); 81 | $email->getDecorator() 82 | ->setDescription( 83 | __( 84 | 'The email address associated with your Cloudflare account.', 85 | 'wp-cloudflare-guard' 86 | ) 87 | ); 88 | 89 | $apiKey = new Text( 90 | 'wpcfg_cloudflare_api_key', 91 | __('Global API Key', 'wp-cloudflare-guard') 92 | ); 93 | $apiKeyDesc = sprintf( 94 | // Translators: %1$s is the url to Cloudflare document. 95 | __('Help: Where do I find my Cloudflare API key?', 'wp-cloudflare-guard'), 96 | esc_url_raw( 97 | 'https://support.cloudflare.com/hc/en-us/articles/200167836-Where-do-I-find-my-CloudFlare-API-key-' 98 | ) 99 | ); 100 | $apiKey->getDecorator() 101 | ->setDescription($apiKeyDesc); 102 | 103 | $zoneId = new Text( 104 | 'wpcfg_cloudflare_zone_id', 105 | __('Zone ID', 'wp-cloudflare-guard') 106 | ); 107 | $zoneId->getDecorator() 108 | ->setDescription( 109 | __('Zone identifier for this domain', 'wp-cloudflare-guard') 110 | ); 111 | 112 | $sections[] = new Section( 113 | 'wpcfg-cloudflare', 114 | __('Cloudflare Settings', 'wp-cloudflare-guard'), 115 | $email, 116 | $apiKey, 117 | $zoneId 118 | ); 119 | 120 | return $sections; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Cloudflare/Helper.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG\Cloudflare; 22 | 23 | use TypistTech\WPCFG\Vendor\CloudFlare\IpRewrite; 24 | 25 | /** 26 | * Final class Helper 27 | */ 28 | final class Helper 29 | { 30 | /** 31 | * CloudFlare's IpRewrite instance 32 | * 33 | * @var IpRewrite 34 | */ 35 | private $ipRewrite; 36 | 37 | /** 38 | * Helper constructor. 39 | * 40 | * Side effect: If current request is coming through Cloudflare, 41 | * $_SERVER["REMOTE_ADDR"] will be rewritten to reflect the end-user's 42 | * IP address. 43 | */ 44 | public function __construct() 45 | { 46 | $this->ipRewrite = new IpRewrite(); 47 | } 48 | 49 | /** 50 | * Retrieve the real ip address of the user in the current request. 51 | * 52 | * @return string IP Address or empty string on error 53 | */ 54 | public function getCurrentIp(): string 55 | { 56 | $rewrittenIp = $this->getRewrittenIp(); 57 | 58 | if ('' !== $rewrittenIp) { 59 | return $rewrittenIp; 60 | } 61 | 62 | return $this->getOriginalIp(); 63 | } 64 | 65 | /** 66 | * Gets the re-written IP after IpRewrite::rewrite() is run. 67 | * 68 | * IpRewrite::getRewrittenIP returns $_SERVER['HTTP_CF_CONNECTING_IP'] 69 | * without sanitization, so treat them as superglobal usage 70 | * as per WordPress coding standard principle. 71 | * 72 | * @return string IP Address or empty string on error 73 | */ 74 | private function getRewrittenIp(): string 75 | { 76 | return sanitize_text_field( 77 | wp_unslash( 78 | $this->ipRewrite->getRewrittenIP() 79 | ) 80 | ); 81 | } 82 | 83 | /** 84 | * Get the original IP Address of a given request. 85 | * 86 | * IpRewrite::getOriginalIP returns $_SERVER['REMOTE_ADDR'] 87 | * without sanitization, so treat them as superglobal usage 88 | * as per WordPress coding standard principle. 89 | * 90 | * @return string IP Address or empty string on error 91 | */ 92 | private function getOriginalIp(): string 93 | { 94 | return sanitize_text_field( 95 | wp_unslash( 96 | $this->ipRewrite->getOriginalIP() 97 | ) 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Container.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG; 22 | 23 | use TypistTech\WPCFG\Ads\I18nPromoter; 24 | use TypistTech\WPCFG\Ads\ReviewNotice; 25 | use TypistTech\WPCFG\BadLogin\Admin as BadLoginAdmin; 26 | use TypistTech\WPCFG\BadLogin\BadLogin; 27 | use TypistTech\WPCFG\Blacklist\Handler; 28 | use TypistTech\WPCFG\Cloudflare\AccessRules; 29 | use TypistTech\WPCFG\Cloudflare\Admin as CloudflareAdmin; 30 | use TypistTech\WPCFG\Vendor\League\Container\Container as LeagueContainer; 31 | use TypistTech\WPCFG\Vendor\League\Container\ReflectionContainer; 32 | 33 | /** 34 | * Final class Container. 35 | */ 36 | final class Container extends LeagueContainer 37 | { 38 | /** 39 | * Initialize container. 40 | * 41 | * @return void 42 | */ 43 | public function initialize() 44 | { 45 | $this->delegate(new ReflectionContainer()); 46 | $this->add(self::class, $this); 47 | 48 | $optionStore = new OptionStore(); 49 | $admin = new Admin($optionStore); 50 | $this->add('\\' . OptionStore::class, $optionStore); 51 | $this->add('\\' . Admin::class, $admin); 52 | 53 | $keys = [ 54 | AccessRules::class, 55 | BadLogin::class, 56 | BadLoginAdmin::class, 57 | CloudflareAdmin::class, 58 | Handler::class, 59 | I18n::class, 60 | I18nPromoter::class, 61 | ReviewNotice::class, 62 | ]; 63 | foreach ($keys as $key) { 64 | $this->add('\\' . $key); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/I18n.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG; 22 | 23 | use TypistTech\WPCFG\Vendor\TypistTech\WPContainedHook\Action; 24 | 25 | /** 26 | * Define the internationalization functionality. 27 | * 28 | * Loads and defines the internationalization files for this plugin 29 | * so that it is ready for translation. 30 | */ 31 | final class I18n implements LoadableInterface 32 | { 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public static function getHooks(): array 37 | { 38 | return [ 39 | new Action('plugins_loaded', __CLASS__, 'loadPluginTextdomain'), 40 | ]; 41 | } 42 | 43 | /** 44 | * Load the plugin text domain for translation. 45 | * 46 | * @return void 47 | */ 48 | public function loadPluginTextdomain() 49 | { 50 | load_plugin_textdomain( 51 | 'wp-cloudflare-guard', 52 | false, 53 | dirname(plugin_basename(__FILE__), 2) . '/languages/' 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/LoadableInterface.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG; 22 | 23 | /** 24 | * Interface LoadableInterface 25 | */ 26 | interface LoadableInterface 27 | { 28 | /** 29 | * Hooks (Action or Filter) getter. 30 | * 31 | * @return \TypistTech\WPCFG\Vendor\TypistTech\WPContainedHook\AbstractHook[] 32 | */ 33 | public static function getHooks(): array; 34 | } 35 | -------------------------------------------------------------------------------- /src/OptionStore.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG; 22 | 23 | use TypistTech\WPCFG\Vendor\TypistTech\WPBetterSettings\OptionStore as WPBSOptionStore; 24 | 25 | /** 26 | * Final class OptionStore 27 | * 28 | * The get_option functionality of the plugin. 29 | */ 30 | final class OptionStore extends WPBSOptionStore 31 | { 32 | /** 33 | * Cloudflare api key getter. 34 | * 35 | * @return string 36 | */ 37 | public function getApiKey(): string 38 | { 39 | $value = $this->get('wpcfg_cloudflare_api_key'); 40 | 41 | return is_string($value) ? $value : ''; 42 | } 43 | 44 | /** 45 | * Bad usernames getter. 46 | * 47 | * @return string[] 48 | */ 49 | public function getBadUsernames(): array 50 | { 51 | $value = $this->get('wpcfg_bad_login_bad_usernames'); 52 | 53 | if (! is_string($value)) { 54 | return []; 55 | } 56 | 57 | return array_map( 58 | function (string $username) { 59 | return sanitize_user($username, true); 60 | }, 61 | explode(',', $value) 62 | ); 63 | } 64 | 65 | /** 66 | * Cloudflare email getter. 67 | * 68 | * @return string 69 | */ 70 | public function getEmail(): string 71 | { 72 | $value = $this->get('wpcfg_cloudflare_email'); 73 | 74 | return is_string($value) ? $value : ''; 75 | } 76 | 77 | /** 78 | * Cloudflare zone id getter. 79 | * 80 | * @return string 81 | */ 82 | public function getZoneId(): string 83 | { 84 | $value = $this->get('wpcfg_cloudflare_zone_id'); 85 | 86 | return is_string($value) ? $value : ''; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/WPCFG.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | declare(strict_types=1); 20 | 21 | namespace TypistTech\WPCFG; 22 | 23 | use TypistTech\WPCFG\Ads\I18nPromoter; 24 | use TypistTech\WPCFG\Ads\ReviewNotice; 25 | use TypistTech\WPCFG\BadLogin\Admin as BadLoginAdmin; 26 | use TypistTech\WPCFG\BadLogin\BadLogin; 27 | use TypistTech\WPCFG\Blacklist\Handler; 28 | use TypistTech\WPCFG\Cloudflare\Admin as CloudflareAdmin; 29 | use TypistTech\WPCFG\Vendor\TypistTech\WPContainedHook\Action; 30 | use TypistTech\WPCFG\Vendor\TypistTech\WPContainedHook\Loader; 31 | 32 | /** 33 | * Final class WPCFG 34 | * 35 | * The core plugin class. 36 | */ 37 | final class WPCFG implements LoadableInterface 38 | { 39 | /** 40 | * The dependency injection container. 41 | * 42 | * @var Container 43 | */ 44 | private $container; 45 | 46 | /** 47 | * The loader that's responsible for maintaining and registering all hooks that power 48 | * the plugin. 49 | * 50 | * @var Loader Maintains and registers all hooks for the plugin. 51 | */ 52 | private $loader; 53 | 54 | /** 55 | * WPCFG constructor. 56 | */ 57 | public function __construct() 58 | { 59 | $this->container = new Container(); 60 | $this->loader = new Loader($this->container); 61 | 62 | $this->container->initialize(); 63 | 64 | $loadables = [ 65 | __CLASS__, 66 | Admin::class, 67 | BadLogin::class, 68 | BadLoginAdmin::class, 69 | Handler::class, 70 | CloudflareAdmin::class, 71 | I18n::class, 72 | I18nPromoter::class, 73 | ReviewNotice::class, 74 | ]; 75 | 76 | foreach ($loadables as $loadable) { 77 | /* @var LoadableInterface $loadable */ 78 | $this->loader->add(...$loadable::getHooks()); 79 | } 80 | } 81 | 82 | /** 83 | * {@inheritdoc} 84 | */ 85 | public static function getHooks(): array 86 | { 87 | return [ 88 | new Action(__CLASS__, 'plugin_loaded', 'giveContainer', 5), 89 | ]; 90 | } 91 | 92 | /** 93 | * Expose Container via WordPress action. 94 | * 95 | * @return void 96 | */ 97 | public function giveContainer() 98 | { 99 | do_action('wpcfg_get_container', $this->container); 100 | } 101 | 102 | /** 103 | * Run the loader to execute all of the hooks with WordPress. 104 | * 105 | * @return void 106 | */ 107 | public function run() 108 | { 109 | $this->loader->run(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | loginAsAdmin(); 33 | $I->waitForText('Dashboard', 10, 'h1'); 34 | $I->seeLink('WP Cloudflare Guard'); 35 | $I->click('WP Cloudflare Guard'); 36 | $I->click('WP Cloudflare Guard'); 37 | $I->waitForText('WP Cloudflare Guard', 10, 'h1'); 38 | $I->seeInCurrentUrl('/wp-admin/admin.php?page=wpcfg-cloudflare'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/_support/FunctionalTester.php: -------------------------------------------------------------------------------- 1 | amOnAdminPage('/admin.php?page=wpcfg-cloudflare'); 32 | } 33 | 34 | public function loginToWPCFGSettingPage() 35 | { 36 | $this->loginAsAdmin(); 37 | $this->amOnAdminPage('/admin.php?page=wpcfg-cloudflare'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/_support/Helper/Acceptance.php: -------------------------------------------------------------------------------- 1 | remoteAddrBackup; 27 | unset($_SERVER['HTTP_CF_CONNECTING_IP']); 28 | 29 | delete_option('wpcfg_cloudflare_email'); 30 | delete_option('wpcfg_cloudflare_api_key'); 31 | delete_option('wpcfg_cloudflare_zone_id'); 32 | delete_option('wpcfg_bad_login_bad_usernames'); 33 | } 34 | 35 | public function _before(TestInterface $test) 36 | { 37 | $this->remoteAddrBackup = $_SERVER['REMOTE_ADDR']; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/_support/Helper/Unit.php: -------------------------------------------------------------------------------- 1 | container = new Container(); 32 | $this->container->initialize(); 33 | 34 | return $this->container; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/_support/UnitTester.php: -------------------------------------------------------------------------------- 1 | wantTo('setting page has tab links'); 9 | 10 | $I->amOnWPCFGSettingPage(); 11 | 12 | $I->seeElement('#wpcfg-cloudflare-tab'); 13 | $I->seeElement('#wpcfg-bad-login-tab'); 14 | 15 | $I->click('#wpcfg-bad-login-tab'); 16 | 17 | $I->waitForText('WP Cloudflare Guard - Bad Login', 10, 'h1'); 18 | $I->seeInCurrentUrl('/wp-admin/admin.php?page=wpcfg-bad-login'); 19 | $I->seeElement('#wpcfg-cloudflare-tab'); 20 | $I->seeElement('#wpcfg-bad-login-tab'); 21 | 22 | $I->click('#wpcfg-cloudflare-tab'); 23 | $I->waitForText('WP Cloudflare Guard', 10, 'h1'); 24 | $I->seeInCurrentUrl('/wp-admin/admin.php?page=wpcfg-cloudflare'); 25 | -------------------------------------------------------------------------------- /tests/acceptance/_bootstrap.php: -------------------------------------------------------------------------------- 1 | wantToTest('review notice shows up after ten days'); 9 | 10 | $optionName = 'wrm_a87d157b6f87b8521211'; 11 | $I->amGoingTo('reset WP Review Me installed time'); 12 | $I->dontHaveOptionInDatabase($optionName); 13 | 14 | $I->loginToWPCFGSettingPage(); 15 | 16 | $I->wantToTest('WP Review Me installed time is set within the past ten seconds'); 17 | $installed = $I->grabOptionFromDatabase($optionName); 18 | $I->assertGreaterOrEquals(time() - 10, $installed); 19 | $I->assertLessOrEquals(time(), $installed); 20 | 21 | $I->amGoingTo('fast forward ten days and one minute (864060 seconds)'); 22 | $I->haveOptionInDatabase($optionName, time() - 864060); 23 | 24 | $I->amOnAdminPage('/options-general.php'); 25 | $I->seeLink( 26 | 'Click here to leave your review', 27 | 'https://wordpress.org/support/plugin/wp-cloudflare-guard/reviews?rate=5#new-post' 28 | ); 29 | -------------------------------------------------------------------------------- /tests/functional/_bootstrap.php: -------------------------------------------------------------------------------- 1 | tester->getContainer(); 33 | 34 | $admin = Test::double( 35 | $container->get(Admin::class), 36 | [ 37 | 'getMenuSlugs' => [ 38 | 'wpcfg-cloudflare', 39 | 'wpcfg-bad-login', 40 | ], 41 | ] 42 | ); 43 | $container->share(Admin::class, $admin->getObject()); 44 | 45 | $this->i18nPromoter = $container->get(I18nPromoter::class); 46 | } 47 | 48 | /** 49 | * @coversNothing 50 | */ 51 | public function testGetFromContainer() 52 | { 53 | $this->assertInstanceOf( 54 | I18nPromoter::class, 55 | $this->tester->getContainer()->get(I18nPromoter::class) 56 | ); 57 | } 58 | 59 | /** 60 | * @covers ::getHooks 61 | */ 62 | public function testHookedIntoAdminMenu() 63 | { 64 | $actual = I18nPromoter::getHooks(); 65 | 66 | $expected = [ new Action('admin_menu', I18nPromoter::class, 'run', 20) ]; 67 | 68 | $this->assertEquals($expected, $actual); 69 | } 70 | 71 | /** 72 | * @covers ::run 73 | */ 74 | public function testYoastI18nWordPressOrgV2Initialized() 75 | { 76 | $yoastI18nWordPressOrgV2 = Test::double(Yoast_I18n_WordPressOrg_v2::class); 77 | 78 | $this->i18nPromoter->run(); 79 | 80 | $yoastI18nWordPressOrgV2->verifyInvokedMultipleTimes('__construct', 2); 81 | $yoastI18nWordPressOrgV2->verifyInvokedOnce( 82 | '__construct', 83 | [ 84 | [ 85 | 'textdomain' => 'wp-cloudflare-guard', 86 | 'plugin_name' => 'WP Cloudflare Guard', 87 | 'hook' => 'wpcfg_cloudflare_after_option_form', 88 | ], 89 | ] 90 | ); 91 | $yoastI18nWordPressOrgV2->verifyInvokedOnce( 92 | '__construct', 93 | [ 94 | [ 95 | 'textdomain' => 'wp-cloudflare-guard', 96 | 'plugin_name' => 'WP Cloudflare Guard', 97 | 'hook' => 'wpcfg_bad_login_after_option_form', 98 | ], 99 | ] 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /tests/integration/Ads/ReviewNoticeTest.php: -------------------------------------------------------------------------------- 1 | reviewNotice = new ReviewNotice(); 31 | } 32 | 33 | /** 34 | * @coversNothing 35 | */ 36 | public function testGetFromContainer() 37 | { 38 | $this->assertInstanceOf( 39 | ReviewNotice::class, 40 | $this->tester->getContainer()->get(ReviewNotice::class) 41 | ); 42 | } 43 | 44 | /** 45 | * @covers ::getHooks 46 | */ 47 | public function testHookedIntoAdminInit() 48 | { 49 | $actual = ReviewNotice::getHooks(); 50 | 51 | $expected = [ new Action('admin_init', ReviewNotice::class, 'run') ]; 52 | 53 | $this->assertEquals($expected, $actual); 54 | } 55 | 56 | /** 57 | * @covers ::run 58 | */ 59 | public function testWPReviewMeInitialized() 60 | { 61 | $wpReviewMe = Test::double(WP_Review_Me::class); 62 | 63 | $this->reviewNotice->run(); 64 | 65 | $wpReviewMe->verifyInvokedMultipleTimes('__construct', 1); 66 | 67 | $params = $wpReviewMe->getCallsForMethod('__construct')[0][0]; 68 | 69 | $this->assertSame('plugin', $params['type']); 70 | $this->assertSame('wp-cloudflare-guard', $params['slug']); 71 | $this->assertInternalType('string', $params['message']); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/integration/BadLogin/BadLoginTest.php: -------------------------------------------------------------------------------- 1 | tester->getContainer(); 37 | $this->badLogin = $container->get(BadLogin::class); 38 | $this->doActionMock = Test::func(__NAMESPACE__, 'do_action', 'done'); 39 | } 40 | 41 | /** 42 | * @covers \TypistTech\WPCFG\BadLogin\BadLogin 43 | */ 44 | public function testCanTriggerBlacklistEventWhenMultipleBadUsernamesAreSaved() 45 | { 46 | update_option('wpcfg_bad_login_bad_usernames', 'bad-boy, bad-girl'); 47 | 48 | $this->badLogin->emitBlacklistEventIfBadUsername('bad-boy'); 49 | $this->badLogin->emitBlacklistEventIfBadUsername('bad-girl'); 50 | 51 | $actual = $this->doActionMock->getCallsForMethod('do_action'); 52 | 53 | $expected = [ 54 | [ 'wpcfg_blacklist', new Event('127.0.0.1', 'WPCFG: Try to login with bad username: bad-boy') ], 55 | [ 'wpcfg_blacklist', new Event('127.0.0.1', 'WPCFG: Try to login with bad username: bad-girl') ], 56 | ]; 57 | 58 | $this->assertEquals($expected, $actual); 59 | $this->doActionMock->verifyInvokedMultipleTimes(2); 60 | } 61 | 62 | /** 63 | * @covers \TypistTech\WPCFG\BadLogin\BadLogin 64 | */ 65 | public function testCanTriggerBlacklistEventWhenSingleBadUsernameIsSaved() 66 | { 67 | update_option('wpcfg_bad_login_bad_usernames', 'bad-boy'); 68 | 69 | $this->badLogin->emitBlacklistEventIfBadUsername('bad-boy'); 70 | 71 | $actual = $this->doActionMock->getCallsForMethod('do_action'); 72 | 73 | $expected = [ 74 | [ 'wpcfg_blacklist', new Event('127.0.0.1', 'WPCFG: Try to login with bad username: bad-boy') ], 75 | ]; 76 | 77 | $this->assertEquals($expected, $actual); 78 | $this->doActionMock->verifyInvokedMultipleTimes(1); 79 | } 80 | 81 | /** 82 | * @coversNothing 83 | */ 84 | public function testGetFromContainer() 85 | { 86 | $this->assertInstanceOf( 87 | BadLogin::class, 88 | $this->tester->getContainer()->get(BadLogin::class) 89 | ); 90 | } 91 | 92 | /** 93 | * @covers \TypistTech\WPCFG\BadLogin\BadLogin 94 | */ 95 | public function testNormalizeInputUsername() 96 | { 97 | update_option('wpcfg_bad_login_bad_usernames', 'bad-boy, bad-girl'); 98 | 99 | $this->badLogin->emitBlacklistEventIfBadUsername('#b!{a}d;-b>oydoActionMock->getCallsForMethod('do_action'); 102 | 103 | $expected = [ 104 | [ 'wpcfg_blacklist', new Event('127.0.0.1', 'WPCFG: Try to login with bad username: #b!{a}d;-b>oyassertEquals($expected, $actual); 108 | $this->doActionMock->verifyInvokedMultipleTimes(1); 109 | } 110 | 111 | /** 112 | * @covers \TypistTech\WPCFG\BadLogin\BadLogin 113 | */ 114 | public function testNormalizeSavedBadUsernames() 115 | { 116 | update_option('wpcfg_bad_login_bad_usernames', ' #b!{a}d;-b>oyb}!a{d->>gir!>##lbadLogin->emitBlacklistEventIfBadUsername('bad-boy'); 119 | $this->badLogin->emitBlacklistEventIfBadUsername('bad-girl'); 120 | 121 | $actual = $this->doActionMock->getCallsForMethod('do_action'); 122 | 123 | $expected = [ 124 | [ 'wpcfg_blacklist', new Event('127.0.0.1', 'WPCFG: Try to login with bad username: bad-boy') ], 125 | [ 'wpcfg_blacklist', new Event('127.0.0.1', 'WPCFG: Try to login with bad username: bad-girl') ], 126 | ]; 127 | 128 | $this->assertEquals($expected, $actual); 129 | $this->doActionMock->verifyInvokedMultipleTimes(2); 130 | } 131 | 132 | /** 133 | * @covers \TypistTech\WPCFG\BadLogin\BadLogin 134 | */ 135 | public function testSkipsEmptyUsername() 136 | { 137 | update_option('wpcfg_bad_login_bad_usernames', ''); 138 | 139 | $this->badLogin->emitBlacklistEventIfBadUsername(''); 140 | 141 | $this->doActionMock->verifyNeverInvoked(); 142 | } 143 | 144 | /** 145 | * @covers \TypistTech\WPCFG\BadLogin\BadLogin 146 | */ 147 | public function testSkipsForNotBadUsername() 148 | { 149 | update_option('wpcfg_bad_login_bad_usernames', 'bad-boy, bad-girl'); 150 | 151 | $this->badLogin->emitBlacklistEventIfBadUsername('good-boy'); 152 | 153 | $this->doActionMock->verifyNeverInvoked(); 154 | } 155 | 156 | /** 157 | * @covers \TypistTech\WPCFG\BadLogin\BadLogin 158 | */ 159 | public function testSkipsIfNoBadUsernameIsSaved() 160 | { 161 | delete_option('wpcfg_bad_login_bad_usernames'); 162 | 163 | $this->badLogin->emitBlacklistEventIfBadUsername('bad-boy'); 164 | 165 | $this->doActionMock->verifyNeverInvoked(); 166 | } 167 | 168 | /** 169 | * @covers ::getHooks 170 | */ 171 | public function testsHookedIntoWpAuthenticate() 172 | { 173 | $actual = BadLogin::getHooks(); 174 | 175 | $expected = [ 176 | new Action('wp_authenticate', BadLogin::class, 'emitBlacklistEventIfBadUsername'), 177 | ]; 178 | 179 | $this->assertEquals($expected, $actual); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /tests/integration/Blacklist/HandlerTest.php: -------------------------------------------------------------------------------- 1 | tester->getContainer(); 37 | 38 | $this->accessRules = Test::double( 39 | $container->get(AccessRules::class), 40 | [ 41 | 'create' => [ true ], 42 | ] 43 | ); 44 | $container->add(AccessRules::class, $this->accessRules->getObject()); 45 | 46 | $this->handler = $container->get(Handler::class); 47 | } 48 | 49 | /** 50 | * @coversNothing 51 | */ 52 | public function testGetFromContainer() 53 | { 54 | $this->assertInstanceOf( 55 | Handler::class, 56 | $this->tester->getContainer()->get(Handler::class) 57 | ); 58 | } 59 | 60 | /** 61 | * @covers \TypistTech\WPCFG\Blacklist\Handler 62 | */ 63 | public function testHandleBlacklistEvent() 64 | { 65 | $event = new Event('127.0.0.1', 'some note'); 66 | 67 | $this->handler->handleBlacklist($event); 68 | 69 | $expectedCreate = [ 70 | 'block', 71 | [ 72 | 'target' => 'ip', 73 | 'value' => '127.0.0.1', 74 | ], 75 | 'some note', 76 | ]; 77 | 78 | $this->accessRules->verifyInvokedMultipleTimes('create', 1); 79 | $actualCreate = $this->accessRules->getCallsForMethod('create')[0]; 80 | $this->assertEquals($expectedCreate, $actualCreate); 81 | } 82 | 83 | /** 84 | * @covers ::getHooks 85 | */ 86 | public function testHookedIntoWpcfgBlacklist() 87 | { 88 | $actual = Handler::getHooks(); 89 | 90 | $expected = [ 91 | new Action('wpcfg_blacklist', Handler::class, 'handleBlacklist'), 92 | ]; 93 | 94 | $this->assertEquals($expected, $actual); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/integration/Cloudflare/AccessRulesTest.php: -------------------------------------------------------------------------------- 1 | cloudflareAccessRules = Test::double( 43 | new CloudflareAccessRules(), 44 | [ 45 | 'create' => [ true ], 46 | ] 47 | ); 48 | 49 | $this->container = $this->tester->getContainer(); 50 | 51 | $optionStore = Test::double( 52 | $this->container->get(OptionStore::class), 53 | [ 54 | 'getApiKey' => 'my-api-key', 55 | 'getEmail' => 'me@example.com', 56 | 'getZoneId' => 'my-zone', 57 | ] 58 | )->getObject(); 59 | $this->container->add(OptionStore::class, $optionStore); 60 | 61 | $this->accessRules = new AccessRules( 62 | $optionStore, 63 | $this->cloudflareAccessRules->getObject() 64 | ); 65 | } 66 | 67 | /** 68 | * @covers ::create 69 | */ 70 | public function testCreate() 71 | { 72 | $this->accessRules->create( 73 | 'block', 74 | [ 75 | 'target' => 'ip', 76 | 'value' => '127.0.0.1', 77 | ], 78 | 'some note' 79 | ); 80 | 81 | $expectedParams = [ 82 | 'my-zone', 83 | 'block', 84 | [ 85 | 'target' => 'ip', 86 | 'value' => '127.0.0.1', 87 | ], 88 | 'some note', 89 | ]; 90 | 91 | $this->cloudflareAccessRules->verifyInvokedMultipleTimes('setEmail', 1); 92 | $this->cloudflareAccessRules->verifyInvokedOnce('setEmail', [ 'me@example.com' ]); 93 | 94 | $this->cloudflareAccessRules->verifyInvokedMultipleTimes('setAuthKey', 1); 95 | $this->cloudflareAccessRules->verifyInvokedOnce('setAuthKey', [ 'my-api-key' ]); 96 | 97 | $this->cloudflareAccessRules->verifyInvokedMultipleTimes('create', 1); 98 | $this->cloudflareAccessRules->verifyInvokedOnce('create', $expectedParams); 99 | } 100 | 101 | /** 102 | * @coversNothing 103 | */ 104 | public function testGetFromContainer() 105 | { 106 | $this->assertInstanceOf( 107 | AccessRules::class, 108 | $this->tester->getContainer()->get(AccessRules::class) 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/integration/Cloudflare/HelperTest.php: -------------------------------------------------------------------------------- 1 | getCurrentIp(); 25 | 26 | $this->assertSame($connectingIp, $actual); 27 | } 28 | 29 | /** 30 | * @covers ::getCurrentIp 31 | */ 32 | public function testGetCurrentIpWhenNotComingThroughCloudflare() 33 | { 34 | $remoteAddr = '103.21.244.2'; 35 | 36 | $_SERVER['REMOTE_ADDR'] = $remoteAddr; 37 | 38 | $helper = new Helper(); 39 | $actual = $helper->getCurrentIp(); 40 | 41 | $this->assertSame($remoteAddr, $actual); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/integration/OptionStoreTest.php: -------------------------------------------------------------------------------- 1 | optionStore = new OptionStore(); 34 | } 35 | 36 | /** 37 | * @covers ::getApiKey 38 | */ 39 | public function testGetApiKey() 40 | { 41 | $actual = $this->optionStore->getApiKey(); 42 | $this->assertSame('passkey123', $actual); 43 | } 44 | 45 | /** 46 | * @covers ::getBadUsernames 47 | */ 48 | public function testGetBadUsernames() 49 | { 50 | $actual = $this->optionStore->getBadUsernames(); 51 | 52 | $expected = [ 'tom', 'mary', 'peter' ]; 53 | 54 | $this->assertSame($expected, $actual); 55 | } 56 | 57 | /** 58 | * @covers ::getEmail 59 | */ 60 | public function testGetEmail() 61 | { 62 | $actual = $this->optionStore->getEmail(); 63 | $this->assertSame('tester@example.com', $actual); 64 | } 65 | 66 | /** 67 | * @coversNothing 68 | */ 69 | public function testGetFromContainer() 70 | { 71 | $this->assertInstanceOf( 72 | OptionStore::class, 73 | $this->tester->getContainer()->get(OptionStore::class) 74 | ); 75 | } 76 | 77 | /** 78 | * @covers ::getBadUsernames 79 | */ 80 | public function testGetSingleBadUsername() 81 | { 82 | update_option('wpcfg_bad_login_bad_usernames', 'tom '); 83 | 84 | $actual = $this->optionStore->getBadUsernames(); 85 | 86 | $expected = [ 'tom' ]; 87 | 88 | $this->assertSame($expected, $actual); 89 | } 90 | 91 | /** 92 | * @covers ::getZoneId 93 | */ 94 | public function testGetZoneId() 95 | { 96 | $actual = $this->optionStore->getZoneId(); 97 | $this->assertSame('two46o1', $actual); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/integration/_bootstrap.php: -------------------------------------------------------------------------------- 1 | init( 11 | [ 12 | 'debug' => true, 13 | 'includePaths' => [ 14 | codecept_root_dir('lib/'), 15 | codecept_root_dir('src/'), 16 | codecept_root_dir('vendor/jamesryanbell/cloudflare/src/'), 17 | ], 18 | ] 19 | ); 20 | -------------------------------------------------------------------------------- /tests/unit.suite.yml: -------------------------------------------------------------------------------- 1 | # Codeception Test Suite Configuration 2 | # 3 | # Suite for unit (internal) tests. 4 | 5 | class_name: UnitTester 6 | modules: 7 | enabled: 8 | - Asserts 9 | - \TypistTech\WPCFG\Helper\Unit 10 | coverage: 11 | enabled: true 12 | -------------------------------------------------------------------------------- /tests/unit/Blacklist/EventTest.php: -------------------------------------------------------------------------------- 1 | getIpAddress(); 21 | $this->assertSame('127.0.0.1', $actual); 22 | } 23 | 24 | /** 25 | * @covers ::getNote 26 | */ 27 | public function testHasNoteGetter() 28 | { 29 | $event = new Event('127.0.0.1', 'some note'); 30 | $actual = $event->getNote(); 31 | $this->assertSame('some note', $actual); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/unit/_bootstrap.php: -------------------------------------------------------------------------------- 1 | init( 11 | [ 12 | 'debug' => true, 13 | 'includePaths' => [ 14 | codecept_root_dir('lib/'), 15 | codecept_root_dir('src/'), 16 | ], 17 | ] 18 | ); 19 | -------------------------------------------------------------------------------- /uninstall.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | /** 20 | * Fired when the plugin is uninstalled. 21 | * 22 | * When populating this file, consider the following flow 23 | * of control: 24 | * 25 | * - This method should be static 26 | * - Check if the $_REQUEST content actually is the plugin name 27 | * - Run an admin referrer check to make sure it goes through authentication 28 | * - Verify the output of $_GET makes sense 29 | * - Repeat with other user roles. Best directly by using the links/query string parameters. 30 | * - Repeat things for multisite. Once for a single site in the network, once sitewide. 31 | * 32 | * This file may be updated more in future version of the Boilerplate; however, this is the 33 | * general skeleton and outline for how the file should work. 34 | * 35 | * For more information, see the following discussion: 36 | * https://github.com/tommcfarlin/WordPress-Plugin-Boilerplate/pull/123#issuecomment-28541913 37 | */ 38 | 39 | declare(strict_types=1); 40 | 41 | // If uninstall not called from WordPress, then exit. 42 | if (! defined('WP_UNINSTALL_PLUGIN')) { 43 | exit; 44 | } 45 | 46 | $keys = [ 47 | 'wpcfg_cloudflare_email', 48 | 'wpcfg_cloudflare_api_key', 49 | 'wpcfg_cloudflare_zone_id', 50 | 'wpcfg_bad_login_bad_usernames', 51 | 'wpcfg_cloudflare', 52 | 'wpcfg_bad_login', 53 | ]; 54 | 55 | foreach ($keys as $key) { 56 | delete_option($key); 57 | } 58 | -------------------------------------------------------------------------------- /wp-cloudflare-guard.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright 2017 Typist Tech 13 | * @license GPL-2.0+ 14 | * 15 | * @see https://www.typist.tech/projects/wp-cloudflare-guard 16 | * @see https://wordpress.org/plugins/wp-cloudflare-guard/ 17 | */ 18 | 19 | /** 20 | * Plugin Name: WP CloudFlare Guard 21 | * Plugin URI: https://www.typist.tech/ 22 | * Description: Connecting WordPress with Cloudflare firewall. 23 | * Version: 0.2.0 24 | * Author: Typist Tech 25 | * Author URI: https://www.typist.tech/ 26 | * License: GPL-2.0+ 27 | * License URI: http://www.gnu.org/licenses/gpl-2.0.txt 28 | * Text Domain: wp-cloudflare-guard 29 | * Domain Path: /languages 30 | */ 31 | 32 | declare(strict_types=1); 33 | 34 | namespace TypistTech\WPCFG; 35 | 36 | // If this file is called directly, abort. 37 | if (! defined('WPINC')) { 38 | die; 39 | } 40 | 41 | require_once plugin_dir_path(__FILE__) . 'vendor/autoload.php'; 42 | require_once plugin_dir_path(__FILE__) . '/lib/julien731/wp-dismissible-notices-handler/handler.php'; 43 | require_once plugin_dir_path(__FILE__) . '/lib/julien731/wp-dismissible-notices-handler/includes/helper-functions.php'; 44 | 45 | /** 46 | * Begins execution of the plugin. 47 | * Since everything within the plugin is registered via hooks, 48 | * then kicking off the plugin from this point in the file does 49 | * not affect the page life cycle. 50 | * 51 | * @return void 52 | */ 53 | function run() 54 | { 55 | $plugin = new WPCFG(); 56 | $plugin->run(); 57 | } 58 | 59 | run(); 60 | --------------------------------------------------------------------------------