Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 51 additions & 45 deletions .github/workflows/pr_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,68 @@ name: CI
on: [push, pull_request]

jobs:
phpunit-9-6:
strategy:
fail-fast: false
matrix:
php_version: ["7.4", "8.1", "8.2", "8.3"]
tests:
name: PHP ${{ matrix.php }} / Deps ${{ matrix.deps }}${{ matrix.coverage && ' / Coverage' || '' }}
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: php-actions/composer@v6
with:
php_version: "7.4"
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php_version }}
coverage: xdebug
- name: Unit Tests with PHPUnit 9.6
run: vendor/bin/phpunit --stderr --version 9.6

phpunit-10-5:
strategy:
fail-fast: false
matrix:
php_version: ["8.1", "8.2", "8.3"]
runs-on: ubuntu-latest
php: ['7.4', '8.0', '8.1', '8.2']
deps: ['latest']
coverage: [false]
include:
- php: '7.4'
deps: 'lowest'
coverage: false
- php: '8.3'
deps: 'latest'
coverage: true

steps:
- uses: actions/checkout@v2
- uses: php-actions/composer@v6
with:
php_version: "8.1"
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php_version }}
coverage: xdebug
- name: Unit Tests with PHPUnit 10.5
run: vendor/bin/phpunit --stderr --version 10.5
php-version: ${{ matrix.php }}
extensions: intl, mbstring
coverage: ${{ matrix.coverage && 'xdebug' || 'none' }}

phpunit-11-2:
strategy:
fail-fast: false
matrix:
php_version: ["8.2", "8.3"]
runs-on: ubuntu-latest
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

steps:
- uses: actions/checkout@v2
- uses: php-actions/composer@v6
- name: Cache Composer Dependencies
uses: actions/cache@v4
with:
php_version: "8.2"
- name: Setup PHP
uses: shivammathur/setup-php@v2
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php-${{ matrix.php }}-deps-${{ matrix.deps }}-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-php-${{ matrix.php }}-deps-

- name: Install Dependencies
run: |
if [ "${{ matrix.deps }}" = "lowest" ]; then
composer update --prefer-lowest --prefer-stable --no-interaction
else
composer update --prefer-dist --no-interaction
fi

- name: Validate composer.json
run: composer validate --strict

- name: Ensure Build Directory Exists
run: mkdir -p build/logs

- name: Run All CI Checks
run: composer ci

- name: Upload Coverage to Codecov
if: ${{ matrix.coverage }}
uses: codecov/codecov-action@v4
with:
php-version: ${{ matrix.php_version }}
coverage: xdebug
- name: Unit Tests with PHPUnit 11.2
run: vendor/bin/phpunit --stderr --version 11.2
token: ${{ secrets.CODECOV_TOKEN }} # Recommended for private repos
file: build/logs/clover.xml
fail_ci_if_error: false # Optional: Prevents CI failure if upload fails
12 changes: 12 additions & 0 deletions .phpcs.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<ruleset name="XoopsRegDom">
<description>XOOPS RegDom coding standards</description>

<file>src</file>

<rule ref="PSR12"/>

<rule ref="Generic.Files.LineLength">
<exclude-pattern>*</exclude-pattern>
</rule>
</ruleset>
62 changes: 50 additions & 12 deletions .scrutinizer.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,57 @@
# .scrutinizer.yml
build:
nodes:
analysis:
environment:
php:
version: 7.4
project_setup:
override: true
tests:
override:
- php-scrutinizer-run
nodes:
analysis:
tests:
override:
- php-scrutinizer-run

php74:
environment:
php: 7.4
tests:
override:
- command: php -v

php80:
environment:
php: 8.0
tests:
override:
- command: php -v

php81:
environment:
php: 8.1
tests:
override:
- command: php -v

php82:
environment:
php: 8.2
tests:
override:
- command: php -v

# php83:
# environment:
# php: 8.3
# tests:
# override:
# - command: php -v

# php84:
# environment:
# php: 8.4
# tests:
# override:
# - command: php -v
filter:
excluded_paths:
- '_archive/*'
- 'tests/*'
tools:
external_code_coverage:
timeout: 300 # Timeout in seconds.
php_analyzer: true
# external_code_coverage:
# timeout: 300 # Timeout in seconds.
111 changes: 111 additions & 0 deletions bin/update-psl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env php
<?php
/**
* XOOPS RegDom Public Suffix List Updater
*
* Downloads and caches the Mozilla Public Suffix List.
* This script is safe to run in any environment and handles multiple failure scenarios gracefully.
*
* @license The PSL is licensed under MPL-2.0 by Mozilla Foundation
* @source https://publicsuffix.org/
*/

echo "Updating XOOPS RegDom Public Suffix List...\n";

