diff --git a/.gitignore b/.gitignore index 4358dc8..352416c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ .phpunit.result.cache phpunit.xml phpcs.xml +phpstan.neon diff --git a/composer.json b/composer.json index 2291c16..9161146 100644 --- a/composer.json +++ b/composer.json @@ -39,17 +39,19 @@ } ], "require-dev": { - "wp-coding-standards/wpcs": "^3.2", + "phpcompatibility/php-compatibility": "dev-develop as 9.99.99", + "phpcompatibility/phpcompatibility-wp": "^2.1", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^9", "roots/wordpress": "^6.8", + "wp-coding-standards/wpcs": "^3.2", "wp-phpunit/wp-phpunit": "^6.8", - "yoast/phpunit-polyfills": "^4.0", - "phpunit/phpunit": "^9", - "wpackagist-theme/twentytwentyfive": "^1.3", "wpackagist-plugin/query-monitor": "^3.20", - "phpcompatibility/phpcompatibility-wp": "^2.1", - "phpcompatibility/php-compatibility": "dev-develop as 9.99.99" + "wpackagist-theme/twentytwentyfive": "^1.3", + "yoast/phpunit-polyfills": "^4.0" }, "config": { + "sort-packages": true, "platform": { "php": "7.4" }, @@ -60,8 +62,11 @@ } }, "scripts": { + "lint:phpcs": "phpcs", + "lint:phpstan": "phpstan --memory-limit=1G analyse", "lint": [ - "phpcs" + "@composer lint:phpcs", + "@composer lint:phpstan" ], "format": [ "phpcbf" diff --git a/composer.lock b/composer.lock index eddba27..b190951 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0f5d8ef51328eb4e16863df49ed0a778", + "content-hash": "d5663eb366b6333938e6d54512edbb01", "packages": [], "packages-dev": [ { @@ -975,6 +975,59 @@ ], "time": "2025-11-11T00:17:56+00:00" }, + { + "name": "phpstan/phpstan", + "version": "2.1.32", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e126cad1e30a99b137b8ed75a85a676450ebb227", + "reference": "e126cad1e30a99b137b8ed75a85a676450ebb227", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-11-11T15:18:17+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.32", diff --git a/minit.php b/minit.php index d880a54..67abcb6 100644 --- a/minit.php +++ b/minit.php @@ -6,7 +6,7 @@ * GitHub Plugin URI: https://github.com/kasparsd/minit * Update URI: https://updates.wpelevator.com/wp-json/update-pilot/v1/plugins * Description: Combine JS and CSS files and serve them from the uploads folder. - * Version: 3.1.0 + * Version: 3.1.1 * Author: Kaspars Dambis * Author URI: https://kaspars.net * Require PHP: 7.4 diff --git a/package.json b/package.json index 8dde40c..3b2f262 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "test": "npm run cli -- composer test", "composer": "npm run cli -- composer", "lint": "run-p lint:*", - "lint:php": "npm run cli -- composer lint" + "lint:php": "npm run cli -- composer lint", + "format": "run-p format:*", + "format:php": "npm run cli -- composer format" } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..1041068 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,10 @@ +parameters: + level: 5 + paths: + - src + - tests + scanDirectories: + - wordpress + - vendor + excludePaths: + - node_modules (?) diff --git a/src/minit-asset-cache.php b/src/minit-asset-cache.php index c0abf70..5b4eab6 100644 --- a/src/minit-asset-cache.php +++ b/src/minit-asset-cache.php @@ -146,7 +146,7 @@ public function purge() { public function files() { $files = glob( $this->dir() . '/*', GLOB_NOSORT ); - if ( ! empty( $files ) && is_array( $files ) ) { + if ( ! empty( $files ) ) { return $files; } diff --git a/src/minit-assets.php b/src/minit-assets.php index bb9b911..928e0ec 100644 --- a/src/minit-assets.php +++ b/src/minit-assets.php @@ -8,7 +8,7 @@ abstract class Minit_Assets { /** * Instance of either WP scripts or styles. * - * @var WP_Dependencies + * @var WP_Styles|WP_Scripts */ public $handler; diff --git a/src/minit-css.php b/src/minit-css.php index 38285e9..1f68f71 100644 --- a/src/minit-css.php +++ b/src/minit-css.php @@ -91,9 +91,13 @@ protected function resolve_urls( $content, $handle, $src ) { } // Make all local asset URLs absolute - $content = preg_replace( - '/url\(["\' ]?+(?!data:|https?:|\/\/)(.*?)["\' ]?\)/i', - sprintf( "url('%s/$1')", $this->handler->base_url . dirname( $src ) ), + $content = preg_replace_callback( + '/url\(\s*(?!["\'\s]*(?:data:|https?:|\/\/))(.*?)\s*\)/i', + fn ( $matches ) => sprintf( + "url('%s/%s')", + $this->handler->base_url . dirname( $src ), + trim( $matches[1], '\'" ' ) + ), $content ); @@ -120,11 +124,8 @@ protected function exclude_with_media_query( $content, $handle, $src ) { return $content; } - // Ignore these media queries. - $allowlist = array( null, '', 'all', 'screen' ); - - // Exclude from Minit if media query specified. - if ( ! in_array( $this->handler->registered[ $handle ]->args, $allowlist, true ) ) { + // Exclude from Minit if non-screen media query specified. + if ( ! empty( $this->handler->registered[ $handle ]->args ) && ! in_array( $this->handler->registered[ $handle ]->args, array( 'all', 'screen' ), true ) ) { return false; } diff --git a/src/minit-plugin.php b/src/minit-plugin.php index 4cc8b85..66af1c5 100644 --- a/src/minit-plugin.php +++ b/src/minit-plugin.php @@ -66,26 +66,22 @@ protected function __construct() { * * @return string */ - public function basename() { + public function basename(): string { return plugin_basename( $this->plugin_file ); } /** * Bump the cache version to bust the cache. - * - * @return boolean */ - public function cache_bump() { - return $this->minit_cache->bump(); + public function cache_bump(): void { + $this->minit_cache->bump(); } /** * Delete all the cache files. - * - * @return boolean */ - public function cache_purge() { - return $this->minit_cache->purge(); + public function cache_purge(): void { + $this->minit_cache->purge(); } public function init() { diff --git a/tests/phpunit/class-minit-css-test.php b/tests/phpunit/class-minit-css-test.php new file mode 100644 index 0000000..9c79475 --- /dev/null +++ b/tests/phpunit/class-minit-css-test.php @@ -0,0 +1,95 @@ +assertEquals( + 'body {}', + $minit_css->minit_item( 'body {}', 'minit-css-media-default', '/path/to/default.css' ), + 'stylesheets with no media query (default) are included in minit' + ); + + $this->assertEquals( + 'body {}', + $minit_css->minit_item( 'body {}', 'minit-css-media-screen', '/path/to/screen.css' ), + 'stylesheets with screen media query are included in minit' + ); + + $this->assertFalse( + $minit_css->minit_item( 'body {}', 'minit-css-media-print', '/path/to/print.css' ), + 'stylesheets with non-screen media queries are excluded from minit' + ); + } + + private function url_to( string $pattern ): string { + return sprintf( $pattern, home_url() ); + } + + public function test_resolve_urls() { + $minit_css = new Minit_Css( + Minit_Plugin::instance(), + new Minit_Asset_Cache( '/resolve/urls/minit-test', 'version' ) + ); + + wp_enqueue_style( 'minit-css-url-paths', 'https://example.com/default.css' ); + + $this->assertEquals( + $this->url_to( 'body { background-image: url(\'%s/path/to/path/to/image.png\'); }' ), + $minit_css->minit_item( 'body { background-image: url( "path/to/image.png" ); }', 'minit-css', '/path/to/css.css' ), + 'relative paths wrapped in quotes' + ); + + $this->assertEquals( + $this->url_to( 'body { background-image: url(\'%s/path/to/direct/image.png\'); }' ), + $minit_css->minit_item( 'body { background-image: url(direct/image.png); }', 'minit-css', '/path/to/css.css' ), + 'relative paths without quotes' + ); + + $this->assertEquals( + $this->url_to( 'body { background-image: url(\'%s/path/to/some/image.jpeg\'); }' ), + $minit_css->minit_item( 'body { background-image: url( "some/image.jpeg\' ); }', 'minit-css', '/path/to/css.css' ), + 'mixed quotes and spaces' + ); + + $this->assertEquals( + 'body { background-image: url( data:image/gif;base64,R0lGODlhEAAQAMQAAO ); }', + $minit_css->minit_item( 'body { background-image: url( data:image/gif;base64,R0lGODlhEAAQAMQAAO ); }', 'minit-css', '/path/to/css.css' ), + 'data uris are kept intact' + ); + + $this->assertEquals( + 'body { background-image: url( "http://example.jpeg" ); }', + $minit_css->minit_item( 'body { background-image: url( "http://example.jpeg" ); }', 'minit-css', '/path/to/css.css' ), + 'absolute urls are kept intact' + ); + } + + public function test_resolve_imports() { + $minit_css = new Minit_Css( + Minit_Plugin::instance(), + new Minit_Asset_Cache( '/resolve/imports/minit-test', 'version' ) + ); + + wp_enqueue_style( 'minit-css-imports', 'https://example.com/imports.css' ); + + $this->assertEquals( + $this->url_to( '@import url(\'%s/path/to/my-imported-styles.css\');' ), + $minit_css->minit_item( '@import "my-imported-styles.css";', 'minit-css', '/path/to/my-imported-styles.css' ), + 'relative imports are made absolute' + ); + + $this->assertEquals( + $this->url_to( '@import url(\'%s/path/relative/relative/path.css\');' ), + $minit_css->minit_item( '@import url("relative/path.css");', 'minit-css', '/path/relative/path.css' ), + 'relative url() imports are made absolute' + ); + } +}