diff --git a/.forgejo/workflows/demo.yml b/.forgejo/workflows/demo.yml deleted file mode 100755 index 6010460..0000000 --- a/.forgejo/workflows/demo.yml +++ /dev/null @@ -1,6 +0,0 @@ -on: [push] -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo All Good diff --git a/.forgejo/workflows/phpcs.yml b/.forgejo/workflows/phpcs.yml new file mode 100755 index 0000000..4db1ac5 --- /dev/null +++ b/.forgejo/workflows/phpcs.yml @@ -0,0 +1,41 @@ +name: PHP Code Checker + +on: + push: + branches: + - main + pull_request: + +jobs: + phpcs: + runs-on: ubuntu-latest + services: + mysql: + image: mariadb + env: + MYSQL_ROOT_PASSWORD: root + strategy: + matrix: + php-versions: ['8.1'] + name: Run PHP Code Checker + env: + extensions: mysql + key: cache-v1 + steps: + - name: Checkout + uses: https://code.forgejo.org/actions/checkout@v4 + + - name: Setup PHP + uses: https://github.com/shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: none + tools: composer, cs2pr + env: + runner: self-hosted + + - name: Install Composer dependencies for PHP + uses: ramsey/composer-install@v3 + + - name: Detect coding standard violations + run: ./vendor/bin/phpcs \ No newline at end of file diff --git a/.forgejo/workflows/phpunit.yml b/.forgejo/workflows/phpunit.yml index 6df0ddf..0daeb73 100644 --- a/.forgejo/workflows/phpunit.yml +++ b/.forgejo/workflows/phpunit.yml @@ -1,42 +1,52 @@ name: Unit Testing on: push: + branches: + - main pull_request: + +env: + WP_TESTS_DIR: /workspace/wordpress-test-lib + WP_CORE_DIR: /workspace/wordpress + jobs: phpunit: runs-on: ubuntu-latest services: mysql: - image: mariadb:11.5 + image: mariadb env: MYSQL_ROOT_PASSWORD: root - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=10s --health-retries=10 - strategy: - matrix: - php-versions: ['8.1', '8.2', '8.3'] - include: - - wp-version: latest + strategy: + matrix: + php-versions: ['8.1'] + name: Run phpunit tests + env: + extensions: mysql + key: cache-v1 steps: - name: Checkout uses: https://code.forgejo.org/actions/checkout@v4 - # - name: Setup PHP - # uses: shivammathur/setup-php@v2 - # with: - # php-version: ${{ matrix.php-versions }} - # coverage: none - # tools: composer, phpunit-polyfills - # extensions: mysql + - name: Setup PHP + uses: https://github.com/shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: none + tools: composer, phpunit-polyfills + env: + runner: self-hosted - # - name: Install Composer dependencies for PHP - # uses: "ramsey/composer-install@v1" + - name: Install Composer dependencies for PHP + uses: ramsey/composer-install@v3 - # - name: Setup Test Environment - # run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 ${{ matrix.wp-version }} + - name: Install mysqladmin + run: sudo apt update && sudo apt -y upgrade && sudo apt -y install mysql-client - # - name: Unit Testing - # run: ./vendor/bin/phpunit - # env: - # PHP_VERSION: ${{ matrix.php-versions }} + - name: Setup Test Environment + run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 6.6 + + - name: Unit Testing + run: cd /workspace/Event-Federation/wordpress-activitypub-event-extensions/ && phpunit + env: + PHP_VERSION: ${{ matrix.php-versions }} diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist new file mode 100644 index 0000000..d7cc4f5 --- /dev/null +++ b/.phpcs.xml.dist @@ -0,0 +1,157 @@ + + + + A custom set of rules to check for a WPized WordPress project + + + + . + + + /docroot/wp-admin/* + /docroot/wp-includes/* + /docroot/wp-*.php + /docroot/index.php + /docroot/xmlrpc.php + /docroot/wp-content/plugins/* + + + /vendor/* + + + /node_modules/* + + + *.min.js + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tests/*.php + templates/*.php + + + tests/*.php + + + tests/*.php + + + \ No newline at end of file diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml new file mode 100644 index 0000000..7e61820 --- /dev/null +++ b/.woodpecker/test.yml @@ -0,0 +1,19 @@ +when: + - event: push + branch: woodpecker + +steps: + - name: build + image: php:8.3 + environment: + WP_TESTS_PHPUNIT_POLYFILLS_PATH: vendor/yoast/phpunit-polyfills + commands: + - apt -y update + - apt -y install libonig-dev + - docker-php-ext-install mbstring mysqli pdo_mysql intl gd zip bz2 + - docker-php-ext-enable mbstring mysqli pdo_mysql intl gd zip bz2 + - composer require --dev yoast/phpunit-polyfills:"^3.0" + - composer install + - bash bin/install-wp-tests.sh wordpress_tests root mysql mysql 6.6 true + - phpunit --configuration phpunit.xml.dist + diff --git a/activitypub-event-extensions.php b/activitypub-event-extensions.php index 392601a..9be6461 100644 --- a/activitypub-event-extensions.php +++ b/activitypub-event-extensions.php @@ -35,27 +35,23 @@ Activitypub_Event_Extensions\Autoloader::register(); // Initialize the plugin. Activitypub_Event_Extensions\Setup::get_instance(); -// For local development purposes: TODO. Remove everything after here. +// BeforeFirstRelease: Remove everything after this after here. /** * Add a filter for http_request_host_is_external * - * TODO: Remove this for release. - * - * @todo This filter is temporary code needed to do local testing. + * BeforeFirstRelease: Remove this for release. */ -add_filter( 'http_request_host_is_external', 'custom_http_request_host_is_external', 10, 3 ); +add_filter( 'http_request_host_is_external', 'activitypub_event_extensions_custom_http_request_host_is_external', 10, 3 ); /** * Add a filter for http_request_host_is_external * - * TODO: Remove this for release. + * BeforeFirstRelease: Remove this for release. * * @param bool $is_external Whether the request is external. - * - * @todo This filter is temporary code needed to do local testing. */ -function custom_http_request_host_is_external( $is_external ) { +function activitypub_event_extensions_custom_http_request_host_is_external( $is_external ) { $is_external = true; return $is_external; @@ -64,15 +60,13 @@ function custom_http_request_host_is_external( $is_external ) { /** * Don't verify ssl certs for testing. * - * TODO: Remove this for release. - * - * @todo This filter is temporary code needed to do local testing. + * BeforeFirstRelease: Remove this for release. */ -add_filter( 'https_ssl_verify', 'dont_verify_local_dev_https', 10, 3 ); +add_filter( 'https_ssl_verify', 'activitypub_event_extensions_dont_verify_local_dev_https', 10, 3 ); /** - * TODO: remove it. + * BeforeFirstRelease: remove it. */ -function dont_verify_local_dev_https() { +function activitypub_event_extensions_dont_verify_local_dev_https() { return false; } diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh new file mode 100644 index 0000000..e772021 --- /dev/null +++ b/bin/install-wp-tests.sh @@ -0,0 +1,188 @@ +#!/usr/bin/env bash + +if [ $# -lt 3 ]; then + echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" + exit 1 +fi + +DB_NAME=$1 +DB_USER=$2 +DB_PASS=$3 +DB_HOST=${4-localhost} +WP_VERSION=${5-latest} +SKIP_DB_CREATE=${6-false} + +TMPDIR=${TMPDIR-/tmp} +TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") +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 [[ $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" +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 +elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + 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" +fi +set -ex + +install_wp() { + rm -rf $WP_CORE_DIR + + 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-trunk + rm -rf $TMPDIR/wordpress-trunk/* + svn export --quiet https://core.svn.wordpress.org/trunk $TMPDIR/wordpress-trunk/wordpress + mv $TMPDIR/wordpress-trunk/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.githubusercontent.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 + rm -rf $WP_TESTS_DIR/{includes,data} + svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn export --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 + +} + +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"$EXTRA --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 $DELETE_EXISTING_DB + else + create_db + fi +} + +install_wp_plugins() { + download https://downloads.wordpress.org/plugin/activitypub.3.2.5.zip $TMPDIR/activitypub.zip + unzip $TMPDIR/activitypub.zip -d $WP_CORE_DIR/wp-content/plugins/ +} + +install_wp +install_wp_plugins +install_test_suite +install_db diff --git a/docker-compose-test.yml b/docker-compose-test.yml new file mode 100644 index 0000000..3926e0e --- /dev/null +++ b/docker-compose-test.yml @@ -0,0 +1,26 @@ +version: '3' +services: + test-db: + platform: linux/x86_64 + image: mariadb + environment: + MYSQL_DATABASE: wordpress + MYSQL_ROOT_PASSWORD: wordpress + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3306"] + interval: 5s + timeout: 2s + retries: 5 + + test-php: + build: + context: . + dockerfile: Dockerfile + depends_on: + test-db: + condition: service_healthy + links: + - test-db + volumes: + - .:/app + command: ["composer", "run-script", "test"] diff --git a/includes/activitypub/transformer/class-events-manager.php b/includes/activitypub/transformer/class-events-manager.php index e6b52bd..487181c 100644 --- a/includes/activitypub/transformer/class-events-manager.php +++ b/includes/activitypub/transformer/class-events-manager.php @@ -6,7 +6,11 @@ * @license AGPL-3.0-or-later */ +namespace Activitypub_Event_Extensions\Activitypub\Transformer; + use Activitypub_Event_Extensions\Activitypub\Transformer\Event as Event_Transformer; +use DateTime; +use DateTimeZone; use EM_Event; use Activitypub\Activity\Extended_Object\Event; use Activitypub\Activity\Extended_Object\Place; @@ -151,7 +155,7 @@ final class Events_Manager extends Event_Transformer { /** * Return the remaining attendee capacity * - * @todo decide whether to include pending bookings or not! + * @return int */ public function get_remaining_attendee_capacity() { $em_bookings = $this->em_event->get_bookings()->get_bookings(); @@ -164,7 +168,7 @@ final class Events_Manager extends Event_Transformer { * * @return int */ - public function get_participant_count() { + public function get_participant_count(): int { $em_bookings = $this->em_event->get_bookings()->get_bookings(); return count( $em_bookings->bookings ); } diff --git a/includes/activitypub/transformer/class-gatherpress.php b/includes/activitypub/transformer/class-gatherpress.php index c875ed1..d04e4a6 100644 --- a/includes/activitypub/transformer/class-gatherpress.php +++ b/includes/activitypub/transformer/class-gatherpress.php @@ -6,6 +6,8 @@ * @license AGPL-3.0-or-later */ +namespace Activitypub_Event_Extensions\Activitypub\Transformer; + use Activitypub_Event_Extensions\Activitypub\Transformer\Event; use Activitypub\Model\Blog; use Activitypub\Activity\Extended_Object\Event as Event_Object; diff --git a/includes/activitypub/transformer/class-the-events-calendar.php b/includes/activitypub/transformer/class-the-events-calendar.php index aa5a16b..e1b42fb 100644 --- a/includes/activitypub/transformer/class-the-events-calendar.php +++ b/includes/activitypub/transformer/class-the-events-calendar.php @@ -51,8 +51,6 @@ final class The_Events_Calendar extends Event { * @return string|null tribe category if it exists */ public function get_tribe_category() { - // TODO: make it possible that one event can have multiple categories? - // Using cat_slugs isn't the best way to do this, don't know if it's a good idea. $categories = tribe_get_event_cat_slugs( $this->wp_object->ID ); if ( count( $categories ) === 0 ) { @@ -133,12 +131,10 @@ final class The_Events_Calendar extends Event { protected function get_content() { $content = parent::get_content(); - // TODO: remove link at the end of the content. - - // TODO: add organizer - // $this->tribe_event->organizers[0]. - - // TODO: do add Cancelled reason in the content (maybe at the end). + // /BeforeFirstRelease: + // * remove link at the end of the content. + // * add organizer. + // * do add Cancelled reason in the content.s return $content; } diff --git a/includes/admin/class-settings-page.php b/includes/admin/class-settings-page.php index dbe5ed5..d7cc288 100644 --- a/includes/admin/class-settings-page.php +++ b/includes/admin/class-settings-page.php @@ -37,7 +37,7 @@ class Settings_Page { public static function admin_menu(): void { \add_options_page( 'Activitypub Event Extension', - __( 'ActivityPub Events', 'activitypub_event_extensions' ), + __( 'ActivityPub Events', 'activitypub-event-extensions' ), 'manage_options', self::SETTINGS_SLUG, array( self::STATIC, 'settings_page' ) diff --git a/includes/class-setup.php b/includes/class-setup.php index 67f1999..2de709f 100644 --- a/includes/class-setup.php +++ b/includes/class-setup.php @@ -60,7 +60,7 @@ class Setup { */ protected function __construct() { $this->activitypub_plugin_is_active = is_plugin_active( 'activitypub/activitypub.php' ); - // TODO: decide whether we want to do anything at all when ActivityPub plugin is note active. + // BeforeFirstRelease: decide whether we want to do anything at all when ActivityPub plugin is note active. // if ( ! $this->activitypub_plugin_is_active ) { // deactivate_plugins( ACTIVITYPUB_EVENT_EXTENSIONS_PLUGIN_FILE ); // return; diff --git a/includes/plugins/class-the-events-calendar.php b/includes/plugins/class-the-events-calendar.php index 1b77a61..6f18a5a 100644 --- a/includes/plugins/class-the-events-calendar.php +++ b/includes/plugins/class-the-events-calendar.php @@ -46,8 +46,12 @@ final class The_Events_Calendar extends Event_plugin { * @return string The settings page url. */ public static function get_settings_page(): string { - // TODO: Tribe\Events\Admin\Settings::settings_page_id. - return 'edit.php?post_type=tribe_events&page=tec-events-settings'; + if ( class_exists( '\Tribe\Events\Admin\Settings' ) ) { + $page = \Tribe\Events\Admin\Settings::$settings_page_id; + } else { + $page = 'tec-events-settings'; + } + return sprintf( 'edit.php?post_type=tribe_events&page=%s', $page ); } /** diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..e8e8560 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,15 @@ + + + + + ./tests/ + + + diff --git a/templates/settings.php b/templates/settings.php index aab3640..df52492 100644 --- a/templates/settings.php +++ b/templates/settings.php @@ -76,7 +76,7 @@ $current_category_mapping = \get_option( 'activitypub_event_extensions_ev

-

+

@@ -96,7 +96,7 @@ $current_category_mapping = \get_option( 'activitypub_event_extensions_ev

-

+

diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100755 index 0000000..7d30eb9 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,38 @@ +assertTrue( true ); + } +}