// --- Configuration ---
$sourceUrl = 'https://publicsuffix.org/list/public_suffix_list.dat';
$packageDir = dirname(__DIR__);
$dataDir = $packageDir . '/data';
$bundledCachePath = $dataDir . '/psl.cache.php';
$metaPath = $dataDir . '/psl.meta.json';
$runtimeCachePath = null;

// Determine runtime cache path if in a XOOPS context
if (defined('XOOPS_VAR_PATH')) {
$runtimeCacheDir = XOOPS_VAR_PATH . '/cache/regdom';
if (!is_dir($runtimeCacheDir)) {
if (!mkdir($runtimeCacheDir, 0777, true) && !is_dir($runtimeCacheDir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $runtimeCacheDir));
}
}
if (is_writable($runtimeCacheDir)) {
$runtimeCachePath = $runtimeCacheDir . '/psl.cache.php';
} else {
echo "WARNING: Runtime cache directory is not writable: {$runtimeCacheDir}\n";
}
}

// --- HTTP Conditional Download ---
$headers = ['User-Agent: XOOPS-RegDom/1.1 (https://xoops.org)'];
$meta = file_exists($metaPath) ? json_decode(file_get_contents($metaPath), true) : [];
if (!empty($meta['etag'])) $headers[] = "If-None-Match: {$meta['etag']}";
if (!empty($meta['last_modified'])) $headers[] = "If-Modified-Since: {$meta['last_modified']}";

echo "Downloading from publicsuffix.org...\n";
$context = stream_context_create(['http' => ['method' => 'GET', 'timeout' => 20, 'header' => implode("\r\n", $headers), 'ignore_errors' => true]]);
$latestList = @file_get_contents($sourceUrl, false, $context);
$responseHeaders = $http_response_header ?? [];
$statusCode = 0;
foreach ($responseHeaders as $header) if (preg_match('/^HTTP\/\d\.\d\s+(\d+)/', $header, $m)) $statusCode = (int)$m[1];

if ($statusCode === 304) {
echo "SUCCESS: Public Suffix List is already up-to-date (304 Not Modified).\n";
exit(0);
}

if ($latestList === false || $statusCode !== 200) {
echo "WARNING: Failed to download PSL (HTTP {$statusCode}). Keeping existing cache.\n";
exit(0); // Not a fatal error; existing cache is still valid.
}

// --- Parse and Generate Cache ---
echo "Parsing rules...\n";
$lines = explode("\n", $latestList);
$rules = ['NORMAL' => [], 'WILDCARD' => [], 'EXCEPTION' => []];
foreach ($lines as $line) {
$line = trim($line);
if (empty($line) || strpos($line, '//') === 0) continue;
if (strpos($line, '!') === 0) $rules['EXCEPTION'][substr($line, 1)] = true;
elseif (strpos($line, '*.') === 0) $rules['WILDCARD'][substr($line, 2)] = true;
else $rules['NORMAL'][$line] = true;
}

$totalRules = count($rules['NORMAL']) + count($rules['WILDCARD']) + count($rules['EXCEPTION']);
if ($totalRules < 1000) {
echo "ERROR: Parsed rule count ({$totalRules}) is suspiciously low. Aborting update.\n";
exit(1);
}

$cacheHeader = "<?php\n/**\n * Public Suffix List Cache\n * Generated: " . date('Y-m-d H:i:s T') . "\n * Source: {$sourceUrl}\n * License: Mozilla Public License 2.0 (https://publicsuffix.org/)\n */\n";
$cacheContent = $cacheHeader . "\nreturn " . var_export($rules, true) . ";\n";

// --- Atomic Write to Caches ---
$writePaths = ['bundled' => $bundledCachePath];
if ($runtimeCachePath) $writePaths['runtime'] = $runtimeCachePath;

foreach ($writePaths as $type => $cachePath) {
$tmpPath = $cachePath . '.tmp.' . getmypid();
if (file_put_contents($tmpPath, $cacheContent, LOCK_EX) && rename($tmpPath, $cachePath)) {
echo "SUCCESS: {$type} cache updated with {$totalRules} rules.\n";
} else {
echo "WARNING: Could not write {$type} cache to {$cachePath}.\n";
// Attempt to delete the temporary file.
if (file_exists($tmpPath) && !unlink($tmpPath)) {
// If unlink() fails, throw an exception to halt execution and signal an error.
throw new \RuntimeException(
"CRITICAL: Failed to delete temporary file at: {$tmpPath}. Please check file permissions."
);
}
}
}

// --- Save Metadata ---
$newMeta = ['updated' => date('c'), 'etag' => null, 'last_modified' => null];
foreach ($responseHeaders as $header) {
if (stripos($header, 'ETag:') === 0) $newMeta['etag'] = trim(substr($header, 5));
if (stripos($header, 'Last-Modified:') === 0) $newMeta['last_modified'] = trim(substr($header, 14));
}
file_put_contents($metaPath, json_encode($newMeta, JSON_PRETTY_PRINT));

echo "Update complete.\n";
exit(0);
Loading