diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..93b25c2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,97 @@ +name: CI + +# The tests live in the plugin repository (https://github.com/codeling/bfstop, +# see tests/README.md there); they cover plugin and component together. + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + syntax: + name: Syntax check (PHP ${{ matrix.php }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ['8.1', '8.4'] + steps: + - uses: actions/checkout@v7 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + - name: Check PHP syntax + run: find . -name '*.php' -not -path './.git/*' -print0 | xargs -0 -n1 php -l + + integration: + name: Joomla ${{ matrix.joomla }}, ${{ matrix.db }}, PHP ${{ matrix.php }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - { joomla: 5.4.8, php: '8.2', db: 'mysql:8.0' } + - { joomla: 5.4.8, php: '8.2', db: 'postgres:12' } + - { joomla: 6.1.3, php: '8.4', db: 'mariadb:11.4' } + - { joomla: 6.1.3, php: '8.4', db: 'postgres:17' } + env: + JOOMLA_VERSION: ${{ matrix.joomla }} + DB_HOST: 127.0.0.1 + DB_NAME: joomla + DB_PASS: bfstop-test + steps: + - name: Set paths + run: | + { + echo "JOOMLA_ROOT=$RUNNER_TEMP/joomla" + echo "COM_BFSTOP_ROOT=$GITHUB_WORKSPACE" + echo "PLUGIN_ROOT=$RUNNER_TEMP/bfstop" + } >> "$GITHUB_ENV" + - uses: actions/checkout@v7 + + # use the plugin's branch of the same name if there is one (for changes + # spanning both), else main + - name: Determine bfstop branch + id: plugin + run: | + branch="${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}" + if git ls-remote --exit-code --heads https://github.com/codeling/bfstop.git "$branch" > /dev/null; then + echo "ref=$branch" >> "$GITHUB_OUTPUT" + else + echo "ref=main" >> "$GITHUB_OUTPUT" + fi + - uses: actions/checkout@v7 + with: + repository: codeling/bfstop + ref: ${{ steps.plugin.outputs.ref }} + path: bfstop + - name: Move bfstop checkout out of the component's + run: mv bfstop "$PLUGIN_ROOT" + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: mysqli, pdo_mysql, pgsql, pdo_pgsql, zip, mbstring, intl, gd + tools: phpunit:11 + coverage: none + + - name: Start database (${{ matrix.db }}) + env: + DB_IMAGE: ${{ matrix.db }} + run: '"$PLUGIN_ROOT"/tests/ci/start-database.sh' + + - name: Install Joomla, bfstop and com_bfstop + run: '"$PLUGIN_ROOT"/tests/ci/install-joomla.sh' + + - name: Tests + run: phpunit -c "$PLUGIN_ROOT"/tests/phpunit.xml.dist + + - name: Plugin log + if: failure() + run: cat "$JOOMLA_ROOT"/administrator/logs/*.php || true diff --git a/admin/src/Controller/DisplayController.php b/admin/src/Controller/DisplayController.php index 67eebe6..fe31faa 100644 --- a/admin/src/Controller/DisplayController.php +++ b/admin/src/Controller/DisplayController.php @@ -43,7 +43,9 @@ function warnIfAdminUserExists() try { $db = Factory::getDbo(); - $query = "SELECT COUNT(*) FROM #__users u WHERE u.username='admin'"; + // LOWER: also catch "Admin" etc. on PostgreSQL, which (unlike + // MySQL's default collations) compares case-sensitively + $query = "SELECT COUNT(*) FROM #__users u WHERE LOWER(u.username)='admin'"; $db->setQuery($query); if ($db->loadResult() > 0) { diff --git a/admin/src/Table/AllowTable.php b/admin/src/Table/AllowTable.php index aca5d7d..00af5bf 100644 --- a/admin/src/Table/AllowTable.php +++ b/admin/src/Table/AllowTable.php @@ -19,4 +19,17 @@ function __construct(DatabaseDriver $db) { parent::__construct('#__bfstop_allowlist', 'id', $db); } + + public function check() + { + // a new entry comes with an empty id from the edit form; MySQL turns + // that into the next auto-increment value, but PostgreSQL rejects it + // (as NULL for the id column) - 0 makes Joomla leave the id out of + // the INSERT on both (issue bfstop#206) + if (empty($this->id)) + { + $this->id = 0; + } + return parent::check(); + } } diff --git a/admin/src/Table/BlockTable.php b/admin/src/Table/BlockTable.php index f8a6da8..0f6668f 100644 --- a/admin/src/Table/BlockTable.php +++ b/admin/src/Table/BlockTable.php @@ -19,4 +19,17 @@ function __construct(DatabaseDriver $db) { parent::__construct('#__bfstop_bannedip', 'id', $db); } + + public function check() + { + // a new entry comes with an empty id from the edit form; MySQL turns + // that into the next auto-increment value, but PostgreSQL rejects it + // (as NULL for the id column) - 0 makes Joomla leave the id out of + // the INSERT on both (issue bfstop#206) + if (empty($this->id)) + { + $this->id = 0; + } + return parent::check(); + } } diff --git a/site/src/Model/TokenunblockModel.php b/site/src/Model/TokenunblockModel.php index b4c921b..91d0792 100644 --- a/site/src/Model/TokenunblockModel.php +++ b/site/src/Model/TokenunblockModel.php @@ -23,9 +23,10 @@ public function unblock($token, $logger) // prune old tokens: try { + // cutoff computed in PHP, as DATE_ADD is MySQL-only (issue bfstop#206) $this->_db->setQuery('DELETE FROM #__bfstop_unblock_token '. - 'WHERE DATE_ADD(crdate, INTERVAL '.self::TokenValidDays.' DAY) < '. - $this->_db->quote(date('Y-m-d H:i:s'))); + 'WHERE crdate < '. + $this->_db->quote(date('Y-m-d H:i:s', time() - self::TokenValidDays * 86400))); $this->_db->execute(); // get token: $this->_db->setQuery('SELECT * FROM #__bfstop_unblock_token WHERE token='.