├── .github └── CODEOWNERS ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .nvmrc ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .wp-env.json ├── CODE_OF_CONDUCT.md ├── Docker ├── Dockerfile-node └── Dockerfile-php ├── Jenkinsfile ├── Makefile ├── README.md ├── composer.json ├── composer.lock ├── jest-puppeteer.config.js ├── package-lock.json ├── package.json ├── phpcs.xml ├── phpunit.xml.dist ├── plugin ├── lib │ ├── class-php-compatibility-checker.php │ └── class-plugin-updater.php ├── readme.txt └── wpengine-phpcompat.php ├── readme.txt ├── src ├── js │ ├── include │ │ ├── download.js │ │ ├── queue-manager.js │ │ └── render.js │ └── scan.js └── scss │ └── scan.scss ├── tests ├── bootstrap.php ├── e2e-tests │ ├── config │ │ └── setup-test-framework.js │ └── insert-grid-block.spec.js └── test-plugin-file.php └── webpack.config.js /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @wpengine/bx-acf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Vim template 2 | # swap 3 | [._]*.s[a-v][a-z] 4 | [._]*.sw[a-p] 5 | [._]s[a-v][a-z] 6 | [._]sw[a-p] 7 | # session 8 | Session.vim 9 | # temporary 10 | .netrwhist 11 | *~ 12 | # auto-generated tag files 13 | tags 14 | ### Vagrant template 15 | .vagrant/ 16 | ### SublimeText template 17 | # cache files for sublime text 18 | *.tmlanguage.cache 19 | *.tmPreferences.cache 20 | *.stTheme.cache 21 | 22 | # workspace files are user-specific 23 | *.sublime-workspace 24 | 25 | # project files should be checked into the repository, unless a significant 26 | # proportion of contributors will probably not be using SublimeText 27 | # *.sublime-project 28 | 29 | # sftp configuration file 30 | sftp-config.json 31 | 32 | # Package control specific files 33 | Package Control.last-run 34 | Package Control.ca-list 35 | Package Control.ca-bundle 36 | Package Control.system-ca-bundle 37 | Package Control.cache/ 38 | Package Control.ca-certs/ 39 | Package Control.merged-ca-bundle 40 | Package Control.user-ca-bundle 41 | oscrypto-ca-bundle.crt 42 | bh_unicode_properties.cache 43 | 44 | # Sublime-github package stores a github token in this file 45 | # https://packagecontrol.io/packages/sublime-github 46 | GitHub.sublime-settings 47 | ### Eclipse template 48 | 49 | .metadata 50 | bin/ 51 | !tests/bin/ 52 | tmp/ 53 | *.tmp 54 | *.bak 55 | *.swp 56 | *~.nib 57 | local.properties 58 | .settings/ 59 | .loadpath 60 | .recommenders 61 | 62 | # Eclipse Core 63 | .project 64 | 65 | # External tool builders 66 | .externalToolBuilders/ 67 | 68 | # Locally stored "Eclipse launch configurations" 69 | *.launch 70 | 71 | # PyDev specific (Python IDE for Eclipse) 72 | *.pydevproject 73 | 74 | # CDT-specific (C/C++ Development Tooling) 75 | .cproject 76 | 77 | # JDT-specific (Eclipse Java Development Tools) 78 | .classpath 79 | 80 | # Java annotation processor (APT) 81 | .factorypath 82 | 83 | # PDT-specific (PHP Development Tools) 84 | .buildpath 85 | 86 | # sbteclipse plugin 87 | .target 88 | 89 | # Tern plugin 90 | .tern-project 91 | 92 | # TeXlipse plugin 93 | .texlipse 94 | 95 | # STS (Spring Tool Suite) 96 | .springBeans 97 | 98 | # Code Recommenders 99 | .recommenders/ 100 | 101 | # Scala IDE specific (Scala & Java development for Eclipse) 102 | .cache-main 103 | .scala_dependencies 104 | .worksheet 105 | ### macOS template 106 | *.DS_Store 107 | .AppleDouble 108 | .LSOverride 109 | 110 | # Icon must end with two \r 111 | Icon 112 | 113 | 114 | # Thumbnails 115 | ._* 116 | 117 | # Files that might appear in the root of a volume 118 | .DocumentRevisions-V100 119 | .fseventsd 120 | .Spotlight-V100 121 | .TemporaryItems 122 | .Trashes 123 | .VolumeIcon.icns 124 | .com.apple.timemachine.donotpresent 125 | 126 | # Directories potentially created on remote AFP share 127 | .AppleDB 128 | .AppleDesktop 129 | Network Trash Folder 130 | Temporary Items 131 | .apdisk 132 | ### Linux template 133 | 134 | # temporary files which can be created if a process still has a handle open of a deleted file 135 | .fuse_hidden* 136 | 137 | # KDE directory preferences 138 | .directory 139 | 140 | # Linux trash folder which might appear on any partition or disk 141 | .Trash-* 142 | 143 | # .nfs files are created when an open file is removed but is still being accessed 144 | .nfs* 145 | ### JetBrains template 146 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 147 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 148 | 149 | # User-specific stuff: 150 | .idea 151 | 152 | ## File-based project format: 153 | *.iws 154 | 155 | ## Plugin-specific files: 156 | 157 | # IntelliJ 158 | /out/ 159 | 160 | # mpeltonen/sbt-idea plugin 161 | .idea_modules/ 162 | 163 | # JIRA plugin 164 | atlassian-ide-plugin.xml 165 | 166 | # Crashlytics plugin (for Android Studio and IntelliJ) 167 | com_crashlytics_export_strings.xml 168 | crashlytics.properties 169 | crashlytics-build.properties 170 | fabric.properties 171 | ### Windows template 172 | # Windows thumbnail cache files 173 | Thumbs.db 174 | ehthumbs.db 175 | ehthumbs_vista.db 176 | 177 | # Folder config file 178 | Desktop.ini 179 | 180 | # Recycle Bin used on file shares 181 | $RECYCLE.BIN/ 182 | 183 | # Windows Installer files 184 | *.cab 185 | *.msi 186 | *.msm 187 | *.msp 188 | 189 | # Windows shortcuts 190 | *.lnk 191 | ### TextMate template 192 | *.tmproj 193 | *.tmproject 194 | tmtags 195 | 196 | # Build Assets 197 | node_modules 198 | vendor 199 | /plugin/languages/*.pot 200 | assets/css/*.css 201 | assets/css/*.map 202 | assets/js/*.js 203 | assets/js/*.map 204 | coverage.clover 205 | /plugin/scripts/*-min.js 206 | /plugin/build/ 207 | wpe-php-compat.zip 208 | .phpunit.result.cache 209 | 210 | # Docker files 211 | Docker/database 212 | Docker/wordpress 213 | Docker/logs 214 | Docker/debug 215 | !Docker/bin 216 | 217 | # Unit tests 218 | !/tests/bin/ 219 | 220 | # WordPress core (for IDEs) 221 | /wordpress/ 222 | 223 | # Build files 224 | /build/ 225 | clover.xml 226 | 227 | .vscode/*.log 228 | 229 | .wp-env.override.json 230 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | gulpfile.js 2 | **/*-min.js 3 | plugin/build/ 4 | wordpress/ 5 | node_modules/ 6 | vendor/ 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "esversion": 3, 7 | "expr": true, 8 | "immed": true, 9 | "noarg": true, 10 | "nonbsp": true, 11 | "onevar": true, 12 | "quotmark": "single", 13 | "trailing": true, 14 | "undef": true, 15 | "unused": true, 16 | "browser": true, 17 | "globals": { 18 | "_": false, 19 | "Backbone": false, 20 | "jQuery": false, 21 | "JSON": false, 22 | "wp": false, 23 | "export": false, 24 | "module": false, 25 | "require": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.20.4 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ikappas.phpcs", 4 | "bmewburn.vscode-intelephense-client", 5 | "neilbrayfield.php-docblocker", 6 | "persoderlind.vscode-phpcbf", 7 | "felixfbecker.php-debug", 8 | "dbaeumer.jshint", 9 | "johnbillion.vscode-wordpress-hooks" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Listen for XDebug", 6 | "type": "php", 7 | "request": "launch", 8 | "port": 9003, 9 | "log": true, 10 | "pathMappings": { 11 | "/app/wordpress/wp-content/plugins/wpe-php-compat/": "${workspaceFolder}/plugin", 12 | "/app/wordpress/": "${workspaceFolder}/wordpress", 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "phpcs.standard": "phpcs.xml", 3 | "phpcbf.executablePath": "${workspaceRoot}/vendor/bin/phpcbf", 4 | "phpcbf.standard": "phpcs.xml", 5 | "intelephense.diagnostics.undefinedConstants": false, 6 | } 7 | -------------------------------------------------------------------------------- /.wp-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["./plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | opensource@wpengine.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /Docker/Dockerfile-node: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | ARG UID=1000 3 | ARG GID=1000 4 | 5 | RUN groupmod -g ${GID} node && usermod -u ${UID} -g ${GID} node 6 | -------------------------------------------------------------------------------- /Docker/Dockerfile-php: -------------------------------------------------------------------------------- 1 | FROM php:7.4-cli 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y \ 5 | git \ 6 | unzip \ 7 | --no-install-recommends && \ 8 | rm -r /var/lib/apt/lists/* 9 | 10 | RUN pecl install xdebug && \ 11 | docker-php-ext-enable xdebug 12 | 13 | RUN echo "xdebug.remote_enable=1" >> /usr/local/etc/php/conf.d/xdebug.ini && \ 14 | echo "xdebug.remote_autostart=1" >> /usr/local/etc/php/conf.d/xdebug.ini && \ 15 | echo "xdebug.remote_connect_back=0" >> /usr/local/etc/php/conf.d/xdebug.ini && \ 16 | echo "xdebug.mode=coverage" >> /usr/local/etc/php/conf.d/xdebug.ini && \ 17 | echo "xdebug.profiler_enable=0" >> /usr/local/etc/php/conf.d/xdebug.ini 18 | 19 | VOLUME [ "/app" ] 20 | 21 | WORKDIR /app 22 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!groovy 2 | @Library('wpshared') _ 3 | 4 | node('docker') { 5 | 6 | workspace.cleanUp() 7 | 8 | wpe.pipeline('woospeed-build') { 9 | 10 | withEnv(["COMPOSE_PROJECT_NAME=wpe-php-compat-${BUILD_TAG}"]) { 11 | stage('Build') { 12 | sh 'make --debug=vji install-composer' 13 | sh 'make --debug=vji install-npm' 14 | sh 'make --debug=vji build-assets' 15 | } 16 | 17 | stage('Test') { 18 | sh 'make --debug=vji test-lint-php' 19 | sh 'make --debug=vji test-lint-javascript' 20 | sh 'make --debug=vji test-unit' 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCKER_RUN := @docker run --rm 2 | COMPOSER_BASE_CONTAINER := -v $$(pwd):/app --user $$(id -u):$$(id -g) composer 3 | COMPOSER_WC_SMOOTH_GENERATOR_CONTAINER := -v $$(pwd)/wordpress/wp-content/plugins/wc-smooth-generator:/app --user $$(id -u):$$(id -g) composer:1.10.17 4 | NODE_IMAGE := -w /home/node/app -v $$(pwd):/home/node/app --user node phpcompatrevamp 5 | HAS_LANDO := $(shell command -v lando 2> /dev/null) 6 | CURRENTUSER := $$(id -u) 7 | CURRENTGROUP := $$(id -g) 8 | HIGHLIGHT :=\033[0;32m 9 | END_HIGHLIGHT :=\033[0m # No Color 10 | 11 | .PHONY: build 12 | build: build-docker build-assets 13 | 14 | .PHONY: build-assets 15 | build-assets: | build-docker-node install-npm install-composer 16 | @echo "Building plugin assets" 17 | rm -f plugin/languages/*.pot plugin/scripts/*-min.js 18 | $(DOCKER_RUN) $(NODE_IMAGE) npm run build 19 | $(DOCKER_RUN) $(COMPOSER_BASE_CONTAINER) run-script makepot 20 | 21 | .PHONY: build-docker 22 | build-docker: build-docker-node build-docker-php 23 | 24 | .PHONY: build-docker-node 25 | build-docker-node: 26 | if [ ! "$$(docker images | grep phpcompatrevamp)" ]; then \ 27 | echo "Building the Node image"; \ 28 | docker build \ 29 | -f Docker/Dockerfile-node \ 30 | --build-arg UID=$(CURRENTUSER) \ 31 | --build-arg GID=$(CURRENTUSER) \ 32 | -t phpcompatrevamp .; \ 33 | fi 34 | 35 | .PHONY: choose-violence 36 | choose-violence: 37 | npm run wp-env destroy 38 | 39 | .PHONY: build-docker-php 40 | build-docker-php: 41 | if [ ! "$$(docker images | grep woounit)" ]; then \ 42 | echo "Building the PHP image"; \ 43 | docker build -f Docker/Dockerfile-php -t woounit .; \ 44 | fi 45 | 46 | .PHONY: clean 47 | clean: clean-assets clean-build 48 | 49 | .PHONY: clean-assets 50 | clean-assets: 51 | @echo "Cleaning up plugin assets" 52 | rm -rf \ 53 | plugin/languages/*.pot \ 54 | plugin/scripts/*-min.js 55 | 56 | .PHONY: clean-build 57 | clean-build: 58 | @echo "Cleaning up build-artifacts" 59 | rm -rf \ 60 | node_modules \ 61 | build \ 62 | vendor \ 63 | clover.xml \ 64 | .phpunit.result.cache 65 | 66 | 67 | .PHONY: install 68 | install: | clean-assets clean-build 69 | $(MAKE) install-composer 70 | $(MAKE) install-npm 71 | 72 | .PHONY: install-composer 73 | install-composer: 74 | $(DOCKER_RUN) $(COMPOSER_BASE_CONTAINER) install 75 | 76 | .PHONY: install-npm 77 | install-npm: | build-docker-node 78 | $(DOCKER_RUN) $(NODE_IMAGE) npm install 79 | 80 | .PHONY: release 81 | release: | build-assets wpe-php-compat.zip 82 | 83 | .PHONY: reset 84 | reset: stop clean 85 | 86 | .PHONY: setup-sample-meta 87 | setup-sample-meta: 88 | @echo "Setting up sample post ids" 89 | # npm run wp-env run cli post generate -- --count=10 --post_type='post' --post_author='admin' --post_content=`<<< "This is a sample post."` 90 | for id in `npm run wp-env run cli post "list --post_type=post --field=ID"`; do \ 91 | npm run wp-env run cli post meta update $$id _wpe_my_super_custom_meta "this is the meta for $$id" --format='plaintext'; \ 92 | done 93 | 94 | .PHONY: setup-sample-data 95 | echo-sample-data: 96 | @echo "Setting up sample post data" 97 | for id in `npm run wp-env run cli post "list --post_type=post --field=ID"`; do echo $$id; done 98 | 99 | .PHONY: run-test-e2e 100 | run-test-e2e: 101 | @echo "Setting up sample post data" 102 | npm run test:e2e 103 | 104 | .PHONY: setup 105 | setup: 106 | @echo "Setting up the project" 107 | $(MAKE) install 108 | $(MAKE) build-assets 109 | 110 | .PHONY: start 111 | start: 112 | @echo "Starting WordPress" 113 | npm run wp-env start 114 | 115 | .PHONY: stop 116 | stop: 117 | @echo "Stopping WordPress" 118 | npm run wp-env stop 119 | 120 | .PHONY: test 121 | test: test-lint test-unit test-e2e 122 | 123 | .PHONY: test-e2e 124 | test-e2e: run-test-e2e 125 | 126 | 127 | .PHONY: test-lint 128 | test-lint: test-lint-php test-lint-javascript 129 | 130 | .PHONY: test-lint-javascript 131 | test-lint-javascript: | build-docker-node 132 | @echo "Running JavaScript linting" 133 | $(DOCKER_RUN) $(NODE_IMAGE) ./node_modules/jshint/bin/jshint 134 | 135 | .PHONY: test-lint-php 136 | test-lint-php: 137 | @echo "Running PHP linting" 138 | ./vendor/bin/phpcs --standard=./phpcs.xml 139 | 140 | .PHONY: php-clean 141 | php-clean: 142 | @echo "Always cleaning up your messes." 143 | ./vendor/bin/phpcbf --standard=./phpcs.xml 144 | 145 | .PHONY: test-unit 146 | test-unit: | build-docker-php 147 | @echo "Running Unit Tests Without Coverage" 148 | docker run -v $$(pwd):/app --rm woounit /app/vendor/bin/phpunit 149 | 150 | .PHONY: test-unit-coverage 151 | test-unit-coverage: | build-docker-php 152 | @echo "Running Unit Tests With Coverage" 153 | docker run -v $$(pwd):/app --rm --user $$(id -u):$$(id -g) woounit /app/vendor/bin/phpunit --coverage-text --coverage-html build/coverage/ 154 | 155 | .PHONY: update-composer 156 | update-composer: lando-stop 157 | $(DOCKER_RUN) $(COMPOSER_BASE_CONTAINER) update 158 | @echo "Composer updated. If your site had been running please run make start again to access it" 159 | 160 | .PHONY: update-npm 161 | update-npm: | build-docker-node 162 | $(DOCKER_RUN) $(NODE_IMAGE) npm update 163 | 164 | .PHONY: watch 165 | watch: | build-docker-node install-npm 166 | $(DOCKER_RUN) $(NODE_IMAGE) npm run start 167 | 168 | wpe-php-compat.zip: 169 | @echo "Building release file: wpe-php-compat.zip" 170 | rm -f wpe-php-compat.zip 171 | cd plugin; zip -r wpe-php-compat.zip * 172 | mv plugin/wpe-php-compat.zip ./ 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Compatibility Checker 2 | 3 | An easy way to check your site for PHP Compatibility. 4 | 5 | ## Setup Development Environment 6 | 7 | Before starting your workstation will need the following: 8 | 9 | * [Docker](https://www.docker.com/) 10 | 11 | 1. Clone the repository 12 | 13 | `git@github.com:wpengine/phpcompat.git` 14 | 15 | 2. Setup WP-Env 16 | 17 | ```bash 18 | make setup && make start 19 | ``` 20 | 21 | When finished, a local WordPress will be configured at http://localhost:8888/wp-admin. 22 | 23 | WordPress Credentials: 24 | 25 | __URL:__ _http://localhost:8888/wp-admin_ 26 | 27 | __Admin User:__ _admin_ 28 | 29 | __Admin Password:__ _password_ 30 | 31 | If anything goes wrong and you suspect your local is frozen, you can always `make choose-violence` to rebuild the local environment and containers. Hopefully you never have to choose violence. :) 32 | 33 | ## Build and Testing 34 | 35 | ```bash 36 | make build 37 | ``` 38 | 39 | Note, assets will also build during the install phase. 40 | 41 | You can run all testing (all lints and unit tests) together with the following: 42 | 43 | ```bash 44 | make test 45 | ``` 46 | 47 | The project uses the [Brain Monkey](https://brain-wp.github.io/BrainMonkey/) library for unit testing. Once setup run the following for unit tests: 48 | 49 | ```bash 50 | make test-unit 51 | ``` 52 | 53 | The project uses the WordPress e2e tests. Run the following for e2e tests: 54 | 55 | ```bash 56 | make test-e2e 57 | ``` 58 | 59 | We also use [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) with [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) and [JSHint](http://jshint.com/) with [WordPress' JS Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/javascript/#installing-and-running-jshint). Linting will automagically be setup for you if you use [Visual Studio Code](https://code.visualstudio.com/). If you want to run it manually use the following: 60 | 61 | ```bash 62 | make test-lint 63 | ``` 64 | 65 | or, to run an individual lint (php or javascript), use one of the following: 66 | 67 | ```bash 68 | make test-lint-php 69 | ``` 70 | 71 | ```bash 72 | make test-lint-javascript 73 | ``` 74 | 75 | Screw something up? You can reset your environment with the following. It will stop the environment and cleanup and the build files as well as anything downloaded. 76 | 77 | ```bash 78 | make reset 79 | ``` 80 | 81 | ## Preparing for release 82 | 83 | To generate a .zip that can be uploaded through any normal WordPress plugin installation workflow, simply run the following: 84 | 85 | ```bash 86 | make release 87 | ``` 88 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wpengine/wpe-php-compat", 3 | "description": "The WP Engine PHP Compatibility Checker can be used by any WordPress website on any web host to check PHP version compatibility.", 4 | "require-dev": { 5 | "phpunit/phpunit": "^9.5.4", 6 | "brain/monkey": "^2.6.0", 7 | "dealerdirect/phpcodesniffer-composer-installer": "^v0.7.1", 8 | "wp-cli/wp-cli-bundle": "*", 9 | "wpengine/wpengine-coding-standards": "^1.0.0", 10 | "johnpbloch/wordpress": "*" 11 | }, 12 | "config": { 13 | "allow-plugins": { 14 | "johnpbloch/wordpress-core-installer": true, 15 | "dealerdirect/phpcodesniffer-composer-installer": true 16 | } 17 | }, 18 | "scripts": { 19 | "makepot": "./vendor/bin/wp i18n make-pot plugin --slug=wpe-php-compat" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | launch: { 3 | headless: true 4 | } 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wpe-php-compat", 3 | "description": "The WP Engine PHP Compatibility Checker can be used by any WordPress website on any web host to check PHP version compatibility.", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/wpengine/phpcompat" 7 | }, 8 | "author": "WP Engine ", 9 | "license": "GPLv2", 10 | "homepage": "https://wpengine.com", 11 | "scripts": { 12 | "build": "wp-scripts build --output-path=plugin/build && cp readme.txt plugin/", 13 | "makepot": "./vendor/bin/wp i18n make-pot plugin --slug=wpe-php-compat", 14 | "test:e2e": "wp-scripts test-e2e", 15 | "start": "wp-scripts start --output-path=plugin/build", 16 | "wp-env": "wp-env" 17 | }, 18 | "babel": { 19 | "presets": [ 20 | "@wordpress/default" 21 | ], 22 | "plugins": [ 23 | "@babel/plugin-transform-class-properties" 24 | ] 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.26", 28 | "@babel/preset-env": "^7.26", 29 | "@testing-library/jest-dom": "^5.11", 30 | "@testing-library/react": "^11.1", 31 | "@testing-library/user-event": "^12.1", 32 | "@wordpress/dependency-extraction-webpack-plugin": "^3.1", 33 | "@wordpress/e2e-test-utils": "^11.3.0", 34 | "@wordpress/env": "^5.16.0", 35 | "@wordpress/hooks": "^2.12", 36 | "@wordpress/scripts": "^30.15.0", 37 | "babel-jest": "^29.7", 38 | "babel-loader": "^8.1", 39 | "compare-versions": "^4.1", 40 | "cross-spawn": "^7.0.5", 41 | "ignore-emit-webpack-plugin": "^2.0", 42 | "jest": "^29.7", 43 | "jest-environment-jsdom-sixteen": "^1.0", 44 | "jshint": "^2.12", 45 | "lodash": "^4.17", 46 | "puppeteer-core": "^22.13.1", 47 | "sass": "^1.53", 48 | "terser-webpack-plugin": "^5.3", 49 | "webpack": "^5.79.0", 50 | "webpack-bundle-analyzer": "^4.2", 51 | "webpack-cli": "^4.6", 52 | "webpack-livereload-plugin": "^2.3", 53 | "webpack-merge": "^5.8.0" 54 | }, 55 | "overrides": { 56 | "ws": "^8.17.1", 57 | "cookie": "^0.7.1", 58 | "http-proxy-middleware": "^2.0.7" 59 | }, 60 | "dependencies": { 61 | "buffer": "^6.0.3", 62 | "mustache": "^4.2" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Coding standards for the current project. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | plugin 17 | */node_modules/* 18 | */vendor/* 19 | *.xml 20 | */tests/* 21 | *.js 22 | *.asset.php 23 | */wordpress/* 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./plugin 6 | 7 | 8 | ./plugin/uninstall.php 9 | 10 | 11 | 12 | 13 | ./tests/ 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /plugin/lib/class-php-compatibility-checker.php: -------------------------------------------------------------------------------- 1 | init(); 36 | } 37 | 38 | return self::$instance; 39 | } 40 | 41 | /** 42 | * Enqueue any needed scripts and styles. 43 | * 44 | * @since 1.0.0 45 | * 46 | * @param string $hook_suffix The current admin page. 47 | */ 48 | public function enqueue_scripts( $hook_suffix = '' ) { 49 | // Only load on our scan admin page. 50 | if ( 'tools_page_wpe-php-compat' !== $hook_suffix ) { 51 | return; 52 | } 53 | 54 | $assets_file = dirname( dirname( __FILE__ ) ) . '/build/scan.asset.php'; 55 | 56 | if ( ! file_exists( $assets_file ) ) { 57 | add_action( 58 | 'admin_notices', 59 | function() { 60 | ?> 61 |
62 |

