From ae23a192da6f90e66c6a66d6d847156678bd82ca Mon Sep 17 00:00:00 2001 From: Raz Ohad Date: Tue, 5 Nov 2024 19:06:44 +0700 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Initial=20Refactor=20commi?= =?UTF-8?q?t=20[APP-687]=20(#109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial refactor commit * ✅ Added build and tests CI/CD * PR Rejects * Rejects leftover --- .build-rsync-exclude | 33 + .eslintignore | 10 + .eslintrc | 52 ++ .github/checkstyle-problem-matcher.json | 23 + .github/scripts/publish-to-wordpress-org.sh | 65 ++ .github/scripts/update-version-in-files.js | 65 ++ .github/scripts/validate-build-files.sh | 49 ++ .github/workflows/build.yml | 92 +++ .github/workflows/js-ci.yml | 45 ++ .github/workflows/php-coding-standards.yml | 55 ++ .github/workflows/phpunit.yml | 146 ++++ .gitignore | 8 + .jshintrc | 16 - .travis.yml | 35 - Gruntfile.js | 297 -------- classes/database/database-constants.php | 73 ++ classes/database/entry.php | 386 ++++++++++ .../exceptions/missing-table-exception.php | 18 + classes/database/table.php | 574 +++++++++++++++ classes/logger.php | 37 + classes/module-base.php | 267 +++++++ classes/rest/route.php | 387 ++++++++++ classes/utils.php | 37 + classes/utils/assets.php | 144 ++++ composer.json | 43 +- includes/manager.php | 57 ++ includes/pojo-a11y-settings.php | 645 ----------------- .../legacy/assets}/css/style.css | 0 .../legacy/assets}/css/style.min.css | 0 .../legacy/assets}/js/app.dev.js | 0 .../legacy/assets}/js/app.min.js | 0 .../legacy/assets}/js/skip-link-focus-fix.js | 0 .../legacy/assets}/less/_background.less | 0 .../legacy/assets}/less/_high-contrast.less | 0 .../legacy/assets}/less/_mixing.less | 0 .../legacy/assets}/less/_toolbar.less | 0 .../legacy/assets}/less/_underline.less | 0 .../legacy/assets}/less/_visibility.less | 0 .../legacy/assets}/less/style.less | 0 .../legacy/components/admin.php | 24 +- .../legacy/components/customizer.php | 308 ++++---- .../legacy/components/elementor.php | 11 +- .../legacy/components/frontend.php | 112 +-- modules/legacy/components/settings.php | 671 ++++++++++++++++++ modules/legacy/module.php | 67 ++ package.json | 54 +- phpunit.xml.dist | 43 ++ plugin.php | 111 +++ pojo-accessibility.php | 100 +-- ruleset.xml | 54 ++ tests/bootstrap.php | 54 +- tests/phpunit/helpers/functions.php | 8 + tests/phpunit/helpers/module-test-base.php | 77 ++ tests/phpunit/helpers/route-test.php | 31 + tests/phpunit/helpers/test-base.php | 40 ++ .../plugin/modules/legacy/module-test.php | 20 + tests/test-base.php | 13 - webpack.config.js | 20 + 58 files changed, 4147 insertions(+), 1330 deletions(-) create mode 100644 .build-rsync-exclude create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .github/checkstyle-problem-matcher.json create mode 100644 .github/scripts/publish-to-wordpress-org.sh create mode 100644 .github/scripts/update-version-in-files.js create mode 100644 .github/scripts/validate-build-files.sh create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/js-ci.yml create mode 100644 .github/workflows/php-coding-standards.yml create mode 100644 .github/workflows/phpunit.yml delete mode 100755 .jshintrc delete mode 100644 .travis.yml delete mode 100644 Gruntfile.js create mode 100644 classes/database/database-constants.php create mode 100644 classes/database/entry.php create mode 100644 classes/database/exceptions/missing-table-exception.php create mode 100644 classes/database/table.php create mode 100644 classes/logger.php create mode 100644 classes/module-base.php create mode 100644 classes/rest/route.php create mode 100644 classes/utils.php create mode 100644 classes/utils/assets.php create mode 100644 includes/manager.php delete mode 100644 includes/pojo-a11y-settings.php rename {assets => modules/legacy/assets}/css/style.css (100%) rename {assets => modules/legacy/assets}/css/style.min.css (100%) rename {assets => modules/legacy/assets}/js/app.dev.js (100%) rename {assets => modules/legacy/assets}/js/app.min.js (100%) rename {assets => modules/legacy/assets}/js/skip-link-focus-fix.js (100%) rename {assets => modules/legacy/assets}/less/_background.less (100%) rename {assets => modules/legacy/assets}/less/_high-contrast.less (100%) rename {assets => modules/legacy/assets}/less/_mixing.less (100%) rename {assets => modules/legacy/assets}/less/_toolbar.less (100%) rename {assets => modules/legacy/assets}/less/_underline.less (100%) rename {assets => modules/legacy/assets}/less/_visibility.less (100%) rename {assets => modules/legacy/assets}/less/style.less (100%) rename includes/pojo-a11y-admin-ui.php => modules/legacy/components/admin.php (93%) rename includes/pojo-a11y-customizer.php => modules/legacy/components/customizer.php (50%) rename includes/pojo-a11y-elementor.php => modules/legacy/components/elementor.php (66%) rename includes/pojo-a11y-frontend.php => modules/legacy/components/frontend.php (89%) create mode 100644 modules/legacy/components/settings.php create mode 100644 modules/legacy/module.php create mode 100644 phpunit.xml.dist create mode 100644 plugin.php create mode 100644 ruleset.xml mode change 100755 => 100644 tests/bootstrap.php create mode 100644 tests/phpunit/helpers/functions.php create mode 100644 tests/phpunit/helpers/module-test-base.php create mode 100644 tests/phpunit/helpers/route-test.php create mode 100644 tests/phpunit/helpers/test-base.php create mode 100644 tests/phpunit/plugin/modules/legacy/module-test.php delete mode 100755 tests/test-base.php create mode 100644 webpack.config.js diff --git a/.build-rsync-exclude b/.build-rsync-exclude new file mode 100644 index 0000000..5a08744 --- /dev/null +++ b/.build-rsync-exclude @@ -0,0 +1,33 @@ +.bpg.json +.babelrc +ruleset.xml +webpack.config.js +phpunit.xml.dist +.DS_Store +node_modules/ +.git +.gitignore +.gitmodules +.github/ +.editorconfig +.gitattributes +.eslintrc +.eslintignore +.phpcs.xml.dist +.phpunit.xml.d +coverage/ +assets/dev/ +.phpunit.result* +bin/ +tests/ +package.json +composer.json +composer.lock +package-lock.json +jest.config.js + +.build-rsync-exclude +one-click-accessibility/ +one-click-accessibility.zip +.svn-assets/ +.husky/ diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..9184afc --- /dev/null +++ b/.eslintignore @@ -0,0 +1,10 @@ +assets/lib/**/*.js +assets/js/*.js +**/*.min.js +**/node_modules/** +**/vendor/** +build/** +libraries/** +coverage/** +assets/build/** +tests/** diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..bd2111c --- /dev/null +++ b/.eslintrc @@ -0,0 +1,52 @@ +{ + "extends": [ + "plugin:react/recommended", + "plugin:@wordpress/eslint-plugin/recommended-with-formatting", + "plugin:import/recommended", + "plugin:jsx-a11y/strict" + ], + "plugins": [ + "babel", + "react" + ], + "env": { + "browser": true, + "node": true, + "es6": true, + "jquery": true + }, + "globals": { + "jQuery": "writable" + }, + "parserOptions": { + "ecmaVersion": 8, + "sourceType": "module", + "requireConfigFile": false + }, + "rules": { + "no-console": "off", + "react-hooks/exhaustive-deps": "off", + "strict": [ "error", "global" ], + "curly": "warn", + "import/order": [ + "error", + { + "groups": [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index", + "object", + "type" + ], + "alphabetize": { + "order": "asc", + "caseInsensitive": false + } + } + ] + }, + "parser": "@babel/eslint-parser" +} diff --git a/.github/checkstyle-problem-matcher.json b/.github/checkstyle-problem-matcher.json new file mode 100644 index 0000000..5c6ce66 --- /dev/null +++ b/.github/checkstyle-problem-matcher.json @@ -0,0 +1,23 @@ +{ + "problemMatcher": [ + { + "owner": "phpcs", + "severity": "error", + "pattern": [ + { + "regexp": "^$", + "file": 1 + }, + { + "regexp": "+)$", + "line": 1, + "column": 2, + "severity": 3, + "message": 4, + "code": 5, + "loop": true + } + ] + } + ] +} diff --git a/.github/scripts/publish-to-wordpress-org.sh b/.github/scripts/publish-to-wordpress-org.sh new file mode 100644 index 0000000..dcbed84 --- /dev/null +++ b/.github/scripts/publish-to-wordpress-org.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -eo pipefail + +if [[ -z "$SVN_USERNAME" ]]; then + echo "Set the SVN_USERNAME secret" + exit 1 +fi + +if [[ -z "$SVN_PASSWORD" ]]; then + echo "Set the SVN_PASSWORD secret" + exit 1 +fi + +if [[ -z "$PLUGIN_VERSION" ]]; then + echo "Set the PLUGIN_VERSION env var" + exit 1 +fi + +if [[ -z "$PLUGIN_SLUG" ]]; then + echo "Set the PLUGIN_SLUG env var" + exit 1 +fi + +echo "Publish version: ${PLUGIN_VERSION}" + +PLUGIN_PATH="$GITHUB_WORKSPACE/${PLUGIN_SLUG}" +SVN_PATH="$GITHUB_WORKSPACE/svn" + +cd $PLUGIN_PATH +mkdir -p $SVN_PATH +cd $SVN_PATH + +echo "Checkout from SVN" +svn co https://plugins.svn.wordpress.org/${PLUGIN_SLUG}/trunk + +echo "Clean trunk folder" +cd $SVN_PATH/trunk +find . -maxdepth 1 -not -name ".svn" -not -name "." -not -name ".." -exec rm -rf {} + + +echo "Copy files" +rsync -ah --progress $PLUGIN_PATH/* $SVN_PATH/trunk + +echo "Preparing files" +cd $SVN_PATH/trunk + +echo "svn delete" +svn status | grep -v '^.[ \t]*\\..*' | { grep '^!' || true; } | awk '{print $2}' | xargs -r svn delete; + +echo "svn add" +svn status | grep -v '^.[ \t]*\\..*' | { grep '^?' || true; } | awk '{print $2}' | xargs -r svn add; + +svn status + +echo "Commit files to trunk" +svn ci -m "Upload v${PLUGIN_VERSION}" --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" + +echo "Copy files from trunk to tag ${PLUGIN_VERSION}" +svn cp https://plugins.svn.wordpress.org/${PLUGIN_SLUG}/trunk https://plugins.svn.wordpress.org/${PLUGIN_SLUG}/tags/${PLUGIN_VERSION} --message "Tagged ${PLUGIN_VERSION}" --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" +svn update + +echo "Remove the SVN folder from the workspace (for multiple releases in the same Action)" +#rm -rf $SVN_PATH + +echo "Back to the workspace root" +cd $GITHUB_WORKSPACE diff --git a/.github/scripts/update-version-in-files.js b/.github/scripts/update-version-in-files.js new file mode 100644 index 0000000..4d816d7 --- /dev/null +++ b/.github/scripts/update-version-in-files.js @@ -0,0 +1,65 @@ +'use strict'; + +const fs = require( "fs" ); +const MAIN_FILE_NAME = 'pojo-accessibility.php'; +const VERSION_CONSTANT = 'EA11Y_VERSION'; + +const { VERSION } = process.env; + if ( ! VERSION ) { + throw new Error( 'VERSION is not defined in the environment variables.' ); +} + +async function replaceInFileWithArray( filePath, replacementArray ) { + try { + let content = fs.readFileSync( filePath, { encoding: 'utf8' } ); + + for ( const replacement of replacementArray ) { + console.log( `Replacing ${ replacement.from } with ${ replacement.to } in ${ filePath }` ); + content = content.replace( replacement.from, replacement.to ); + } + console.log( content ); + //fs.writeFileSync( filePath, content, { encoding: 'utf8' } ); + console.log( `All replacements in ${ filePath } were successful.`); + } catch (error) { + console.error( `An error occurred in ${ filePath }:`, error ); + } +} + +const run = async () => { + try { + // update stable tag in readme.txt + await replaceInFileWithArray( './readme.txt', + [ + { + from: /Stable tag: (.*)/m, + to: `Stable tag: ${ VERSION }`, + }, + { + from: /= NEXT_VERSION =/m, + to: `= ${ VERSION } =`, + }, + ] + ); + + // update version in MAIN_FILE_NAME + await replaceInFileWithArray( `./${MAIN_FILE_NAME}`, + [ + { + from: /\* Version: (.*)/m, + to: `* Version: ${ VERSION }`, + }, + { + from: new RegExp(`define\\( '${VERSION_CONSTANT}', '(.*)' \\);`, 'm' ), + to: `define( '${VERSION_CONSTANT}', '${VERSION}' );`, + } + ] + ); + + } catch ( err ) { + // eslint-disable-next-line no-console + console.error( 'Error occurred:', err ); + process.exit( 1 ); + } +}; + +run(); diff --git a/.github/scripts/validate-build-files.sh b/.github/scripts/validate-build-files.sh new file mode 100644 index 0000000..31fb621 --- /dev/null +++ b/.github/scripts/validate-build-files.sh @@ -0,0 +1,49 @@ +#!/bin/bash +set -eo pipefail + +if [[ -z "$PLUGIN_SLUG" ]]; then + echo "Set the PLUGIN_SLUG env var" + exit 1 +fi + +if [[ -z "$PLUGIN_VERSION" ]]; then + echo "Set the PLUGIN_VERSION env var" + exit 1 +fi + +PLUGIN_PATH="$GITHUB_WORKSPACE/${PLUGIN_SLUG}" + +cd $PLUGIN_PATH + +PLUGIN_MAIN_FILE="${PLUGIN_SLUG}.php" + +if [ ! -f "${PLUGIN_MAIN_FILE}" ]; then + echo "${PLUGIN_MAIN_FILE} file does not exist" + exit 1 +fi + +if [ ! -f "readme.txt" ]; then + echo "readme.txt file does not exist" + exit 1 +fi + +if [[ $(grep -c "Version: $PLUGIN_VERSION" "$PLUGIN_MAIN_FILE") -eq 0 ]]; then + echo "${PLUGIN_MAIN_FILE} file does not contain the correct build version : $PLUGIN_VERSION" + EXISTING_VERSION=$(sed -n 's/.*Version: \(.*\)/\1/p' "$PLUGIN_MAIN_FILE") + echo "Existing version: $EXISTING_VERSION" + exit 1 +fi + +if [[ $(grep -c "Stable tag: $PLUGIN_VERSION" "readme.txt") -eq 0 ]]; then + echo "readme.txt file does not contain the correct stable tag version : $PLUGIN_VERSION" + EXISTING_VERSION=$(sed -n 's/.*Stable tag: \(.*\)/\1/p' "readme.txt") + echo "Existing version: $EXISTING_VERSION" + exit 1 +fi + +echo "validate-build-files Details:" +echo "---" +echo "SVN Tag name: $PLUGIN_VERSION" +echo "Package Version: $PACKAGE_VERSION" +echo "Trunk Files:" +ls -la diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..bfe9bf8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,92 @@ +name: Build +on: + pull_request: + workflow_call: + workflow_dispatch: + inputs: + push_to_slack: + description: 'Push to Slack' + type: boolean + required: true + default: 'false' + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +permissions: + contents: read + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0 # 2.27.1 + with: + php-version: '7.4' + env: + fail-fast: 'true' + + - name: OAuth Composer Authentication + run: + composer config -g github-oauth.github.com ${{ secrets.DEVOPS_TOKEN }} + + - name: Install Composer dependencies cache + uses: ramsey/composer-install@v2 + + - name: Install Composer dependencies + run: + composer install --no-dev + + - name: Get current date + id: date + run: echo "::set-output name=date::$(date +'%Y-%m-%d')" + + - name: "Debug info: show tooling versions" + continue-on-error: true + run: | + set +e + echo "Start debug Info" + echo "Date: ${{ steps.date.outputs.date }}" + echo "PHP Version: $(php -v)" + echo "Composer Version: $(composer --version)" + echo "Node Version: $(node --version)" + echo "NPM Version: $(npm --version)" + echo "GIT Version: $(git --version)" + echo "The github context is:" + echo "${{ toJson(github) }}" + echo "End debug Info" + echo "exitcode=$?" >> $GITHUB_OUTPUT + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Build Plugin + run: | + rsync -av --exclude-from=.build-rsync-exclude . one-click-accessibility + zip -r one-click-accessibility.zip one-click-accessibility + + - name: Archive + uses: actions/upload-artifact@v4 + with: + name: one-click-accessibility + path: one-click-accessibility.zip + retention-days: 14 + + - name: Push to Slack on PR merge + if: ${{ github.event.pull_request.merged == true }} + run: | + curl -F file=@one-click-accessibility.zip -F "initial_comment=One Click Accessibility - A new PR has been pushed to the master branch by ${{ github.actor }}." title="${{ github.event.pull_request.title }}" -F channels=C07LFCFNGDB -H "Authorization: Bearer ${{ secrets.CLOUD_SLACK_BOT_TOKEN }}" https://slack.com/api/files.upload + + - name: Push to Slack on Manual Trigger + if: ${{ github.event.inputs.push_to_slack == 'true' }} + run: | + curl -F file=@one-click-accessibility.zip -F "initial_comment=One Click Accessibility - A new build was triggered by ${{ github.actor }}." title="Manuall ${{ steps.date.outputs.date }}" -F channels=C07LFCFNGDB -H "Authorization: Bearer ${{ secrets.CLOUD_SLACK_BOT_TOKEN }}" https://slack.com/api/files.upload diff --git a/.github/workflows/js-ci.yml b/.github/workflows/js-ci.yml new file mode 100644 index 0000000..efcf141 --- /dev/null +++ b/.github/workflows/js-ci.yml @@ -0,0 +1,45 @@ +name: NPM CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +env: + token: ${{ secrets.CLOUD_DEVOPS_TOKEN }} + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x] + + steps: + - uses: actions/checkout@v2 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Install Dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Lint + run: npm run lint:js + +# - name: Test +# run: npm run test diff --git a/.github/workflows/php-coding-standards.yml b/.github/workflows/php-coding-standards.yml new file mode 100644 index 0000000..26a91dc --- /dev/null +++ b/.github/workflows/php-coding-standards.yml @@ -0,0 +1,55 @@ +name: PHP Lint + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +permissions: + contents: read + +jobs: + PHP-Code-Standards: + name: Lint PHP files + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[skip PHPCS]') || !contains(github.event.head_commit.message, '[skip CI]')" + steps: + - name: Check out source code + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0 # 2.27.1 + with: + php-version: '7.4' + coverage: none + tools: composer, cs2pr, phpcs + env: + fail-fast: 'true' + + - name: Log debug information + run: | + export PATH=$HOME/.composer/vendor/bin:$PATH + php --version + phpcs -i + composer --version + + - name: Oauth Composer authentication + run: composer config -g github-oauth.github.com ${{ secrets.DEVOPS_TOKEN }} + + - name: Install dependencies + uses: ramsey/composer-install@83af392bf5f031813d25e6fe4cd626cdba9a2df6 # 2.2.0 + + - name: Add error matcher + run: echo "::add-matcher::$(pwd)/.github/checkstyle-problem-matcher.json" + + - name: Run style check + run: | + export PATH=$HOME/.composer/vendor/bin:$PATH + composer run lint -- --report=checkstyle + diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml new file mode 100644 index 0000000..2f9665c --- /dev/null +++ b/.github/workflows/phpunit.yml @@ -0,0 +1,146 @@ +name: PHPUnit + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +permissions: + contents: read + +jobs: + unit-tests: + name: "WP ${{ matrix.wp }}, multisite: ${{ matrix.ms }}, PHP: ${{ matrix.php }}" + if: "!contains(github.event.head_commit.message, '[ci skip]')" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: [7.4, 8.0, 8.1, 8.2] + wp: [6.4.x, latest, nightly] + ms: [no, yes] + coverage: [no] + phpunit: [9] + include: + # PHP 7.4 is not supported by PHPUnit 9 + - phpunit: 7 + php: 7.4 + # add coverage job + - coverage: yes + php: 8.0 + wp: latest + ms: no + phpunit: 9 + + services: + mysql: + image: mariadb:latest + ports: + - "3306:3306" + env: + MYSQL_ROOT_PASSWORD: wordpress + MARIADB_INITDB_SKIP_TZINFO: 1 + MYSQL_USER: wordpress + MYSQL_PASSWORD: wordpress + MYSQL_DATABASE: wordpress_test + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Matrix variables + id: matrix + run: | + echo "::group::Matrix variables" + echo "PHP version = ${{ matrix.php }}" + echo "WordPress = ${{ matrix.wp }}" + echo "Multisite = ${{ matrix.ms }}" + echo "Coverage = ${{ matrix.coverage }}" + echo "PHPUnit = ${{ matrix.phpunit }}" + echo "::endgroup::" + + - name: Decide whether to enable coverage + id: coverage + run: | + if [ "${{ matrix.coverage }}" = "yes" ]; then + echo "coverage=pcov" >> $GITHUB_OUTPUT + echo 'ini=pcov.directory=inc, pcov.exclude="~/(vendor|tests|node_modules)/~"' + else + echo "coverage=none" >> $GITHUB_OUTPUT + echo "ini=opcache.enable_cli=1, opcache.jit=tracing, opcache.jit_buffer_size=64M" >> $GITHUB_OUTPUT + fi + + - name: Set up PHP + uses: shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0 # 2.27.1 + with: + coverage: ${{ steps.coverage.outputs.coverage }} + ini-values: ${{ steps.coverage.outputs.ini }} + php-version: ${{ matrix.php }} + env: + fail-fast: 'true' + + - name: Install PHPUnit + run: | + wget -q -O /usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${{ matrix.phpunit }}.phar" + chmod +x /usr/local/bin/phpunit + + - name: Oauth Composer authentication + run: + composer config -g github-oauth.github.com ${{ secrets.DEVOPS_TOKEN }} + + - name: Install dependencies + uses: ramsey/composer-install@83af392bf5f031813d25e6fe4cd626cdba9a2df6 # 2.2.0 + + #- name: Install Node.js + # uses: actions/setup-node@v2 + # with: + # node-version: 18.x + + #- name: Install dependencies + # run: npm ci + + #- name: Build assets + # run: npm run build + + - name: Set up WordPress and WordPress Test Library + uses: sjinks/setup-wordpress-test-library@dd7f8144e892b042e034d713b734f7a19446abe1 # 2.0.1 + with: + version: ${{ matrix.wp }} + + - name: Set up multisite mode + run: echo "WP_MULTISITE=1" >> $GITHUB_ENV + if: matrix.ms == 'yes' + + - name: Update wp-test-config.php + run: | + if php -r 'exit(PHP_VERSION_ID < 80100);'; then + echo "Disabling WP_DEBUG in wp-test-config.php" + sed -i "s@define( 'WP_DEBUG', true );@// define( 'WP_DEBUG', true );@" /tmp/wordpress-tests-lib/wp-tests-config.php + fi + + - name: Verify MariaDB connection + run: | + while ! mysqladmin ping -h 127.0.0.1 -P ${{ job.services.mysql.ports[3306] }} --silent; do + sleep 1 + done + timeout-minutes: 1 + + - name: Run tests + run: | + OPTIONS= + if [ "${{ steps.coverage.outputs.coverage }}" != 'none' ]; then + OPTIONS="$OPTIONS --coverage-clover=clover.xml" + fi + phpunit --order-by=random ${OPTIONS} + + - name: Upload coverage report + uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 + with: + files: clover.xml + flags: unittests + if: ${{ steps.coverage.outputs.coverage != 'none' }} diff --git a/.gitignore b/.gitignore index 26b1f11..4d1728c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,11 @@ composer.lock npm-debug.log .DS_Store package-lock.json +bin/.env +*/node_modules +!assets/dev/* +assets/js/ +coverage/ +.phpunit.result.cache +.editorconfig +.vscode diff --git a/.jshintrc b/.jshintrc deleted file mode 100755 index 1164285..0000000 --- a/.jshintrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "bitwise": true, - "browser": true, - "curly": true, - "eqeqeq": true, - "eqnull": true, - "esnext": true, - "immed": true, - "jquery": true, - "latedef": true, - "newcap": true, - "noarg": true, - "node": true, - "strict": false, - "trailing": true -} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5ab28c5..0000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Travis CI Configuration File - -sudo: false - -# Tell Travis CI we're using PHP -language: php - -# PHP version used in first build configuration. -php: - - 5.3 - - 5.4 - - 5.5 - - 5.6 - - 7.0 - -# WordPress version used in first build configuration. -env: - - WP_VERSION=latest WP_MULTISITE=0 - - WP_VERSION=latest WP_MULTISITE=1 - - WP_VERSION=4.0 WP_MULTISITE=0 - - WP_VERSION=4.0 WP_MULTISITE=1 - - WP_VERSION=4.1 WP_MULTISITE=0 - - WP_VERSION=4.1 WP_MULTISITE=1 - - WP_VERSION=4.2 WP_MULTISITE=0 - - WP_VERSION=4.2 WP_MULTISITE=1 - - WP_VERSION=4.3 WP_MULTISITE=0 - - WP_VERSION=4.3 WP_MULTISITE=1 - -before_script: - - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION - -after_success: - - bash <(curl -s https://codecov.io/bash) - -script: phpunit --coverage-clover=coverage.xml \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 6f76dca..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,297 +0,0 @@ -/** - * Pojo Makefile - */ -'use strict'; - -module.exports = function( grunt ) { - - require( 'matchdep' ).filterDev( 'grunt-*' ).forEach( grunt.loadNpmTasks ); - - // Project configuration. - grunt.initConfig( { - pkg: grunt.file.readJSON( 'package.json' ), - - banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + - '<%= grunt.template.today("dd-mm-yyyy") %> */', - - checktextdomain: { - standard: { - options:{ - text_domain: 'pojo-accessibility', - correct_domain: true, - keywords: [ - // WordPress keywords - '__:1,2d', - '_e:1,2d', - '_x:1,2c,3d', - 'esc_html__:1,2d', - 'esc_html_e:1,2d', - 'esc_html_x:1,2c,3d', - 'esc_attr__:1,2d', - 'esc_attr_e:1,2d', - 'esc_attr_x:1,2c,3d', - '_ex:1,2c,3d', - '_n:1,2,4d', - '_nx:1,2,4c,5d', - '_n_noop:1,2,3d', - '_nx_noop:1,2,3c,4d' - ] - }, - files: [ { - src: [ - '**/*.php', - '!node_modules/**', - '!build/**', - '!tests/**', - '!vendor/**', - '!*~' - ], - expand: true - } ] - } - }, - - usebanner: { - dist: { - options: { - banner: '<%= banner %>' - }, - files: { - src: [ - 'assets/js/app.min.js', - 'assets/css/style.css', - 'assets/css/style.min.css' - ] - } - } - }, - - jshint: { - options: { - jshintrc: '.jshintrc' - }, - all: [ - 'Gruntfile.js', - 'assets/js/skip-link-focus-fix.js', - 'assets/js/app.dev.js' - ] - }, - - uglify: { - options: {}, - dist: { - files: { - 'assets/js/app.min.js': [ - 'assets/js/skip-link-focus-fix.js', - 'assets/js/app.dev.js' - ] - } - } - }, - - less: { - dist: { - options: { - //cleancss: true - }, - files: { - 'assets/css/style.css': 'assets/less/style.less' - } - }, - - mincss: { - options: { - compress: true - }, - files: { - 'assets/css/style.min.css': 'assets/less/style.less' - } - } - }, - - watch: { - js: { - files: [ - '**/*.js', - '!**/*.min.js' - ], - tasks: [ - 'scripts' - ], - options: {} - }, - - less: { - - files: [ - '**/*.less' - ], - tasks: [ - 'styles' - ], - options: {} - } - }, - - bumpup: { - options: { - updateProps: { - pkg: 'package.json' - } - }, - file: 'package.json' - }, - - replace: { - plugin_main: { - src: [ 'pojo-accessibility.php' ], - overwrite: true, - replacements: [ - { - from: /Version: \d{1,1}\.\d{1,2}\.\d{1,2}/g, - to: 'Version: <%= pkg.version %>' - } - ] - }, - - readme: { - src: [ 'readme.txt' ], - overwrite: true, - replacements: [ - { - from: /Stable tag: \d{1,1}\.\d{1,2}\.\d{1,2}/g, - to: 'Stable tag: <%= pkg.version %>' - } - ] - } - }, - - shell: { - git_add_all : { - command: [ - 'git add --all', - 'git commit -m "Bump to <%= pkg.version %>"' - ].join( '&&' ) - } - }, - - release: { - options: { - bump: false, - npm: false, - commit: false, - tagName: 'v<%= version %>', - commitMessage: 'released v<%= version %>', - tagMessage: 'Tagged as v<%= version %>' - } - }, - - wp_readme_to_markdown: { - github: { - options: { - screenshot_url: 'assets/{screenshot}.png', - wordpressPluginSlug: 'pojo-accessibility', - travisUrlRepo: 'https://travis-ci.org/pojome/one-click-accessibility', - gruntDependencyStatusUrl: 'https://david-dm.org/pojome/pojo-accessibility' - }, - files: { - 'README.md': 'readme.txt' - } - } - }, - - copy: { - main: { - src: [ - '**', - '!node_modules/**', - '!build/**', - '!bin/**', - '!.git/**', - '!tests/**', - '!.travis.yml', - '!.jshintrc', - '!README.md', - '!phpunit.xml', - '!vendor/**', - '!Gruntfile.js', - '!package.json', - '!package-lock.json', - '!npm-debug.log', - '!composer.json', - '!composer.lock', - '!assets/less/**', - '!wp-assets/**', - '!.gitignore', - '!.gitmodules', - '!*~' - ], - expand: true, - dest: 'build/' - } - }, - - clean: { - //Clean up build folder - main: [ - 'build' - ] - }, - - wp_deploy: { - deploy:{ - options: { - plugin_slug: '<%= pkg.slug %>', - svn_user: 'KingYes', - build_dir: 'build/', - assets_dir: 'wp-assets/' - } - } - }, - - phpunit: { - classes: { - dir: '' - }, - options: { - bin: 'phpunit', - bootstrap: 'tests/bootstrap.php', - colors: true - } - } - - } ); - - // Default task(s). - grunt.registerTask( 'default', [ - 'checktextdomain', - 'scripts', - 'styles', - 'usebanner', - 'wp_readme_to_markdown', - //'phpunit' - ] ); - - grunt.registerTask( 'build', [ - 'default', - 'clean', - 'copy' - ] ); - - grunt.registerTask( 'scripts', [ - 'jshint', - 'uglify' - ] ); - - grunt.registerTask( 'styles', [ - 'less' - ] ); - - grunt.registerTask( 'publish', [ - 'default', - 'bumpup', - 'replace', - 'shell:git_add_all', - 'release' - ] ); -}; diff --git a/classes/database/database-constants.php b/classes/database/database-constants.php new file mode 100644 index 0000000..1af5f37 --- /dev/null +++ b/classes/database/database-constants.php @@ -0,0 +1,73 @@ + value entries for direct comparison + * joined with the AND logical operator, or in the format of column => [column => + * string, value =>string|int|array, comparison operator => string, + * relation_before=>string, optional, relation_after=>string, optional]. + * + * @return Entry Returns the entry with the data found. + */ + private function init_by( string $by, $value ): Entry { + $data = static::get_by( $by, $value ); + + return $this->init_by_data( $data ); + } + + /** + * get_by + * + * Returns the first row in the table that satisfies the conditions set by the parameters. + * + * @param string $by The name of the field to compare the value parameter to in case the + * /value/ parameter is a string. + * Optional. + * Defaults to an empty string. + * @param array|string $value If a string, the value to compare against the column specified + * by the /by/ parameter. + * If an array, can be either a list of column => value entries for direct comparison + * joined with the AND logical operator, or in the format of column => [column => + * string, value =>string|int|array, comparison operator => string, + * relation_before=>string, optional, relation_after=>string, optional]. Optional. + * Defaults to an empty string. + * + * @return mixed|\stdClass|null An object representing the row or NULL in case of an error. + */ + protected function get_by( string $by = '', $value = '' ) { + $fields = '*'; + $where = is_array( $value ) ? $value : [ $by => $value ]; + + return $this->db_table::first( $fields, $where ); + } + + /** + * init_by_data + * + * Returns an Entry object with its data set to the data passed by the /data/ parameter. + * + * @param array|object $data The data to set the entry with. + * + * @return $this Returns an entry loaded with the data passes in the parameter, or en Empty entry (representing + * null) on error + */ + protected function init_by_data( $data ): Entry { + if ( ! $data || is_wp_error( $data ) ) { + return $this->return_empty(); + } + + return $this->set_data( $data ); + } + + /** + * return_empty + * + * Returns an empty entry project, representing an empty set/null. + * + * NOTE: An empty entry cannot be saved in the database unless you either unset the + * /id/ field or set it for a valid value for an update. + * + * @return Entry Returns the object back + */ + private function return_empty(): Entry { + $this->set_data( [] ); + + return $this->set( 'id', 0 ); + } + + /** + * set_data + * + * Sets the data of this entry object + * + * @param array|object $data The data to set in the current object in the format of key => value + * + * @return $this Returns the object back + */ + public function set_data( $data ): Entry { + $this->entry_data = (array) $data; + + return $this; + } + + /** + * init_by_id + * + * Loads the Entry with data from the database fetched by the /id/ column compared to the value passed with the + * /id/ parameter. + * + * @param string $id The value of the /id/ field + * + * @return $this The Entry loaded with the data gotten from the database by the specified ID. + */ + protected function init_by_id( $id ): Entry { + $data = static::get_by( 'id', $id ); + + return $this->init_by_data( $data ); + } + + /** + * __get + * magic get properties + * + * Returns an Entry's data field by key name + * + * @param string $name The name of the field to return + * + * @return mixed|null The value of the field or null if it doesn't exist' + */ + public function __get( $name ) { + return $this->entry_data[ $name ] ?? null; + } + + /** + * __set + * magic set properties + * + * Sets an Entry's data field value by the specified key + * + * @param string $name The key of the field being set + * @param mixed $value The new value for the field + * + * @return $this Returns the current object back + */ + public function __set( string $name, $value ) { + return $this->set( $name, $value ); + } + + /** + * set + * + * Sets and entry data field value by the specified key + * + * @param string $name The key of the field being set + * @param mixed $value The new value for the field + * + * @return $this Returns the current object back + */ + public function set( string $name, $value ): Entry { + $this->entry_data[ $name ] = $value; + + return $this; + } + + /** + * class_short_name + * + * Returns just the name of the class, without its namespace. Used for hooks. + * Taken from https://coderwall.com/p/cpxxxw/php-get-class-name-without-namespace + * + * NOTE: Called on a class without a namespace will return the name of the class + * without the first letter in the name + * @return string The name of the current class + */ + private function class_short_name(): string { + $class_name = get_called_class(); + + return ( substr( $class_name, strrpos( $class_name, '\\' ) + 1 ) ); + } + + /** + * trigger_change + * + * Raises action hooks following the create, delete and update operations. + * Raises the hook [ea11y/db/class_name>/change]. If the /event/ + * parameter is not null, then it also raises the hook [ea11y/db/class_name/event]. + * + * The parameters sent to both hooks is a reference to the current object and the value of the /data/ parameter. + * + * @param int|bool $data Numbers of rows changed or FALSE on database action failure + * @param string|null $event The name of the custom event hook to raise in addition to the /changed/ event hook + * Optional. + * Defaults to null. In this case, will raise only the defaults /changed/ event. + */ + private function trigger_change( $data, string $event = null ): void { + if ( $event ) { + /** + * event specific + * @var Entry $this + * @var false|int $data + */ + do_action( 'ea11y/db/' . $this->class_short_name() . '/' . $event, $this, $data ); + } + + /** + * entity change + * @var Entry $this + * @var false|int $data + */ + do_action( 'ea11y/db/' . $this->class_short_name() . '/change', $this, $data ); + } + + /** + * save + * + * Writes the entry to the database. + * If the entry has a field called /id/ set, will + * perform an update, if it doesn't, will perform an insert. + * Update and create function triggers the change action hooks and the respective custom event + * @return false|int The number of rows inserted or FALSE on error. + */ + public function save() { + if ( isset( $this->entry_data[ 'id' ] ) ) { + return $this->update( [ 'id' => $this->entry_data[ 'id' ] ] ); + } + + return $this->create(); + } + + + /** + * delete + * + * Delete entries from the table. + * Bases on the field specified by the /by/ parameter and its current value. + * Triggers change action hooks and raises the /delete/ custom event. + * + * @param string $by The field name to delete by. + * Optional. + * Defaults to 'id'. + * + * @return false|int The number of rows deleted or false on error. + */ + public function delete( string $by = 'id' ) { + $results = $this->db_table::delete( [ $by => $this->{$by} ] ); + + $this->trigger_change( $results, 'delete' ); + + return $results; + } + + /** + * update + * + * Updates the database with the data of this entry based on the conditions passed + * by the /where/ parameter. + * Triggers change action hooks and raises the /update/ custom event. + * + * NOTE: If no conditions are supplied, the update is going to be performed on all rows, + * + * @param array $where Array of column => (raw) values as a group of AND WHERE conditionals for the UPDATE + * statement. Optional. Defaults to an empty array. + * + * @return false|int The number of rows updated or false on error. + */ + public function update( array $where = [] ) { + $results = $this->db_table::update( $this->entry_data, $where ); + + $this->trigger_change( $results, 'update' ); + + return $results; + } + + /** + * create + * + * Inserts the entry to the database table. + * Trigger change action hooks and raises the /create/ custom event, + * + * @param string $id On successful insertion, will set the field passes in the /id/ parameter + * ot the value of the last inserted ID as returned from the database. + * Optional. + * Defaults to 'id' + * + * @return false|int The numbers of rows affected or FALSE on error + */ + public function create( string $id = 'id' ) { + $results = $this->db_table::insert( $this->entry_data ); + if ( $results ) { + // Set row id once created + $this->set( $id, $this->db_table::db()->insert_id ); + } + + $this->trigger_change( $results, 'create' ); + + return $results; + } + + /** + * reset + * + * Clears all of this entry's data. + */ + public function reset(): void { + $this->entry_data = []; + } + + /** + * @return bool + */ + public function exists(): bool { + return isset( $this->entry_data['id'] ) && 0 < $this->entry_data['id']; + } + + /** + * DB_Entry_Base constructor. + * Uses the passed on arguments to initialize/set the Entry. + * Will through an exception in case the Entry's table property is not set correctly, and the /Table/ class + * linking this entry to a database table is not found. + * + * @param array $args The arguments to set this Entry by. + * If arguments /by/ and /value/ are set, + * will fetch the data according to the column name specified by /by/ and the value(s) specified + * by /value/. See doc for /init_by/ for details. + * + * if argument /data/ is set, will set the Entry's data with the data specified. + * See doc for /init_by_data/ for details. + * + * if argument /id/ is specified, will load the data of the row whose /id/ column has the value specified by the + * argument. + * + * Optional. + * Defaults to an empty array which will make this entry as the Empty Entry representing null, + * which cannot be saved unless properly modified to a non-null entry. + * + * @throws Missing_Table_Exception If the Entry table property is not found. + */ + public function __construct( array $args = [] ) { + $this->db_table = static::get_helper_class(); + + if ( empty( $this->db_table ) ) { + throw new Missing_Table_Exception(); + } + if ( isset( $args[ 'by' ] ) && isset( $args[ 'value' ] ) ) { + return $this->init_by( $args[ 'by' ], $args[ 'value' ] ); + } + + if ( isset( $args[ 'data' ] ) ) { + return $this->init_by_data( $args[ 'data' ] ); + } + + if ( isset( $args[ 'id' ] ) ) { + return $this->init_by_id( $args[ 'id' ] ); + } + + return $this->return_empty(); + } +} diff --git a/classes/database/exceptions/missing-table-exception.php b/classes/database/exceptions/missing-table-exception.php new file mode 100644 index 0000000..5a9a249 --- /dev/null +++ b/classes/database/exceptions/missing-table-exception.php @@ -0,0 +1,18 @@ +prefix . static::$table_name; + } + + /** + * set_table_prefix + * + * Saves the table name as a property in the WP database object and + * sets it value to the table name and its prefix. + */ + protected static function set_table_prefix(): void { + static::db()->{static::$table_name} = static::table_name(); + } + + /** + * get_columns + * + * Should return an array of table columns details in the format of + * column_name => [ type => db_type, key => key_data (optional), flags => other_modifiers (optional) ] + * + * NOTE: A primary key column named /id/ which is an auto-incremented int/big int is assumed to exist and must be one + * of the columns this function returns. + * @return array The table column data. + */ + public static function get_columns(): array { + return []; + } + + /** + * get_extra_keys + * + * Extra keys to the table definitions to be merged with column key definitions + * @return string[] SQL table key definitions + */ + protected static function get_extra_keys(): array { + return []; + } + + /** + * get_keys + * + * Extracts the key definitions from the table's columns and merges with + * any extra key definitions + * @return string[] SQL table key definitions + */ + protected static function get_keys(): array { + $columns = static::get_columns(); + $keys = []; + foreach ( $columns as $column ) { + if ( ! isset( $column['key'] ) ) { + continue; + } + $keys[] = $column['key']; + } + return array_merge( $keys, static::get_extra_keys() ); + } + + /** + * install + * + * This function compares the version of the installed table and the current version as reported by + * the class. + * If the versions are different, the table will be installed or updated, and the option + * will be set to the current version. + */ + public static function install(): void { + $installed_ver = get_option( static::DB_VERSION_FLAG_NAME, -1 ); + + if ( static::DB_VERSION !== $installed_ver ) { + + $sql = static::get_create_table_sql(); + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( $sql ); + + update_option( static::DB_VERSION_FLAG_NAME, static::DB_VERSION, false ); + } + + static::set_table_prefix(); + } + + /** + * get_create_table_sql + * + * Generates the SQL command to run in the database to create the table + * based on the definitions of columns and keys. + * @return string The SQL command to create the table + */ + protected static function get_create_table_sql(): string { + $table = static::table_name(); + $keys = static::get_keys(); + $charset_collate = static::db()->get_charset_collate(); + $table_columns = []; + $sql = []; + $sql[] = 'CREATE TABLE ' . $table . ' ('; + $columns = static::get_columns(); + foreach ( $columns as $column_name => $column ) { + $table_columns[] = sprintf( '`%s` %s %s,', + $column_name, + $column['type'], + $column['flags'] ?? '' + ); + } + + $sql[] = "\t" . implode( "\n\t", $table_columns ); + $sql[] = "\t" . implode( ",\n\t", $keys ); + $sql[] = ") AUTO_INCREMENT=11 {$charset_collate};"; + return implode( "\n", $sql ); + } + + /** + * where + * + * Generates a proper WHERE clause for an SQL query. + * @param string|array $where Either a string of where clause (returns as is) or an array of + * conditions join with an AND, and in the format of column => (int|string) for exact value comparison + * or in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * + * @return string WHERE clause built from the function input + */ + public static function where( $where ): string { + if ( ! is_array( $where ) ) { + return $where; + } + $needs_relationship = false; + $where_string = ''; + foreach ( $where as $key => $filter ) { + if ( ! is_array( $filter ) ) { + if ( $needs_relationship ) { + $where_string .= ' AND'; + } + $where_string .= ' ' . self::get_where_string( $key, $filter ); + $needs_relationship = true; + continue; + } + + $where_string .= self::maybe_add_relation( $filter ); + $where_string .= self::get_where_string( $filter['column'], $filter['value'], $filter['operator'] ); + $where_string .= self::maybe_add_relation( $filter, false ); + + } + return $where_string; + + } + + /** + * maybe_add_relation + * + * Adds a logical relation ship (AND, OR...) if exists, based on the position (before|after) related to the condition. + * @param array $filter Object array describing the where condition that may contain the keys /relation_before/ + * or /relation_after/ containing the logical relationship to add to the main WHERE condition. + * @param bool $is_before Whether the current position in the text is before the condition the object describes. + * Optional. + * Defaults to TRUE. + * + * @return string If the logical relationship exists, returns it. Otherwise - an empty string. + */ + private static function maybe_add_relation( array $filter, bool $is_before = true ): string { + $key_to_check = $is_before ? 'relation_before' : 'relation_after'; + return isset( $filter[ $key_to_check ] ) ? ' ' . $filter[ $key_to_check ] : ' '; + } + + /** + * get_where_string + * + * @param string $key The column name in the condition + * @param string|int|array $value The value being compared. If an array will be translated to a set. + * @param string $operator The comparison operator. + * Optional. + * Defaults to '='. + * @param null $format Unused. + * + * @return string An SQL condition based on the parameters. + */ + private static function get_where_string( string $key, $value, string $operator = '=', $format = null ): string { + $param_string = is_int( $value ) ? '%d' : '%s'; + if ( is_array( $value ) ) { + $param_string = '('; + $count = count( $value ); + for ( $i = 0; $i < $count; $i++ ) { + $param_string .= is_int( $value[ $i ] ) ? '%d' : '%s'; + $param_string .= ( $i !== $count - 1 ) ? ', ' : ''; + } + $param_string .= ')'; + } + return static::db()->prepare( "$key $operator $param_string", $value ); + } + + /** + * get_columns_for_insert + * This function tries to get the column names for an INSERT operation based on the table column + * definition, and if that fails based on the /data/ parameter. + * The function will remove any column called /id/. + * @param mixed $data If a two-dimensional array, where the elements are in the form of column => value, + * the function will try to get the names of the columns off the first element. + * + * @return false|string A string representing the list of columns, comma separated and surrounded by parenthesis; + * or false in case of function failure. + */ + private static function get_columns_for_insert( $data ) { + $cols = static::get_columns(); + if ( count( $cols ) ) { + $columns = array_keys( $cols ); + } elseif ( is_array( $data ) ) { + //try to get from data + $columns = array_keys( $data[0] ); + } + + if ( empty( $columns ) || ! is_array( $columns ) ) { + return false; + } + + // remove id $column + if ( ! empty( $columns['id'] ) ) { + unset( $columns['id'] ); + } + $index = array_search( 'id', $columns, true ); + if ( false !== $index ) { + unset( $columns[ $index ] ); + } + + return ' (`' . implode( '`,`', $columns ) . '`) '; + } + + /** + * select_var + * + * Selects a single cell in the table and returns its value as string. + * Will return the first cell of the first row in the result set. + * @param string|array $fields A string of comma-separated list, or an array of columns from the table. + * Optional. + * Defaults to '*' (all table columns) + * @param string|array $where A string of WHERE conditions or an array of column => value entries connected with the + * AND logical operator. Or in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * Optional. + * Defaults tp '1', which is evaluated to true and will bring all records (no condition). + * @param int|null $limit Limit the number of results to return to this number, or NULL for no limit. + * Optional. + * Defaults to NULL (no limit) + * @param int|null $offset Skip this number of results or NULL for no skip + * Optional. + * Defaults to NULL (no offset) + * @param string $join JOIN table clause. + * Optional. + * Defaults to an empty string (no join) + * + * @return string|null The query result or NULL on error. + */ + public static function select_var( $fields = '*', $where = '1', int $limit = null, int $offset = null, string $join = '' ): ?string { + return static::db()->get_var( static::build_sql_string( $fields, $where, $limit, $offset, $join ) ); + } + + /** + * build_sql_string + * + * Generates a SELECT query based on the function input. + * @param string|array $fields A string of comma-separated list, or an array of columns from the table. + * Optional. + * Defaults to '*' (all table columns) + * @param string|array $where A string of WHERE conditions or an array of column => value entries connected with + * the AND logical operator. r in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * Optional. + * Defaults to '1', which is evaluated to true and will bring all records (no condition). + * @param int|null $limit Maximum number of results to return. + * Optional. + * Defaults to NULL (no limit) + * @param int|null $offset Start the results from a certain ordinal position. + * Optional. + * Defaults to NULL (no offset) + * @param string $join A table JOIN clause. + * Optional. + * Defaults to an empty string (no join) + * @param array $order_by an array of column => direction (asc|desc) to sort the results by. + * Optional. + * Defaults to an empty array (Default sort). + * + * @return string The SQL SELECT statement built according to the function parameters. + */ + private static function build_sql_string( $fields = '*', $where = '1', int $limit = null, int $offset = null, string $join = '', array $order_by = [] ): string { + if ( is_array( $fields ) ) { + $fields = implode( ', ', $fields ); + } + + $db = static::db(); + $query_string = 'SELECT %s FROM %s %s WHERE %s'; + $query_string = sprintf( $query_string, + $fields, + static::table_name(), + $join, + static::where( $where ) + ); + + if ( $order_by ) { + $query_string .= static::build_order_by_sql_string( $order_by ); + } + + if ( $limit ) { + $query_string .= $db->prepare( ' LIMIT %d', $limit ); + } + + if ( $offset ) { + $query_string .= $db->prepare( ' OFFSET %d', $offset ); + } + + return $query_string; + } + + /** + * build_order_by_sql_string + * + * Generates the ORDER BY clause of the query based on the passed on parameter + * @param array $order_by An array of column => direction (asc/desc) pairs + * + * @return string The ORDER BY clause for a query + */ + public static function build_order_by_sql_string( array $order_by ): string { + return ' ORDER BY ' . implode( ', ', array_map( function( $column, $direction ) { + return "{$column} {$direction}"; + }, array_keys( $order_by ), $order_by ) ); + } + + /** + * select + * + * Runs a SELECT query and returns the results as an array of objects, each object represents a row, + * @param string|array $fields A string of comma-separated list, or an array of columns from the table. + * Optional. + * Defaults to '*' (all table columns) + * @param string|array $where A string of WHERE conditions, or an array of column => value enteries + * for direct comparison joined with the AND logical operator, or in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * Optional. + * Defaults to '1', which is evaluated to true and will bring all records (no condition). + * @param int|null $limit Maximum number of results to return. + * Optional. + * Defaults to NULL (no limit) + * @param int|null $offset Start the results from a certain ordinal position. + * Optional. + * Defaults to NULL (no offset) + * @param string|array $join A table JOIN clause. + * Optional. + * Defaults to an empty string (no join) + * @param array $order_by an array of column => direction (asc|desc) to sort the results by. + * Optional. + * Defaults to an empty array (Default sort). + * + * @return array|object|\stdClass[]|null On success, an array of objects. Null on error. + */ + public static function select( $fields = '*', $where = '1', int $limit = null, int $offset = null, $join = '', array $order_by = [] ) { + // TODO: handle $wpdb->last_error + $query = static::build_sql_string( $fields, $where, $limit, $offset, $join, $order_by ); + return static::db()->get_results( $query ); + } + + /** + * get_col + * + * Returns the first column of the result set. + * @param string $column The column to return. + * Optional. + * Defaults to an empty string. + * @param string|array $where A string of WHERE conditions or an array of column => value entries + * for direct comparison joined with the AND logical operator or in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * Optional. + * Defaults to '1', which is evaluated to true and will bring all records (no condition). + * @param int|null $limit Maximum number of results to return. + * Optional. + * Defaults to NULL (no limit) + * @param int|null $offset Start the results from a certain ordinal position. + * Optional. + * Defaults to NULL (no offset) + * @param string $join A table JOIN clause. + * Optional. + * Defaults to an empty string (no join) + * @param array $order_by an array of column => direction (asc|desc) to sort the results by. + * Optional. + * Defaults to an empty array (Default sort). + * + * @return string[] Array of the values of the column as strings, or an empty one on error. + */ + public static function get_col( string $column = '', $where = '1', int $limit = null, int $offset = null, string $join = '', array $order_by = [] ) : array { + return static::db()->get_col( static::build_sql_string( $column, $where, $limit, $offset, $join, $order_by ) ); + } + + /** + * first + * + * Returns the first row in the table according to the query filters. + * @param string|array $fields A string of comma-separated list, or an array of columns from the table. + * Optional. + * Defaults to '*' (all table columns) + * @param string|array $where A string of WHERE conditions or an array of column => value entries + * for direct comparison joined with the logical AND operator, + * or in the format of column => [column => string, value =>string|int|array, + * comparison operator => string, relation_before=>string, optional, relation_after=>string, optional] + * Optional. + * Defaults to '1', which is evaluated to true and will bring all records (no condition). + * @param int $limit Unnecessary since we are only returning the first row. + * Optional. + * Defaults to 1. + * @param null $offset Start the results from a certain ordinal position. + * Optional. + * Defaults to NULL (no offset) + * @param string $join A table JOIN clause. + * Optional. + * Defaults to an empty string (no join) + * + * @return \stdClass|null An object representing the first row, or null on error + */ + public static function first( $fields = '*', $where = '1', int $limit = 1, $offset = null, string $join = '' ): ?\stdClass { + $result = static::select( $fields, $where, $limit, $offset, $join ); + return ( ! empty( $result[0] ) ) ? $result[0] : null; + } + + /** + * replace + * + * Replace a row in a table if it exists or insert a new row in a table if the row does not already exist. + * @param array $data Array of data in the form of column => (raw) value. + * Optional. + * Defaults to an empty array. + * + * @return false|int The number of rows affected or FALSE on error. + */ + public static function replace( array $data = [] ) { + return static::db()->replace( static::table_name(), $data ); + } + + /** + * insert + * + * Insert a single row into the table. + * @param array $data Array of data to insert in column => (raw) value format. + * Optional, defaults to an empty array. + * + * @return false|int The number of rows affected or FALSE on error + */ + public static function insert( array $data = [] ) { + return static::db()->insert( static::table_name(), $data ); + } + + /** + * insert_many + * Performs a bulk INSERT of many datasets/rows to the table + * + * @param array $data Optional. Defaults to an empty array. + * An array of datasets to be INSERTed into the table. Each value needs to be seperated by a comma, + * each data set needs to be surrounded by parenthesis. + * @param string|null $columns Optional. The columns being inserted. Defaults to NULL. + * Either a string of comma-separated column names surrounded by parenthesis, or NULL for the + * function to try guessing based on the data and column definitions. + * + * @return false|int Number of rows affected or false on error + */ + public static function insert_many( array $data = [], string $columns = null ) { + if ( null === $columns ) { + $columns = static::get_columns_for_insert( $data ); + if ( ! $columns ) { + return false; + } + } + $insert_sql = 'INSERT INTO ' . static::table_name() . $columns . ' VALUES ' . implode( ",\n", $data ) . ';'; + return static::db()->query( $insert_sql ); // no params so no need for `prepare`. + } + + /** + * update + * + * Updates data in the table, based on where conditionals + * @param array $data Optional. Array of column => (raw) values to be updated. + * Defaults to an empty array. + * @param array $where Optional. Array of column => (raw) values as a group of AND + * WHERE conditionals for the UPDATE statement. Defaults to an empty array. + * + * @return false|int The numbers of rows affected, or FALSE on error + */ + public static function update( array $data = [], array $where = [] ) { + return static::db()->update( static::table_name(), $data, $where ); + } + + /** + * delete + * + * Delete rows from this table based on optional where conditions. + * + * @param array $where Optional. And array of column => (raw) values + * as a group of AND conditions for the DELETE statement. Defaults to an empty array. + * + * @return false|int The number of rows updated, or false on error. + */ + public static function delete( array $where = [] ) { + return static::db()->delete( static::table_name(), $where ); + } + + /** + * query + * + * Execute any SQL query on the database. + * It is best used when there is a need for specific, + * custom, or otherwise complex SQL queries. + * @param string $query The query to be executed. Defaults to an empty string + * + * @return false|int Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. + * Number of rows affected/selected for all other queries. Boolean false on error. + */ + public static function query( string $query = '' ) { + return static::db()->query( $query ); + } + + /** + * get_class_name + * + * Returns the name of this /Table/ class (or its derivative) + * @return string - The name of the current class + */ + public static function get_class_name(): string { + return get_called_class(); + } +} diff --git a/classes/logger.php b/classes/logger.php new file mode 100644 index 0000000..e68a69b --- /dev/null +++ b/classes/logger.php @@ -0,0 +1,37 @@ +reflection ) { + try { + $this->reflection = new ReflectionClass( $this ); + } catch ( ReflectionException $e ) { + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( $e->getMessage() ); + } + } + } + + return $this->reflection; + } + + /** + * Add module component. + * + * Add new component to the current module. + * @access public + * + * @param string $id Component ID. + * @param mixed $instance An instance of the component. + */ + public function add_component( $id, $instance ) { + $this->components[ $id ] = $instance; + } + + /** + * Add module route. + * + * Add new route to the current module. + * @access public + * + * @param string $id Route ID. + * @param mixed $instance An instance of the route. + */ + public function add_route( string $id, $instance ) { + $this->routes[ $id ] = $instance; + } + + /** + * @access public + * @return Module[] + */ + public function get_components() { + return $this->components; + } + + /** + * Get module component. + * + * Retrieve the module component. + * @access public + * + * @param string $id Component ID. + * + * @return mixed An instance of the component, or `false` if the component + * doesn't exist. + * @codeCoverageIgnore + */ + public function get_component( $id ) { + if ( isset( $this->components[ $id ] ) ) { + return $this->components[ $id ]; + } + + return false; + } + + /** + * Retrieve the namespace of the class + * + * @access public + * @static + */ + public static function namespace_name() { + $class_name = static::class_name(); + return substr( $class_name, 0, strrpos( $class_name, '\\' ) ); + } + + + + public static function routes_list() : array { + return []; + } + + public static function component_list() : array { + return []; + } + + /** + * Adds an array of components. + * Assumes namespace structure contains `\Components\` + * + * @param array $components_ids => component's class name. + */ + public function register_components( $components_ids = null ) { + $namespace = static::namespace_name(); + $components_ids = $components_ids ?? static::component_list(); + foreach ( $components_ids as $component_id ) { + $class_name = $namespace . '\\Components\\' . $component_id; + $this->add_component( $component_id, new $class_name() ); + } + } + + /** + * Adds an array of routes. + * Assumes namespace structure contains `\Rest\` + */ + public function register_routes() { + $namespace = static::namespace_name(); + $routes_ids = static::routes_list(); + + foreach ( $routes_ids as $route_id ) { + $class_name = $namespace . '\\Rest\\' . $route_id; + $this->add_route( $route_id, new $class_name() ); + } + } +} + diff --git a/classes/rest/route.php b/classes/rest/route.php new file mode 100644 index 0000000..921b8a4 --- /dev/null +++ b/classes/rest/route.php @@ -0,0 +1,387 @@ +get_methods(); + if ( empty( $methods ) ) { + return; + } + + //$callbacks = false; + $callbacks = []; + foreach ( (array) $methods as $method ) { + if ( ! in_array( $method, $this->valid_http_methods, true ) ) { + continue; + } + $callbacks[] = $this->build_endpoint_method_config( $method ); + } + + $arguments = $this->get_arguments(); + + if ( ! $callbacks && empty( $arguments ) ) { + return; + } + + $arguments = array_merge( $arguments, (array) $callbacks ); + register_rest_route( $this->namespace, '/' . $this->get_endpoint() . '/', $arguments, $this->override ); + } + + /** + * get_methods + * Rest Endpoint methods + * + * Returns an array of the supported REST methods for this route + * @return array REST methods being configured for this route. + */ + abstract public function get_methods(): array; + + /** + * get_callback + * + * Returns a reference to the callback function to handle the REST method specified by the /method/ parameter. + * @param string $method The REST method name + * + * @return callable A reference to a member function with the same name as the REST method being passed as a parameter, + * or a reference to the default function /callback/. + */ + public function get_callback_method( string $method ): callable { + $method_name = strtolower( $method ); + $callback = $this->method_exists_in_current_class( $method_name ) ? $method_name : 'callback'; + return [ $this, $callback ]; + } + + /** + * get_permission_callback_method + * + * Returns a reference to the permission callback for the method if exists or the default one if it doesn't. + * @param string $method The REST method name + * + * @return callable If a method called (rest-method)_permission_callback exists, returns a reference to it, otherwise + * returns a reference to the default member method /permission_callback/. + */ + public function get_permission_callback_method( string $method ): callable { + $method_name = strtolower( $method ); + $permission_callback_method = $method_name . '_permission_callback'; + $permission_callback = $this->method_exists_in_current_class( $permission_callback_method ) ? $permission_callback_method : 'permission_callback'; + return [ $this, $permission_callback ]; + } + + /** + * maybe_add_args_to_config + * + * Checks if the class has a method call (rest-method)_args. + * If it does, the function calls it and adds its response to the config object passed to the function, under the /args/ key. + * @param string $method The REST method name being configured + * @param array $config The configuration object for the method + * + * @return array The configuration object for the method, possibly after being amended + */ + public function maybe_add_args_to_config( string $method, array $config ): array { + $method_name = strtolower( $method ); + $method_args = $method_name . '_args'; + if ( $this->method_exists_in_current_class( $method_args ) ) { + $config['args'] = $this->{$method_args}(); + } + return $config; + } + + /** + * maybe_add_response_to_swagger + * + * If the function method /(rest-method)_response_callback/ exists, adds the filter + * /swagger_api_response_(namespace with slashes replaced with underscores)_(endpoint with slashes replaced with underscores)/ + * with the aforementioned function method. + * This filter is used with the WP API Swagger UI plugin to create documentation for the API. + * The value being passed is an array: [ + '200' => ['description' => 'OK'], + '404' => ['description' => 'Not Found'], + '400' => ['description' => 'Bad Request'] + ] + * @param string $method REST method name + */ + public function maybe_add_response_to_swagger( string $method ): void { + $method_name = strtolower( $method ); + $method_response_callback = $method_name . '_response_callback'; + if ( $this->method_exists_in_current_class( $method_response_callback ) ) { + $response_filter = $method_name . '_' . str_replace( + '/', + '_', + $this->namespace . '/' . $this->get_endpoint() + ); + add_filter( 'swagger_api_responses_' . $response_filter, [ $this, $method_response_callback ] ); + } + } + + /** + * build_endpoint_method_config + * + * Builds a configuration array for the endpoint based on the presence of the callback, permission, additional parameters, + * and response to Swagger member functions. + * @param string $method The REST method for the endpoint + * + * @return array The endpoint configuration for the method specified by the parameter + */ + private function build_endpoint_method_config( string $method ): array { + $config = [ + 'methods' => $method, + 'callback' => $this->get_callback_method( $method ), + 'permission_callback' => $this->get_permission_callback_method( $method ), + ]; + $config = $this->maybe_add_args_to_config( $method, $config ); + return $config; + } + + /** + * method_exists_in_current_class + * + * Uses reflection to check if this class has the /method/ method. + * @param string $method The name of the method being checked. + * + * @return bool TRUE if the class has the /method/ method, FALSE otherwise. + */ + private function method_exists_in_current_class( string $method ): bool { + $class_name = get_class( $this ); + try { + $reflection = new ReflectionClass( $class_name ); + } catch ( \ReflectionException $e ) { + return false; + } + if ( ! $reflection->hasMethod( $method ) ) { + return false; + } + $method_ref = $reflection->getMethod( $method ); + + return ( $method_ref && $class_name === $method_ref->class ); + } + + /** + * @param $data + * + * @return WP_REST_Response + */ + public function respond_success_json( $data = [] ): WP_REST_Response { + return new WP_REST_Response([ + 'success' => true, + 'data' => $data, + ]); + } + + /** + * @param array{message: string, code: string} $data + * + * @return WP_Error + */ + public function respond_error_json( array $data ): WP_Error { + if ( ! isset( $data['message'] ) || ! isset( $data['code'] ) ) { + _doing_it_wrong( + __FUNCTION__, + esc_html__( 'Both `message` and `code` keys must be provided', 'pojo-accessibility' ), + '1.0.0' + ); // @codeCoverageIgnore + } + + return new WP_Error( + $data['code'] ?? 'internal_server_error', + $data['message'] ?? esc_html__( 'Internal server error', 'pojo-accessibility' ), + ); + } + + public function verify_capability( $capability = 'manage_options' ) { + if ( ! current_user_can( $capability ) ) { + return $this->respond_error_json([ + 'message' => esc_html__( 'You do not have sufficient permissions to access this data.', 'pojo-accessibility' ), + 'code' => '401 Unauthorized', + ]); + } + } + + /** + * permission_callback + * Permissions callback fallback for the endpoint + * Gets the current user ID and sets the /current_user_id/ property. + * If the /auth/ property is set to /true/ will make sure that the user is logged in (has an id greater than 0) + * + * @param WP_REST_Request $request unused + * + * @return bool TRUE, if permission granted, FALSE otherwise + */ + public function permission_callback( WP_REST_Request $request ): bool { + // try to get current user + $this->current_user_id = get_current_user_id(); + if ( $this->auth ) { + return $this->current_user_id > 0; + } + + return true; + } + + /** + * callback + * Fallback callback function, returns a response consisting of the string /ok/. + * + * @param WP_REST_Request $request unused + * + * @return WP_REST_Response Default Response of the string 'ok'. + */ + public function callback( WP_REST_Request $request ): WP_REST_Response { + return rest_ensure_response( [ 'OK' ] ); + } + + /** + * respond_wrong_method + * + * Creates a WordPress error object with the /rest_no_route/ code and the message and code supplied or the defaults. + * @param null $message The error message for the wrong method. + * Optional. + * Defaults to null, which makes sets the message to /No route was found matching the URL and request method/ + * @param int $code The HTTP status code. + * Optional. + * Defaults to 404 (Not found). + * + * @return WP_Error The WordPress error object with the error message and status code supplied + */ + public function respond_wrong_method( $message = null, int $code = 404 ): WP_Error { + if ( null === $message ) { + $message = __( 'No route was found matching the URL and request method', 'pojo-accessibility' ); + } + + return new WP_Error( 'rest_no_route', $message, [ 'status' => $code ] ); + } + + /** + * respond_with_code + * Create a new /WP_REST_Response/ object with the specified data and HTTP response code. + * + * @param array|null $data The data to return in this response + * @param int $code The HTTP response code. + * Optional. + * Defaults to 200 (OK). + * + * @return WP_REST_Response The WordPress response object loaded with the data and the response code. + */ + public function respond_with_code( ?array $data = null, int $code = 200 ): WP_REST_Response { + return new WP_REST_Response( $data, $code ); + } + + /** + * get_user_from_request + * + * Returns the current user object. + * Depends on the property /current_user_id/ to be set. + * @return WP_User|false The user object or false if not found or on error. + */ + public function get_user_from_request() { + return get_user_by( 'id', $this->current_user_id ); + } + + /** + * get_arguments + * Rest Endpoint extra arguments + * @return array Additional arguments for the route configuration + */ + public function get_arguments(): array { + return []; + } + + /** + * get_endpoint + * Rest route Endpoint + * @return string Endpoint uri component (comes after the route namespace) + */ + abstract public function get_endpoint(): string; + + /** + * get_name + * @return string The name of the route + */ + abstract public function get_name(): string; + + /** + * get_self_url + * + * @param string $endpoint + * + * @return string + */ + public function get_self_url( string $endpoint = '' ): string { + return rest_url( $this->namespace . '/' . $endpoint ); + } + + public function verify_nonce( $nonce = '', $name = '' ) { + if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), $name ) ) { + return $this->respond_error_json([ + 'message' => esc_html__( 'Invalid nonce', 'pojo-accessibility' ), + 'code' => 'bad_request', + ]); + } + } + + public function verify_nonce_and_capability( $nonce = '', $name = '', $capability = 'manage_options' ) { + $this->verify_nonce( $nonce, $name ); + + if ( ! current_user_can( $capability ) ) { + return $this->respond_error_json([ + 'message' => esc_html__( 'You do not have sufficient permissions to access this data.', 'pojo-accessibility' ), + 'code' => 'bad_request', + ]); + } + } + +} diff --git a/classes/utils.php b/classes/utils.php new file mode 100644 index 0000000..ee12487 --- /dev/null +++ b/classes/utils.php @@ -0,0 +1,37 @@ +id, 'ea11y-' ); + } + public static function user_is_admin(): bool { + return current_user_can( 'manage_options' ); + } + public static function is_wp_dashboard_page(): bool { + $current_screen = get_current_screen(); + + return str_contains( $current_screen->id, 'dashboard' ); + } + + public static function is_wp_settings_page(): bool { + $current_screen = get_current_screen(); + + return str_contains( $current_screen->id, 'options-' ); + } + + public static function is_elementor_installed() { + $file_path = 'elementor/elementor.php'; + $installed_plugins = get_plugins(); + return isset( $installed_plugins[ $file_path ] ); + } + +} diff --git a/classes/utils/assets.php b/classes/utils/assets.php new file mode 100644 index 0000000..db3ac8e --- /dev/null +++ b/classes/utils/assets.php @@ -0,0 +1,144 @@ + self::get_assets_version( $version ), + 'suffix' => self::get_assets_suffix(), + ]; + } + + /** + * get_assets_path + * + * @param string $asset_name + * @param string $asset_type + * @param string $suffix + * + * @return string + */ + private static function get_assets_path( string $asset_name, string $asset_type, string $suffix = '' ) : string { + return EA11Y_ASSETS_URL . '/build/' . $asset_name . $suffix . '.' . $asset_type; + } + + /** + * enqueue_app_assets + * + * @param string $handle + * @param bool $with_css + */ + public static function enqueue_app_assets( string $handle = '', bool $with_css = true ) : void { + $dir = EA11Y_ASSETS_PATH . 'build/'; + $url = EA11Y_ASSETS_URL . 'build/'; + + $script_asset_path = $dir . $handle . '.asset.php'; + if ( ! file_exists( $script_asset_path ) ) { + throw new \Error( + 'You need to run `npm start` or `npm run build` for the "' . esc_html( $handle ) . '" script first.' + ); + } + + // enqueue js + $script_asset = require $script_asset_path; + wp_enqueue_script( + $handle, + $url . $handle . '.js', + array_merge( $script_asset['dependencies'], [ 'wp-util' ] ), + $script_asset['version'], + true, + ); + + // add translation support + wp_set_script_translations( $handle, 'elementor-sw' ); + + if ( ! $with_css ) { + return; + } + // enqueue css + $css_file_name = 'style-' . $handle . '.css'; + $css_version = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? filemtime( $dir . $css_file_name ) : EA11Y_VERSION; + wp_enqueue_style( + $handle, + $url . $css_file_name, + [ 'wp-components' ], + $css_version + ); + } +} diff --git a/composer.json b/composer.json index d91f30d..97f699d 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,44 @@ "email": "yakir@pojo.me" } ], - "require": { - "phpunit/phpunit": "4.8.8" - } + "require-dev": { + "johnpbloch/wordpress-core": "^6.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", + "squizlabs/php_codesniffer": "^3.6", + "wp-coding-standards/wpcs": "2.3", + "php-stubs/wordpress-stubs": "^6.0", + "elementor/eunit": "^0.0.10", + "phpcompatibility/phpcompatibility-wp": "^2.1", + "phpunit/php-code-coverage": "^9.2", + "wildwolf/wordpress-test-library-stubs": "^6.0", + "wp-phpunit/wp-phpunit": "^6.0", + "yoast/phpunit-polyfills": "^2.0" + }, + "scripts": { + "lint": "phpcs --standard=./ruleset.xml ./**/*.php" + }, + "require": { + "firebase/php-jwt": "^6.10" + }, + "config": { + "allow-plugins": { + "composer/installers": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "extra": { + "imposter": { + "namespace": "...", + "excludes": [ "firebase/php-jwt"] + }, + "installer-paths": { + "vendor/{$vendor}/{$name}/": [ "firebase/php-jwt"] + } + }, + "repositories":[ + { + "type": "vcs", + "url": "git@github.com:elementor/eunit.git" + } + ] } diff --git a/includes/manager.php b/includes/manager.php new file mode 100644 index 0000000..a8b29d6 --- /dev/null +++ b/includes/manager.php @@ -0,0 +1,57 @@ +modules[ $module_name ] = $class_name::instance(); + } + } + } + + /** + * @param string $module_name + * + * @return Module_Base|Module_Base[] + */ + public function get_modules( string $module_name ) { + if ( $module_name ) { + if ( isset( $this->modules[ $module_name ] ) ) { + return $this->modules[ $module_name ]; + } + + return null; + } + + return $this->modules; + } +} diff --git a/includes/pojo-a11y-settings.php b/includes/pojo-a11y-settings.php deleted file mode 100644 index f2e43c2..0000000 --- a/includes/pojo-a11y-settings.php +++ /dev/null @@ -1,645 +0,0 @@ - 'pojo_a11y_toolbar', - 'title' => __( 'Display Toolbar', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'options' => array( - 'enable' => __( 'Show on all devices', 'pojo-accessibility' ), - 'visible-desktop' => __( 'Visible Desktop', 'pojo-accessibility' ), - 'visible-tablet' => __( 'Visible Tablet', 'pojo-accessibility' ), - 'visible-phone' => __( 'Visible Phone', 'pojo-accessibility' ), - 'hidden-desktop' => __( 'Hidden Desktop', 'pojo-accessibility' ), - 'hidden-tablet' => __( 'Hidden Tablet', 'pojo-accessibility' ), - 'hidden-phone' => __( 'Hidden Phone', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $toolbar_options_classes = 'pojo-a11y-toolbar-button'; - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_title', - 'title' => __( 'Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'desc' => __( 'Title top of the toolbar (recommended).', 'pojo-accessibility' ), - 'class' => $toolbar_options_classes, - 'std' => __( 'Accessibility Tools', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_resize_font', - 'title' => __( 'Resize Font', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_resize_font_add_title', - 'title' => __( 'Increase Text', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row no-border', - 'std' => __( 'Increase Text', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_resize_font_less_title', - 'title' => __( 'Decrease Text', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Decrease Text', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_grayscale', - 'title' => __( 'Grayscale', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_grayscale_title', - 'title' => __( 'Grayscale Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Grayscale', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_high_contrast', - 'title' => __( 'High Contrast', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_high_contrast_title', - 'title' => __( 'High Contrast Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'High Contrast', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_negative_contrast', - 'title' => __( 'Negative Contrast', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_negative_contrast_title', - 'title' => __( 'Negative Contrast Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Negative Contrast', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_light_bg', - 'title' => __( 'Light Background', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_light_bg_title', - 'title' => __( 'Light Background Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Light Background', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_links_underline', - 'title' => __( 'Links Underline', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_links_underline_title', - 'title' => __( 'Links Underline Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Links Underline', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_readable_font', - 'title' => __( 'Readable Font', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'class' => $toolbar_options_classes, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_readable_font_title', - 'title' => __( 'Readable Font Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => __( 'Readable Font', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_sitemap_title', - 'title' => __( 'Sitemap Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes, - 'std' => __( 'Sitemap', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_sitemap_link', - 'title' => __( 'Sitemap Link', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'placeholder' => 'http://your-domain.com/sitemap', - 'desc' => __( 'Link for sitemap page in your website. Leave blank to disable.', 'pojo-accessibility' ), - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => '', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_help_title', - 'title' => __( 'Help Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes, - 'std' => __( 'Help', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_help_link', - 'title' => __( 'Help Link', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'placeholder' => 'http://your-domain.com/help', - 'desc' => __( 'Link for help page in your website. Leave blank to disable.', 'pojo-accessibility' ), - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => '', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_feedback_title', - 'title' => __( 'Feedback Title', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'class' => $toolbar_options_classes, - 'std' => __( 'Feedback', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'pojo_a11y_toolbar_button_feedback_link', - 'title' => __( 'Feedback Link', 'pojo-accessibility' ), - 'type' => self::FIELD_TEXT, - 'placeholder' => 'http://your-domain.com/feedback', - 'desc' => __( 'Link for feedback page in your website. Leave blank to disable.', 'pojo-accessibility' ), - 'class' => $toolbar_options_classes . ' pojo-settings-child-row', - 'std' => '', - ); - - $sections[] = array( - 'id' => 'section-a11y-toolbar', - 'page' => self::TOOLBAR_PAGE, - 'title' => __( 'Toolbar Settings', 'pojo-accessibility' ), - 'intro' => '', - 'fields' => $fields, - ); - - $sections[] = array( - 'id' => 'section-a11y-styles', - 'page' => self::TOOLBAR_PAGE, - 'title' => __( 'Style Settings', 'pojo-accessibility' ), - 'intro' => sprintf( __( 'For style settings of accessibility tools go to > Customize > Accessibility.', 'pojo-accessibility' ), $this->get_admin_url( 'customizer' ) ), - 'fields' => array(), - ); - - return $sections; - } - - public function section_a11y_settings( $sections ) { - $fields = array(); - - $fields[] = array( - 'id' => 'pojo_a11y_focusable', - 'title' => __( 'Add Outline Focus', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'desc' => __( 'Add outline to elements on keyboard focus.', 'pojo-accessibility' ), - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'disable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_skip_to_content_link', - 'title' => __( 'Skip to Content link', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'desc' => __( 'Add skip to content link when using keyboard.', 'pojo-accessibility' ), - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_skip_to_content_link_element_id', - 'title' => __( 'Skip to Content Element ID', 'pojo-accessibility' ), - 'placeholder' => 'content', - 'type' => self::FIELD_TEXT, - 'std' => 'content', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_remove_link_target', - 'title' => __( 'Remove target attribute from links', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'desc' => __( 'This option will reset all your target links to open in the same window or tab.', 'pojo-accessibility' ), - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'disable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_add_role_links', - 'title' => __( 'Add landmark roles to all links', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'desc' => __( 'This option will add role="link" to all links on the page.', 'pojo-accessibility' ), - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_save', - 'title' => __( 'Sitewide Accessibility', 'pojo-accessibility' ), - 'desc' => __( 'Consistent accessibility throughout your site visit. Site remembers you and stays accessible.', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'options' => array( - 'enable' => __( 'Enable', 'pojo-accessibility' ), - 'disable' => __( 'Disable', 'pojo-accessibility' ), - ), - 'std' => 'enable', - ); - - $fields[] = array( - 'id' => 'pojo_a11y_save_expiration', - 'title' => __( 'Remember user for', 'pojo-accessibility' ), - 'type' => self::FIELD_SELECT, - 'desc' => __( 'Define how long your toolbar settings will be remembered', 'pojo-accessibility' ), - 'options' => array( - '1' => __( '1 Hour', 'pojo-accessibility' ), - '6' => __( '6 Hours', 'pojo-accessibility' ), - '12' => __( '12 Hours', 'pojo-accessibility' ), - '24' => __( '1 Day', 'pojo-accessibility' ), - '48' => __( '2 Days', 'pojo-accessibility' ), - '72' => __( '3 Days', 'pojo-accessibility' ), - '168' => __( '1 Week', 'pojo-accessibility' ), - '720' => __( '1 Month', 'pojo-accessibility' ), - ), - 'std' => '12', - ); - - $sections[] = array( - 'id' => 'section-a11y-settings', - 'page' => self::SETTINGS_PAGE, - 'title' => __( 'General Settings', 'pojo-accessibility' ), - 'intro' => '', - 'fields' => $fields, - ); - - return $sections; - } - - public function print_js() { - // TODO: Maybe need to move to other file - ?> - - section_a11y_toolbar( $sections ); - $sections = $this->section_a11y_settings( $sections ); - $this->_sections = $sections; - return $this->_sections; - } - - public function add_settings_section( $args = array() ) { - $args = wp_parse_args( $args, array( - 'id' => '', - 'title' => '', - ) ); - - foreach ( $this->_sections as $section ) { - if ( $args['id'] !== $section['id'] ) { - continue; - } - if ( empty( $section['intro'] ) ) { - return; - } - printf( '

%s

', $section['intro'] ); - break; - } - } - - public function add_settings_field( $args = array() ) { - if ( empty( $args ) ) { - return; - } - - $args = wp_parse_args( $args, array( - 'id' => '', - 'std' => '', - 'type' => self::FIELD_TEXT, - ) ); - - if ( empty( $args['id'] ) || empty( $args['type'] ) ) { - return; - } - - $field_callback = 'render_' . $args['type'] . '_field'; - if ( method_exists( $this, $field_callback ) ) { - call_user_func( array( $this, $field_callback ), $args ); - } - } - - public function render_select_field( $field ) { - $options = array(); - foreach ( $field['options'] as $option_key => $option_value ) { - $options[] = sprintf( - '', - esc_attr( $option_key ), - selected( get_option( $field['id'], $field['std'] ), $option_key, false ), - $option_value - ); - } - ?> - - - -

- - - /> - - -

- - get_settings_sections() as $section_key => $section ) { - add_settings_section( - $section['id'], - $section['title'], - array( &$this, 'add_settings_section' ), - $section['page'] - ); - - if ( empty( $section['fields'] ) ) { - continue; - } - - foreach ( $section['fields'] as $field ) { - add_settings_field( - $field['id'], - $field['title'], - array( &$this, 'add_settings_field' ), - $section['page'], - $section['id'], - $field - ); - - $sanitize_callback = array( $this, 'field_html' ); - if ( ! empty( $field['type'] ) && self::FIELD_CHECKBOX_LIST === $field['type'] ) { - $sanitize_callback = array( $this, 'field_checkbox_list' ); - } - if ( ! empty( $field['sanitize_callback'] ) ) { - $sanitize_callback = $field['sanitize_callback']; - } - - register_setting( $section['page'], $field['id'], $sanitize_callback ); - } - } - } - - public static function field_html( $input ) { - return stripslashes( wp_filter_post_kses( addslashes( $input ) ) ); - } - - public static function field_checkbox_list( $input ) { - if ( empty( $input ) ) { - $input = array(); - } - - return $input; - } - - public function display_settings_page() { - $screen = get_current_screen(); - $screen_id = $screen->id; - if ( false !== strpos( $screen_id, 'toolbar' ) ) { - $screen_id = self::TOOLBAR_PAGE; - } - ?> -
-

_page_title; ?>

- -
- -
- -
- menu_slug = add_menu_page( - __( 'Accessibility', 'pojo-accessibility' ), - __( 'Accessibility', 'pojo-accessibility' ), - 'manage_options', - 'accessibility-settings', - array( &$this, 'display_settings_page' ), - 'dashicons-universal-access-alt' - ); - add_submenu_page( - 'accessibility-settings', - __( 'Accessibility Settings', 'pojo-accessibility' ), - __( 'Settings', 'pojo-accessibility' ), - 'manage_options', - 'accessibility-settings', - array( &$this, 'display_settings_page' ) - ); - add_submenu_page( - 'accessibility-settings', - __( 'Accessibility Toolbar', 'pojo-accessibility' ), - __( 'Toolbar', 'pojo-accessibility' ), - 'manage_options', - 'accessibility-toolbar', - array( &$this, 'display_settings_page' ) - ); - add_submenu_page( - 'accessibility-settings', - __( 'Customize', 'pojo-accessibility' ), - __( 'Customize', 'pojo-accessibility' ), - 'manage_options', - '/customize.php?autofocus[section]=accessibility' - ); - } - - public function plugin_action_links( $links, $plugin_file ) { - if ( POJO_A11Y_BASE === $plugin_file ) { - $settings = '' . __( 'Settings', 'pojo-accessibility' ) . ''; - $toolbar = '' . __( 'Toolbar', 'pojo-accessibility' ) . ''; - $customizer = '' . __( 'Customize', 'pojo-accessibility' ) . ''; - array_unshift( $links, $customizer ); - array_unshift( $links, $toolbar ); - array_unshift( $links, $settings ); - } - return $links; - } - - private function get_admin_url( $type ) { - switch ( $type ) { - case 'customizer': - return admin_url( 'customize.php?autofocus[section]=accessibility' ); - break; - case 'general': - return admin_url( 'admin.php?page=accessibility-settings' ); - break; - case 'toolbar': - return admin_url( 'admin.php?page=accessibility-toolbar' ); - break; - } - } - - private function get_default_values() { - if ( empty( $this->_defaults ) ) { - if ( empty( $this->_sections ) ) { - $this->get_settings_sections(); - } - $defaults = array(); - foreach ( $this->_sections as $section ) { - foreach ( $section['fields'] as $field ) { - $defaults[ $field['id'] ] = isset( $field['std'] ) ? $field['std'] : ''; - } - } - $this->_defaults = $defaults; - } - } - - public function get_default_title_text( $option ) { - $this->get_default_values(); - $default = isset( $this->_defaults[ $option ] ) ? $this->_defaults[ $option ] : ''; - - return get_option( $option, $default ); - } - - public function __construct() { - $this->_page_title = __( 'One Click Accessibility', 'pojo-accessibility' ); - $this->_page_menu_title = __( 'One Click Accessibility', 'pojo-accessibility' ); - $this->_menu_parent = 'themes.php'; - - add_action( 'admin_menu', array( &$this, 'admin_menu' ), 20 ); - add_action( 'admin_init', array( &$this, 'admin_init' ), 20 ); - add_action( 'admin_footer', array( &$this, 'print_js' ) ); - add_filter( 'plugin_action_links_' . POJO_A11Y_BASE, [ $this, 'plugin_action_links' ], 10, 2 ); - } -} \ No newline at end of file diff --git a/assets/css/style.css b/modules/legacy/assets/css/style.css similarity index 100% rename from assets/css/style.css rename to modules/legacy/assets/css/style.css diff --git a/assets/css/style.min.css b/modules/legacy/assets/css/style.min.css similarity index 100% rename from assets/css/style.min.css rename to modules/legacy/assets/css/style.min.css diff --git a/assets/js/app.dev.js b/modules/legacy/assets/js/app.dev.js similarity index 100% rename from assets/js/app.dev.js rename to modules/legacy/assets/js/app.dev.js diff --git a/assets/js/app.min.js b/modules/legacy/assets/js/app.min.js similarity index 100% rename from assets/js/app.min.js rename to modules/legacy/assets/js/app.min.js diff --git a/assets/js/skip-link-focus-fix.js b/modules/legacy/assets/js/skip-link-focus-fix.js similarity index 100% rename from assets/js/skip-link-focus-fix.js rename to modules/legacy/assets/js/skip-link-focus-fix.js diff --git a/assets/less/_background.less b/modules/legacy/assets/less/_background.less similarity index 100% rename from assets/less/_background.less rename to modules/legacy/assets/less/_background.less diff --git a/assets/less/_high-contrast.less b/modules/legacy/assets/less/_high-contrast.less similarity index 100% rename from assets/less/_high-contrast.less rename to modules/legacy/assets/less/_high-contrast.less diff --git a/assets/less/_mixing.less b/modules/legacy/assets/less/_mixing.less similarity index 100% rename from assets/less/_mixing.less rename to modules/legacy/assets/less/_mixing.less diff --git a/assets/less/_toolbar.less b/modules/legacy/assets/less/_toolbar.less similarity index 100% rename from assets/less/_toolbar.less rename to modules/legacy/assets/less/_toolbar.less diff --git a/assets/less/_underline.less b/modules/legacy/assets/less/_underline.less similarity index 100% rename from assets/less/_underline.less rename to modules/legacy/assets/less/_underline.less diff --git a/assets/less/_visibility.less b/modules/legacy/assets/less/_visibility.less similarity index 100% rename from assets/less/_visibility.less rename to modules/legacy/assets/less/_visibility.less diff --git a/assets/less/style.less b/modules/legacy/assets/less/style.less similarity index 100% rename from assets/less/style.less rename to modules/legacy/assets/less/style.less diff --git a/includes/pojo-a11y-admin-ui.php b/modules/legacy/components/admin.php similarity index 93% rename from includes/pojo-a11y-admin-ui.php rename to modules/legacy/components/admin.php index 05c963a..ffc15fd 100644 --- a/includes/pojo-a11y-admin-ui.php +++ b/modules/legacy/components/admin.php @@ -1,23 +1,27 @@ _is_elementor_installed() ) { + if ( ! current_user_can( 'install_plugins' ) || Utils::is_elementor_installed() ) { return; } @@ -152,7 +156,7 @@ public function admin_footer_text( $footer_text ) { $current_screen = get_current_screen(); if ( in_array( $current_screen->id, array( self::SETTINGS_SLUG, self::TOOLBAR_SLUG ) ) ) { $footer_text = sprintf( - /* translators: 1: One Click Accessibility, 2: Link to plugin review */ + /* translators: 1: One Click Accessibility, 2: Link to plugin review */ __( 'Enjoyed %1$s? Please leave us a %2$s rating. We really appreciate your support!', 'pojo-accessibility' ), '' . __( 'One Click Accessibility', 'pojo-accessibility' ) . '', '★★★★★' diff --git a/includes/pojo-a11y-customizer.php b/modules/legacy/components/customizer.php similarity index 50% rename from includes/pojo-a11y-customizer.php rename to modules/legacy/components/customizer.php index ed8985f..464c831 100644 --- a/includes/pojo-a11y-customizer.php +++ b/modules/legacy/components/customizer.php @@ -1,158 +1,166 @@ 'a11y_toolbar_icon', - 'title' => __( 'Toolbar Icon', 'pojo-accessibility' ), - 'type' => 'select', - 'choices' => array( - 'one-click' => __( 'One Click', 'pojo-accessibility' ), - 'wheelchair' => __( 'Wheelchair', 'pojo-accessibility' ), + $fields = []; + + $fields[] = [ + 'id' => 'a11y_toolbar_icon', + 'title' => __( 'Toolbar Icon', 'pojo-accessibility' ), + 'type' => 'select', + 'choices' => [ + 'one-click' => __( 'One Click', 'pojo-accessibility' ), + 'wheelchair' => __( 'Wheelchair', 'pojo-accessibility' ), 'accessibility' => __( 'Accessibility', 'pojo-accessibility' ), - ), - 'std' => 'one-click', + ], + 'std' => 'one-click', 'description' => __( 'Set Toolbar Icon', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_toolbar_position', - 'title' => __( 'Toolbar Position', 'pojo-accessibility' ), - 'type' => 'select', - 'choices' => array( - 'left' => __( 'Left', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_toolbar_position', + 'title' => __( 'Toolbar Position', 'pojo-accessibility' ), + 'type' => 'select', + 'choices' => [ + 'left' => __( 'Left', 'pojo-accessibility' ), 'right' => __( 'Right', 'pojo-accessibility' ), - ), - 'std' => is_rtl() ? 'right' : 'left', + ], + 'std' => is_rtl() ? 'right' : 'left', 'description' => __( 'Set Toolbar Position', 'pojo-accessibility' ), - ); + ]; - $fields[] = array( - 'id' => 'a11y_toolbar_distance_top', - 'title' => __( 'Offset from Top (Desktop)', 'pojo-accessibility' ), - 'type' => 'text', - 'std' => '100px', + $fields[] = [ + 'id' => 'a11y_toolbar_distance_top', + 'title' => __( 'Offset from Top (Desktop)', 'pojo-accessibility' ), + 'type' => 'text', + 'std' => '100px', 'description' => __( 'Set Toolbar top offset (Desktop)', 'pojo-accessibility' ), - ); + ]; - $fields[] = array( - 'id' => 'a11y_toolbar_distance_top_mobile', - 'title' => __( 'Offset from Top (Mobile)', 'pojo-accessibility' ), - 'type' => 'text', - 'std' => '50px', + $fields[] = [ + 'id' => 'a11y_toolbar_distance_top_mobile', + 'title' => __( 'Offset from Top (Mobile)', 'pojo-accessibility' ), + 'type' => 'text', + 'std' => '50px', 'description' => __( 'Set Toolbar top offset (Mobile)', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_bg_toolbar', - 'title' => __( 'Toolbar Background', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#ffffff', - 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay', + ]; + + $fields[] = [ + 'id' => 'a11y_bg_toolbar', + 'title' => __( 'Toolbar Background', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#ffffff', + 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay', 'change_type' => 'bg_color', 'description' => __( 'Set Toolbar background color', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_color_toolbar', - 'title' => __( 'Toolbar Color', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#333333', - 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a, #pojo-a11y-toolbar .pojo-a11y-toolbar-overlay p.pojo-a11y-toolbar-title', + ]; + + $fields[] = [ + 'id' => 'a11y_color_toolbar', + 'title' => __( 'Toolbar Color', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#333333', + 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a, #pojo-a11y-toolbar .pojo-a11y-toolbar-overlay p.pojo-a11y-toolbar-title', 'change_type' => 'color', 'description' => __( 'Set Toolbar text color', 'pojo-accessibility' ), - ); + ]; - $fields[] = array( - 'id' => 'a11y_toggle_button_bg_color', - 'title' => __( 'Toggle Button Background', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#4054b2', + $fields[] = [ + 'id' => 'a11y_toggle_button_bg_color', + 'title' => __( 'Toggle Button Background', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#4054b2', 'description' => __( 'Set Toolbar toggle button background color', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_toggle_button_color', - 'title' => __( 'Toggle Button Color', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#ffffff', - 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-toggle a', + ]; + + $fields[] = [ + 'id' => 'a11y_toggle_button_color', + 'title' => __( 'Toggle Button Color', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#ffffff', + 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-toggle a', 'change_type' => 'color', 'description' => __( 'Set Toolbar toggle button icon color', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_bg_active', - 'title' => __( 'Active Background', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#4054b2', - 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a.active', + ]; + + $fields[] = [ + 'id' => 'a11y_bg_active', + 'title' => __( 'Active Background', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#4054b2', + 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a.active', 'change_type' => 'bg_color', 'description' => __( 'Set Toolbar active background color', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_color_active', - 'title' => __( 'Active Color', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#ffffff', - 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a.active', + ]; + + $fields[] = [ + 'id' => 'a11y_color_active', + 'title' => __( 'Active Color', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#ffffff', + 'selector' => '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items li.pojo-a11y-toolbar-item a.active', 'change_type' => 'color', 'description' => __( 'Set Toolbar active text color', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_focus_outline_style', - 'title' => __( 'Focus Outline Style', 'pojo-accessibility' ), - 'type' => 'select', - 'choices' => array( - 'solid' => __( 'Solid', 'pojo-accessibility' ), - 'dotted' => __( 'Dotted', 'pojo-accessibility' ), - 'dashed' => __( 'Dashed', 'pojo-accessibility' ), - 'double' => __( 'Double', 'pojo-accessibility' ), - 'groove' => __( 'Groove', 'pojo-accessibility' ), - 'ridge' => __( 'Ridge', 'pojo-accessibility' ), - 'outset' => __( 'Outset', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'a11y_focus_outline_style', + 'title' => __( 'Focus Outline Style', 'pojo-accessibility' ), + 'type' => 'select', + 'choices' => [ + 'solid' => __( 'Solid', 'pojo-accessibility' ), + 'dotted' => __( 'Dotted', 'pojo-accessibility' ), + 'dashed' => __( 'Dashed', 'pojo-accessibility' ), + 'double' => __( 'Double', 'pojo-accessibility' ), + 'groove' => __( 'Groove', 'pojo-accessibility' ), + 'ridge' => __( 'Ridge', 'pojo-accessibility' ), + 'outset' => __( 'Outset', 'pojo-accessibility' ), 'initial' => __( 'Initial', 'pojo-accessibility' ), - ), - 'std' => 'solid', + ], + 'std' => 'solid', 'description' => __( 'Set Focus outline style', 'pojo-accessibility' ), - ); - - $fields[] = array( - 'id' => 'a11y_focus_outline_width', - 'title' => __( 'Focus Outline Width', 'pojo-accessibility' ), - 'type' => 'select', - 'choices' => array( - '1px' => '1px', - '2px' => '2px', - '3px' => '3px', - '4px' => '4px', - '5px' => '5px', - '6px' => '6px', - '7px' => '7px', - '8px' => '8px', - '9px' => '9px', + ]; + + $fields[] = [ + 'id' => 'a11y_focus_outline_width', + 'title' => __( 'Focus Outline Width', 'pojo-accessibility' ), + 'type' => 'select', + 'choices' => [ + '1px' => '1px', + '2px' => '2px', + '3px' => '3px', + '4px' => '4px', + '5px' => '5px', + '6px' => '6px', + '7px' => '7px', + '8px' => '8px', + '9px' => '9px', '10px' => '10px', - ), - 'std' => '1px', + ], + 'std' => '1px', 'description' => __( 'Set Focus outline width', 'pojo-accessibility' ), - ); + ]; - $fields[] = array( - 'id' => 'a11y_focus_outline_color', - 'title' => __( 'Focus Outline Color', 'pojo-accessibility' ), - 'type' => 'color', - 'std' => '#FF0000', + $fields[] = [ + 'id' => 'a11y_focus_outline_color', + 'title' => __( 'Focus Outline Color', 'pojo-accessibility' ), + 'type' => 'color', + 'std' => '#FF0000', 'description' => __( 'Set Focus outline color', 'pojo-accessibility' ), - ); + ]; return $fields; } @@ -162,51 +170,52 @@ public function customize_a11y( $wp_customize ) { $section_description = '

' . __( 'Use the control below to customize the appearance and layout of the Accessibility Toolbar', 'pojo-accessibility' ) . '

' . sprintf( __( 'Additional Toolbar settings can be configured at the %s page.', 'pojo-accessibility' ), - '' . __( 'Accessibility Toolbar', 'pojo-accessibility' ) . '' + '' . __( 'Accessibility Toolbar', 'pojo-accessibility' ) . '' ) . '

'; - $wp_customize->add_section( 'accessibility', array( - 'title' => __( 'Accessibility', 'pojo-accessibility' ), - 'priority' => 30, + $wp_customize->add_section( 'accessibility', [ + 'title' => __( 'Accessibility', 'pojo-accessibility' ), + 'priority' => 30, 'description' => $section_description, - ) ); + ] ); foreach ( $fields as $field ) { $customizer_id = POJO_A11Y_CUSTOMIZER_OPTIONS . '[' . $field['id'] . ']'; - $wp_customize->add_setting( $customizer_id, array( + $wp_customize->add_setting( $customizer_id, [ 'default' => $field['std'] ? $field['std'] : null, - 'type' => 'option', - ) ); + 'type' => 'option', + ] ); switch ( $field['type'] ) { case 'color': - $wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, $field['id'], array( - 'label' => $field['title'], - 'section' => 'accessibility', - 'settings' => $customizer_id, + $wp_customize->add_control( new \WP_Customize_Color_Control( $wp_customize, $field['id'], [ + 'label' => $field['title'], + 'section' => 'accessibility', + 'settings' => $customizer_id, 'description' => isset( $field['description'] ) ? $field['description'] : null, - ) ) ); + ] ) ); break; case 'select': case 'text': - $wp_customize->add_control( $field['id'], array( - 'label' => $field['title'], - 'section' => 'accessibility', - 'settings' => $customizer_id, - 'type' => $field['type'], - 'choices' => isset( $field['choices'] ) ? $field['choices'] : null, + $wp_customize->add_control( $field['id'], [ + 'label' => $field['title'], + 'section' => 'accessibility', + 'settings' => $customizer_id, + 'type' => $field['type'], + 'choices' => isset( $field['choices'] ) ? $field['choices'] : null, 'description' => isset( $field['description'] ) ? $field['description'] : null, - ) ); + ] ); break; } } } public function get_custom_css_code() { - $options = $this->get_customizer_options(); + $options = $this->get_customizer_options(); $bg_color = $options['a11y_toggle_button_bg_color']; // get_theme_mod( 'a11y_toggle_button_bg_color', '#4054b2' ); if ( ! empty( $bg_color ) ) { $this->add_css_rule( '#pojo-a11y-toolbar .pojo-a11y-toolbar-toggle a', 'background-color', $bg_color ); - $this->add_css_rule( '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay, #pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items.pojo-a11y-links', 'border-color', $bg_color ); + $this->add_css_rule( '#pojo-a11y-toolbar .pojo-a11y-toolbar-overlay, #pojo-a11y-toolbar .pojo-a11y-toolbar-overlay ul.pojo-a11y-toolbar-items.pojo-a11y-links', + 'border-color', $bg_color ); } $outline_style = $options['a11y_focus_outline_style']; //get_theme_mod( 'a11y_focus_outline_style', 'solid' ); @@ -255,12 +264,13 @@ private function get_customizer_options() { if ( false === $options ) { $options = get_option( POJO_A11Y_CUSTOMIZER_OPTIONS ); } + return $options; } private function add_css_rule( $selector, $rule, $value ) { if ( ! isset( $this->css_rules[ $selector ] ) ) { - $this->css_rules[ $selector ] = array(); + $this->css_rules[ $selector ] = []; } $this->css_rules[ $selector ][] = $rule . ': ' . $value . ';'; } @@ -279,7 +289,7 @@ public function print_css_code() { } public function __construct() { - add_filter( 'customize_register', array( &$this, 'customize_a11y' ), 610 ); - add_filter( 'wp_head', array( &$this, 'print_css_code' ) ); + add_filter( 'customize_register', [ $this, 'customize_a11y' ], 610 ); + add_filter( 'wp_head', [ $this, 'print_css_code' ] ); } -} \ No newline at end of file +} diff --git a/includes/pojo-a11y-elementor.php b/modules/legacy/components/elementor.php similarity index 66% rename from includes/pojo-a11y-elementor.php rename to modules/legacy/components/elementor.php index 819c1aa..a260e5c 100644 --- a/includes/pojo-a11y-elementor.php +++ b/modules/legacy/components/elementor.php @@ -1,8 +1,15 @@ preview->is_preview_mode() ) { $is_active = false; diff --git a/includes/pojo-a11y-frontend.php b/modules/legacy/components/frontend.php similarity index 89% rename from includes/pojo-a11y-frontend.php rename to modules/legacy/components/frontend.php index 05a5659..1e957e1 100644 --- a/includes/pojo-a11y-frontend.php +++ b/modules/legacy/components/frontend.php @@ -1,8 +1,18 @@ settings->get_default_title_text( "pojo_a11y_toolbar_button_{$button_type}_title" ); + /** + * @var Settings $settings + */ + $settings = Module::get_settings(); + $title = $settings->get_default_title_text( "pojo_a11y_toolbar_button_{$button_type}_title" ); return '' . $this->get_toolbar_svg( $button_type, $title ) . '' . $title . ''; } public function enqueue_scripts() { wp_register_script( 'pojo-a11y', - POJO_A11Y_ASSETS_URL . 'js/app.min.js', - array( - 'jquery', - ), + EA11Y_URL . 'modules/legacy/js/app.min.js', + [ 'jquery' ], '1.0.0', true ); wp_register_style( 'pojo-a11y', - POJO_A11Y_ASSETS_URL . 'css/style.min.css', - array(), + EA11Y_URL . 'modules/legacy/assets/css/style.min.css', + [], '1.0.0' ); @@ -46,13 +58,13 @@ public function enqueue_scripts() { wp_localize_script( 'pojo-a11y', 'PojoA11yOptions', - array( + [ 'focusable' => ( 'enable' === get_option( 'pojo_a11y_focusable' ) ), 'remove_link_target' => ( 'enable' === get_option( 'pojo_a11y_remove_link_target' ) ), 'add_role_links' => ( 'enable' === get_option( 'pojo_a11y_add_role_links' ) ), 'enable_save' => ( 'enable' === get_option( 'pojo_a11y_save' ) ), 'save_expiration' => get_option( 'pojo_a11y_save_expiration' ), - ) + ] ); } @@ -77,16 +89,16 @@ public function print_toolbar() { $customizer_options = get_option( POJO_A11Y_CUSTOMIZER_OPTIONS ); $toolbar_position = $customizer_options['a11y_toolbar_position']; - if ( empty( $toolbar_position ) || ! in_array( $toolbar_position, array( 'right', 'left' ) ) ) { + if ( empty( $toolbar_position ) || ! in_array( $toolbar_position, [ 'right', 'left' ] ) ) { $toolbar_position = 'left'; } - - $toolbar_title = Pojo_Accessibility::$instance->settings->get_default_title_text( 'pojo_a11y_toolbar_title' ); + $settings = Module::get_settings(); + $toolbar_title = $settings->get_default_title_text( 'pojo_a11y_toolbar_title' ); $toolbar_visibility = get_option( 'pojo_a11y_toolbar' ); - $wrapper_classes = array( + $wrapper_classes = [ 'pojo-a11y-toolbar-' . $toolbar_position, - ); + ]; if ( 'enable' !== $toolbar_visibility ) { $wrapper_classes[] = 'pojo-a11y-' . $toolbar_visibility; @@ -114,7 +126,7 @@ public function print_toolbar() { @@ -219,7 +231,7 @@ public function print_toolbar() { } private function get_toolbar_svg( $icon, $icon_title = '' ) { - $icons = array( + $icons = [ 'resize_font_add' => '', 'resize_font_less' => '', 'grayscale' => '', @@ -232,7 +244,7 @@ private function get_toolbar_svg( $icon, $icon_title = '' ) { 'sitemap' => '', 'help' => '', 'feedback' => '', - ); + ]; if ( isset( $icons[ $icon ] ) ) { $icon_title_html = ''; @@ -248,11 +260,11 @@ private function get_toolbar_svg( $icon, $icon_title = '' ) { private function get_svg_icon( $icon ) { if ( null === $this->svg_icons ) { - $this->svg_icons = array( + $this->svg_icons = [ 'wheelchair' => '', 'one-click' => '', 'accessibility' => '', - ); + ]; } if ( isset( $this->svg_icons[ $icon ] ) ) { @@ -263,10 +275,8 @@ private function get_svg_icon( $icon ) { } public function __construct() { - add_action( 'wp_enqueue_scripts', array( &$this, 'enqueue_scripts' ) ); - - add_action( 'wp_footer', array( &$this, 'print_skip_to_content_link' ), 20 ); - add_action( 'wp_footer', array( &$this, 'print_toolbar' ), 30 ); + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); + add_action( 'wp_footer', [ $this, 'print_skip_to_content_link' ], 20 ); + add_action( 'wp_footer', [ $this, 'print_toolbar' ], 30 ); } - } diff --git a/modules/legacy/components/settings.php b/modules/legacy/components/settings.php new file mode 100644 index 0000000..6e86fb4 --- /dev/null +++ b/modules/legacy/components/settings.php @@ -0,0 +1,671 @@ + 'pojo_a11y_toolbar', + 'title' => __( 'Display Toolbar', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'options' => [ + 'enable' => __( 'Show on all devices', 'pojo-accessibility' ), + 'visible-desktop' => __( 'Visible Desktop', 'pojo-accessibility' ), + 'visible-tablet' => __( 'Visible Tablet', 'pojo-accessibility' ), + 'visible-phone' => __( 'Visible Phone', 'pojo-accessibility' ), + 'hidden-desktop' => __( 'Hidden Desktop', 'pojo-accessibility' ), + 'hidden-tablet' => __( 'Hidden Tablet', 'pojo-accessibility' ), + 'hidden-phone' => __( 'Hidden Phone', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $toolbar_options_classes = 'pojo-a11y-toolbar-button'; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_title', + 'title' => __( 'Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'desc' => __( 'Title top of the toolbar (recommended).', 'pojo-accessibility' ), + 'class' => $toolbar_options_classes, + 'std' => __( 'Accessibility Tools', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_resize_font', + 'title' => __( 'Resize Font', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_resize_font_add_title', + 'title' => __( 'Increase Text', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row no-border', + 'std' => __( 'Increase Text', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_resize_font_less_title', + 'title' => __( 'Decrease Text', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Decrease Text', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_grayscale', + 'title' => __( 'Grayscale', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_grayscale_title', + 'title' => __( 'Grayscale Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Grayscale', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_high_contrast', + 'title' => __( 'High Contrast', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_high_contrast_title', + 'title' => __( 'High Contrast Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'High Contrast', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_negative_contrast', + 'title' => __( 'Negative Contrast', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_negative_contrast_title', + 'title' => __( 'Negative Contrast Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Negative Contrast', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_light_bg', + 'title' => __( 'Light Background', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_light_bg_title', + 'title' => __( 'Light Background Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Light Background', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_links_underline', + 'title' => __( 'Links Underline', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_links_underline_title', + 'title' => __( 'Links Underline Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Links Underline', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_readable_font', + 'title' => __( 'Readable Font', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'class' => $toolbar_options_classes, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_readable_font_title', + 'title' => __( 'Readable Font Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => __( 'Readable Font', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_sitemap_title', + 'title' => __( 'Sitemap Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes, + 'std' => __( 'Sitemap', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_sitemap_link', + 'title' => __( 'Sitemap Link', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'placeholder' => 'http://your-domain.com/sitemap', + 'desc' => __( 'Link for sitemap page in your website. Leave blank to disable.', + 'pojo-accessibility' ), + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => '', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_help_title', + 'title' => __( 'Help Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes, + 'std' => __( 'Help', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_help_link', + 'title' => __( 'Help Link', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'placeholder' => 'http://your-domain.com/help', + 'desc' => __( 'Link for help page in your website. Leave blank to disable.', 'pojo-accessibility' ), + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => '', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_feedback_title', + 'title' => __( 'Feedback Title', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'class' => $toolbar_options_classes, + 'std' => __( 'Feedback', 'pojo-accessibility' ), + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_toolbar_button_feedback_link', + 'title' => __( 'Feedback Link', 'pojo-accessibility' ), + 'type' => self::FIELD_TEXT, + 'placeholder' => 'http://your-domain.com/feedback', + 'desc' => __( 'Link for feedback page in your website. Leave blank to disable.', + 'pojo-accessibility' ), + 'class' => $toolbar_options_classes . ' pojo-settings-child-row', + 'std' => '', + ]; + + $sections[] = [ + 'id' => 'section-a11y-toolbar', + 'page' => self::TOOLBAR_PAGE, + 'title' => __( 'Toolbar Settings', 'pojo-accessibility' ), + 'intro' => '', + 'fields' => $fields, + ]; + + $sections[] = [ + 'id' => 'section-a11y-styles', + 'page' => self::TOOLBAR_PAGE, + 'title' => __( 'Style Settings', 'pojo-accessibility' ), + 'intro' => sprintf( __( 'For style settings of accessibility tools go to > Customize > Accessibility.', 'pojo-accessibility' ), + $this->get_admin_url( 'customizer' ) + ), + 'fields' => [], + ]; + + return $sections; + } + + public function section_a11y_settings( $sections ) { + $fields = []; + + $fields[] = [ + 'id' => 'pojo_a11y_focusable', + 'title' => __( 'Add Outline Focus', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'desc' => __( 'Add outline to elements on keyboard focus.', 'pojo-accessibility' ), + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'disable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_skip_to_content_link', + 'title' => __( 'Skip to Content link', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'desc' => __( 'Add skip to content link when using keyboard.', 'pojo-accessibility' ), + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_skip_to_content_link_element_id', + 'title' => __( 'Skip to Content Element ID', 'pojo-accessibility' ), + 'placeholder' => 'content', + 'type' => self::FIELD_TEXT, + 'std' => 'content', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_remove_link_target', + 'title' => __( 'Remove target attribute from links', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'desc' => __( 'This option will reset all your target links to open in the same window or tab.', + 'pojo-accessibility' ), + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'disable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_add_role_links', + 'title' => __( 'Add landmark roles to all links', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'desc' => __( 'This option will add role="link" to all links on the page.', + 'pojo-accessibility' ), + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_save', + 'title' => __( 'Sitewide Accessibility', 'pojo-accessibility' ), + 'desc' => __( 'Consistent accessibility throughout your site visit. Site remembers you and stays accessible.', + 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'options' => [ + 'enable' => __( 'Enable', 'pojo-accessibility' ), + 'disable' => __( 'Disable', 'pojo-accessibility' ), + ], + 'std' => 'enable', + ]; + + $fields[] = [ + 'id' => 'pojo_a11y_save_expiration', + 'title' => __( 'Remember user for', 'pojo-accessibility' ), + 'type' => self::FIELD_SELECT, + 'desc' => __( 'Define how long your toolbar settings will be remembered', 'pojo-accessibility' ), + 'options' => [ + '1' => __( '1 Hour', 'pojo-accessibility' ), + '6' => __( '6 Hours', 'pojo-accessibility' ), + '12' => __( '12 Hours', 'pojo-accessibility' ), + '24' => __( '1 Day', 'pojo-accessibility' ), + '48' => __( '2 Days', 'pojo-accessibility' ), + '72' => __( '3 Days', 'pojo-accessibility' ), + '168' => __( '1 Week', 'pojo-accessibility' ), + '720' => __( '1 Month', 'pojo-accessibility' ), + ], + 'std' => '12', + ]; + + $sections[] = [ + 'id' => 'section-a11y-settings', + 'page' => self::SETTINGS_PAGE, + 'title' => __( 'General Settings', 'pojo-accessibility' ), + 'intro' => '', + 'fields' => $fields, + ]; + + return $sections; + } + + public function print_js() { + // TODO: Maybe need to move to other file + ?> + + section_a11y_toolbar( $sections ); + $sections = $this->section_a11y_settings( $sections ); + $this->_sections = $sections; + + return $this->_sections; + } + + public function add_settings_section( $args = [] ) { + $args = wp_parse_args( $args, [ + 'id' => '', + 'title' => '', + ] ); + + foreach ( $this->_sections as $section ) { + if ( $args['id'] !== $section['id'] ) { + continue; + } + if ( empty( $section['intro'] ) ) { + return; + } + printf( '

%s

', $section['intro'] ); + break; + } + } + + public function add_settings_field( $args = [] ) { + if ( empty( $args ) ) { + return; + } + + $args = wp_parse_args( $args, [ + 'id' => '', + 'std' => '', + 'type' => self::FIELD_TEXT, + ] ); + + if ( empty( $args['id'] ) || empty( $args['type'] ) ) { + return; + } + + $field_callback = 'render_' . $args['type'] . '_field'; + if ( method_exists( $this, $field_callback ) ) { + call_user_func( [ $this, $field_callback ], $args ); + } + } + + public function render_select_field( $field ) { + $options = []; + foreach ( $field['options'] as $option_key => $option_value ) { + $options[] = sprintf( + '', + esc_attr( $option_key ), + selected( get_option( $field['id'], $field['std'] ), $option_key, false ), + $option_value + ); + } + ?> + + + +

+ + + /> + + +

+ + get_settings_sections() as $section_key => $section ) { + add_settings_section( + $section['id'], + $section['title'], + [ &$this, 'add_settings_section' ], + $section['page'] + ); + + if ( empty( $section['fields'] ) ) { + continue; + } + + foreach ( $section['fields'] as $field ) { + add_settings_field( + $field['id'], + $field['title'], + [ &$this, 'add_settings_field' ], + $section['page'], + $section['id'], + $field + ); + + $sanitize_callback = [ $this, 'field_html' ]; + if ( ! empty( $field['type'] ) && self::FIELD_CHECKBOX_LIST === $field['type'] ) { + $sanitize_callback = [ $this, 'field_checkbox_list' ]; + } + if ( ! empty( $field['sanitize_callback'] ) ) { + $sanitize_callback = $field['sanitize_callback']; + } + + register_setting( $section['page'], $field['id'], $sanitize_callback ); + } + } + } + + public static function field_html( $input ) { + return stripslashes( wp_filter_post_kses( addslashes( $input ) ) ); + } + + public static function field_checkbox_list( $input ) { + if ( empty( $input ) ) { + $input = []; + } + + return $input; + } + + public function display_settings_page() { + $screen = get_current_screen(); + $screen_id = $screen->id; + if ( false !== strpos( $screen_id, 'toolbar' ) ) { + $screen_id = self::TOOLBAR_PAGE; + } + ?> +
+

_page_title; ?>

+ +
+ +
+ +
+ menu_slug = add_menu_page( + __( 'Accessibility', 'pojo-accessibility' ), + __( 'Accessibility', 'pojo-accessibility' ), + 'manage_options', + 'accessibility-settings', + [ &$this, 'display_settings_page' ], + 'dashicons-universal-access-alt' + ); + add_submenu_page( + 'accessibility-settings', + __( 'Accessibility Settings', 'pojo-accessibility' ), + __( 'Settings', 'pojo-accessibility' ), + 'manage_options', + 'accessibility-settings', + [ &$this, 'display_settings_page' ] + ); + add_submenu_page( + 'accessibility-settings', + __( 'Accessibility Toolbar', 'pojo-accessibility' ), + __( 'Toolbar', 'pojo-accessibility' ), + 'manage_options', + 'accessibility-toolbar', + [ &$this, 'display_settings_page' ] + ); + add_submenu_page( + 'accessibility-settings', + __( 'Customize', 'pojo-accessibility' ), + __( 'Customize', 'pojo-accessibility' ), + 'manage_options', + '/customize.php?autofocus[section]=accessibility' + ); + } + + public function plugin_action_links( $links, $plugin_file ) { + if ( EA11Y_BASE === $plugin_file ) { + $settings = '' . __( 'Settings', 'pojo-accessibility' ) . ''; + $toolbar = '' . __( 'Toolbar', 'pojo-accessibility' ) . ''; + $customizer = '' . __( 'Customize', 'pojo-accessibility' ) . ''; + array_unshift( $links, $customizer ); + array_unshift( $links, $toolbar ); + array_unshift( $links, $settings ); + } + + return $links; + } + + private function get_admin_url( $type ) { + switch ( $type ) { + case 'customizer': + return admin_url( 'customize.php?autofocus[section]=accessibility' ); + break; + case 'general': + return admin_url( 'admin.php?page=accessibility-settings' ); + break; + case 'toolbar': + return admin_url( 'admin.php?page=accessibility-toolbar' ); + break; + } + } + + private function get_default_values() { + if ( empty( $this->_defaults ) ) { + if ( empty( $this->_sections ) ) { + $this->get_settings_sections(); + } + $defaults = []; + foreach ( $this->_sections as $section ) { + foreach ( $section['fields'] as $field ) { + $defaults[ $field['id'] ] = isset( $field['std'] ) ? $field['std'] : ''; + } + } + $this->_defaults = $defaults; + } + } + + public function get_default_title_text( $option ) { + $this->get_default_values(); + $default = isset( $this->_defaults[ $option ] ) ? $this->_defaults[ $option ] : ''; + + return get_option( $option, $default ); + } + + public function __construct() { + $this->_page_title = __( 'One Click Accessibility', 'pojo-accessibility' ); + $this->_page_menu_title = __( 'One Click Accessibility', 'pojo-accessibility' ); + $this->_menu_parent = 'themes.php'; + + add_action( 'admin_menu', [ &$this, 'admin_menu' ], 20 ); + add_action( 'admin_init', [ &$this, 'admin_init' ], 20 ); + add_action( 'admin_footer', [ &$this, 'print_js' ] ); + add_filter( 'plugin_action_links_' . EA11Y_BASE, [ $this, 'plugin_action_links' ], 10, 2 ); + } +} diff --git a/modules/legacy/module.php b/modules/legacy/module.php new file mode 100644 index 0000000..3b04414 --- /dev/null +++ b/modules/legacy/module.php @@ -0,0 +1,67 @@ +get_components( 'Customizer' ); + $customizer_fields = $customizer->get_customizer_fields(); + $options = []; + $mods = get_theme_mods(); + foreach ( $customizer_fields as $field ) { + if ( isset( $mods[ $field['id'] ] ) ) { + $options[ $field['id'] ] = $mods[ $field['id'] ]; + } else { + $options[ $field['id'] ] = $field['std']; + } + } + update_option( POJO_A11Y_CUSTOMIZER_OPTIONS, $options ); + } + } + + public function add_elementor_support() { + $this->register_components( [ 'Elementor' ] ); + } + + public static function get_settings() { + /** + * @var Settings $settings + */ + return Plugin::instance()->modules_manager->get_modules( 'Legacy' )->get_component( 'Settings' ); + } + + /** + * Module constructor. + */ + public function __construct() { + $this->register_components(); + add_action( 'admin_init', [ $this, 'backwards_compatibility' ] ); + add_action( 'elementor/init', [ $this, 'add_elementor_support' ] ); + } +} diff --git a/package.json b/package.json index 8caef29..2ed5569 100644 --- a/package.json +++ b/package.json @@ -4,23 +4,41 @@ "homepage": "http://pojo.me/", "description": "", "version": "2.1.0", + "scripts": { + "build": "wp-scripts build", + "start": "wp-scripts start", + "format": "wp-scripts format", + "lint:js": "wp-scripts lint-js", + "prepare": "husky install", + "local:start": "wp-env start", + "local:stop": "wp-env stop", + "local:composer:dev": "wp-env run cli --env-cwd=wp-content/plugins/one-click-accessibility composer install", + "local:composer:build": "wp-env run cli --env-cwd=wp-content/plugins/one-click-accessibility composer install --no-dev", + "local:wp-cli": "wp-env run cli --env-cwd=wp-content/plugins/one-click-accessibility wp", + "local:phpunit": "wp-env run tests-cli --env-cwd=wp-content/plugins/one-click-accessibility ./vendor/bin/phpunit", + "local:phpunit:cov": "wp-env start --xdebug=coverage && wp-env run tests-cli --env-cwd=wp-content/plugins/one-click-accessibility ./vendor/bin/phpunit --coverage-html /var/www/html/wp-content/plugins/one-click-accessibility/coverage" + }, "devDependencies": { - "grunt": "~1.0.2", - "grunt-checktextdomain": "~1.0.1", - "grunt-phpunit": "~0.3.6", - "grunt-wp-readme-to-markdown-with-extra": "~2.2.0", - "grunt-contrib-jshint": "~1.1.0", - "grunt-contrib-watch": "~1.0.1", - "grunt-contrib-uglify": "~3.3.0", - "grunt-contrib-less": "~1.4.1", - "grunt-banner": "~0.6.0", - "grunt-bumpup": "~0.6.3", - "grunt-shell": "~2.1.0", - "grunt-text-replace": "~0.4.0", - "grunt-release": "~0.14.0", - "grunt-contrib-copy": "~1.0.0", - "grunt-contrib-clean": "~1.1.0", - "grunt-wp-deploy": "~1.2.1", - "matchdep": "~2.0.0" + "@eslint/js": "^9.13.0", + "@wordpress/dom-ready": "^4.10.0", + "@wordpress/env": "^10.10.0", + "@wordpress/eslint-plugin": "^21.3.0", + "@wordpress/scripts": "^30.3.0", + "eslint": "^8.57.1", + "eslint-plugin-babel": "^5.3.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.1", + "eslint-plugin-react": "^7.37.1" + }, + "dependencies": { + "@elementor/icons": "^1.17.0", + "@elementor/ui": "^1.21.2", + "@wordpress/api-fetch": "^7.10.0", + "@wordpress/core-data": "^7.10.0", + "@wordpress/data": "^10.10.0", + "@wordpress/date": "^5.10.0", + "@wordpress/element": "^6.10.0", + "@wordpress/i18n": "^5.10.0", + "@wordpress/url": "^4.10.0" } -} \ No newline at end of file +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..24f5e89 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,43 @@ + + + + + + + + + + + + ./tests/phpunit/plugin + + + + + + ./ + + .github + assets + bin + build + docs + node_modules + tests + includes/libraries + libraries + libraries/action-scheduler + vendor + coverage + + + + diff --git a/plugin.php b/plugin.php new file mode 100644 index 0000000..d0f561b --- /dev/null +++ b/plugin.php @@ -0,0 +1,111 @@ +classes_aliases[ $class ] ); + + // Backward Compatibility: Save old class name for set an alias after the new class is loaded + if ( $has_class_alias ) { + $class_alias_name = $this->classes_aliases[ $class ]; + $class_to_load = $class_alias_name; + } else { + $class_to_load = $class; + } + + if ( ! class_exists( $class_to_load ) ) { + $filename = strtolower( + preg_replace( + [ '/^' . __NAMESPACE__ . '\\\/', '/([a-z])([A-Z])/', '/_/', '/\\\/' ], + [ '', '$1-$2', '-', DIRECTORY_SEPARATOR ], + $class_to_load + ) + ); + $filename = EA11Y_PATH . $filename . '.php'; + + if ( is_readable( $filename ) ) { + include $filename; + } + } + + if ( $has_class_alias ) { + class_alias( $class_alias_name, $class ); + } + } + + private function includes() { + require_once EA11Y_PATH . 'includes/manager.php'; + $this->modules_manager = new \EA11y\Manager(); + } + + /** + * Plugin class constructor + * + * Register plugin action hooks and filters + * + * @access public + */ + public function __construct() { + static $autoloader_registered = false; + if ( ! $autoloader_registered ) { + $autoloader_registered = spl_autoload_register( [ $this, 'autoload' ] ); + } + $this->includes(); + } +} +// Instantiate Plugin Class +Plugin::instance(); diff --git a/pojo-accessibility.php b/pojo-accessibility.php index 54f0af1..b41e9e9 100644 --- a/pojo-accessibility.php +++ b/pojo-accessibility.php @@ -9,15 +9,21 @@ Text Domain: pojo-accessibility Domain Path: /languages/ */ -if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly +if ( ! defined( 'ABSPATH' ) ) { + exit; +} // Exit if accessed directly -define( 'POJO_A11Y__FILE__', __FILE__ ); -define( 'POJO_A11Y_BASE', plugin_basename( POJO_A11Y__FILE__ ) ); -define( 'POJO_A11Y_URL', plugins_url( '/', POJO_A11Y__FILE__ ) ); -define( 'POJO_A11Y_ASSETS_PATH', plugin_dir_path( POJO_A11Y__FILE__ ) . 'assets/' ); -define( 'POJO_A11Y_ASSETS_URL', POJO_A11Y_URL . 'assets/' ); +// Legacy define( 'POJO_A11Y_CUSTOMIZER_OPTIONS', 'pojo_a11y_customizer_options' ); +define( 'EA11Y_VERSION', '2.1.0' ); +define( 'EA11Y_MAIN_FILE', __FILE__ ); +define( 'EA11Y_BASE', plugin_basename( EA11Y_MAIN_FILE ) ); +define( 'EA11Y_PATH', plugin_dir_path( __FILE__ ) ); +define( 'EA11Y_URL', plugins_url( '/', __FILE__ ) ); +define( 'EA11Y_ASSETS_PATH', EA11Y_PATH . 'assets/' ); +define( 'EA11Y_ASSETS_URL', EA11Y_URL . 'assets/' ); + final class Pojo_Accessibility { /** @@ -26,38 +32,16 @@ final class Pojo_Accessibility { */ public static $instance = null; - /** - * @var Pojo_A11y_Frontend - */ - public $frontend; - - /** - * @var Pojo_A11y_Customizer - */ - public $customizer; - - /** - * @var Pojo_A11y_Settings - */ - public $settings; - - /** - * @var Pojo_A11y_Admin_UI - */ - public $admin_ui; - - public function load_textdomain() { + public function i18n() { load_plugin_textdomain( 'pojo-accessibility' ); } /** * Throw error on object clone - * * The whole idea of the singleton design pattern is that there is a single * object therefore, we don't want the object to be cloned. - * - * @since 1.0.0 * @return void + * @since 1.0.0 */ public function __clone() { // Cloning instances of the class is forbidden @@ -66,9 +50,8 @@ public function __clone() { /** * Disable unserializing of the class - * - * @since 1.0.0 * @return void + * @since 1.0.0 */ public function __wakeup() { // Unserializing instances of the class is forbidden @@ -82,49 +65,30 @@ public static function instance() { if ( is_null( self::$instance ) ) { self::$instance = new self(); } - return self::$instance; - } - - public function bootstrap() { - require( 'includes/pojo-a11y-frontend.php' ); - require( 'includes/pojo-a11y-customizer.php' ); - require( 'includes/pojo-a11y-settings.php' ); - require( 'includes/pojo-a11y-admin-ui.php' ); - $this->frontend = new Pojo_A11y_Frontend(); - $this->customizer = new Pojo_A11y_Customizer(); - $this->settings = new Pojo_A11y_Settings(); - $this->admin_ui = new Pojo_A11y_Admin_UI(); - } - - public function backwards_compatibility() { - if ( false === get_option( POJO_A11Y_CUSTOMIZER_OPTIONS, false ) ) { - $customizer_fields = $this->customizer->get_customizer_fields(); - $options = array(); - $mods = get_theme_mods(); - foreach ( $customizer_fields as $field ) { - if ( isset( $mods[ $field['id'] ] ) ) { - $options[ $field['id'] ] = $mods[ $field['id'] ]; - } else { - $options[ $field['id'] ] = $field['std']; - } - } - update_option( POJO_A11Y_CUSTOMIZER_OPTIONS, $options ); - } + return self::$instance; } - public function add_elementor_support() { - require( 'includes/pojo-a11y-elementor.php' ); - - new Pojo_A11y_Elementor(); + /** + * Initialize the plugin + * Do your Validations here: + * for example checks for basic plugin requirements, if one check fail don't continue, + * if all check have passed include the plugin class. + * Fired by `plugins_loaded` action hook. + * @since 2.2.0 + * @access public + */ + public function init() { + // Once we get here, We have passed all validation checks, so we can safely include our plugin + require_once 'plugin.php'; } private function __construct() { - add_action( 'init', array( &$this, 'bootstrap' ) ); - add_action( 'admin_init', array( &$this, 'backwards_compatibility' ) ); - add_action( 'plugins_loaded', array( &$this, 'load_textdomain' ) ); + // Load translation + add_action( 'init', [ $this, 'i18n' ] ); - add_action( 'elementor/init', array( $this, 'add_elementor_support' ) ); + // Init Plugin + add_action( 'plugins_loaded', [ $this, 'init' ] ); } } diff --git a/ruleset.xml b/ruleset.xml new file mode 100644 index 0000000..654bb9f --- /dev/null +++ b/ruleset.xml @@ -0,0 +1,54 @@ + + + Elementor Coding Standard + + + + + + vendor/ + tmp/ + build/ + node_modules/ + includes/libraries/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/bootstrap.php b/tests/bootstrap.php old mode 100755 new mode 100644 index 2b9ce1b..b239294 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,32 +1,40 @@ array( PLUGIN_PATH ), -); +if ( ! file_exists( $_tests_dir . '/includes/functions.php' ) ) { + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- CLI + throw new Exception( "Could not find $_tests_dir/includes/functions.php, have you run bin/install-wp-tests.sh?" ); // NOSONAR +} +// Give access to tests_add_filter() function. +/** @psalm-suppress UnresolvableInclude */ require_once $_tests_dir . '/includes/functions.php'; -tests_add_filter( - 'muplugins_loaded', - function() { - // Manually load plugin - require dirname( __DIR__ ) . '/' . PLUGIN_FILE; - } -); +function _manually_load_plugin(): void { + require dirname( __DIR__ ) . '/pojo-accessibility.php'; +} + +tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); + +// Start up the WP testing environment. +/** @psalm-suppress UnresolvableInclude */ +require $_tests_dir . '/includes/bootstrap.php'; -// Removes all sql tables on shutdown -// Do this action last -tests_add_filter( 'shutdown', 'drop_tables', 999999 ); +// @phpcs:disable -require $_tests_dir . '/includes/bootstrap.php'; \ No newline at end of file +//module tests +include_once dirname( __DIR__ ) . '/tests/phpunit/helpers/module-test-base.php'; +//rest tests +include_once dirname( __DIR__ ) . '/tests/phpunit/helpers/route-test.php'; +//test base with factories +include_once dirname( __DIR__ ) . '/tests/phpunit/helpers/test-base.php'; +// functions +include_once dirname( __DIR__ ) . '/tests/phpunit/helpers/functions.php'; +// phpcs:enable diff --git a/tests/phpunit/helpers/functions.php b/tests/phpunit/helpers/functions.php new file mode 100644 index 0000000..979173d --- /dev/null +++ b/tests/phpunit/helpers/functions.php @@ -0,0 +1,8 @@ +module; + + + if ( ! method_exists( $module, 'routes_list' ) || empty( $module::routes_list() ) ) { + $this->assertTrue( true, 'No routes to test' ); + return; + } + $routes = $module::routes_list(); + + $this->assertTrue( is_array( $routes ), + 'Test that the routes are returned as an array' + ); + + $this->assertCount( count( $this->routes ), $routes, + 'Test that the number of routes are as expected' + ); + + foreach ( $this->routes as $route ) { + $this->assertContains( $route, $routes, + 'Test that Route "' . $route . '" is registered' + ); + } + } + + public function test_components() { + /** + * @var Module_Base $module + */ + $module = $this->module; + if ( method_exists( $module, 'component_list' ) === false || empty( $module::component_list() ) ) { + $this->assertTrue( true, 'No components to test' ); + return; + } + $components = $module::component_list(); + + $this->assertTrue( is_array( $components ), + 'Test that the components are returned as an array' + ); + + $this->assertCount( count( $this->components ), $components, + 'Test that the number of components are as expected' + ); + + foreach ( $this->components as $component ) { + $this->assertContains( $component, $components, + 'Test that component "' . $component . '" is registered' + ); + } + } + + /** + * test_constructor + */ + public function test_constructor() { + $this->test_components(); + $this->test_routes(); + } +} diff --git a/tests/phpunit/helpers/route-test.php b/tests/phpunit/helpers/route-test.php new file mode 100644 index 0000000..446ce0f --- /dev/null +++ b/tests/phpunit/helpers/route-test.php @@ -0,0 +1,31 @@ +route with an actual instance of the route class we are testing + $this->route = $this->get_plugin_module( $this->module_name )->routes[ $this->class_name ]; + + /** + * this includes automated unit tests for: + * - Test that the route is registered + * - Test route name is correct + * - Test allowed methods are correct + * - Test Endpoint is configured correctly + */ + } +} diff --git a/tests/phpunit/helpers/test-base.php b/tests/phpunit/helpers/test-base.php new file mode 100644 index 0000000..a9d165c --- /dev/null +++ b/tests/phpunit/helpers/test-base.php @@ -0,0 +1,40 @@ +subscriber = $this->factory->user->create( [ 'role' => 'subscriber' ] ); + $this->editor = $this->factory->user->create( [ 'role' => 'editor' ] ); + $this->administrator = $this->factory->user->create( [ 'role' => 'administrator' ] ); + + // Factories + $this->factory->log = new \WP_UnitTest_Factory_For_Log( $this->factory ); + } + + + /** + * tearDown + */ + public function tearDown() : void { + parent::tearDown(); + } + + public function get_plugin() { + return \EA11y\Plugin::instance(); + } + + public function get_plugin_module( $name ) { + /** + * @var Module_Base $module + */ + $module = '\EA11y\\Modules\\' . $name . '\Module'; + return $module::instance(); + } +} diff --git a/tests/phpunit/plugin/modules/legacy/module-test.php b/tests/phpunit/plugin/modules/legacy/module-test.php new file mode 100644 index 0000000..4f5af7a --- /dev/null +++ b/tests/phpunit/plugin/modules/legacy/module-test.php @@ -0,0 +1,20 @@ +assertTrue( is_plugin_active( PLUGIN_PATH ) ); - } - - public function test_getInstance() { - $this->assertInstanceOf( 'Pojo_Accessibility', Pojo_Accessibility::instance() ); - } - -} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..7216c1c --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,20 @@ +const path = require( 'path' ); +const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); + +// add your entry points here +const entryPoints = { + // admin: path.resolve( + // process.cwd(), + // 'assets/dev/js', + // 'admin.js', + // ), +}; + +module.exports = { + ...defaultConfig, + entry: entryPoints, + output: { + ...defaultConfig.output, + path: path.resolve( process.cwd(), 'assets/build' ), + }, +};