diff --git a/.github/actions/ci-setup/action.yml b/.github/actions/ci-setup/action.yml new file mode 100644 index 0000000..c6acb68 --- /dev/null +++ b/.github/actions/ci-setup/action.yml @@ -0,0 +1,70 @@ +name: CI Setup +description: "Sets up the environment for jobs during CI workflow" +# https://alejandrocelaya.blog/2022/08/19/how-to-reduce-duplication-in-your-github-actions-workflows/ + +inputs: + extensions: + default: 'curl, mysql, zip' + description: 'shivammathur/setup-php extensions' + required: false + extensions-cache-key: + description: 'The key used to cache PHP extensions' + required: true + php-version: + description: 'The PHP version to be setup' + required: true + token: + description: 'A GitHub PAT' + required: true + tools: + default: 'composer' + description: 'shivammathur/setup-php tools' + required: false +outputs: + files: + description: 'All changed files' + value: ${{ steps.files.outputs.all }} + +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup cache extensions + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ inputs.php-version }} + extensions: ${{ inputs.extensions }} + key: ${{ inputs.extensions-cache-key }} + + - name: Cache extensions + uses: actions/cache@v3 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ steps.extcache.outputs.key }} + restore-keys: ${{ steps.extcache.outputs.key }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ inputs.php-version }} + coverage: xdebug + extensions: ${{ inputs.extensions }} + tools: ${{ inputs.tools }} + + - name: Composer config + run: | + composer config github-oauth.github.com "${{ env.GITHUB_TOKEN }}" + env: + GITHUB_TOKEN: ${{ inputs.token }} + shell: bash + + - name: Composer update + run: composer update --no-interaction --optimize-autoloader + shell: bash + + - name: Get Changed Files + id: files + uses: masesgroup/retrieve-changed-files@v2 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 36c7895..5fe6f8e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Workflow +name: CI Tests on: push: @@ -6,6 +6,9 @@ on: - develop pull_request: +env: + WP_VERSION: 6.1.1 + # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: # The concurrency group contains the workflow name and the branch name. @@ -13,51 +16,125 @@ concurrency: cancel-in-progress: true jobs: - Build: + run-phpcs: + name: Run PHPCS runs-on: ubuntu-latest + if: ${{ github.event_name == 'pull_request' }} strategy: matrix: - php-versions: [ 7.4, 8.0, 8.1 ] - coverage: [ true ] - + php-version: [ "8.0", "8.1", "8.2" ] steps: - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 # No shallow clone, we need all history! + uses: actions/checkout@v3 - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - name: Setup + id: ci-setup + uses: ./.github/actions/ci-setup with: - php-version: ${{ matrix.php-versions }} - coverage: xdebug - tools: composer, cs2pr, phpunit + extensions: 'curl' + extensions-cache-key: run-phpcs-${{ matrix.php-version }} + php-version: ${{ matrix.php-version }} + token: ${{ secrets.GITHUB_TOKEN }} + tools: 'composer, cs2pr, phpcs' - - name: Get composer cache directory - id: composercache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: Run PHPCS + continue-on-error: false + id: phpcs + run: composer phpcs + env: + CHANGED_FILES: ${{ steps.ci-setup.outputs.files }} + PHP_VERSION: ${{ matrix.php-version }} - - name: Cache dependencies - uses: actions/cache@v2 + - name: Show PHPCS results in PR + if: ${{ always() && steps.phpcs.outcome == 'failure' }} + run: cs2pr ./phpcs-report.xml + + run-phpmd: + name: Run PHPMD + runs-on: ubuntu-latest + if: ${{ github.event_name == 'pull_request' }} + strategy: + matrix: + php-version: [ "8.0", "8.1", "8.2" ] + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup + id: ci-setup + uses: ./.github/actions/ci-setup with: - path: ${{ steps.composercache.outputs.dir }} - key: php-${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- + extensions: 'curl' + extensions-cache-key: run-phpunit-${{ matrix.php-version }} + php-version: ${{ matrix.php-version }} + token: ${{ secrets.GITHUB_TOKEN }} + tools: 'composer, phpmd' + + - name: Run PHPMD + continue-on-error: true + id: phpmd + run: composer phpmd + env: + CHANGED_FILES: ${{ steps.ci-setup.outputs.files }} - - name: Install dependencies - run: composer update --prefer-dist --no-interaction + run-phpunit: + name: Run PHPUnit + runs-on: ubuntu-latest + if: ${{ github.event_name == 'pull_request' }} + strategy: + matrix: + php-version: [ "8.0", "8.1", "8.2" ] + coverage: [ true ] + steps: + - name: Checkout + uses: actions/checkout@v3 - - name: Create all branches - run: source ./bin/create-all-branches.sh + - name: Setup + id: ci-setup + uses: ./.github/actions/ci-setup + with: + extensions: 'curl, mysql, mysqli, tar, zip' + extensions-cache-key: run-phpunit-${{ matrix.php-version }} + php-version: ${{ matrix.php-version }} + token: ${{ secrets.GITHUB_TOKEN }} + tools: 'composer, phpunit' - - name: Run composer tests + - name: Run PHPUnit continue-on-error: false - run: composer tests - - - name: Show PHPCS results in PR - run: cs2pr ./phpcs-report.xml + id: phpunit + run: composer phpunit + env: + CHANGED_FILES: ${{ steps.ci-setup.outputs.files }} - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: fail_ci_if_error: false + + run-phpstan: + name: Run PHPStan + runs-on: ubuntu-latest + if: ${{ github.event_name == 'pull_request' }} + strategy: + matrix: + php-version: [ "8.0", "8.1", "8.2" ] + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup + id: ci-setup + uses: ./.github/actions/ci-setup + with: + extensions: 'curl' + extensions-cache-key: run-phpunit-${{ matrix.php-version }} + php-version: ${{ matrix.php-version }} + token: ${{ secrets.GITHUB_TOKEN }} + tools: 'composer, phpstan' + + - name: Run PHPStan + continue-on-error: true + id: phpstan + run: composer phpstan + env: + CHANGED_FILES: ${{ steps.ci-setup.outputs.files }} diff --git a/README.md b/README.md index 916ca56..46f5cb0 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,17 @@ A library containing my standard development resources to build high quality Wor ### Requirements ``` -PHP >= 7.4 OR >= 8.0 -WordPress >= 5.6 +PHP >= 8.0 +WordPress >= 6.0 ``` -| PHP version | WordPress Utilities | -| ----------- | ------------------- | -| < 7.1 | 1.3.x | -| \>= 7.1 && < 7.3 | 1.9.x | -| 7.3 | 2.0.0 | -| \>= 7.4 | 2.1.0 | +| PHP version | WP Utilities | +|------------------|--------------| +| < 7.1 | 1.3.x | +| \>= 7.1 && < 7.3 | 1.9.x | +| 7.3 | 2.0.0 | +| \>= 7.4 | 2.1.0 | +| \>= 8.0 | 3.0 | The required WordPress version will always be the most recent point release of the previous major release branch. @@ -38,7 +39,7 @@ compatibility is entirely coincidental. To install this library, use Composer: ``` -composer require thefrosty/wp-utilities:^2.7 +composer require thefrosty/wp-utilities:^3.0 ``` Then follow examples in the [Plugin README](./src/Plugin/README.md) diff --git a/bin/create-all-branches.sh b/bin/create-all-branches.sh index 16deba5..f8189a6 100644 --- a/bin/create-all-branches.sh +++ b/bin/create-all-branches.sh @@ -4,26 +4,30 @@ set -e # https://stackoverflow.com/a/44036486/558561 function create_all_branches() { - # Keep track of where Travis put us. - # We are on a detached head, and we need to be able to go back to it. - local build_head - build_head=$(git rev-parse HEAD) + # Keep track of where Travis put us. + # We are on a detached head, and we need to be able to go back to it. + local build_head + build_head=$(git rev-parse HEAD) - # Fetch all the remote branches. Travis clones with `--depth`, which - # implies `--single-branch`, so we need to overwrite remote.origin.fetch to - # do that. - git config --replace-all remote.origin.fetch +refs/heads/*:refs/remotes/origin/* - git fetch - # optionally, we can also fetch the tags - git fetch --tags + # Fetch all the remote branches. Travis clones with `--depth`, which + # implies `--single-branch`, so we need to overwrite remote.origin.fetch to + # do that. + git config --replace-all remote.origin.fetch +refs/heads/*:refs/remotes/origin/* + git fetch --prune + # optionally, we can also fetch the tags, pass `true` to the function call. + if [[ $# -eq 1 ]]; then + git fetch --tags + fi - # create the tacking branches - for branch in $(git branch -r | grep -v HEAD); do - git checkout -qf "${branch#origin/}" - done + # create the tacking branches + for branch in $(git branch -r | grep -v HEAD); do + git checkout -qf "${branch#origin/}" + done - # finally, go back to where we were at the beginning - git checkout "${build_head}" + # finally, go back to where we were at the beginning + git checkout "${build_head}" } -create_all_branches +if [[ -n "$CI" ]]; then + create_all_branches "$@" +fi; diff --git a/bin/eslint.sh b/bin/eslint.sh index a13659a..b83559b 100755 --- a/bin/eslint.sh +++ b/bin/eslint.sh @@ -2,8 +2,8 @@ set -e -echo 'Checking ESLint' source "$(dirname "$0")/functions.sh" +echo 'Checking ESLint' jsFiles="" jsFilesCount=0 @@ -21,6 +21,8 @@ if [[ ${jsFilesCount} == 0 ]]; then exit 0 fi +jsFiles=$(echo "${jsFiles}" | xargs) echo "Checking files: $jsFiles" -npx standard "${jsFiles}" +# shellcheck disable=SC2086 +npx standard ${jsFiles} diff --git a/bin/functions.sh b/bin/functions.sh index 5ae7dae..2a6a86b 100644 --- a/bin/functions.sh +++ b/bin/functions.sh @@ -5,40 +5,89 @@ set -e # Default values of arguments # https://stackoverflow.com/a/44750379/558561 -- get the default git branch name DEFAULT_BRANCH=$(git remote show $(git remote) | sed -n '/HEAD branch/s/.*: //p') -TEST_VERSION="7.4" OTHER_ARGUMENTS=() +PHP_VERSION=${PHP_VERSION:-"8.0"} # Loop through arguments and process them # @ref https://pretzelhands.com/posts/command-line-flags/ -for arg in "$@"; do - case $arg in - --default-branch=*) - DEFAULT_BRANCH="${arg#*=}" - shift # Remove --default-branch= from processing - ;; - --test-version=*) - TEST_VERSION="${arg#*=}" - shift # Remove --test-version= from processing - ;; - *) - OTHER_ARGUMENTS+=("$1") - shift # Remove generic argument from processing - ;; - esac -done +function get_arguments() { + for arg in "$@"; do + case $arg in + --default-branch=*) + DEFAULT_BRANCH="${arg#*=}" + shift # Remove --default-branch= from processing + ;; + --test-version=*) + TEST_VERSION="${arg#*=}" + shift # Remove --test-version= from processing + ;; + *) + OTHER_ARGUMENTS+=("$1") + shift # Remove generic argument from processing + ;; + esac + done +} -# Based off: https://gist.github.com/Hounddog/3891872 -if [[ $(git rev-parse --verify HEAD) ]]; then - against='HEAD' -elif [[ $(git rev-parse --verify develop) ]]; then - against='develop' -elif [[ $(git rev-parse --verify main) ]]; then - against='main' -elif [[ $(git rev-parse --verify master) ]]; then - against='master' +# Composer 2.2.x https://getcomposer.org/doc/articles/vendor-binaries.md#finding-the-composer-bin-dir-from-a-binary +if [[ -z "$COMPOSER_BIN_DIR" ]]; then + BIN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" else - echo "git can't verify HEAD, develop, master, or main." - exit 1 + BIN_DIR="$COMPOSER_BIN_DIR" +fi + +export BIN_DIR + +function get_branch() { + echo "${GITHUB_BASE_REF:=develop}" +} + +# Based off: https://gist.github.com/Hounddog/3891872 +# Branch to check current commit against +function get_commit_against() { + if [[ $(git rev-parse --verify HEAD) ]]; then + echo 'HEAD' + elif [[ $(git rev-parse --verify develop) ]]; then + echo 'develop' + elif [[ $(git rev-parse --verify main) ]]; then + echo 'main' + elif [[ $(git rev-parse --verify master) ]]; then + echo 'master' + elif [[ $(git rev-parse --verify "$1") ]]; then + echo "$1" + else + echo "git can't verify HEAD, develop, main, or master." + exit 1 + fi +} + +# Helper function to call a bash file with arguments +# $1: The file to call +# $2: The first arguments to pass to the file +# $3: The second arguments to pass to the file +function source_bin_file() { + if [[ ! "${1+x}" ]]; then + echo "Error: missing file" && exit 1 + fi + + FILE=./vendor/bin/"$1" + if [[ -f "$FILE" ]]; then + # shellcheck disable=SC2086 + "$FILE" "${@:2}" + else + # shellcheck disable=SC2086 + "$BIN_DIR"/"$1" "${@:2}" + fi +} + +against=$(get_commit_against "$@") +commit=$(get_branch) +echo "git merge-base commit: ${commit} against: ${against}" +if [[ -z ${CHANGED_FILES+x} ]]; then + #commitFiles=$(git diff --name-only "$(git merge-base "${commit}" "${against}")") + commitFiles=$(git diff --name-only "$(git merge-base ${DEFAULT_BRANCH:-develop} ${against})") + else + commitFiles="${CHANGED_FILES}" fi -commitFiles=$(git diff --name-only "$(git merge-base ${DEFAULT_BRANCH:-develop} ${against})") +export commitFiles diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh index 67db54f..878881f 100644 --- a/bin/install-wp-tests.sh +++ b/bin/install-wp-tests.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash if [ $# -lt 3 ]; then - echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" - exit 1 + echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" + exit 1 fi DB_NAME=$1 @@ -18,159 +18,133 @@ WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/} download() { - if [ $(which curl) ]; then - curl -s "$1" >"$2" - elif [ $(which wget) ]; then - wget -nv -O "$2" "$1" - fi + if [ `which curl` ]; then + curl -s "$1" > "$2"; + elif [ `which wget` ]; then + wget -nv -O "$2" "$1" + fi } -if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then - WP_BRANCH=${WP_VERSION%\-*} - WP_TESTS_TAG="branches/$WP_BRANCH" - -elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then - WP_TESTS_TAG="branches/$WP_VERSION" +if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then + WP_TESTS_TAG="branches/$WP_VERSION" elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then - if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then - # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x - WP_TESTS_TAG="tags/${WP_VERSION%??}" - else - WP_TESTS_TAG="tags/$WP_VERSION" - fi + if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then + # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x + WP_TESTS_TAG="tags/${WP_VERSION%??}" + else + WP_TESTS_TAG="tags/$WP_VERSION" + fi elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then - WP_TESTS_TAG="trunk" + WP_TESTS_TAG="trunk" else - # http serves a single offer, whereas https serves multiple. we only want one - download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json - grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json - LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') - if [[ -z "$LATEST_VERSION" ]]; then - echo "Latest WordPress version could not be found" - exit 1 - fi - WP_TESTS_TAG="tags/$LATEST_VERSION" + # http serves a single offer, whereas https serves multiple. we only want one + download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json + grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json + LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') + if [[ -z "$LATEST_VERSION" ]]; then + echo "Latest WordPress version could not be found" + exit 1 + fi + WP_TESTS_TAG="tags/$LATEST_VERSION" fi + set -ex install_wp() { - if [ -d $WP_CORE_DIR ]; then - return - fi - - mkdir -p $WP_CORE_DIR - - if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then - mkdir -p $TMPDIR/wordpress-nightly - download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip - unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/ - mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR - else - if [ $WP_VERSION == 'latest' ]; then - local ARCHIVE_NAME='latest' - elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then - # https serves multiple offers, whereas http serves single. - download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json - if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then - # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x - LATEST_VERSION=${WP_VERSION%??} - else - # otherwise, scan the releases and get the most up to date minor version of the major release - local VERSION_ESCAPED=$(echo $WP_VERSION | sed 's/\./\\\\./g') - LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) - fi - if [[ -z "$LATEST_VERSION" ]]; then - local ARCHIVE_NAME="wordpress-$WP_VERSION" - else - local ARCHIVE_NAME="wordpress-$LATEST_VERSION" - fi - else - local ARCHIVE_NAME="wordpress-$WP_VERSION" - fi - download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz - tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR - fi - - download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php + if [ -d $WP_CORE_DIR ]; then + return; + fi + + mkdir -p $WP_CORE_DIR + + if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + mkdir -p $TMPDIR/wordpress-nightly + download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip + unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/ + mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR + else + if [ $WP_VERSION == 'latest' ]; then + local ARCHIVE_NAME='latest' + elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then + # https serves multiple offers, whereas http serves single. + download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json + if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then + # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x + LATEST_VERSION=${WP_VERSION%??} + else + # otherwise, scan the releases and get the most up to date minor version of the major release + local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` + LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) + fi + if [[ -z "$LATEST_VERSION" ]]; then + local ARCHIVE_NAME="wordpress-$WP_VERSION" + else + local ARCHIVE_NAME="wordpress-$LATEST_VERSION" + fi + else + local ARCHIVE_NAME="wordpress-$WP_VERSION" + fi + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz + tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR + fi + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php } install_test_suite() { - # portable in-place argument for both GNU sed and Mac OSX sed - if [[ $(uname -s) == 'Darwin' ]]; then - local ioption='-i.bak' - else - local ioption='-i' - fi - - # set up testing suite if it doesn't yet exist - if [ ! -d $WP_TESTS_DIR ]; then - # set up testing suite - mkdir -p $WP_TESTS_DIR - svn co --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes - svn co --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data - fi - - if [ ! -f wp-tests-config.php ]; then - download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php - # remove all forward slashes in the end - WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") - sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s:__DIR__ . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php - fi + # portable in-place argument for both GNU sed and Mac OSX sed + if [[ $(uname -s) == 'Darwin' ]]; then + local ioption='-i .bak' + else + local ioption='-i' + fi + + # set up testing suite if it doesn't yet exist + if [ ! -d $WP_TESTS_DIR ]; then + # set up testing suite + mkdir -p $WP_TESTS_DIR + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data + fi + + if [ ! -f wp-tests-config.php ]; then + download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php + # remove all forward slashes in the end + WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") + sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php + fi } -recreate_db() { - shopt -s nocasematch - if [[ $1 =~ ^(y|yes)$ ]]; then - mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA - create_db - echo "Recreated the database ($DB_NAME)." - else - echo "Leaving the existing database ($DB_NAME) in place." - fi - shopt -u nocasematch -} - -create_db() { - mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA -} - install_db() { - if [ ${SKIP_DB_CREATE} = "true" ]; then - return 0 - fi - - # parse DB_HOST for port or socket references - local PARTS=(${DB_HOST//\:/ }) - local DB_HOSTNAME=${PARTS[0]} - local DB_SOCK_OR_PORT=${PARTS[1]} - local EXTRA="" - - if ! [ -z $DB_HOSTNAME ]; then - if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then - EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" - elif ! [ -z $DB_SOCK_OR_PORT ]; then - EXTRA=" --socket=$DB_SOCK_OR_PORT" - elif ! [ -z $DB_HOSTNAME ]; then - EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" - fi - fi - - # create database - if [ $(mysql --user="$DB_USER" --password="$DB_PASS" --execute='show databases;' | grep ^$DB_NAME$) ]; then -# echo "Reinstalling will delete the existing test database ($DB_NAME)" -# read -p 'Are you sure you want to proceed? [y/N]: ' DELETE_EXISTING_DB - recreate_db "yes" - else - create_db - fi + if [ ${SKIP_DB_CREATE} = "true" ]; then + return 0 + fi + + # parse DB_HOST for port or socket references + local PARTS=(${DB_HOST//\:/ }) + local DB_HOSTNAME=${PARTS[0]}; + local DB_SOCK_OR_PORT=${PARTS[1]}; + local EXTRA="" + + if ! [ -z $DB_HOSTNAME ] ; then + if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then + EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" + elif ! [ -z $DB_SOCK_OR_PORT ] ; then + EXTRA=" --socket=$DB_SOCK_OR_PORT" + elif ! [ -z $DB_HOSTNAME ] ; then + EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" + fi + fi + + # create database + mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA } install_wp diff --git a/bin/phpcs.sh b/bin/phpcs.sh index c0abc97..ada6d62 100644 --- a/bin/phpcs.sh +++ b/bin/phpcs.sh @@ -2,10 +2,10 @@ set -e -echo 'Checking PHPCS' source "$(dirname "$0")/functions.sh" +echo 'Checking PHPCS' -args="-s --colors --extensions=php --tab-width=4 --standard=phpcs-ruleset.xml --runtime-set testVersion ${TEST_VERSION}-" +args="--runtime-set testVersion ${PHP_VERSION}-" phpFiles="" phpFilesCount=0 for f in ${commitFiles}; do @@ -22,10 +22,9 @@ if [[ ${phpFilesCount} == 0 ]]; then exit 0 fi +phpFiles=$(echo "${phpFiles}" | xargs) echo "Checking files: $phpFiles" +echo "Args: $args" -if [[ ${phpFilesCount} -gt 2 ]] && { [[ ${GITHUB_ACTIONS+x} ]] || [[ ${CIRCLECI+x} ]]; }; then - args="$args --report=summary" -fi - -./vendor/bin/phpcs ${args} ${phpFiles} +# shellcheck disable=SC2086 +source_bin_file phpcs ${args} ${phpFiles} --report-full --report-checkstyle=./phpcs-report.xml diff --git a/bin/phpmd.sh b/bin/phpmd.sh index 32a9ecd..c38e357 100644 --- a/bin/phpmd.sh +++ b/bin/phpmd.sh @@ -2,8 +2,8 @@ set -e -echo 'Checking PHPMD' source "$(dirname "$0")/functions.sh" +echo 'Checking PHPMD' args="text phpmd-ruleset.xml --exclude tests,vendor --suffixes php" phpFiles="" @@ -22,8 +22,10 @@ if [[ ${phpFilesCount} == 0 ]]; then exit 0 fi +phpFiles=$(echo "${phpFiles:1}" | xargs) echo "Checking files: $phpFiles" +# shellcheck disable=SC2086 for file in ${phpFiles}; do - ./vendor/bin/phpmd ${file} ${args} + source_bin_file phpmd ${file} "${args}" done diff --git a/composer.json b/composer.json index dc998a3..fe96a6b 100644 --- a/composer.json +++ b/composer.json @@ -17,14 +17,14 @@ }, "optimize-autoloader": true, "platform": { - "php": "7.4" + "php": "8.0" }, "sort-packages": true }, "minimum-stability": "dev", "prefer-stable": true, "require": { - "php": "^7.4 || ^8.0", + "php": "^8.0", "ext-json": "*", "ext-openssl": "*", "johnbillion/args": "^1.1", @@ -32,20 +32,21 @@ }, "require-dev": { "ext-simplexml": "*", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", "phpcompatibility/php-compatibility": "^9.3", - "phpmd/phpmd": "^2.6", + "phpmd/phpmd": "^2.13", "phpunit/php-code-coverage": "^9.2", "phpunit/phpunit": "^9", "pimple/pimple": "^3.5", - "roots/wordpress": "^6.0.0", - "squizlabs/php_codesniffer": "^3.2", + "roots/wordpress": "^6.0", + "slevomat/coding-standard": "^8.8", + "squizlabs/php_codesniffer": "^3.7", "szepeviktor/phpstan-wordpress": "^1.0", - "wp-phpunit/wp-phpunit": "^6.0.0", + "wp-phpunit/wp-phpunit": "^6.0", + "wp-coding-standards/wpcs": "dev-develop", "yoast/phpunit-polyfills": "^1.0.2" }, "suggest": { - "pimple/pimple": "", "symfony/http-foundation": "" }, "autoload": { @@ -72,7 +73,7 @@ "./vendor/bin/phpunit --colors --coverage-html ./tests/results && php ./tests/clover-results.php ./tests/clover.xml 2" ], "phpstan": [ - "./vendor/bin/phpstan analyze" + "./vendor/bin/phpstan analyze src" ], "tests": [ "@phpcs", diff --git a/phpcs-ruleset.xml b/phpcs.xml similarity index 93% rename from phpcs-ruleset.xml rename to phpcs.xml index 6e2132b..6594af0 100644 --- a/phpcs-ruleset.xml +++ b/phpcs.xml @@ -23,7 +23,7 @@ - + @@ -37,5 +37,5 @@ - + diff --git a/phpunit.xml b/phpunit.xml index aa70677..382e2c5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -20,7 +20,7 @@ - ./tests/unit + ./tests/unit diff --git a/src/Api/GitHub/Updater.php b/src/Api/GitHub/Updater.php index 6dfe0c6..e37e69b 100644 --- a/src/Api/GitHub/Updater.php +++ b/src/Api/GitHub/Updater.php @@ -47,7 +47,7 @@ class Updater implements WpHooksInterface private array $missing_config; /** - * GitHub response data. + * GitHub's response data. * @var array $github_data Temp data fetched from GitHub, * allows us to only load the data once per class instance */ @@ -227,6 +227,9 @@ protected function apiCheck($value) */ protected function getPluginInfo($result, string $action, $args) { + if (!\is_object($args)) { + return $result; + } // Check if this call API is for the right plugin if (empty($args->slug) || $args->slug !== $this->config['slug']) { return $result; diff --git a/src/Models/BaseModel.php b/src/Models/BaseModel.php index e4f282c..a4c5b9b 100644 --- a/src/Models/BaseModel.php +++ b/src/Models/BaseModel.php @@ -3,10 +3,15 @@ namespace TheFrosty\WpUtilities\Models; use Exception; +use function date_create; +use function in_array; +use function is_array; +use function is_null; +use function is_object; +use function method_exists; /** * Class BaseModel - * * @package TheFrosty\WpUtilities\Models */ abstract class BaseModel @@ -14,11 +19,13 @@ abstract class BaseModel /** * BaseModel constructor. - * - * @param array $fields + * @param array|null $fields */ - public function __construct(array $fields) + public function __construct(?array $fields = null) { + if (!is_array($fields)) { + return; + } $this->populate($fields); } @@ -33,12 +40,10 @@ public function getCustomDelimiters(): array } /** - * Optional method to get a model as an array - * Default implementation is to engage fields listed in getSerializableFields() - * - * You should implement customized toArray() if you are using - * logic different from described in getSerializableFields() in child classes - * + * Optional method to get a model as an array. + * Default implementation is to engage fields listed in getSerializableFields(). + * You should implement customized toArray() if you are using logic different from described in + * getSerializableFields() in child classes. * @return array * @throws Exception */ @@ -47,23 +52,32 @@ public function toArray(): array if (!empty($this->getSerializableFields())) { $result = []; - foreach ($this->getSerializableFields() as $index => $field_name) { + foreach ($this->getSerializableFields() as $field_name) { $method = $this->getGetMethod($field_name); - if (!\method_exists($this, $method)) { + if (!method_exists($this, $method)) { continue; } $value = $this->$method(); - if (\is_object($value) && \method_exists($value, 'toArray')) { + if (is_object($value) && method_exists($value, 'toArray')) { $result[$field_name] = $value->toArray(); continue; } + if ( + (is_array($value) && !empty($value[0])) && + (is_object($value[0]) && method_exists($value[0], 'toArray')) + ) { + $result[$field_name] = $this->toArrayDeep($value); + continue; + } $result[$field_name] = $value; } return $result; } - throw new Exception('If you are going to use toArray() in your model you have - to implement custom logic or return a list of fields in getSerializableFields().'); + throw new Exception( + 'If you are going to use toArray() in your model you have + to implement custom logic or return a list of fields in getSerializableFields().' + ); } /** @@ -75,7 +89,6 @@ public function toArray(): array * script functionality. * * @param BaseModel[] $models - * * @return array * @throws Exception */ @@ -90,8 +103,7 @@ public function toArrayDeep(array $models): array } /** - * Get datetime fields - * + * Get datetime fields. * @return array */ protected function getDateTimeFields(): array @@ -101,9 +113,7 @@ protected function getDateTimeFields(): array /** * Get the fields to be used in toArray() - * Field names should be in camelCase (ex. propertyName) - * so that getPropertyName could easily be called - * + * Field names should be in camelCase (ex. propertyName) so that getPropertyName could easily be called * @return array */ protected function getSerializableFields(): array @@ -112,16 +122,14 @@ protected function getSerializableFields(): array } /** - * Populate model - * + * Populate model. * @param array $fields - * @return void */ protected function populate(array $fields): void { foreach ($fields as $field => $value) { // If field value is null we just leave it blank - if (\is_null($value)) { + if (is_null($value)) { continue; } @@ -129,13 +137,13 @@ protected function populate(array $fields): void $populate_method = $this->getPopulateMethod($field); // First try to proceed with custom population logic - if (\method_exists($this, $populate_method)) { + if (method_exists($this, $populate_method)) { $this->$populate_method($value); // If no custom logic found proceed with regular setters - } elseif (\method_exists($this, $setter_method)) { + } elseif (method_exists($this, $setter_method)) { // Should we convert it to datetime? - if (\in_array($field, $this->getDateTimeFields(), true)) { - $value = \date_create($value); + if (in_array($field, $this->getDateTimeFields(), true)) { + $value = date_create($value); } $this->$setter_method($value); } @@ -143,33 +151,33 @@ protected function populate(array $fields): void } /** - * Gets the 'set' method. + * Gets the 'get' method. * @param string $field * @return string */ - private function getSetterMethod(string $field): string + protected function getGetMethod(string $field): string { - return $this->getMethod('set', $field); + return $this->getMethod('get', $field); } /** - * Gets the 'get' method. + * Gets the 'populate' method. * @param string $field * @return string */ - private function getGetMethod(string $field): string + private function getPopulateMethod(string $field): string { - return $this->getMethod('get', $field); + return $this->getMethod('populate', $field); } /** - * Gets the 'populate' method. + * Gets the 'set' method. * @param string $field * @return string */ - private function getPopulateMethod(string $field): string + private function getSetterMethod(string $field): string { - return $this->getMethod('populate', $field); + return $this->getMethod('set', $field); } /** @@ -183,6 +191,7 @@ private function getMethod(string $prefix, string $field): string $search = \array_merge(['_', '-'], $this->getCustomDelimiters()); $delimiters = '_-'; $delimiters .= !empty($this->getCustomDelimiters()) ? \implode('', $this->getCustomDelimiters()) : ''; + return $prefix . \str_replace($search, '', \ucwords($field, $delimiters)); } } diff --git a/views/dashboard-widget.php b/views/dashboard-widget.php index 16f02ef..3882c55 100644 --- a/views/dashboard-widget.php +++ b/views/dashboard-widget.php @@ -14,15 +14,10 @@ $div_open = '
    '; $div_close = '
'; echo $div_open; -switch ($this->getWidget()->getType()) { - case Widget::TYPE_RSS: - $template = __DIR__ . 'dashboard-widget/rss.php'; - break; - case Widget::TYPE_REST: - default: - $template = __DIR__ . '/dashboard-widget/rest.php'; - break; -} +$template = match ($this->getWidget()->getType()) { + Widget::TYPE_RSS => __DIR__ . 'dashboard-widget/rss.php', + default => __DIR__ . '/dashboard-widget/rest.php', +}; include $template; echo $div_close;