PHP Compatibility Checker. Please run make build to build assets.', 'wpe-php-compat' ) ); ?>

63 |
64 | $this->get_plugins_to_scan(), 97 | 'themes' => $this->get_themes_to_scan(), 98 | ) 99 | ); 100 | 101 | wp_enqueue_script( 'tide-checker' ); 102 | } 103 | 104 | /** 105 | * Setup our hooks. 106 | */ 107 | public function init() { 108 | $instance = self::instance(); 109 | 110 | // Build our tools page. 111 | add_action( 'admin_menu', array( $instance, 'create_menu' ) ); 112 | 113 | // Enqueue scripts and styles. 114 | add_action( 'admin_enqueue_scripts', array( $instance, 'enqueue_scripts' ) ); 115 | } 116 | 117 | /** 118 | * Get all plugins that should be scanned. 119 | * 120 | * @return array 121 | */ 122 | public function get_plugins_to_scan() { 123 | if ( ! function_exists( 'get_plugins' ) ) { 124 | // phpcs:ignore PEAR.Files.IncludingFile.UseIncludeOnce -- strict requirement 125 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; 126 | } 127 | 128 | $plugins = get_plugins(); 129 | 130 | /** 131 | * Filter which plugins should be excluded from scans. 132 | * 133 | * This will exclude based on the plugin name, not the plugin slug. 134 | * 135 | * @param string[] $excluded_plugins Plugins we want to exclude. 136 | * 137 | * @since 1.6.0 138 | */ 139 | $excluded_plugins = apply_filters( 'phpcompat_excluded_plugins', array( 'PHP Compatibility Checker', 'Hello Dolly' ) ); 140 | 141 | // Exclude some plugins. 142 | $plugins = array_filter( 143 | $plugins, 144 | function ( $plugin_data ) use ( $excluded_plugins ) { 145 | return ! in_array( $plugin_data['Name'], $excluded_plugins, true ); 146 | } 147 | ); 148 | 149 | if ( empty( $plugins ) ) { 150 | return array(); 151 | } 152 | 153 | $active_plugins = get_option( 'active_plugins' ); 154 | 155 | // On multisite, get network active plugins. 156 | if ( is_multisite() ) { 157 | $active_plugins = array_merge( 158 | $active_plugins, 159 | array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ) 160 | ); 161 | } 162 | 163 | // Add "active" attribute. 164 | $plugins = array_map( 165 | function( $plugin_file, $plugin_data ) use ( $active_plugins ) { 166 | $plugin_data['plugin_file'] = $plugin_file; 167 | $plugin_data['active'] = in_array( $plugin_file, $active_plugins, true ) ? 'yes' : 'no'; 168 | return $plugin_data; 169 | }, 170 | array_keys( $plugins ), 171 | $plugins 172 | ); 173 | 174 | $plugin_info = get_site_transient( 'update_plugins' ); 175 | 176 | // Extract real plugin slugs from the update_plugins transient. 177 | foreach ( $plugins as $key => $plugin_data ) { 178 | $plugin_file = $plugin_data['plugin_file']; 179 | 180 | if ( isset( $plugin_info->response[ $plugin_file ] ) ) { 181 | $plugins[ $key ]['slug'] = $plugin_info->response[ $plugin_file ]->slug; 182 | } elseif ( isset( $plugin_info->no_update[ $plugin_file ] ) ) { 183 | $plugins[ $key ]['slug'] = $plugin_info->no_update[ $plugin_file ]->slug; 184 | } else { 185 | $plugins[ $key ]['slug'] = dirname( $plugin_file ); 186 | } 187 | } 188 | 189 | // Compact output. 190 | $plugins = array_map( 191 | function( $plugin ) { 192 | return array( 193 | 'slug' => sanitize_title( $plugin['slug'] ), 194 | 'name' => sanitize_text_field( $plugin['Name'] ), 195 | 'version' => sanitize_text_field( $plugin['Version'] ), 196 | 'active' => $plugin['active'], 197 | ); 198 | }, 199 | $plugins 200 | ); 201 | 202 | /** 203 | * Filter which plugins should be scanned. 204 | * 205 | * @param array[] $plugins { 206 | * List of plugins. 207 | * 208 | * @type string $slug Plugin slug. 209 | * @type string $name Plugin name. 210 | * @type string $version Plugin version. 211 | * @type string $active Whether the plugin is active (yes or no). 212 | * } 213 | * 214 | * @since 1.6.0 215 | */ 216 | return apply_filters( 'phpcompat_plugins_to_scan', $plugins ); 217 | } 218 | 219 | /** 220 | * Get all themes that should be scanned. 221 | * 222 | * @return array 223 | */ 224 | public function get_themes_to_scan() { 225 | $themes_data = wp_prepare_themes_for_js(); 226 | 227 | /** 228 | * Filter which themes should be excluded from scans. 229 | * 230 | * This will exclude based on the theme name, not the theme slug. 231 | * 232 | * @param string[] $excluded_themes Themes we want to exclude. 233 | * 234 | * @since 1.6.0 235 | */ 236 | $excluded_themes = apply_filters( 'phpcompat_excluded_themes', array() ); 237 | 238 | // Exclude some themes. 239 | $themes_data = array_filter( 240 | $themes_data, 241 | function ( $theme_data ) use ( $excluded_themes ) { 242 | return ! in_array( $theme_data['name'], $excluded_themes, true ); 243 | } 244 | ); 245 | 246 | $themes = array_map( 247 | function( $theme ) { 248 | return array( 249 | 'slug' => sanitize_title( $theme['id'] ), 250 | 'name' => sanitize_text_field( $theme['name'] ), 251 | 'version' => sanitize_text_field( $theme['version'] ), 252 | 'active' => true === $theme['active'] ? 'yes' : 'no', 253 | ); 254 | }, 255 | $themes_data 256 | ); 257 | 258 | /** 259 | * Filter which themes should be scanned. 260 | * 261 | * @param array[] $themes { 262 | * List of themes. 263 | * 264 | * @type string $slug Theme slug. 265 | * @type string $name Theme name. 266 | * @type string $version Theme version. 267 | * @type string $active Whether the theme is active (yes or no). 268 | * } 269 | * 270 | * @since 1.6.0 271 | */ 272 | return apply_filters( 'phpcompat_themes_to_scan', $themes ); 273 | } 274 | 275 | /** 276 | * Add the scan page to the wp-admin menu. 277 | * 278 | * @since 1.0.0 279 | * 280 | * @action admin_menu 281 | */ 282 | public function create_menu() { 283 | // Create under Tools sub-menu. 284 | $this->page = add_submenu_page( 'tools.php', esc_html__( 'PHP Compatibility', 'wpe-php-compat' ), esc_html__( 'PHP Compatibility', 'wpe-php-compat' ), WPEPHPCOMPAT_CAPABILITY, WPEPHPCOMPAT_ADMIN_PAGE_SLUG, array( self::instance(), 'scan_page' ) ); 285 | } 286 | 287 | /** 288 | * Render method for the scan page. 289 | * 290 | * @since 1.0.0 291 | */ 292 | public function scan_page() { 293 | 294 | // Determine if current site is a WP Engine customer. 295 | $is_wpe_customer = function_exists( '\is_wpe' ) && \is_wpe(); 296 | 297 | // Content variables. 298 | $url_get_hosting = esc_url( 'https://wpeng.in/5a0336/' ); 299 | $url_wpe_agency_partners = esc_url( 'https://wpeng.in/fa14e4/' ); 300 | $url_wpe_customer_upgrade = esc_url( 'https://wpeng.in/407b79/' ); 301 | $url_wpe_logo = esc_url( 'https://wpeng.in/22f22b/' ); 302 | $url_codeable_submit = esc_url( 'https://codeable.io/wp-admin/admin-ajax.php?action=wp_engine_phpcompat' ); 303 | ?> 304 | 305 |
306 |

307 |
308 |

309 |

310 |
311 |
312 |

313 | 314 | 315 | 316 | 317 | 323 | 324 | 325 | 326 | 338 | 339 | 340 | 341 |
318 |
319 |
320 | 321 |
322 |
327 |
328 | 329 |
330 | 331 | 335 |
336 |
337 |
342 |
343 | 344 | 377 | 378 | 395 |
396 | 397 | 398 |
399 | 400 | 401 |
402 |
403 | 404 |

405 | 406 |

407 | 408 | 409 |

410 | 411 | 412 | 413 | 414 | 415 |

416 | 417 | 418 |
419 |
420 | 421 | 440 | 441 |
442 | 443 | 444 |
445 | 446 | 495 | 496 | ' . esc_html__( 'Start Scan', 'wpe-php-compat' ) . '' ); 511 | 512 | return $links; 513 | } 514 | 515 | } 516 | -------------------------------------------------------------------------------- /plugin/lib/class-plugin-updater.php: -------------------------------------------------------------------------------- 1 | api_url = 'https://wpe-plugin-updates.wpengine.com/'; 53 | 54 | $this->cache_time = time() + HOUR_IN_SECONDS * 5; 55 | 56 | $this->properties = $this->get_full_plugin_properties( $properties, $this->api_url ); 57 | 58 | if ( ! $this->properties ) { 59 | return; 60 | } 61 | 62 | $this->register(); 63 | } 64 | 65 | /** 66 | * Get the full plugin properties, including the directory name, version, basename, and add a transient name. 67 | * 68 | * @param array $properties These properties are passed in when instantiating to identify the plugin and it's update location. 69 | * @param int $api_url The URL where the api is located. 70 | */ 71 | public function get_full_plugin_properties( $properties, $api_url ) { 72 | $plugins = \get_plugins(); 73 | 74 | // Scan through all plugins installed and find the one which matches this one in question. 75 | foreach ( $plugins as $plugin_basename => $plugin_data ) { 76 | // Match using the passed-in plugin's basename. 77 | if ( $plugin_basename === $properties['plugin_basename'] ) { 78 | // Add the values we need to the properties. 79 | $properties['plugin_dirname'] = dirname( $plugin_basename ); 80 | $properties['plugin_version'] = $plugin_data['Version']; 81 | $properties['plugin_update_transient_name'] = 'wpesu-plugin-' . sanitize_title( $properties['plugin_dirname'] ); 82 | $properties['plugin_update_transient_exp_name'] = 'wpesu-plugin-' . sanitize_title( $properties['plugin_dirname'] ) . '-expiry'; 83 | $properties['plugin_manifest_url'] = trailingslashit( $api_url ) . trailingslashit( $properties['plugin_slug'] ) . 'info.json'; 84 | 85 | return $properties; 86 | } 87 | } 88 | 89 | // No matching plugin was found installed. 90 | return null; 91 | } 92 | 93 | /** 94 | * Register hooks. 95 | * 96 | * @return void 97 | */ 98 | public function register() { 99 | add_filter( 'plugins_api', array( $this, 'filter_plugin_update_info' ), 20, 3 ); 100 | add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'filter_plugin_update_transient' ) ); 101 | } 102 | 103 | /** 104 | * Filter the plugin update transient to take over update notifications. 105 | * 106 | * @param ?object $transient_value The value of the `site_transient_update_plugins` transient. 107 | * 108 | * @handles site_transient_update_plugins 109 | * @return object|null 110 | */ 111 | public function filter_plugin_update_transient( $transient_value ) { 112 | // No update object exists. Return early. 113 | if ( empty( $transient_value ) ) { 114 | return $transient_value; 115 | } 116 | 117 | $result = $this->fetch_plugin_info(); 118 | 119 | if ( false === $result ) { 120 | return $transient_value; 121 | } 122 | 123 | $res = $this->parse_plugin_info( $result ); 124 | 125 | if ( version_compare( $this->properties['plugin_version'], $result->version, '<' ) ) { 126 | $transient_value->response[ $res->plugin ] = $res; 127 | $transient_value->checked[ $res->plugin ] = $result->version; 128 | } else { 129 | $transient_value->no_update[ $res->plugin ] = $res; 130 | } 131 | 132 | return $transient_value; 133 | } 134 | 135 | /** 136 | * Filters the plugin update information. 137 | * 138 | * @param object $res The response to be modified for the plugin in question. 139 | * @param string $action The action in question. 140 | * @param object $args The arguments for the plugin in question. 141 | * 142 | * @handles plugins_api 143 | * @return object 144 | */ 145 | public function filter_plugin_update_info( $res, $action, $args ) { 146 | // Do nothing if this is not about getting plugin information. 147 | if ( 'plugin_information' !== $action ) { 148 | return $res; 149 | } 150 | 151 | // Do nothing if it is not our plugin. 152 | if ( $this->properties['plugin_dirname'] !== $args->slug ) { 153 | return $res; 154 | } 155 | 156 | $result = $this->fetch_plugin_info(); 157 | 158 | // Do nothing if we don't get the correct response from the server. 159 | if ( false === $result ) { 160 | return $res; 161 | } 162 | 163 | return $this->parse_plugin_info( $result ); 164 | } 165 | 166 | /** 167 | * Fetches the plugin update object from the WP Product Info API. 168 | * 169 | * @return object|false 170 | */ 171 | private function fetch_plugin_info() { 172 | // Fetch cache first. 173 | $expiry = get_option( $this->properties['plugin_update_transient_exp_name'], 0 ); 174 | $response = get_option( $this->properties['plugin_update_transient_name'] ); 175 | 176 | if ( empty( $expiry ) || time() > $expiry || empty( $response ) ) { 177 | $response = wp_remote_get( 178 | $this->properties['plugin_manifest_url'], 179 | array( 180 | 'timeout' => 10, 181 | 'headers' => array( 182 | 'Accept' => 'application/json', 183 | ), 184 | ) 185 | ); 186 | 187 | if ( 188 | is_wp_error( $response ) || 189 | 200 !== wp_remote_retrieve_response_code( $response ) || 190 | empty( wp_remote_retrieve_body( $response ) ) 191 | ) { 192 | return false; 193 | } 194 | 195 | $response = wp_remote_retrieve_body( $response ); 196 | 197 | // Cache the response. 198 | update_option( $this->properties['plugin_update_transient_exp_name'], $this->cache_time, false ); 199 | update_option( $this->properties['plugin_update_transient_name'], $response, false ); 200 | } 201 | 202 | $decoded_response = json_decode( $response ); 203 | 204 | if ( json_last_error() !== JSON_ERROR_NONE ) { 205 | return false; 206 | } 207 | 208 | return $decoded_response; 209 | } 210 | 211 | /** 212 | * Parses the product info response into an object that WordPress would be able to understand. 213 | * 214 | * @param object $response The response object. 215 | * 216 | * @return stdClass 217 | */ 218 | private function parse_plugin_info( $response ) { 219 | 220 | global $wp_version; 221 | 222 | $res = new stdClass(); 223 | $res->name = $response->name; 224 | $res->slug = $response->slug; 225 | $res->version = $response->version; 226 | $res->requires = $response->requires; 227 | $res->download_link = $response->download_link; 228 | $res->trunk = $response->download_link; 229 | $res->new_version = $response->version; 230 | $res->plugin = $this->properties['plugin_basename']; 231 | $res->package = $response->download_link; 232 | 233 | // Plugin information modal and core update table use a strict version comparison, which is weird. 234 | // If we're genuinely not compatible with the point release, use our WP tested up to version. 235 | // otherwise use exact same version as WP to avoid false positive. 236 | $res->tested = 1 === version_compare( substr( $wp_version, 0, 3 ), $response->tested ) 237 | ? $response->tested 238 | : $wp_version; 239 | 240 | $res->sections = array( 241 | 'description' => $response->sections->description, 242 | 'changelog' => $response->sections->changelog, 243 | ); 244 | 245 | return $res; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /plugin/readme.txt: -------------------------------------------------------------------------------- 1 | === PHP Compatibility Checker === 2 | Contributors: wpengine, octalmage, stevenkword, Taylor4484, pross, jcross, rfmeier, cadic, dkotter, ankit-k-gupta, jeffpaul 3 | Tags: php 7, php 8, php, version, compat, compatibility, checker, wp engine, wpe, wpengine 4 | Requires at least: 5.6 5 | Tested up to: 6.4 6 | Stable tag: 1.6.4 7 | License: GPLv2 or later 8 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 9 | 10 | Make sure your plugins and themes are compatible with newer PHP versions. 11 | 12 | == Description == 13 | 14 | The WP Engine PHP Compatibility Checker can be used by any WordPress website on any web host to check PHP version compatibility. 15 | 16 | This plugin will lint theme and plugin code installed on your WordPress site and give you back a report of compatibility issues as reported by [Tide](https://wptide.org) for you to fix. Compatibility issues are categorized into errors and warnings and will list the file and line number of the offending code, as well as the info about why that line of code is incompatible with the chosen version of PHP. The plugin will also suggest updates to themes and plugins, as a new version may offer compatible code. 17 | 18 | **This plugin does not execute your theme and plugin code, as such this plugin cannot detect runtime compatibility issues.** 19 | 20 | **Please note that linting code is not perfect. This plugin cannot detect unused code-paths that might be used for backwards compatibility, and thus might show false positives. We maintain a [whitelist of plugins](https://github.com/wpengine/phpcompat/wiki/Results) that can cause false positives. We are continuously working to ensure the checker provides the most accurate results possible.** 21 | 22 | **This plugin relies on [Tide](https://wptide.org) that constantly scans updated versions of plugins and themes in the background. Your scan results should be near real-time, but if not that just means Tide has not yet scanned your specific plugin or theme version. Please be patient as this may take up to 10 minutes for results to be returned from Tide. Please see the [FAQ](https://wordpress.org/plugins/php-compatibility-checker/faq/) for more information.** 23 | 24 | = Update to PHP 7.4 = 25 | 26 | * Use this plugin to check your site for compatibility up to PHP 7.4! 27 | * As of [July 2022](https://wordpress.org/about/stats/), 8.52% of WordPress websites run a PHP version older than PHP 7.0. 28 | * These versions of PHP have been [deprecated and unsupported](https://secure.php.net/supported-versions.php) for over 2 years. 29 | * Only 7.1% of WordPress websites run PHP 8, the current main version of PHP. 30 | 31 | = Disclaimer = 32 | *While this plugin is written to detect as many problems as accurately as possible, 100% reliable detection is very difficult to ensure. It is best practice to run comprehensive tests before you migrate to a new PHP version.* 33 | 34 | The plugin was created by WP Engine to help the WordPress community increase adoption of modern PHP versions. We [welcome contributors](https://github.com/wpengine/phpcompat) to this plugin, and are excited to see how developers and other WordPress hosts use this plugin. 35 | 36 | To disclose security issues for this plugin please email WordPress@wpengine.com. 37 | 38 | == Installation == 39 | 40 | *Note: Go to 'Plugins' > 'Add New' in the WordPress admin and search for "PHP Compatibility Checker" and install it from there.* 41 | 42 | To manually install: 43 | 1. Upload `phpcompat` to the `/wpengine-wp-content/plugins/` directory 44 | 2. Activate the plugin through the 'Plugins' menu in WordPress 45 | 46 | You will find the plugin options in the WP Admin `Tools => PHP Compatibility` menu. Once you click `run` it will take a few minutes to conduct the test. Feel free to navigate away from the page and check back later. 47 | 48 | == Frequently Asked Questions == 49 | 50 | = 1. Will this work outside of the WP Engine hosting account? = 51 | 52 | Yes, this plugin can be used any ANY WordPress website on ANY host. 53 | 54 | = 2. Are there WP-CLI commands available? = 55 | 56 | As of the 1.6.0 release this plugin no longer includes the `phpcompat` WP-CLI command. If you still require use of that command, then please run version 1.5.2 or older of this plugin as those versions extend WP-CLI and provide commands. 57 | 58 | = 3. A plugin I created is listed as not compatible, what should I do? = 59 | 60 | We maintain a [whitelist of plugins](https://github.com/wpengine/phpcompat/wiki/Results) that cause false positives. If your plugin shows up as incompatible but you think that is wrong, please open a [GitHub issue](https://github.com/wpengine/phpcompat/issues/new) on the project, or email wordpress@wpengine.com with info about your plugin and why you know it is compatible (you have automated tests, the failure is on backwards compatibility code paths, etc). 61 | 62 | = 4. Can I use this to test non-WordPress PHP Projects? = 63 | 64 | Yes! While you cannot use this WordPress plugin to test your non-WordPress projects, you can use the [Open Source PHPCompatibility Library](https://github.com/wimg/PHPCompatibility) that this plugin is built on. 65 | 66 | = 5. Why was my plugin/theme skipped? = 67 | 68 | If your plugin or theme is not available on WordPress.org, then [Tide](https://wptide.org) will not be able to scan or return results of that plugin or theme. 69 | 70 | If your plugin or theme is available on WordPress.org, but Tide is not immediately returning results than it likely means Tide has not yet audited that plugin or theme and within a few minutes results should be available once Tide completes its audit. 71 | 72 | = 6. The scan is stuck, what can I do? = 73 | 74 | As of version 1.6.0 of this plugin, there should no longer be issues of the scan getting stuck as it no longer runs on your WordPress host server. If you are seeing significantly slow or unresponsive results from a plugin or theme that is available on WordPress.org, then please [open an issue](https://github.com/wptide/wptide.org/issues/new/choose) with those details for the Tide team to investigate why that specific plugin or theme version is not appearing in the Tide results. 75 | 76 | = 7. I found a bug, or have a suggestion, can I contribute back? = 77 | 78 | Yes! WP Engine has a public GitHub repo where you can contribute back to this plugin. Please open an issue on the [Plugin GitHub](https://github.com/wpengine/phpcompat). We actively develop this plugin, and are always happy to receive pull requests. 79 | 80 | The plugin was created by WP Engine to help the WordPress community increase adoption of modern PHP versions. We welcome contributors to this plugin, and are excited to see how developers and other WordPress hosts use this plugin. 81 | 82 | To disclose security issues for this plugin please email WordPress@wpengine.com. 83 | 84 | == Screenshots == 85 | 86 | 1. Main screen: compatibility checker options 87 | 2. Compatibility results screen 88 | 89 | == Changelog == 90 | 91 | = 1.6.4 = 92 | - Updates are now served from WP Engine servers. 93 | - Update dependencies. 94 | 95 | = 1.6.3 = 96 | - Fix issue where Buffer was not available to the scans. 97 | 98 | = 1.6.2 = 99 | - Update packages. 100 | 101 | = 1.6.1 = 102 | - Fix issue on update where old files were included. 103 | 104 | = 1.6.0 = 105 | - Changed from running PHP Compatibility scans on your WordPress server to using scan data from [Tide](https://wptide.org). 106 | - Removed `phpcompat` WP-CLI command. 107 | - Update dependencies. 108 | 109 | = 1.5.2 = 110 | - Removed PHP 5.2 checks 111 | - Fixed PHP 8 issue where plugin cannot cannot be uninstalled. 112 | 113 | = 1.5.1 = 114 | - Added Smart Plugin Manager to whitelisted plugins. 115 | 116 | = 1.5.0 = 117 | - Added support for PHP 7.3 compatibility checks. 118 | 119 | = 1.4.8 = 120 | - Update dependencies. 121 | 122 | = 1.4.7 = 123 | - Better translation support. 124 | 125 | = 1.4.6 = 126 | - Switched to new PHPCompatibilityWP library to help prevent false positives. 127 | 128 | = 1.4.5 = 129 | - Use plugin version number to enqueue scripts and styles. 130 | 131 | = 1.4.4 = 132 | - PHP 5.2 Support & PHP 7.1 and 7.2 Lints. 133 | - Updated call to action sidebar depending on platform. 134 | 135 | = 1.4.3 = 136 | - Fixed Composer issue. 137 | 138 | = 1.4.1 = 139 | - Updated PHP_CodeSniffer to fix a security advisory. 140 | - Whitelisted a number of plugins. 141 | 142 | = 1.4.0 = 143 | - Updated UX for viewing PHP errors to be more intuitive and require less scrolling. 144 | - Added links for non-technical users who need assistance from developers to fix PHP errors or to test their site in PHP 7 enabled hosting environments. 145 | 146 | = 1.3.2 = 147 | - Added a "Clean up" button and uninstall.php. 148 | - Added phpcompat_phpversions filter. 149 | 150 | = 1.3.1 = 151 | - Whitelisted a number of plugins. 152 | 153 | = 1.3.0 = 154 | - Updated the PHPCompatibility library to latest version. Should fix many false positives. 155 | - Changed language and added help text to Admin UI. 156 | 157 | = 1.2.4 = 158 | - Fixed Composer issue. 159 | 160 | = 1.2.3 = 161 | - Updated the PHPCompatibility library to latest version. 162 | - Whitelisted TablePress. 163 | 164 | = 1.2.2 = 165 | - Whitelisted UpdraftPlus and Max Mega Menu. 166 | 167 | = 1.2.1 = 168 | - Updated the PHPCompatibility library to latest version. 169 | 170 | = 1.2.0 = 171 | - Updated the PHPCompatibility library to latest version. 172 | - Added support for PHP 5.6 173 | 174 | = 1.1.2 = 175 | - Fixed issue with WordPress notices breaking the plugin header. 176 | - Changed the way we send and parse JSON. 177 | - You can now restart an in progress scan. 178 | - Updated download.js to v4.2 for better Safari compatibility. 179 | 180 | = 1.1.1 = 181 | - Fixed bug with active job display. 182 | - Updated progress bar calculation. 183 | 184 | = 1.1.0 = 185 | - Test results now persist page reloads. 186 | - Failed tests will show an overview of the results. 187 | - The scan timeout is now configurable using a filter. See the FAQ for more details. 188 | 189 | = 1.0.3 = 190 | - Fixed a bug in the WP-CLI command. 191 | - Added a handful of PHP 7 compatible plugins to the whitelist. 192 | 193 | = 1.0.2 = 194 | - Added additional role protections. 195 | - Changed the UI colors to better understand output at a glance. 196 | - Exclude checking node_modules and tmp directories. 197 | - Added support for child theme's parent theme. 198 | 199 | = 1.0.1 = 200 | - Updated compatibility library with a few bugfixes. 201 | - Added skip logic to prevent checker from hanging. 202 | 203 | = 1.0.0 = 204 | - Major update to add PHP 7 checking support. 205 | - Improved the UX of the progress bar. 206 | - Fixed bug with the way the plugin menu was registered. 207 | 208 | = 0.1.0 = 209 | - Initial version. 210 | - PHP 5.5, 5.4, and 5.3 Support. 211 | - Basic WP-CLI Commands. 212 | 213 | == Upgrade Notice == 214 | 215 | = 1.6.0 = 216 | - WordPress minimum increased from 4.8 to 5.6. 217 | - PHP Compatibility scans now run via [Tide](https://wptide.org) and no longer run on your host server! 218 | - The WP-CLI `phpcompat` command has been removed as this plugin no longer runs on your host server and relies upon Tide. 219 | 220 | = 1.4.8 = 221 | - Update dependencies. 222 | -------------------------------------------------------------------------------- /plugin/wpengine-phpcompat.php: -------------------------------------------------------------------------------- 1 | init(); 35 | 36 | // Load the text domain. 37 | load_plugin_textdomain( 'wpe-php-compat', false, dirname( dirname( __FILE__ ) ) . '/languages' ); 38 | 39 | // Add plugin action link. 40 | add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( $register_phpcompat, 'filter_plugin_links' ) ); 41 | } 42 | 43 | /** 44 | * Builds the class file name for the plugin 45 | * 46 | * @since 1.0.0 47 | * 48 | * @param string $class The name of the class to get. 49 | * @return string 50 | */ 51 | function wpe_phpcompat_get_class_file( $class ) { 52 | 53 | $prefix = 'WPEngine_PHPCompat\\'; 54 | $base_dir = __DIR__ . '/lib/'; 55 | 56 | $len = strlen( $prefix ); 57 | 58 | if ( 0 !== strncmp( $prefix, $class, $len ) ) { 59 | return ''; 60 | } 61 | 62 | $relative_class = substr( $class, $len ); 63 | $file = $base_dir . str_replace( '\\', '/', 'class-' . strtolower( str_replace( '_', '-', $relative_class ) ) ) . '.php'; 64 | 65 | $relative_class_parts = explode( '\\', $relative_class ); 66 | 67 | if ( 1 < count( $relative_class_parts ) ) { 68 | $class_file = $relative_class_parts[0] . '/class-' . strtolower( str_replace( '_', '-', $relative_class_parts[1] ) ); 69 | $file = $base_dir . str_replace( '\\', '/', $class_file ) . '.php'; 70 | } 71 | 72 | return $file; 73 | } 74 | 75 | /** 76 | * Auto-loading functionality for the plugin features 77 | * 78 | * @since 1.0.0 79 | * 80 | * @param object $class The class to load. 81 | */ 82 | function wpe_phpcompat_autoloader( $class ) { 83 | 84 | $file = wpe_phpcompat_get_class_file( $class ); 85 | 86 | if ( ! empty( $file ) && file_exists( $file ) ) { 87 | include $file; 88 | } 89 | } 90 | 91 | spl_autoload_register( __NAMESPACE__ . '\wpe_phpcompat_autoloader' ); 92 | 93 | add_action( 'plugins_loaded', __NAMESPACE__ . '\wpe_phpcompat_loader' ); 94 | 95 | /** 96 | * Remove old options and custom post type 97 | * 98 | * @return void 99 | */ 100 | function maybe_migrate_to_wptide() { 101 | $is_wptide = get_option( 'wpephpcompat_is_wptide', false ); 102 | 103 | if ( $is_wptide ) { 104 | // No need to clean legacy options. 105 | return; 106 | } 107 | 108 | delete_option( 'wpephpcompat.test_version' ); 109 | delete_option( 'wpephpcompat.only_active' ); 110 | delete_option( 'wpephpcompat.scan_results' ); 111 | delete_option( 'wpephpcompat.lock' ); 112 | delete_option( 'wpephpcompat.status' ); 113 | delete_option( 'wpephpcompat.numdirs' ); 114 | delete_option( 'wpephpcompat.show_notice' ); 115 | 116 | wp_clear_scheduled_hook( 'wpephpcompat_start_test_cron' ); 117 | 118 | $paged = 1; 119 | 120 | do { 121 | $jobs = get_posts( 122 | array( 123 | 'posts_per_page' => 100, 124 | 'paged' => $paged, 125 | 'post_type' => 'wpephpcompat_jobs', 126 | 'fields' => 'ids', 127 | ) 128 | ); 129 | 130 | foreach ( $jobs as $job ) { 131 | wp_delete_post( $job ); 132 | } 133 | 134 | $found_jobs = count( $jobs ); 135 | $paged ++; 136 | } while ( $found_jobs ); 137 | 138 | update_option( 'wpephpcompat_is_wptide', 1 ); 139 | } 140 | 141 | /** 142 | * Activate plugin 143 | * 144 | * @return void 145 | */ 146 | function activate() { 147 | maybe_migrate_to_wptide(); 148 | } 149 | 150 | /** 151 | * Uninstall plugin 152 | * 153 | * @return void 154 | */ 155 | function uninstall() { 156 | maybe_migrate_to_wptide(); 157 | delete_option( 'wpephpcompat_is_wptide' ); 158 | } 159 | 160 | /** 161 | * Perform operations when the plugin is upgraded 162 | * 163 | * @param WP_Upgrader $upgrader WordPress upgrader instance. 164 | * @param array $hook_extra Options. 165 | * @return void 166 | */ 167 | function upgrade( $upgrader, $hook_extra ) { 168 | $current_plugin_path_name = plugin_basename( __FILE__ ); 169 | 170 | if ( 'update' === $hook_extra['action'] && 'plugin' === $hook_extra['type'] ) { 171 | foreach ( $hook_extra['plugins'] as $plugin ) { 172 | if ( $plugin === $current_plugin_path_name ) { 173 | maybe_migrate_to_wptide(); 174 | } 175 | } 176 | } 177 | } 178 | 179 | register_activation_hook( __FILE__, __NAMESPACE__ . '\activate' ); 180 | register_uninstall_hook( __FILE__, __NAMESPACE__ . '\uninstall' ); 181 | add_action( 'upgrader_process_complete', __NAMESPACE__ . '\upgrade', 10, 2 ); 182 | 183 | /** 184 | * Registers the plugin updater. 185 | * 186 | * @return void 187 | */ 188 | function check_for_upgrades() { 189 | $properties = array( 190 | 'plugin_slug' => 'php-compatibility-checker', 191 | 'plugin_basename' => plugin_basename( __FILE__ ), 192 | ); 193 | 194 | require_once __DIR__ . '/lib/class-plugin-updater.php'; 195 | new \WPEngine_PHPCompat\PluginUpdater( $properties ); 196 | } 197 | add_action( 'admin_init', __NAMESPACE__ . '\check_for_upgrades' ); 198 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === PHP Compatibility Checker === 2 | Contributors: wpengine, octalmage, stevenkword, Taylor4484, pross, jcross, rfmeier, cadic, dkotter, ankit-k-gupta, jeffpaul 3 | Tags: php 7, php 8, php, version, compat, compatibility, checker, wp engine, wpe, wpengine 4 | Requires at least: 5.6 5 | Tested up to: 6.4 6 | Stable tag: 1.6.4 7 | License: GPLv2 or later 8 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 9 | 10 | Make sure your plugins and themes are compatible with newer PHP versions. 11 | 12 | == Description == 13 | 14 | The WP Engine PHP Compatibility Checker can be used by any WordPress website on any web host to check PHP version compatibility. 15 | 16 | This plugin will lint theme and plugin code installed on your WordPress site and give you back a report of compatibility issues as reported by [Tide](https://wptide.org) for you to fix. Compatibility issues are categorized into errors and warnings and will list the file and line number of the offending code, as well as the info about why that line of code is incompatible with the chosen version of PHP. The plugin will also suggest updates to themes and plugins, as a new version may offer compatible code. 17 | 18 | **This plugin does not execute your theme and plugin code, as such this plugin cannot detect runtime compatibility issues.** 19 | 20 | **Please note that linting code is not perfect. This plugin cannot detect unused code-paths that might be used for backwards compatibility, and thus might show false positives. We maintain a [whitelist of plugins](https://github.com/wpengine/phpcompat/wiki/Results) that can cause false positives. We are continuously working to ensure the checker provides the most accurate results possible.** 21 | 22 | **This plugin relies on [Tide](https://wptide.org) that constantly scans updated versions of plugins and themes in the background. Your scan results should be near real-time, but if not that just means Tide has not yet scanned your specific plugin or theme version. Please be patient as this may take up to 10 minutes for results to be returned from Tide. Please see the [FAQ](https://wordpress.org/plugins/php-compatibility-checker/faq/) for more information.** 23 | 24 | = Update to PHP 7.4 = 25 | 26 | * Use this plugin to check your site for compatibility up to PHP 7.4! 27 | * As of [July 2022](https://wordpress.org/about/stats/), 8.52% of WordPress websites run a PHP version older than PHP 7.0. 28 | * These versions of PHP have been [deprecated and unsupported](https://secure.php.net/supported-versions.php) for over 2 years. 29 | * Only 7.1% of WordPress websites run PHP 8, the current main version of PHP. 30 | 31 | = Disclaimer = 32 | *While this plugin is written to detect as many problems as accurately as possible, 100% reliable detection is very difficult to ensure. It is best practice to run comprehensive tests before you migrate to a new PHP version.* 33 | 34 | The plugin was created by WP Engine to help the WordPress community increase adoption of modern PHP versions. We [welcome contributors](https://github.com/wpengine/phpcompat) to this plugin, and are excited to see how developers and other WordPress hosts use this plugin. 35 | 36 | To disclose security issues for this plugin please email WordPress@wpengine.com. 37 | 38 | == Installation == 39 | 40 | *Note: Go to 'Plugins' > 'Add New' in the WordPress admin and search for "PHP Compatibility Checker" and install it from there.* 41 | 42 | To manually install: 43 | 1. Upload `phpcompat` to the `/wpengine-wp-content/plugins/` directory 44 | 2. Activate the plugin through the 'Plugins' menu in WordPress 45 | 46 | You will find the plugin options in the WP Admin `Tools => PHP Compatibility` menu. Once you click `run` it will take a few minutes to conduct the test. Feel free to navigate away from the page and check back later. 47 | 48 | == Frequently Asked Questions == 49 | 50 | = 1. Will this work outside of the WP Engine hosting account? = 51 | 52 | Yes, this plugin can be used any ANY WordPress website on ANY host. 53 | 54 | = 2. Are there WP-CLI commands available? = 55 | 56 | As of the 1.6.0 release this plugin no longer includes the `phpcompat` WP-CLI command. If you still require use of that command, then please run version 1.5.2 or older of this plugin as those versions extend WP-CLI and provide commands. 57 | 58 | = 3. A plugin I created is listed as not compatible, what should I do? = 59 | 60 | We maintain a [whitelist of plugins](https://github.com/wpengine/phpcompat/wiki/Results) that cause false positives. If your plugin shows up as incompatible but you think that is wrong, please open a [GitHub issue](https://github.com/wpengine/phpcompat/issues/new) on the project, or email wordpress@wpengine.com with info about your plugin and why you know it is compatible (you have automated tests, the failure is on backwards compatibility code paths, etc). 61 | 62 | = 4. Can I use this to test non-WordPress PHP Projects? = 63 | 64 | Yes! While you cannot use this WordPress plugin to test your non-WordPress projects, you can use the [Open Source PHPCompatibility Library](https://github.com/wimg/PHPCompatibility) that this plugin is built on. 65 | 66 | = 5. Why was my plugin/theme skipped? = 67 | 68 | If your plugin or theme is not available on WordPress.org, then [Tide](https://wptide.org) will not be able to scan or return results of that plugin or theme. 69 | 70 | If your plugin or theme is available on WordPress.org, but Tide is not immediately returning results than it likely means Tide has not yet audited that plugin or theme and within a few minutes results should be available once Tide completes its audit. 71 | 72 | = 6. The scan is stuck, what can I do? = 73 | 74 | As of version 1.6.0 of this plugin, there should no longer be issues of the scan getting stuck as it no longer runs on your WordPress host server. If you are seeing significantly slow or unresponsive results from a plugin or theme that is available on WordPress.org, then please [open an issue](https://github.com/wptide/wptide.org/issues/new/choose) with those details for the Tide team to investigate why that specific plugin or theme version is not appearing in the Tide results. 75 | 76 | = 7. I found a bug, or have a suggestion, can I contribute back? = 77 | 78 | Yes! WP Engine has a public GitHub repo where you can contribute back to this plugin. Please open an issue on the [Plugin GitHub](https://github.com/wpengine/phpcompat). We actively develop this plugin, and are always happy to receive pull requests. 79 | 80 | The plugin was created by WP Engine to help the WordPress community increase adoption of modern PHP versions. We welcome contributors to this plugin, and are excited to see how developers and other WordPress hosts use this plugin. 81 | 82 | To disclose security issues for this plugin please email WordPress@wpengine.com. 83 | 84 | == Screenshots == 85 | 86 | 1. Main screen: compatibility checker options 87 | 2. Compatibility results screen 88 | 89 | == Changelog == 90 | 91 | = 1.6.4 = 92 | - Updates are now served from WP Engine servers. 93 | - Update dependencies. 94 | 95 | = 1.6.3 = 96 | - Fix issue where Buffer was not available to the scans. 97 | 98 | = 1.6.2 = 99 | - Update packages. 100 | 101 | = 1.6.1 = 102 | - Fix issue on update where old files were included. 103 | 104 | = 1.6.0 = 105 | - Changed from running PHP Compatibility scans on your WordPress server to using scan data from [Tide](https://wptide.org). 106 | - Removed `phpcompat` WP-CLI command. 107 | - Update dependencies. 108 | 109 | = 1.5.2 = 110 | - Removed PHP 5.2 checks 111 | - Fixed PHP 8 issue where plugin cannot cannot be uninstalled. 112 | 113 | = 1.5.1 = 114 | - Added Smart Plugin Manager to whitelisted plugins. 115 | 116 | = 1.5.0 = 117 | - Added support for PHP 7.3 compatibility checks. 118 | 119 | = 1.4.8 = 120 | - Update dependencies. 121 | 122 | = 1.4.7 = 123 | - Better translation support. 124 | 125 | = 1.4.6 = 126 | - Switched to new PHPCompatibilityWP library to help prevent false positives. 127 | 128 | = 1.4.5 = 129 | - Use plugin version number to enqueue scripts and styles. 130 | 131 | = 1.4.4 = 132 | - PHP 5.2 Support & PHP 7.1 and 7.2 Lints. 133 | - Updated call to action sidebar depending on platform. 134 | 135 | = 1.4.3 = 136 | - Fixed Composer issue. 137 | 138 | = 1.4.1 = 139 | - Updated PHP_CodeSniffer to fix a security advisory. 140 | - Whitelisted a number of plugins. 141 | 142 | = 1.4.0 = 143 | - Updated UX for viewing PHP errors to be more intuitive and require less scrolling. 144 | - Added links for non-technical users who need assistance from developers to fix PHP errors or to test their site in PHP 7 enabled hosting environments. 145 | 146 | = 1.3.2 = 147 | - Added a "Clean up" button and uninstall.php. 148 | - Added phpcompat_phpversions filter. 149 | 150 | = 1.3.1 = 151 | - Whitelisted a number of plugins. 152 | 153 | = 1.3.0 = 154 | - Updated the PHPCompatibility library to latest version. Should fix many false positives. 155 | - Changed language and added help text to Admin UI. 156 | 157 | = 1.2.4 = 158 | - Fixed Composer issue. 159 | 160 | = 1.2.3 = 161 | - Updated the PHPCompatibility library to latest version. 162 | - Whitelisted TablePress. 163 | 164 | = 1.2.2 = 165 | - Whitelisted UpdraftPlus and Max Mega Menu. 166 | 167 | = 1.2.1 = 168 | - Updated the PHPCompatibility library to latest version. 169 | 170 | = 1.2.0 = 171 | - Updated the PHPCompatibility library to latest version. 172 | - Added support for PHP 5.6 173 | 174 | = 1.1.2 = 175 | - Fixed issue with WordPress notices breaking the plugin header. 176 | - Changed the way we send and parse JSON. 177 | - You can now restart an in progress scan. 178 | - Updated download.js to v4.2 for better Safari compatibility. 179 | 180 | = 1.1.1 = 181 | - Fixed bug with active job display. 182 | - Updated progress bar calculation. 183 | 184 | = 1.1.0 = 185 | - Test results now persist page reloads. 186 | - Failed tests will show an overview of the results. 187 | - The scan timeout is now configurable using a filter. See the FAQ for more details. 188 | 189 | = 1.0.3 = 190 | - Fixed a bug in the WP-CLI command. 191 | - Added a handful of PHP 7 compatible plugins to the whitelist. 192 | 193 | = 1.0.2 = 194 | - Added additional role protections. 195 | - Changed the UI colors to better understand output at a glance. 196 | - Exclude checking node_modules and tmp directories. 197 | - Added support for child theme's parent theme. 198 | 199 | = 1.0.1 = 200 | - Updated compatibility library with a few bugfixes. 201 | - Added skip logic to prevent checker from hanging. 202 | 203 | = 1.0.0 = 204 | - Major update to add PHP 7 checking support. 205 | - Improved the UX of the progress bar. 206 | - Fixed bug with the way the plugin menu was registered. 207 | 208 | = 0.1.0 = 209 | - Initial version. 210 | - PHP 5.5, 5.4, and 5.3 Support. 211 | - Basic WP-CLI Commands. 212 | 213 | == Upgrade Notice == 214 | 215 | = 1.6.0 = 216 | - WordPress minimum increased from 4.8 to 5.6. 217 | - PHP Compatibility scans now run via [Tide](https://wptide.org) and no longer run on your host server! 218 | - The WP-CLI `phpcompat` command has been removed as this plugin no longer runs on your host server and relies upon Tide. 219 | 220 | = 1.4.8 = 221 | - Update dependencies. 222 | -------------------------------------------------------------------------------- /src/js/include/download.js: -------------------------------------------------------------------------------- 1 | import $ from "jquery"; 2 | 3 | export function showDownload() { 4 | $("#wpe-pcc-download-report").show(); 5 | } 6 | 7 | export function hideDownload() { 8 | $("#wpe-pcc-download-report").hide(); 9 | } 10 | 11 | export function downloadReport() { 12 | const rawData = $("#testResults").val(); 13 | 14 | if (rawData.length) { 15 | var el = document.createElement("a"); 16 | el.setAttribute( 17 | "href", 18 | "data:text/plain;charset=utf-8," + encodeURIComponent(rawData) 19 | ); 20 | el.setAttribute("download", "report.txt"); 21 | 22 | el.style.display = "none"; 23 | document.body.appendChild(el); 24 | 25 | el.click(); 26 | 27 | document.body.removeChild(el); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/js/include/queue-manager.js: -------------------------------------------------------------------------------- 1 | import { updateResultFailure, updateResult } from "./render"; 2 | import $ from "jquery"; 3 | import { hideDownload, showDownload } from "./download"; 4 | 5 | const progress = $(".wpe-progress-active"); 6 | const progressCount = $(".wpe-pcc-progress-count"); 7 | 8 | export function initQueue(itemsToScan, activeOnly) { 9 | // Reset the queue. 10 | window.phpcompat.queue = []; 11 | window.phpcompat.results = []; 12 | window.phpcompat.total = 0; 13 | window.phpcompat.completed = 0; 14 | 15 | clearTimeout(window.phpcompat.ticker); 16 | if (window.phpcompat.xhr) { 17 | window.phpcompat.xhr.abort(); 18 | } 19 | 20 | hideDownload(); 21 | resetProgress(); 22 | $("#testResults").val(""); 23 | 24 | itemsToScan.plugins.forEach((plugin) => { 25 | if ("yes" === plugin.active || "no" === activeOnly) { 26 | window.phpcompat.queue.push({ 27 | ...plugin, 28 | type: "plugin", 29 | }); 30 | } 31 | }); 32 | 33 | itemsToScan.themes.forEach((theme) => { 34 | if ("yes" === theme.active || "no" === activeOnly) { 35 | window.phpcompat.queue.push({ 36 | ...theme, 37 | type: "theme", 38 | }); 39 | } 40 | }); 41 | 42 | window.phpcompat.total = window.phpcompat.queue.length; 43 | } 44 | 45 | export function executeJob(job, cb) { 46 | var endpoint = `https://wptide.org/api/v1/audit/wporg/${job.type}/${job.slug}/${job.version}?reports=phpcs_phpcompatibilitywp`; 47 | 48 | // Only allow 1 concurrent request at a time 49 | if ( 50 | false === window.phpcompat.xhr || 51 | [0, 4].includes(window.phpcompat.xhr.readyState) 52 | ) { 53 | window.phpcompat.xhr = $.ajax(endpoint, { 54 | dataType: "json", 55 | beforeSend: () => { 56 | const resultItem = $(`#${job.type}_${job.slug}`); 57 | if (!resultItem.find(".spinner").length) { 58 | const spinner = $(''); 59 | spinner.show().appendTo(resultItem); 60 | } 61 | }, 62 | }) 63 | .done((response) => { 64 | if ("complete" === response.status) { 65 | window.phpcompat.results.push({ ...job, ...response }); 66 | updateResult(response, job); 67 | updateProgress(); 68 | } else if ("pending" === response.status) { 69 | const now = new Date(); 70 | console.log("Report is pending, retry in 5 seconds"); 71 | // Retry in 5 seconds. 72 | job.retryAt = new Date(now.getTime() + 5000); 73 | window.phpcompat.queue.push(job); 74 | } else { 75 | updateProgress(); 76 | // Unexpected behaviour. Stop scanning and display current status. 77 | updateResultFailure(response, job); 78 | } 79 | }) 80 | .fail((jqXHR) => { 81 | updateProgress(); 82 | 83 | // If connection was lost during scan, show error message. 84 | if (!jqXHR.responseJSON && 0 === jqXHR.status && 0 === jqXHR.readyState) { 85 | updateResultFailure( 86 | { 87 | status: "failed", 88 | message: "The audit of this code was interrupted, please scan again.", 89 | }, 90 | job 91 | ); 92 | } else { 93 | updateResultFailure( 94 | jqXHR.responseJSON ?? { 95 | status: jqXHR.status, 96 | message: jqXHR.responseText, 97 | }, 98 | job 99 | ); 100 | } 101 | }) 102 | .always(() => { 103 | cb(); 104 | }); 105 | } 106 | } 107 | 108 | export function runNextJob() { 109 | if (window.phpcompat.queue.length === 0) { 110 | resetProgress(); 111 | showDownload(); 112 | $(".wpe-pcc-information").show(); 113 | return; 114 | } 115 | 116 | const now = new Date(); 117 | 118 | // Pick up next job from queue. 119 | var job = window.phpcompat.queue.shift(); 120 | 121 | // The job is new or the time has come. 122 | if ("undefined" === typeof job.retryAt || job.retryAt <= now) { 123 | // Run the job now. 124 | executeJob(job, () => { 125 | runNextJob(); 126 | }); 127 | } else { 128 | // Or put it back to the end of queue. 129 | window.phpcompat.queue.push(job); 130 | window.phpcompat.ticker = setTimeout(runNextJob, 1000); 131 | } 132 | } 133 | 134 | export function updateProgress() { 135 | window.phpcompat.completed++; 136 | progress.show(); 137 | progressCount.text(`${window.phpcompat.completed} of ${window.phpcompat.total}`); 138 | } 139 | 140 | function resetProgress() { 141 | $(".wpe-pcc-spinner").hide(); 142 | progressCount.text(""); 143 | progress.hide(); 144 | } 145 | -------------------------------------------------------------------------------- /src/js/include/render.js: -------------------------------------------------------------------------------- 1 | import Mustache from "mustache"; 2 | import $ from "jquery"; 3 | import compareVersions from "compare-versions"; 4 | const { __ } = wp.i18n; 5 | 6 | export function initResults(itemsToScan, activeOnly) { 7 | const container = $("#wpe_pcc_results"); 8 | const template = $("#result-template").html().toString(); 9 | container.empty(); 10 | 11 | if (itemsToScan.plugins.length) { 12 | container.append("

" + __("Plugins", "wpe-php-compat") + "

"); 13 | itemsToScan.plugins.forEach((plugin) => { 14 | if ("yes" === plugin.active || "no" === activeOnly) { 15 | const view = { 16 | ...plugin, 17 | type: "plugin", 18 | status: "pending", 19 | }; 20 | 21 | const output = Mustache.render(template, view); 22 | container.append(output); 23 | } 24 | }); 25 | } 26 | 27 | if (itemsToScan.themes.length) { 28 | container.append("

" + __("Themes", "wpe-php-compat") + "

"); 29 | itemsToScan.themes.forEach((theme) => { 30 | if ("yes" === theme.active || "no" === activeOnly) { 31 | const view = { 32 | ...theme, 33 | type: "theme", 34 | status: "pending", 35 | }; 36 | 37 | const output = Mustache.render(template, view); 38 | container.append(output); 39 | } 40 | }); 41 | } 42 | 43 | $(".wpe-pcc-results").show(); 44 | } 45 | 46 | export function updateResult(response, job) { 47 | const report = response.reports?.phpcs_phpcompatibilitywp?.report; 48 | 49 | // If we have a success response but don't have report data, show an error. 50 | if (!report) { 51 | updateResultFailure( 52 | { 53 | status: "failed", 54 | message: "No scan results found", 55 | }, 56 | job 57 | ); 58 | return; 59 | } 60 | 61 | const resultItem = $(`#${job.type}_${job.slug}`); 62 | const template = $("#result-template").html().toString(); 63 | 64 | // Data to render the template. 65 | const view = { 66 | ...job, 67 | }; 68 | 69 | // Success if no errors. 70 | view.status = 71 | report.totals.errors === 0 && report.totals.warnings === 0 72 | ? "success" 73 | : "error"; 74 | 75 | // Create index for PHP Versions. 76 | const phpVersions = Object.keys(report.versions).sort(compareVersions); 77 | let rawReport = `${job.name} ${job.version}\n\n`; 78 | 79 | view.php = []; 80 | view.reports = []; 81 | 82 | phpVersions.forEach((phpVersion) => { 83 | view.php.push({ 84 | phpversion: phpVersion, 85 | passed: report.compatible.includes(phpVersion), 86 | }); 87 | 88 | // Extract all error messages from incompatible PHP versions. 89 | if (report.incompatible.includes(phpVersion)) { 90 | const messages = []; 91 | const files = report.versions[phpVersion].files; 92 | const fileReports = []; 93 | 94 | // In each file of the plugin, add error messages. 95 | Object.keys(files).forEach((file) => { 96 | if (files[file].messages.length) { 97 | fileReports.push(processFileReport(file, files[file])); 98 | 99 | files[file].messages.forEach((message) => { 100 | // Compile plain text with source file, line, error code and error text. 101 | messages.push( 102 | `${file}:${message.line}\n${message.source}\n${message.message}` 103 | ); 104 | }); 105 | } 106 | }); 107 | 108 | // Override raw report with most recent PHP version 109 | const rawVersionReport = 110 | `PHP ${phpVersion} incompatibilities:\n\n` + 111 | fileReports.join("\n\n") + 112 | "\n\n"; 113 | 114 | rawReport += rawVersionReport; 115 | 116 | view.reports.push({ 117 | phpversion: phpVersion, 118 | messages: [rawVersionReport], 119 | }); 120 | } else { 121 | rawReport += `Compatible with PHP ${phpVersion}\n`; 122 | } 123 | }); 124 | 125 | view.has_errors = !( 126 | report.totals.errors === 0 && report.totals.warnings === 0 127 | ); 128 | 129 | const output = Mustache.render(template, view); 130 | 131 | resultItem.replaceWith(output); 132 | 133 | const fullReport = $("#testResults").val(); 134 | const updatedReport = 135 | fullReport + (fullReport.length ? "\n\n\n" : "") + rawReport; 136 | 137 | $("#testResults").val(updatedReport); 138 | const blob = new Blob([updatedReport], { type: "text/plain"}); 139 | const reader = new FileReader(); 140 | reader.readAsDataURL(blob); 141 | reader.onloadend = function () { 142 | const base64Data = reader.result.split(",")[1]; 143 | $("#wpe-pcc-codeable-data").val(base64Data); 144 | } 145 | } 146 | 147 | export function processFileReport(fileName, fileReport) { 148 | const colWidths = fileReport.messages.reduce( 149 | (prev, current) => { 150 | return [ 151 | `${current.line}`.length > prev[0] ? `${current.line}`.length : prev[0], 152 | `${current.type}`.length > prev[1] ? `${current.type}`.length : prev[1], 153 | `${current.message}`.length > prev[2] 154 | ? `${current.message}`.length 155 | : prev[2], 156 | ]; 157 | }, 158 | [0, 0, 0] 159 | ); 160 | 161 | const uinqueLines = fileReport.messages.reduce((lines, message) => { 162 | if (-1 === lines.indexOf(message.line)) { 163 | lines.push(message.line); 164 | } 165 | return lines; 166 | }, []); 167 | 168 | const plainMessages = fileReport.messages.map((message) => { 169 | const col1 = message.line.toString().padStart(colWidths[0], " "); 170 | const col2 = message.type.toString().padEnd(colWidths[1], " "); 171 | const col3 = message.message.toString().padEnd(colWidths[2], " "); 172 | 173 | return ` ${col1} | ${col2} | ${col3} `; 174 | }); 175 | 176 | const header = `FILE: ${fileName}`; 177 | const found = `FOUND ${fileReport.errors} ERRORS AND ${fileReport.warnings} WARNINGS AFFECTING ${uinqueLines.length} LINES`; 178 | const maxWidth = Math.max( 179 | header.length, 180 | found.length, 181 | plainMessages[0].length 182 | ); 183 | const hr = new Array(maxWidth + 1).join("-"); 184 | const output = 185 | `${header}\n${hr}\n${found}\n${hr}\n` + 186 | plainMessages.join("\n") + 187 | `\n${hr}`; 188 | 189 | return output; 190 | } 191 | 192 | export function updateResultFailure(response, job) { 193 | const resultItem = $(`#${job.type}_${job.slug}`); 194 | const template = $("#result-template").html().toString(); 195 | const view = { 196 | ...job, 197 | status: "error", 198 | custom_error: true, 199 | response: response, 200 | }; 201 | 202 | const output = Mustache.render(template, view); 203 | resultItem.replaceWith(output); 204 | 205 | let rawReport = `${job.name} ${job.version}\n\n`; 206 | if (response.status) { 207 | rawReport += `Scan status: ${response.status}\n`; 208 | } 209 | if (response.message) { 210 | rawReport += response.message + "\n"; 211 | } 212 | if (response.errors) { 213 | response.errors.forEach((error) => { 214 | rawReport += error.message + "\n"; 215 | }); 216 | } 217 | rawReport += "\n"; 218 | const fullReport = $("#testResults").val(); 219 | const updatedReport = 220 | fullReport + (fullReport.length ? "\n\n\n" : "") + rawReport; 221 | 222 | $("#testResults").val(updatedReport); 223 | $("#wpe-pcc-codeable-data").val( 224 | Buffer.from(updatedReport).toString("base64") 225 | ); 226 | } 227 | -------------------------------------------------------------------------------- /src/js/scan.js: -------------------------------------------------------------------------------- 1 | import "../scss/scan.scss"; 2 | import jQuery from "jquery"; 3 | import { initQueue, runNextJob } from "./include/queue-manager"; 4 | import { initResults } from "./include/render"; 5 | import { downloadReport } from "./include/download"; 6 | 7 | (function ($) { 8 | if ("undefined" === typeof checkerList) { 9 | return; 10 | } 11 | 12 | window.phpcompat = {}; 13 | window.phpcompat.queue = []; 14 | window.phpcompat.total = 0; 15 | window.phpcompat.completed = 0; 16 | window.phpcompat.xhr = false; 17 | window.phpcompat.ticker = false; 18 | window.phpcompat.results = []; 19 | 20 | const activeOnlySwitch = $("input[type=radio][name=active_plugins]"); 21 | const runButton = $("#runButton"); 22 | 23 | init(checkerList); 24 | 25 | activeOnlySwitch.on("change", function () { 26 | init(checkerList); 27 | runButton.prop("disabled", false); 28 | disableDeveloperMode(); 29 | }); 30 | 31 | $("#cleanupButton").on("click", function (event) { 32 | event.preventDefault(); 33 | runButton.prop("disabled", false); 34 | disableDeveloperMode(); 35 | init(checkerList); 36 | }); 37 | 38 | runButton.on("click", function (event) { 39 | event.preventDefault(); 40 | runNextJob(); 41 | $(".wpe-pcc-spinner").show(); 42 | $(this).prop("disabled", true); 43 | }); 44 | 45 | $(document).on("click", ".wpe-pcc-php-version-errors", function (event) { 46 | event.preventDefault(); 47 | const phpVersion = $(this).data("php-version"); 48 | const reports = $(this).closest(".wpe-pcc-alert").find("#wpe_pcc_reports"); 49 | const report = reports.find(`[data-php-version="${phpVersion}"]`); 50 | $(".wpe-pcc-php-version-report").not(report).hide(); 51 | $(report).toggle(); 52 | }); 53 | 54 | $(document).on( 55 | "click", 56 | ".wpe-pcc-php-version-report-close", 57 | function (event) { 58 | event.preventDefault(); 59 | $(this).closest(".wpe-pcc-php-version-report").hide(); 60 | } 61 | ); 62 | 63 | $("#developermode").on("change", function (event) { 64 | if ($(this).is(":checked")) { 65 | $("#developerMode").show(); 66 | $("#wpe_pcc_results").hide(); 67 | } else { 68 | $("#developerMode").hide(); 69 | $("#wpe_pcc_results").show(); 70 | } 71 | }); 72 | 73 | $("#downloadReport").on("click", function (event) { 74 | event.preventDefault(); 75 | downloadReport(); 76 | }); 77 | 78 | function init(itemsToScan) { 79 | var activeOnly = $("input[type=radio][name=active_plugins]:checked").val(); 80 | initQueue(itemsToScan, activeOnly); 81 | initResults(itemsToScan, activeOnly); 82 | } 83 | 84 | function disableDeveloperMode() { 85 | $("#developermode").prop("checked", false); 86 | $("#developerMode").hide(); 87 | $("#wpe_pcc_results").show(); 88 | } 89 | })(jQuery); 90 | -------------------------------------------------------------------------------- /src/scss/scan.scss: -------------------------------------------------------------------------------- 1 | $color-error: #d63638; 2 | $color-success: #00a32a; 3 | $color-background: #fff; 4 | $color-border: #c3c4c7; 5 | 6 | @mixin textarea { 7 | width: 100%; 8 | height: 30em; 9 | max-height: 80vh; 10 | font-size: 12px; 11 | font-family: Consolas, Monaco, Lucida Console, Liberation Mono, 12 | DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace; 13 | } 14 | 15 | .wpe-pcc { 16 | &-wrap { 17 | .button-primary { 18 | .dashicons, 19 | .dashicons-before { 20 | line-height: 1.4em; 21 | } 22 | } 23 | } 24 | &-alert { 25 | background: $color-background; 26 | border: 1px solid $color-border; 27 | border-left-width: 4px; 28 | box-shadow: 0 1px 1px rgb(0 0 0 / 4%); 29 | margin: 5px 15px 2px; 30 | padding: 1px 12px; 31 | position: relative; 32 | 33 | .spinner { 34 | position: absolute; 35 | right: 0; 36 | top: 10px; 37 | visibility: visible; 38 | } 39 | 40 | &-error { 41 | border-left-color: $color-error; 42 | } 43 | 44 | &-success { 45 | border-left-color: $color-success; 46 | } 47 | } 48 | &-php-version { 49 | &-passed { 50 | color: $color-success; 51 | } 52 | 53 | &-errors { 54 | color: $color-error; 55 | text-decoration: none; 56 | } 57 | } 58 | 59 | &-results { 60 | textarea { 61 | @include textarea(); 62 | } 63 | } 64 | 65 | &-download-report { 66 | clear: both; 67 | } 68 | 69 | &-developer-mode { 70 | float: right; 71 | } 72 | 73 | &-clear-results { 74 | border: 0 none; 75 | color: #b4b9be; 76 | background: none; 77 | cursor: pointer; 78 | line-height: 28px; 79 | margin-left: 10px; 80 | } 81 | 82 | &-main, 83 | &-aside { 84 | display: table-cell; 85 | } 86 | 87 | &-main { 88 | padding-right: 40px; 89 | } 90 | 91 | &-aside { 92 | width: 277px; 93 | 94 | h2 { 95 | line-height: 1.15em; 96 | text-transform: capitalize; 97 | 98 | &:first-child { 99 | margin-top: 0; 100 | } 101 | } 102 | 103 | &-content { 104 | border: 1px solid #babec3; 105 | padding: 20px; 106 | margin-top: 20px; 107 | margin-bottom: 20px; 108 | 109 | p { 110 | margin: 0; 111 | padding: 0; 112 | } 113 | } 114 | } 115 | 116 | &-scan-information { 117 | display: inline-block; 118 | 119 | .wpe-pcc-spinner { 120 | float: none; 121 | margin: 4px 0 4px 10px; 122 | padding: 0; 123 | } 124 | 125 | .wpe-progress-active { 126 | margin-left: 10px; 127 | line-height: 1.8; 128 | display: inline-block; 129 | } 130 | } 131 | 132 | &-logo { 133 | display: block; 134 | max-width: 182px; 135 | text-align: center; 136 | margin: 0 auto; 137 | } 138 | 139 | &-get-hosting { 140 | background: #e8e9ea; 141 | } 142 | 143 | &-button { 144 | display: block; 145 | padding: 15px; 146 | margin-top: 20px; 147 | margin-bottom: 20px; 148 | text-transform: uppercase; 149 | font-weight: bold; 150 | font-size: 14px; 151 | text-align: center; 152 | background: #40bac8; 153 | color: #fff; 154 | text-decoration: none; 155 | 156 | &:hover, 157 | &:focus { 158 | text-decoration: none; 159 | cursor: pointer; 160 | background: #3199a5; 161 | color: #fff; 162 | } 163 | 164 | &-primary { 165 | background: #eb6126; 166 | 167 | &:hover, 168 | &:focus { 169 | background: #bf4a18; 170 | } 171 | } 172 | } 173 | } 174 | 175 | .rtl { 176 | direction: rtl; 177 | unicode-bidi: embed; 178 | 179 | .wpe-pcc { 180 | &-alert { 181 | border-left-width: 1px; 182 | border-right-width: 4px; 183 | 184 | .spinner { 185 | left: 0; 186 | right: auto; 187 | } 188 | 189 | &-error { 190 | border-left-color: $color-border; 191 | border-right-color: $color-error; 192 | } 193 | 194 | &-success { 195 | border-left-color: $color-border; 196 | border-right-color: $color-success; 197 | } 198 | } 199 | 200 | &-developer-mode { 201 | float: left; 202 | } 203 | 204 | &-clear-results { 205 | margin-left: 0; 206 | margin-right: 10px; 207 | } 208 | 209 | &-main { 210 | padding-left: 40px; 211 | padding-right: 0; 212 | } 213 | } 214 | } 215 | 216 | input.wpe-pcc-button { 217 | display: block; 218 | border: 0 none; 219 | width: 100%; 220 | } -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | } 32 | */ 33 | export const OBSERVED_CONSOLE_MESSAGE_TYPES = { 34 | warning: 'warn', 35 | error: 'error', 36 | }; 37 | 38 | export const PLUGIN = 'wpe-rest-block'; 39 | 40 | /** 41 | * Array of page event tuples of [ eventName, handler ]. 42 | * 43 | * @type {Array} 44 | */ 45 | export const pageEvents = []; 46 | 47 | // The Jest timeout is increased because these tests are a bit slow 48 | jest.setTimeout(PUPPETEER_TIMEOUT || 100000); 49 | 50 | export async function setupBrowser() { 51 | console.log("Seting up Browser"); 52 | await setBrowserViewport('large'); 53 | } 54 | 55 | /** 56 | * Navigates to the post listing screen and bulk-trashes any posts which exist. 57 | * 58 | * @param {string} postType - String slug for type of post to trash. 59 | * 60 | * @return {Promise} Promise resolving once posts have been trashed. 61 | */ 62 | export async function trashExistingPosts(postType = 'post') { 63 | console.log('Trashing Existing Posts'); 64 | await switchUserToAdmin(); 65 | // Visit `/wp-admin/edit.php` so we can see a list of posts and delete them. 66 | const query = addQueryArgs('', { 67 | post_type: postType, 68 | }).slice(1); 69 | await visitAdminPage('edit.php', query); 70 | 71 | // If this selector doesn't exist there are no posts for us to delete. 72 | const bulkSelector = await page.$('#bulk-action-selector-top'); 73 | if (!bulkSelector) { 74 | return; 75 | } 76 | 77 | // Select all posts. 78 | await page.waitForSelector('[id^=cb-select-all-]'); 79 | await page.click('[id^=cb-select-all-]'); 80 | // Select the "bulk actions" > "trash" option. 81 | await page.select('#bulk-action-selector-bottom', 'trash'); 82 | // Submit the form to send all draft/scheduled/published posts to the trash. 83 | await page.click('#doaction2'); 84 | await page.waitForXPath( 85 | '//*[contains(@class, "updated notice")]/p[contains(text(), "moved to the Trash.")]' 86 | ); 87 | await switchUserToTest(); 88 | } 89 | 90 | /** 91 | * Adds an event listener to the page to handle additions of page event 92 | * handlers, to assure that they are removed at test teardown. 93 | */ 94 | export function capturePageEventsForTearDown(pageEvents) { 95 | console.log('Capturing Page Events') 96 | page.on('newListener', (eventName, listener) => { 97 | pageEvents.push([eventName, listener]); 98 | }); 99 | } 100 | 101 | /** 102 | * Removes all bound page event handlers. 103 | */ 104 | export function removePageEvents(pageEvents) { 105 | pageEvents.forEach(([eventName, handler]) => { 106 | page.removeListener(eventName, handler); 107 | }); 108 | } -------------------------------------------------------------------------------- /tests/e2e-tests/insert-grid-block.spec.js: -------------------------------------------------------------------------------- 1 | import { createNewPost, enablePageDialogAccept, insertBlock } from '@wordpress/e2e-test-utils'; 2 | 3 | describe('WPE Blocks E2E', () => { 4 | beforeAll(async () => { 5 | await enablePageDialogAccept(); 6 | 7 | jest.setTimeout(process.env.PUPPETEER_TIMEOUT || 1000000); 8 | }); 9 | 10 | beforeEach(async () => { 11 | await createNewPost() 12 | await insertBlock("WP Engine Dynamic Post Grid"); 13 | }) 14 | 15 | it("should add the block to the page", async () => { 16 | const el = await page.$('[data-type="wpe-rest-block/wpe-dynamic-post-grid"]'); 17 | expect(el).not.toBeNull(); 18 | }); 19 | 20 | it('should display multiple posts', async () => { 21 | const el = await page.$$('.wpe-dynamic-post-grid-block__list article'); 22 | expect(el.length).toBeGreaterThan(0) 23 | }) 24 | }); -------------------------------------------------------------------------------- /tests/test-plugin-file.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 29 | } 30 | /** 31 | * Test loader function 32 | */ 33 | public function test_wpe_phpcompat_loader() { 34 | 35 | Monkey\Functions\expect( 'load_plugin_textdomain' )->once(); 36 | 37 | wpe_phpcompat_loader(); 38 | 39 | $this->assertTrue( defined( 'WPENGINE_PHP_COMPATIBILITY_VERSION' ) ); 40 | 41 | } 42 | 43 | public function test_autoloader_registered() { 44 | $this->assertContains( 'WPEngine_PHPCompat\wpe_phpcompat_autoloader', spl_autoload_functions() ); 45 | } 46 | 47 | public function test_autoloader() { 48 | 49 | $home = dirname( __DIR__ ); 50 | $test_classes = array( 51 | 'WPEngine_PHPCompat\Class_One' => $home. '/plugin/lib/class-class-one.php', 52 | 'WPEngine_PHPCompat\Sub_Classes\Class_Two' => $home . '/plugin/lib/Sub_Classes/class-class-two.php', 53 | 'Class_Three' => '', 54 | ); 55 | 56 | foreach ( $test_classes as $test_class => $class_file ) { 57 | 58 | $file = wpe_phpcompat_get_class_file( $test_class ); 59 | 60 | $this->assertEquals( $class_file, $file ); 61 | 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { merge } = require("webpack-merge"); 2 | const defaultConfig = require("@wordpress/scripts/config/webpack.config"); 3 | const DependencyExtractionWebpackPlugin = require("@wordpress/dependency-extraction-webpack-plugin"); 4 | const path = require("path"); 5 | const webpack = require('webpack'); 6 | 7 | module.exports = merge(defaultConfig, { 8 | output: { 9 | path: path.resolve(process.cwd(), "plugin/build"), 10 | }, 11 | entry: { 12 | scan: path.resolve(process.cwd(), "src/js", "scan.js"), 13 | }, 14 | plugins: [ 15 | ...defaultConfig.plugins.filter( 16 | (plugin) => 17 | plugin.constructor.name !== "DependencyExtractionWebpackPlugin" 18 | ), 19 | new DependencyExtractionWebpackPlugin(), 20 | new webpack.ProvidePlugin({ 21 | Buffer: ['buffer', 'Buffer'], 22 | }), 23 | ], 24 | resolve: { 25 | fallback: { 26 | buffer: require.resolve("buffer/"), 27 | } 28 | } 29 | }); --------------------------------------------------------------------------------