diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 39ef9cc4a..9357dda8e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: strategy: max-parallel: 6 matrix: - phpVersions: ['8.2', '8.3', '8.4'] + phpVersions: ['8.2', '8.3', '8.4', '8.5'] fail-fast: false name: PHP ${{ matrix.phpVersions }} steps: diff --git a/.gitignore b/.gitignore index 151a78eb1..859eccd37 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,9 @@ composer.lock # Editor files .idea .vscode +.claude # Other files .DS_Store php_errors.log -.phpunit.result.cache \ No newline at end of file +.phpunit.result.cache diff --git a/composer.json b/composer.json index c68ba4f0d..2ba9c90c5 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,9 @@ "php": "^8.0.2", "composer/composer": "^2.0.0", "composer/installers": "^1 || ^2", + "larajax/larajax": "^2.0", "doctrine/dbal": "^2.13.3|^3.1.4", + "intervention/image": "^3.10", "jaybizzle/crawler-detect": "^1.3", "linkorb/jsmin-php": "~1.0", "wikimedia/less.php": "~5.2", @@ -66,5 +68,10 @@ } }, "minimum-stability": "dev", - "prefer-stable": true + "prefer-stable": true, + "config": { + "allow-plugins": { + "composer/installers": true + } + } } diff --git a/globals/Vite.php b/globals/Vite.php new file mode 100644 index 000000000..00f3c57ea --- /dev/null +++ b/globals/Vite.php @@ -0,0 +1,8 @@ +asset->ensureFilter($filter); } @@ -41,7 +41,7 @@ public function ensureFilter(FilterInterface $filter) /** * getFilters */ - public function getFilters() + public function getFilters(): array { return $this->asset->getFilters(); } @@ -49,7 +49,7 @@ public function getFilters() /** * clearFilters */ - public function clearFilters() + public function clearFilters(): void { $this->asset->clearFilters(); } @@ -57,7 +57,7 @@ public function clearFilters() /** * load */ - public function load(?FilterInterface $additionalFilter = null) + public function load(?FilterInterface $additionalFilter = null): void { $cacheKey = self::getCacheKey($this->asset, $additionalFilter, 'load'); if ($this->cache->has($cacheKey)) { @@ -73,7 +73,7 @@ public function load(?FilterInterface $additionalFilter = null) /** * dump */ - public function dump(?FilterInterface $additionalFilter = null) + public function dump(?FilterInterface $additionalFilter = null): string { $cacheKey = self::getCacheKey($this->asset, $additionalFilter, 'dump'); if ($this->cache->has($cacheKey)) { @@ -89,7 +89,7 @@ public function dump(?FilterInterface $additionalFilter = null) /** * getContent */ - public function getContent() + public function getContent(): ?string { return $this->asset->getContent(); } @@ -97,7 +97,7 @@ public function getContent() /** * setContent */ - public function setContent($content) + public function setContent(?string $content): void { $this->asset->setContent($content); } @@ -105,7 +105,7 @@ public function setContent($content) /** * getSourceRoot */ - public function getSourceRoot() + public function getSourceRoot(): ?string { return $this->asset->getSourceRoot(); } @@ -113,7 +113,7 @@ public function getSourceRoot() /** * getSourcePath */ - public function getSourcePath() + public function getSourcePath(): ?string { return $this->asset->getSourcePath(); } @@ -121,7 +121,7 @@ public function getSourcePath() /** * getSourceDirectory */ - public function getSourceDirectory() + public function getSourceDirectory(): ?string { return $this->asset->getSourceDirectory(); } @@ -129,7 +129,7 @@ public function getSourceDirectory() /** * getTargetPath */ - public function getTargetPath() + public function getTargetPath(): ?string { return $this->asset->getTargetPath(); } @@ -137,7 +137,7 @@ public function getTargetPath() /** * setTargetPath */ - public function setTargetPath($targetPath) + public function setTargetPath(?string $targetPath): void { $this->asset->setTargetPath($targetPath); } @@ -145,7 +145,7 @@ public function setTargetPath($targetPath) /** * getLastModified */ - public function getLastModified() + public function getLastModified(): ?int { return $this->asset->getLastModified(); } @@ -153,7 +153,7 @@ public function getLastModified() /** * getVars */ - public function getVars() + public function getVars(): array { return $this->asset->getVars(); } @@ -161,7 +161,7 @@ public function getVars() /** * setValues */ - public function setValues(array $values) + public function setValues(array $values): void { $this->asset->setValues($values); } @@ -169,7 +169,7 @@ public function setValues(array $values) /** * getValues */ - public function getValues() + public function getValues(): array { return $this->asset->getValues(); } @@ -190,7 +190,7 @@ public function getValues() * * @return string A key for identifying the current asset */ - protected static function getCacheKey(AssetInterface $asset, ?FilterInterface $additionalFilter = null, $salt = '') + protected static function getCacheKey(AssetInterface $asset, ?FilterInterface $additionalFilter = null, string $salt = ''): string { if ($additionalFilter) { $asset = clone $asset; diff --git a/src/Assetic/Asset/AssetCollection.php b/src/Assetic/Asset/AssetCollection.php index 0bd21604d..74b15f7a6 100644 --- a/src/Assetic/Asset/AssetCollection.php +++ b/src/Assetic/Asset/AssetCollection.php @@ -18,42 +18,42 @@ class AssetCollection implements IteratorAggregate, AssetCollectionInterface { /** - * @var mixed assets + * @var array assets */ protected $assets; /** - * @var mixed filters + * @var FilterCollection filters */ protected $filters; /** - * @var mixed sourceRoot + * @var string|null sourceRoot */ protected $sourceRoot; /** - * @var mixed targetPath + * @var string|null targetPath */ protected $targetPath; /** - * @var mixed content + * @var string|null content */ protected $content; /** - * @var mixed clones + * @var SplObjectStorage clones */ protected $clones; /** - * @var mixed vars + * @var array vars */ protected $vars; /** - * @var mixed values + * @var array values */ protected $values; @@ -65,7 +65,7 @@ class AssetCollection implements IteratorAggregate, AssetCollectionInterface * @param string $sourceRoot The root directory * @param array $vars */ - public function __construct($assets = [], $filters = [], $sourceRoot = null, array $vars = []) + public function __construct(array $assets = [], array $filters = [], ?string $sourceRoot = null, array $vars = []) { $this->assets = []; foreach ($assets as $asset) { @@ -91,7 +91,7 @@ public function __clone() /** * all */ - public function all() + public function all(): array { return $this->assets; } @@ -99,7 +99,7 @@ public function all() /** * add */ - public function add(AssetInterface $asset) + public function add(AssetInterface $asset): void { $this->assets[] = $asset; } @@ -107,7 +107,7 @@ public function add(AssetInterface $asset) /** * removeLeaf */ - public function removeLeaf(AssetInterface $needle, $graceful = false) + public function removeLeaf(AssetInterface $needle, bool $graceful = false): bool { foreach ($this->assets as $i => $asset) { $clone = isset($this->clones[$asset]) ? $this->clones[$asset] : null; @@ -132,11 +132,11 @@ public function removeLeaf(AssetInterface $needle, $graceful = false) /** * replaceLeaf */ - public function replaceLeaf(AssetInterface $needle, AssetInterface $replacement, $graceful = false) + public function replaceLeaf(AssetInterface $needle, AssetInterface $replacement, bool $graceful = false): bool { foreach ($this->assets as $i => $asset) { $clone = isset($this->clones[$asset]) ? $this->clones[$asset] : null; - if (in_array($needle, array($asset, $clone), true)) { + if (in_array($needle, [$asset, $clone], true)) { unset($this->clones[$asset]); $this->assets[$i] = $replacement; @@ -158,7 +158,7 @@ public function replaceLeaf(AssetInterface $needle, AssetInterface $replacement, /** * ensureFilter */ - public function ensureFilter(FilterInterface $filter) + public function ensureFilter(FilterInterface $filter): void { $this->filters->ensure($filter); } @@ -166,7 +166,7 @@ public function ensureFilter(FilterInterface $filter) /** * getFilters */ - public function getFilters() + public function getFilters(): array { return $this->filters->all(); } @@ -174,7 +174,7 @@ public function getFilters() /** * clearFilters */ - public function clearFilters() + public function clearFilters(): void { $this->filters->clear(); $this->clones = new SplObjectStorage(); @@ -183,7 +183,7 @@ public function clearFilters() /** * load */ - public function load(?FilterInterface $additionalFilter = null) + public function load(?FilterInterface $additionalFilter = null): void { // loop through leaves and load each asset $parts = []; @@ -198,7 +198,7 @@ public function load(?FilterInterface $additionalFilter = null) /** * dump */ - public function dump(?FilterInterface $additionalFilter = null) + public function dump(?FilterInterface $additionalFilter = null): string { // loop through leaves and dump each asset $parts = []; @@ -212,7 +212,7 @@ public function dump(?FilterInterface $additionalFilter = null) /** * getContent */ - public function getContent() + public function getContent(): ?string { return $this->content; } @@ -220,7 +220,7 @@ public function getContent() /** * setContent */ - public function setContent($content) + public function setContent(?string $content): void { $this->content = $content; } @@ -228,7 +228,7 @@ public function setContent($content) /** * getSourceRoot */ - public function getSourceRoot() + public function getSourceRoot(): ?string { return $this->sourceRoot; } @@ -236,25 +236,28 @@ public function getSourceRoot() /** * getSourcePath */ - public function getSourcePath() + public function getSourcePath(): ?string { + return null; } /** * getSourceDirectory returns the first available source directory, useful * when extracting imports and a singular collection is returned */ - public function getSourceDirectory() + public function getSourceDirectory(): ?string { foreach ($this as $asset) { return $asset->getSourceDirectory(); } + + return null; } /** * getTargetPath */ - public function getTargetPath() + public function getTargetPath(): ?string { return $this->targetPath; } @@ -262,7 +265,7 @@ public function getTargetPath() /** * setTargetPath */ - public function setTargetPath($targetPath) + public function setTargetPath(?string $targetPath): void { $this->targetPath = $targetPath; } @@ -270,12 +273,12 @@ public function setTargetPath($targetPath) /** * getLastModified returns the highest last-modified value of all assets in the current collection. * - * @return integer|null A UNIX timestamp + * @return int|null A UNIX timestamp */ - public function getLastModified() + public function getLastModified(): ?int { if (!count($this->assets)) { - return; + return null; } $mtime = 0; @@ -300,7 +303,7 @@ public function getIterator(): Traversable /** * getVars */ - public function getVars() + public function getVars(): array { return $this->vars; } @@ -308,7 +311,7 @@ public function getVars() /** * setValues */ - public function setValues(array $values) + public function setValues(array $values): void { $this->values = $values; @@ -320,7 +323,7 @@ public function setValues(array $values) /** * getValues */ - public function getValues() + public function getValues(): array { return $this->values; } diff --git a/src/Assetic/Asset/AssetCollectionInterface.php b/src/Assetic/Asset/AssetCollectionInterface.php index 895f62454..e300b0f07 100644 --- a/src/Assetic/Asset/AssetCollectionInterface.php +++ b/src/Assetic/Asset/AssetCollectionInterface.php @@ -12,37 +12,37 @@ interface AssetCollectionInterface extends AssetInterface, \Traversable * * @return array An array of AssetInterface objects */ - public function all(); + public function all(): array; /** * Adds an asset to the current collection. * * @param AssetInterface $asset An asset */ - public function add(AssetInterface $asset); + public function add(AssetInterface $asset): void; /** * Removes a leaf. * * @param AssetInterface $leaf The leaf to remove - * @param Boolean $graceful Whether the failure should return false or throw an exception + * @param bool $graceful Whether the failure should return false or throw an exception * - * @return Boolean Whether the asset has been found + * @return bool Whether the asset has been found * * @throws \InvalidArgumentException If the asset cannot be found */ - public function removeLeaf(AssetInterface $leaf, $graceful = false); + public function removeLeaf(AssetInterface $leaf, bool $graceful = false): bool; /** * Replaces an existing leaf with a new one. * * @param AssetInterface $needle The current asset to replace * @param AssetInterface $replacement The new asset - * @param Boolean $graceful Whether the failure should return false or throw an exception + * @param bool $graceful Whether the failure should return false or throw an exception * - * @return Boolean Whether the asset has been found + * @return bool Whether the asset has been found * * @throws \InvalidArgumentException If the asset cannot be found */ - public function replaceLeaf(AssetInterface $needle, AssetInterface $replacement, $graceful = false); + public function replaceLeaf(AssetInterface $needle, AssetInterface $replacement, bool $graceful = false): bool; } diff --git a/src/Assetic/Asset/AssetInterface.php b/src/Assetic/Asset/AssetInterface.php index f69ee9fdd..0ff32a382 100644 --- a/src/Assetic/Asset/AssetInterface.php +++ b/src/Assetic/Asset/AssetInterface.php @@ -14,19 +14,19 @@ interface AssetInterface * * @param FilterInterface $filter A filter */ - public function ensureFilter(FilterInterface $filter); + public function ensureFilter(FilterInterface $filter): void; /** * Returns an array of filters currently applied. * * @return array An array of filters */ - public function getFilters(); + public function getFilters(): array; /** * Clears all filters from the current asset. */ - public function clearFilters(); + public function clearFilters(): void; /** * Loads the asset into memory and applies load filters. @@ -35,7 +35,7 @@ public function clearFilters(); * * @param FilterInterface $additionalFilter An additional filter */ - public function load(?FilterInterface $additionalFilter = null); + public function load(?FilterInterface $additionalFilter = null): void; /** * Applies dump filters and returns the asset as a string. @@ -51,23 +51,23 @@ public function load(?FilterInterface $additionalFilter = null); * * @return string The filtered content of the current asset */ - public function dump(?FilterInterface $additionalFilter = null); + public function dump(?FilterInterface $additionalFilter = null): string; /** * Returns the loaded content of the current asset. * - * @return string The content + * @return string|null The content */ - public function getContent(); + public function getContent(): ?string; /** * Sets the content of the current asset. * * Filters can use this method to change the content of the asset. * - * @param string $content The asset content + * @param string|null $content The asset content */ - public function setContent($content); + public function setContent(?string $content): void; /** * Returns an absolute path or URL to the source asset's root directory. @@ -83,7 +83,7 @@ public function setContent($content); * * @return string|null The asset's root */ - public function getSourceRoot(); + public function getSourceRoot(): ?string; /** * Returns the relative path for the source asset. @@ -99,7 +99,7 @@ public function getSourceRoot(); * * @return string|null The source asset path */ - public function getSourcePath(); + public function getSourcePath(): ?string; /** * Returns the asset's source directory. @@ -109,47 +109,47 @@ public function getSourcePath(); * * @return string|null The asset's source directory */ - public function getSourceDirectory(); + public function getSourceDirectory(): ?string; /** * Returns the URL for the current asset. * * @return string|null A web URL where the asset will be dumped */ - public function getTargetPath(); + public function getTargetPath(): ?string; /** * Sets the URL for the current asset. * - * @param string $targetPath A web URL where the asset will be dumped + * @param string|null $targetPath A web URL where the asset will be dumped */ - public function setTargetPath($targetPath); + public function setTargetPath(?string $targetPath): void; /** * Returns the time the current asset was last modified. * - * @return integer|null A UNIX timestamp + * @return int|null A UNIX timestamp */ - public function getLastModified(); + public function getLastModified(): ?int; /** * Returns an array of variable names for this asset. * * @return array */ - public function getVars(); + public function getVars(): array; /** * Sets the values for the asset's variables. * * @param array $values */ - public function setValues(array $values); + public function setValues(array $values): void; /** * Returns the current values for this asset. * * @return array an array of strings */ - public function getValues(); + public function getValues(): array; } diff --git a/src/Assetic/Asset/BaseAsset.php b/src/Assetic/Asset/BaseAsset.php index 93888e26e..4d2ed4e7c 100644 --- a/src/Assetic/Asset/BaseAsset.php +++ b/src/Assetic/Asset/BaseAsset.php @@ -1,6 +1,5 @@ filters = new FilterCollection($filters); $this->sourceRoot = $sourceRoot; @@ -76,7 +75,7 @@ public function __construct($filters = array(), $sourceRoot = null, $sourcePath $this->sourceDir = dirname("$sourceRoot/$sourcePath"); } $this->vars = $vars; - $this->values = array(); + $this->values = []; $this->loaded = false; } @@ -85,17 +84,17 @@ public function __clone() $this->filters = clone $this->filters; } - public function ensureFilter(FilterInterface $filter) + public function ensureFilter(FilterInterface $filter): void { $this->filters->ensure($filter); } - public function getFilters() + public function getFilters(): array { return $this->filters->all(); } - public function clearFilters() + public function clearFilters(): void { $this->filters->clear(); } @@ -106,7 +105,7 @@ public function clearFilters() * @param string $content The asset content * @param FilterInterface $additionalFilter An additional filter */ - protected function doLoad($content, ?FilterInterface $additionalFilter = null) + protected function doLoad(?string $content, ?FilterInterface $additionalFilter = null): void { $filter = clone $this->filters; if ($additionalFilter) { @@ -122,7 +121,7 @@ protected function doLoad($content, ?FilterInterface $additionalFilter = null) $this->loaded = true; } - public function dump(?FilterInterface $additionalFilter = null) + public function dump(?FilterInterface $additionalFilter = null): string { if (!$this->loaded) { $this->load(); @@ -136,40 +135,40 @@ public function dump(?FilterInterface $additionalFilter = null) $asset = clone $this; $filter->filterDump($asset); - return $asset->getContent(); + return $asset->getContent() ?? ''; } - public function getContent() + public function getContent(): ?string { return $this->content; } - public function setContent($content) + public function setContent(?string $content): void { $this->content = $content; } - public function getSourceRoot() + public function getSourceRoot(): ?string { return $this->sourceRoot; } - public function getSourcePath() + public function getSourcePath(): ?string { return $this->sourcePath; } - public function getSourceDirectory() + public function getSourceDirectory(): ?string { return $this->sourceDir; } - public function getTargetPath() + public function getTargetPath(): ?string { return $this->targetPath; } - public function setTargetPath($targetPath) + public function setTargetPath(?string $targetPath): void { if ($this->vars) { foreach ($this->vars as $var) { @@ -182,12 +181,12 @@ public function setTargetPath($targetPath) $this->targetPath = $targetPath; } - public function getVars() + public function getVars(): array { return $this->vars; } - public function setValues(array $values) + public function setValues(array $values): void { foreach ($values as $var => $v) { if (!in_array($var, $this->vars, true)) { @@ -199,7 +198,7 @@ public function setValues(array $values) $this->loaded = false; } - public function getValues() + public function getValues(): array { return $this->values; } diff --git a/src/Assetic/Asset/FileAsset.php b/src/Assetic/Asset/FileAsset.php index 5e3c68c3a..65d9234be 100644 --- a/src/Assetic/Asset/FileAsset.php +++ b/src/Assetic/Asset/FileAsset.php @@ -29,7 +29,7 @@ class FileAsset extends BaseAsset * * @throws InvalidArgumentException If the supplied root doesn't match the source when guessing the path */ - public function __construct($source, $filters = [], $sourceRoot = null, $sourcePath = null, array $vars = []) + public function __construct(string $source, array $filters = [], ?string $sourceRoot = null, ?string $sourcePath = null, array $vars = []) { if ($sourceRoot === null) { $sourceRoot = dirname($source); @@ -53,7 +53,7 @@ public function __construct($source, $filters = [], $sourceRoot = null, $sourceP /** * load */ - public function load(?FilterInterface $additionalFilter = null) + public function load(?FilterInterface $additionalFilter = null): void { $source = VarUtils::resolve($this->source, $this->getVars(), $this->getValues()); @@ -67,7 +67,7 @@ public function load(?FilterInterface $additionalFilter = null) /** * getLastModified */ - public function getLastModified() + public function getLastModified(): ?int { $source = VarUtils::resolve($this->source, $this->getVars(), $this->getValues()); diff --git a/src/Assetic/Asset/GlobAsset.php b/src/Assetic/Asset/GlobAsset.php index b0be9faf8..7ed5fd585 100644 --- a/src/Assetic/Asset/GlobAsset.php +++ b/src/Assetic/Asset/GlobAsset.php @@ -12,12 +12,12 @@ class GlobAsset extends AssetCollection { /** - * @var mixed globs + * @var array globs */ protected $globs; /** - * @var mixed initialized + * @var bool initialized */ protected $initialized; @@ -29,7 +29,7 @@ class GlobAsset extends AssetCollection * @param string $root The root directory * @param array $vars */ - public function __construct($globs, $filters = [], $root = null, array $vars = []) + public function __construct($globs, array $filters = [], ?string $root = null, array $vars = []) { $this->globs = (array) $globs; $this->initialized = false; @@ -40,7 +40,7 @@ public function __construct($globs, $filters = [], $root = null, array $vars = [ /** * all */ - public function all() + public function all(): array { if (!$this->initialized) { $this->initialize(); @@ -52,7 +52,7 @@ public function all() /** * load */ - public function load(?FilterInterface $additionalFilter = null) + public function load(?FilterInterface $additionalFilter = null): void { if (!$this->initialized) { $this->initialize(); @@ -64,7 +64,7 @@ public function load(?FilterInterface $additionalFilter = null) /** * dump */ - public function dump(?FilterInterface $additionalFilter = null) + public function dump(?FilterInterface $additionalFilter = null): string { if (!$this->initialized) { $this->initialize(); @@ -76,7 +76,7 @@ public function dump(?FilterInterface $additionalFilter = null) /** * getLastModified */ - public function getLastModified() + public function getLastModified(): ?int { if (!$this->initialized) { $this->initialize(); @@ -100,7 +100,7 @@ public function getIterator(): Traversable /** * setValues */ - public function setValues(array $values) + public function setValues(array $values): void { parent::setValues($values); $this->initialized = false; diff --git a/src/Assetic/Asset/HttpAsset.php b/src/Assetic/Asset/HttpAsset.php index 23490a009..32132ec35 100644 --- a/src/Assetic/Asset/HttpAsset.php +++ b/src/Assetic/Asset/HttpAsset.php @@ -13,12 +13,12 @@ class HttpAsset extends BaseAsset { /** - * @var mixed sourceUrl + * @var string sourceUrl */ protected $sourceUrl; /** - * @var mixed ignoreErrors + * @var bool ignoreErrors */ protected $ignoreErrors; @@ -32,7 +32,7 @@ class HttpAsset extends BaseAsset * * @throws InvalidArgumentException If the first argument is not an URL */ - public function __construct($sourceUrl, $filters = [], $ignoreErrors = false, array $vars = array()) + public function __construct(string $sourceUrl, array $filters = [], bool $ignoreErrors = false, array $vars = []) { if (strpos($sourceUrl, '//') === 0) { $sourceUrl = 'http:'.$sourceUrl; @@ -44,8 +44,8 @@ public function __construct($sourceUrl, $filters = [], $ignoreErrors = false, ar $this->sourceUrl = $sourceUrl; $this->ignoreErrors = $ignoreErrors; - list($scheme, $url) = explode('://', $sourceUrl, 2); - list($host, $path) = explode('/', $url, 2); + [$scheme, $url] = explode('://', $sourceUrl, 2); + [$host, $path] = explode('/', $url, 2); parent::__construct($filters, $scheme.'://'.$host, $path, $vars); } @@ -69,16 +69,18 @@ public function load(?FilterInterface $additionalFilter = null) /** * getLastModified */ - public function getLastModified() + public function getLastModified(): ?int { - if (false !== @file_get_contents($this->sourceUrl, false, stream_context_create(array('http' => array('method' => 'HEAD'))))) { + if (false !== @file_get_contents($this->sourceUrl, false, stream_context_create(['http' => ['method' => 'HEAD']]))) { foreach ($http_response_header as $header) { if (stripos($header, 'Last-Modified: ') === 0) { - list(, $mtime) = explode(':', $header, 2); + [, $mtime] = explode(':', $header, 2); return strtotime(trim($mtime)); } } } + + return null; } } diff --git a/src/Assetic/Asset/StringAsset.php b/src/Assetic/Asset/StringAsset.php index 9540815e9..faae3b25c 100644 --- a/src/Assetic/Asset/StringAsset.php +++ b/src/Assetic/Asset/StringAsset.php @@ -9,7 +9,14 @@ */ class StringAsset extends BaseAsset { + /** + * @var string string + */ private $string; + + /** + * @var int|null lastModified + */ private $lastModified; /** @@ -20,7 +27,7 @@ class StringAsset extends BaseAsset * @param string $sourceRoot The source asset root directory * @param string $sourcePath The source asset path */ - public function __construct($content, $filters = [], $sourceRoot = null, $sourcePath = null) + public function __construct(string $content, array $filters = [], ?string $sourceRoot = null, ?string $sourcePath = null) { $this->string = $content; @@ -30,7 +37,7 @@ public function __construct($content, $filters = [], $sourceRoot = null, $source /** * load */ - public function load(?FilterInterface $additionalFilter = null) + public function load(?FilterInterface $additionalFilter = null): void { $this->doLoad($this->string, $additionalFilter); } @@ -38,7 +45,7 @@ public function load(?FilterInterface $additionalFilter = null) /** * setLastModified */ - public function setLastModified($lastModified) + public function setLastModified(?int $lastModified): void { $this->lastModified = $lastModified; } @@ -46,7 +53,7 @@ public function setLastModified($lastModified) /** * getLastModified */ - public function getLastModified() + public function getLastModified(): ?int { return $this->lastModified; } diff --git a/src/Assetic/AssetManager.php b/src/Assetic/AssetManager.php index 46be4e4ed..28be568ae 100644 --- a/src/Assetic/AssetManager.php +++ b/src/Assetic/AssetManager.php @@ -22,7 +22,7 @@ class AssetManager * @return AssetInterface The asset * @throws InvalidArgumentException If there is no asset by that name */ - public function get($name) + public function get(string $name): AssetInterface { if (!isset($this->assets[$name])) { throw new InvalidArgumentException(sprintf('There is no "%s" asset.', $name)); @@ -35,9 +35,9 @@ public function get($name) * has checks if the current asset manager has a certain asset. * * @param string $name an asset name - * @return Boolean True if the asset has been set, false if not + * @return bool True if the asset has been set, false if not */ - public function has($name) + public function has(string $name): bool { return isset($this->assets[$name]); } @@ -49,7 +49,7 @@ public function has($name) * @param AssetInterface $asset The asset * @throws InvalidArgumentException If the asset name is invalid */ - public function set($name, AssetInterface $asset) + public function set(string $name, AssetInterface $asset): void { if (!ctype_alnum(str_replace('_', '', $name))) { throw new InvalidArgumentException(sprintf('The name "%s" is invalid.', $name)); @@ -63,7 +63,7 @@ public function set($name, AssetInterface $asset) * * @return array An array of asset names */ - public function getNames() + public function getNames(): array { return array_keys($this->assets); } @@ -71,7 +71,7 @@ public function getNames() /** * clear clears all assets. */ - public function clear() + public function clear(): void { $this->assets = []; } diff --git a/src/Assetic/AssetWriter.php b/src/Assetic/AssetWriter.php index 1a909151a..b95b62aa2 100644 --- a/src/Assetic/AssetWriter.php +++ b/src/Assetic/AssetWriter.php @@ -30,7 +30,7 @@ class AssetWriter * @param array $values * @throws InvalidArgumentException */ - public function __construct($dir, array $values = array()) + public function __construct(string $dir, array $values = []) { foreach ($values as $var => $vals) { foreach ($vals as $value) { @@ -47,7 +47,7 @@ public function __construct($dir, array $values = array()) /** * writeManagerAssets */ - public function writeManagerAssets(AssetManager $am) + public function writeManagerAssets(AssetManager $am): void { foreach ($am->getNames() as $name) { $this->writeAsset($am->get($name)); @@ -57,7 +57,7 @@ public function writeManagerAssets(AssetManager $am) /** * writeAsset */ - public function writeAsset(AssetInterface $asset) + public function writeAsset(AssetInterface $asset): void { foreach (VarUtils::getCombinations($asset->getVars(), $this->values) as $combination) { $asset->setValues($combination); @@ -76,7 +76,7 @@ public function writeAsset(AssetInterface $asset) /** * write */ - protected static function write($path, $contents) + protected static function write(string $path, string $contents): void { if (!is_dir($dir = dirname($path)) && false === @mkdir($dir, 0755, true)) { throw new RuntimeException('Unable to create directory '.$dir); diff --git a/src/Assetic/AsseticServiceProvider.php b/src/Assetic/AsseticServiceProvider.php index ce97c02d7..8b7dfe6a1 100644 --- a/src/Assetic/AsseticServiceProvider.php +++ b/src/Assetic/AsseticServiceProvider.php @@ -5,13 +5,16 @@ /** * AsseticServiceProvider + * + * @package october/assetic + * @author Alexey Bobkov, Samuel Georges */ class AsseticServiceProvider extends ServiceProvider implements DeferrableProvider { /** - * register the service provider. + * Register the service provider. */ - public function register() + public function register(): void { $this->app->singleton('assetic', function ($app) { $combiner = new Combiner; @@ -22,10 +25,9 @@ public function register() } /** - * provides the returned services. - * @return array + * Provides the returned services. */ - public function provides() + public function provides(): array { return [ 'assetic', diff --git a/src/Assetic/Cache/ArrayCache.php b/src/Assetic/Cache/ArrayCache.php deleted file mode 100644 index 892fe728a..000000000 --- a/src/Assetic/Cache/ArrayCache.php +++ /dev/null @@ -1,47 +0,0 @@ - - */ -class ArrayCache implements CacheInterface -{ - private $cache = array(); - - /** - * @see CacheInterface::has() - */ - public function has($key) - { - return isset($this->cache[$key]); - } - - /** - * @see CacheInterface::get() - */ - public function get($key) - { - if (!$this->has($key)) { - throw new \RuntimeException('There is no cached value for '.$key); - } - - return $this->cache[$key]; - } - - /** - * @see CacheInterface::set() - */ - public function set($key, $value) - { - $this->cache[$key] = $value; - } - - /** - * @see CacheInterface::remove() - */ - public function remove($key) - { - unset($this->cache[$key]); - } -} diff --git a/src/Assetic/Combiner.php b/src/Assetic/Combiner.php index a0aa3a01a..0a9a82f7e 100644 --- a/src/Assetic/Combiner.php +++ b/src/Assetic/Combiner.php @@ -39,7 +39,7 @@ class Combiner /** * parse */ - public function parse(array $assets, $options = []) + public function parse(array $assets, array $options = []): string { return $this->prepareCombiner($assets, $options)->dump(); } @@ -47,14 +47,12 @@ public function parse(array $assets, $options = []) /** * prepareCombiner before dumping */ - public function prepareCombiner(array $assets, $options = []) + public function prepareCombiner(array $assets, array $options = []): AssetCollection { - extract(array_merge([ - 'targetPath' => null, - 'production' => false, - 'useCache' => true, - 'deepHashKey' => null - ], $options)); + $targetPath = $options['targetPath'] ?? null; + $production = $options['production'] ?? false; + $useCache = $options['useCache'] ?? true; + $deepHashKey = $options['deepHashKey'] ?? null; if ($deepHashKey !== null) { $this->setDeepHashKeyOnFilters($deepHashKey); @@ -102,7 +100,7 @@ public function prepareCombiner(array $assets, $options = []) /** * registerDefaultFilters */ - public function registerDefaultFilters() + public function registerDefaultFilters(): void { // Default JavaScript filters $this->registerFilter('js', new \October\Rain\Assetic\Filter\JavascriptImporter); @@ -121,7 +119,7 @@ public function registerDefaultFilters() /** * setStoragePath */ - public function setStoragePath($path) + public function setStoragePath(?string $path): void { $this->storagePath = $path; } @@ -129,7 +127,7 @@ public function setStoragePath($path) /** * setLocalPath */ - public function setLocalPath($path) + public function setLocalPath(?string $path): void { $this->localPath = $path; } diff --git a/src/Assetic/Exception/Exception.php b/src/Assetic/Exception/Exception.php deleted file mode 100644 index 2f106977b..000000000 --- a/src/Assetic/Exception/Exception.php +++ /dev/null @@ -1,10 +0,0 @@ - - */ -interface Exception -{ -} diff --git a/src/Assetic/Exception/FilterException.php b/src/Assetic/Exception/FilterException.php deleted file mode 100644 index 005403105..000000000 --- a/src/Assetic/Exception/FilterException.php +++ /dev/null @@ -1,43 +0,0 @@ - - */ -class FilterException extends \RuntimeException implements Exception -{ - private $originalMessage; - private $input; - - public function __construct($message, $code = 0, ?\Exception $previous = null) - { - parent::__construct($message, $code, $previous); - - $this->originalMessage = $message; - } - - public function setInput($input) - { - $this->input = $input; - $this->updateMessage(); - - return $this; - } - - public function getInput() - { - return $this->input; - } - - private function updateMessage() - { - $message = $this->originalMessage; - - if (!empty($this->input)) { - $message .= "\n\nInput:\n".$this->input; - } - - $this->message = $message; - } -} diff --git a/src/Assetic/Factory/AssetFactory.php b/src/Assetic/Factory/AssetFactory.php index 73f132567..d2c178b48 100644 --- a/src/Assetic/Factory/AssetFactory.php +++ b/src/Assetic/Factory/AssetFactory.php @@ -148,11 +148,11 @@ public function setFilterManager(FilterManager $fm) public function createAsset($inputs = [], $filters = [], array $options = []) { if (!is_array($inputs)) { - $inputs = array($inputs); + $inputs = [$inputs]; } if (!is_array($filters)) { - $filters = array($filters); + $filters = [$filters]; } if (!isset($options['output'])) { @@ -168,11 +168,11 @@ public function createAsset($inputs = [], $filters = [], array $options = []) } if (!isset($options['root'])) { - $options['root'] = array($this->root); + $options['root'] = [$this->root]; } else { if (!is_array($options['root'])) { - $options['root'] = array($options['root']); + $options['root'] = [$options['root']]; } $options['root'][] = $this->root; @@ -189,7 +189,7 @@ public function createAsset($inputs = [], $filters = [], array $options = []) foreach ($inputs as $input) { if (is_array($input)) { // nested formula - $asset->add(call_user_func_array(array($this, 'createAsset'), $input)); + $asset->add($this->createAsset(...$input)); } else { $asset->add($this->parseInput($input, $options)); @@ -240,9 +240,9 @@ public function createAsset($inputs = [], $filters = [], array $options = []) /** * generateAssetName */ - public function generateAssetName($inputs, $filters, $options = []) + public function generateAssetName($inputs, $filters, $options = []): string { - foreach (array_diff(array_keys($options), array('output', 'debug', 'root')) as $key) { + foreach (array_diff(array_keys($options), ['output', 'debug', 'root']) as $key) { unset($options[$key]); } diff --git a/src/Assetic/Factory/Resource/DirectoryResource.php b/src/Assetic/Factory/Resource/DirectoryResource.php deleted file mode 100644 index 4dd87a8c1..000000000 --- a/src/Assetic/Factory/Resource/DirectoryResource.php +++ /dev/null @@ -1,73 +0,0 @@ - - */ -class DirectoryResource implements IteratorResourceInterface -{ - private $path; - private $pattern; - - /** - * Constructor. - * - * @param string $path A directory path - * @param string $pattern A filename pattern - */ - public function __construct($path, $pattern = null) - { - if (DIRECTORY_SEPARATOR != substr($path, -1)) { - $path .= DIRECTORY_SEPARATOR; - } - - $this->path = $path; - $this->pattern = $pattern; - } - - public function isFresh($timestamp) - { - if (!is_dir($this->path) || filemtime($this->path) > $timestamp) { - return false; - } - - foreach ($this as $resource) { - if (!$resource->isFresh($timestamp)) { - return false; - } - } - - return true; - } - - /** - * Returns the combined content of all inner resources. - */ - public function getContent() - { - $content = array(); - foreach ($this as $resource) { - $content[] = $resource->getContent(); - } - - return implode("\n", $content); - } - - public function __toString() - { - return $this->path; - } - - public function getIterator() - { - return is_dir($this->path) - ? new DirectoryResourceIterator($this->getInnerIterator()) - : new \EmptyIterator(); - } - - protected function getInnerIterator() - { - return new DirectoryResourceFilterIterator(new \RecursiveDirectoryIterator($this->path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS), $this->pattern); - } -} diff --git a/src/Assetic/Factory/Resource/DirectoryResourceFilterIterator.php b/src/Assetic/Factory/Resource/DirectoryResourceFilterIterator.php deleted file mode 100644 index 11a85be03..000000000 --- a/src/Assetic/Factory/Resource/DirectoryResourceFilterIterator.php +++ /dev/null @@ -1,36 +0,0 @@ - - * @access private - */ -class DirectoryResourceFilterIterator extends \RecursiveFilterIterator -{ - protected $pattern; - - public function __construct(\RecursiveDirectoryIterator $iterator, $pattern = null) - { - parent::__construct($iterator); - - $this->pattern = $pattern; - } - - public function accept() - { - $file = $this->current(); - $name = $file->getBasename(); - - if ($file->isDir()) { - return '.' != $name[0]; - } - - return null === $this->pattern || 0 < preg_match($this->pattern, $name); - } - - public function getChildren() - { - return new self(new \RecursiveDirectoryIterator($this->current()->getPathname(), \RecursiveDirectoryIterator::FOLLOW_SYMLINKS), $this->pattern); - } -} diff --git a/src/Assetic/Factory/Resource/DirectoryResourceIterator.php b/src/Assetic/Factory/Resource/DirectoryResourceIterator.php deleted file mode 100644 index bc8da7d6d..000000000 --- a/src/Assetic/Factory/Resource/DirectoryResourceIterator.php +++ /dev/null @@ -1,18 +0,0 @@ - - * @access private - */ -class DirectoryResourceIterator extends \RecursiveIteratorIterator -{ - /** - * current - */ - public function current() - { - return new FileResource(parent::current()->getPathname()); - } -} diff --git a/src/Assetic/Factory/Resource/FileResource.php b/src/Assetic/Factory/Resource/FileResource.php deleted file mode 100644 index 9bc720f46..000000000 --- a/src/Assetic/Factory/Resource/FileResource.php +++ /dev/null @@ -1,36 +0,0 @@ - - */ -class FileResource implements ResourceInterface -{ - protected $path; - - /** - * __construct - * - * @param string $path The path to a file - */ - public function __construct($path) - { - $this->path = $path; - } - - public function isFresh($timestamp) - { - return file_exists($this->path) && filemtime($this->path) <= $timestamp; - } - - public function getContent() - { - return file_exists($this->path) ? file_get_contents($this->path) : ''; - } - - public function __toString() - { - return $this->path; - } -} diff --git a/src/Assetic/Factory/Resource/IteratorResourceInterface.php b/src/Assetic/Factory/Resource/IteratorResourceInterface.php deleted file mode 100644 index 83cdd6478..000000000 --- a/src/Assetic/Factory/Resource/IteratorResourceInterface.php +++ /dev/null @@ -1,10 +0,0 @@ - - */ -interface IteratorResourceInterface extends ResourceInterface, \IteratorAggregate -{ -} diff --git a/src/Assetic/Factory/Resource/ResourceInterface.php b/src/Assetic/Factory/Resource/ResourceInterface.php deleted file mode 100644 index 623911f45..000000000 --- a/src/Assetic/Factory/Resource/ResourceInterface.php +++ /dev/null @@ -1,32 +0,0 @@ - - */ -interface ResourceInterface -{ - /** - * Checks if a timestamp represents the latest resource. - * - * @param integer $timestamp A UNIX timestamp - * - * @return Boolean True if the timestamp is up to date - */ - public function isFresh($timestamp); - - /** - * Returns the content of the resource. - * - * @return string The content - */ - public function getContent(); - - /** - * Returns a unique string for the current resource. - * - * @return string A unique string to identity the current resource - */ - public function __toString(); -} diff --git a/src/Assetic/Filter/BaseCssFilter.php b/src/Assetic/Filter/BaseCssFilter.php index 823d80018..1d1fd3fba 100644 --- a/src/Assetic/Filter/BaseCssFilter.php +++ b/src/Assetic/Filter/BaseCssFilter.php @@ -12,32 +12,32 @@ abstract class BaseCssFilter implements FilterInterface /** * @see CssUtils::filterReferences() */ - protected function filterReferences($content, $callback, $limit = -1, &$count = 0) + protected function filterReferences(string $content, callable $callback): string { - return CssUtils::filterReferences($content, $callback, $limit, $count); + return CssUtils::filterReferences($content, $callback); } /** * @see CssUtils::filterUrls() */ - protected function filterUrls($content, $callback, $limit = -1, &$count = 0) + protected function filterUrls(string $content, callable $callback): string { - return CssUtils::filterUrls($content, $callback, $limit, $count); + return CssUtils::filterUrls($content, $callback); } /** * @see CssUtils::filterImports() */ - protected function filterImports($content, $callback, $limit = -1, &$count = 0, $includeUrl = true) + protected function filterImports(string $content, callable $callback, bool $includeUrl = true): string { - return CssUtils::filterImports($content, $callback, $limit, $count, $includeUrl); + return CssUtils::filterImports($content, $callback, $includeUrl); } /** * @see CssUtils::filterIEFilters() */ - protected function filterIEFilters($content, $callback, $limit = -1, &$count = 0) + protected function filterIEFilters(string $content, callable $callback): string { - return CssUtils::filterIEFilters($content, $callback, $limit, $count); + return CssUtils::filterIEFilters($content, $callback); } } diff --git a/src/Assetic/Filter/CallablesFilter.php b/src/Assetic/Filter/CallablesFilter.php deleted file mode 100644 index 07dd1b7fe..000000000 --- a/src/Assetic/Filter/CallablesFilter.php +++ /dev/null @@ -1,51 +0,0 @@ - - */ -class CallablesFilter implements FilterInterface, DependencyExtractorInterface -{ - private $loader; - private $dumper; - private $extractor; - - /** - * @param callable|null $loader - * @param callable|null $dumper - * @param callable|null $extractor - */ - public function __construct($loader = null, $dumper = null, $extractor = null) - { - $this->loader = $loader; - $this->dumper = $dumper; - $this->extractor = $extractor; - } - - public function filterLoad(AssetInterface $asset) - { - if (null !== $callable = $this->loader) { - $callable($asset); - } - } - - public function filterDump(AssetInterface $asset) - { - if (null !== $callable = $this->dumper) { - $callable($asset); - } - } - - public function getChildren(AssetFactory $factory, $content, $loadPath = null) - { - if (null !== $callable = $this->extractor) { - return $callable($factory, $content, $loadPath); - } - - return array(); - } -} diff --git a/src/Assetic/Filter/CssCacheBustingFilter.php b/src/Assetic/Filter/CssCacheBustingFilter.php deleted file mode 100644 index 1d8d73fab..000000000 --- a/src/Assetic/Filter/CssCacheBustingFilter.php +++ /dev/null @@ -1,54 +0,0 @@ - - */ -class CssCacheBustingFilter extends BaseCssFilter -{ - private $version; - private $format = '%s?%s'; - - public function setVersion($version) - { - $this->version = $version; - } - - public function setFormat($versionFormat) - { - $this->format = $versionFormat; - } - - public function filterLoad(AssetInterface $asset) - { - } - - public function filterDump(AssetInterface $asset) - { - if (!$this->version) { - return; - } - - $version = $this->version; - $format = $this->format; - - $asset->setContent($this->filterReferences( - $asset->getContent(), - function ($matches) use ($version, $format) { - if (0 === strpos($matches['url'], 'data:')) { - return $matches[0]; - } - - return str_replace( - $matches['url'], - sprintf($format, $matches['url'], $version), - $matches[0] - ); - } - )); - } -} diff --git a/src/Assetic/Filter/CssImportFilter.php b/src/Assetic/Filter/CssImportFilter.php index 0e41097b3..217f16353 100644 --- a/src/Assetic/Filter/CssImportFilter.php +++ b/src/Assetic/Filter/CssImportFilter.php @@ -15,12 +15,12 @@ class CssImportFilter extends BaseCssFilter implements HashableInterface, DependencyExtractorInterface { /** - * @var mixed importFilter + * @var FilterInterface|null importFilter */ protected $importFilter; /** - * @var string lastHash + * @var string|null lastHash */ protected $lastHash; @@ -37,7 +37,7 @@ public function __construct(?FilterInterface $importFilter = null) /** * filterLoad */ - public function filterLoad(AssetInterface $asset) + public function filterLoad(AssetInterface $asset): void { $importFilter = $this->importFilter; $sourceRoot = $asset->getSourceRoot(); @@ -52,13 +52,13 @@ public function filterLoad(AssetInterface $asset) // Absolute if (strpos($matches['url'], '://') !== false) { - list($importScheme, $tmp) = explode('://', $matches['url'], 2); - list($importHost, $importPath) = explode('/', $tmp, 2); + [$importScheme, $tmp] = explode('://', $matches['url'], 2); + [$importHost, $importPath] = explode('/', $tmp, 2); $importRoot = $importScheme.'://'.$importHost; } // Protocol-relative elseif (strpos($matches['url'], '//') === 0) { - list($importHost, $importPath) = explode('/', substr($matches['url'], 2), 2); + [$importHost, $importPath] = explode('/', substr($matches['url'], 2), 2); $importRoot = '//'.$importHost; } // Root-relative @@ -107,7 +107,7 @@ public function filterLoad(AssetInterface $asset) /** * filterDump */ - public function filterDump(AssetInterface $asset) + public function filterDump(AssetInterface $asset): void { } diff --git a/src/Assetic/Filter/CssMinFilter.php b/src/Assetic/Filter/CssMinFilter.php index dbea87ff3..9b341d6ca 100644 --- a/src/Assetic/Filter/CssMinFilter.php +++ b/src/Assetic/Filter/CssMinFilter.php @@ -15,8 +15,8 @@ class CssMinFilter implements FilterInterface public function __construct() { - $this->filters = array(); - $this->plugins = array(); + $this->filters = []; + $this->plugins = []; } public function setFilters(array $filters) @@ -39,11 +39,11 @@ public function setPlugin($name, $value) $this->plugins[$name] = $value; } - public function filterLoad(AssetInterface $asset) + public function filterLoad(AssetInterface $asset): void { } - public function filterDump(AssetInterface $asset) + public function filterDump(AssetInterface $asset): void { $filters = $this->filters; $plugins = $this->plugins; diff --git a/src/Assetic/Filter/CssRewriteFilter.php b/src/Assetic/Filter/CssRewriteFilter.php index 1465e5f67..62b19281c 100644 --- a/src/Assetic/Filter/CssRewriteFilter.php +++ b/src/Assetic/Filter/CssRewriteFilter.php @@ -12,14 +12,14 @@ class CssRewriteFilter extends BaseCssFilter /** * filterLoad */ - public function filterLoad(AssetInterface $asset) + public function filterLoad(AssetInterface $asset): void { } /** * filterDump */ - public function filterDump(AssetInterface $asset) + public function filterDump(AssetInterface $asset): void { $sourceBase = $asset->getSourceRoot(); $sourcePath = $asset->getSourcePath(); @@ -31,8 +31,8 @@ public function filterDump(AssetInterface $asset) // Learn how to get from the target back to the source if (strpos($sourceBase, '://') !== false) { - list($scheme, $url) = explode('://', $sourceBase.'/'.$sourcePath, 2); - list($host, $path) = explode('/', $url, 2); + [$scheme, $url] = explode('://', $sourceBase.'/'.$sourcePath, 2); + [$host, $path] = explode('/', $url, 2); $host = $scheme.'://'.$host.'/'; $path = false === strpos($path, '/') ? '' : dirname($path); diff --git a/src/Assetic/Filter/FilterCollection.php b/src/Assetic/Filter/FilterCollection.php index d5dc7fbc1..a67431998 100644 --- a/src/Assetic/Filter/FilterCollection.php +++ b/src/Assetic/Filter/FilterCollection.php @@ -10,9 +10,15 @@ */ class FilterCollection implements FilterInterface, \IteratorAggregate, \Countable { - private $filters = array(); + /** + * @var array filters + */ + protected $filters = []; - public function __construct($filters = array()) + /** + * __construct + */ + public function __construct(array $filters = []) { foreach ($filters as $filter) { $this->ensure($filter); @@ -25,7 +31,7 @@ public function __construct($filters = array()) * If the supplied filter is another filter collection, each of its * filters will be checked. */ - public function ensure(FilterInterface $filter) + public function ensure(FilterInterface $filter): void { if ($filter instanceof \Traversable) { foreach ($filter as $f) { @@ -36,35 +42,53 @@ public function ensure(FilterInterface $filter) } } - public function all() + /** + * all + */ + public function all(): array { return $this->filters; } - public function clear() + /** + * clear + */ + public function clear(): void { - $this->filters = array(); + $this->filters = []; } - public function filterLoad(AssetInterface $asset) + /** + * filterLoad + */ + public function filterLoad(AssetInterface $asset): void { foreach ($this->filters as $filter) { $filter->filterLoad($asset); } } - public function filterDump(AssetInterface $asset) + /** + * filterDump + */ + public function filterDump(AssetInterface $asset): void { foreach ($this->filters as $filter) { $filter->filterDump($asset); } } + /** + * getIterator + */ public function getIterator(): Traversable { return new \ArrayIterator($this->filters); } + /** + * count + */ public function count(): int { return count($this->filters); diff --git a/src/Assetic/Filter/FilterInterface.php b/src/Assetic/Filter/FilterInterface.php index 3cbbc6da1..fb4daead5 100644 --- a/src/Assetic/Filter/FilterInterface.php +++ b/src/Assetic/Filter/FilterInterface.php @@ -14,12 +14,12 @@ interface FilterInterface * * @param AssetInterface $asset An asset */ - public function filterLoad(AssetInterface $asset); + public function filterLoad(AssetInterface $asset): void; /** * Filters an asset just before it's dumped. * * @param AssetInterface $asset An asset */ - public function filterDump(AssetInterface $asset); + public function filterDump(AssetInterface $asset): void; } diff --git a/src/Assetic/Filter/JSMinFilter.php b/src/Assetic/Filter/JSMinFilter.php index 668070643..19ba8e609 100644 --- a/src/Assetic/Filter/JSMinFilter.php +++ b/src/Assetic/Filter/JSMinFilter.php @@ -17,7 +17,7 @@ class JSMinFilter implements FilterInterface /** * filterLoad */ - public function filterLoad(AssetInterface $asset) + public function filterLoad(AssetInterface $asset): void { } @@ -25,7 +25,7 @@ public function filterLoad(AssetInterface $asset) * filterDump will use JSMin to minify the asset and checks the filename * for "min.js" to issues arising from double minification. */ - public function filterDump(AssetInterface $asset) + public function filterDump(AssetInterface $asset): void { $contents = $asset->getContent(); diff --git a/src/Assetic/Filter/JSqueezeFilter.php b/src/Assetic/Filter/JSqueezeFilter.php index ef0214483..70c4053ae 100644 --- a/src/Assetic/Filter/JSqueezeFilter.php +++ b/src/Assetic/Filter/JSqueezeFilter.php @@ -11,12 +11,34 @@ */ class JSqueezeFilter implements FilterInterface { - private $singleLine = true; - private $keepImportantComments = true; - private $className; - private $specialVarRx = false; - private $defaultRx; + /** + * @var mixed singleLine + */ + protected $singleLine = true; + /** + * @var bool keepImportantComments + */ + protected $keepImportantComments = true; + + /** + * @var mixed className + */ + protected $className; + + /** + * @var bool specialVarRx + */ + protected $specialVarRx = false; + + /** + * @var mixed defaultRx + */ + protected $defaultRx; + + /** + * __construct + */ public function __construct() { // JSqueeze is namespaced since 2.x, this works with both 1.x and 2.x @@ -50,11 +72,11 @@ public function keepImportantComments($bool) $this->keepImportantComments = (bool) $bool; } - public function filterLoad(AssetInterface $asset) + public function filterLoad(AssetInterface $asset): void { } - public function filterDump(AssetInterface $asset) + public function filterDump(AssetInterface $asset): void { $parser = new $this->className(); $asset->setContent($parser->squeeze( diff --git a/src/Assetic/Filter/JavascriptImporter.php b/src/Assetic/Filter/JavascriptImporter.php index 478fe0b30..f8efcff1b 100644 --- a/src/Assetic/Filter/JavascriptImporter.php +++ b/src/Assetic/Filter/JavascriptImporter.php @@ -7,7 +7,7 @@ /** * JavascriptImporter importer JS Filter - * Class used to import referenced javascript files. + * Class used to import referenced javascript files, inside comments. * * =include library/jquery.js; * =require library/jquery.js; @@ -17,7 +17,6 @@ */ class JavascriptImporter implements FilterInterface { - /** * @var string Location of where the processed JS script resides. */ @@ -41,14 +40,14 @@ class JavascriptImporter implements FilterInterface /** * filterLoad */ - public function filterLoad(AssetInterface $asset) + public function filterLoad(AssetInterface $asset): void { } /** * filterDump */ - public function filterDump(AssetInterface $asset) + public function filterDump(AssetInterface $asset): void { $this->scriptPath = dirname($asset->getSourceRoot() . '/' . $asset->getSourcePath()); $this->scriptFile = basename($asset->getSourcePath()); @@ -58,10 +57,10 @@ public function filterDump(AssetInterface $asset) /** * Process JS imports inside a string of javascript - * @param $content string JS code to process. + * @param string $content JS code to process. * @return string Processed JS. */ - protected function parse($content) + protected function parse(string $content): string { $macros = []; $imported = ''; @@ -92,7 +91,7 @@ protected function parse($content) /** * Directive to process script includes */ - protected function directiveInclude($data, $required = false) + protected function directiveInclude(string $data, bool $required = false): string { $require = explode(',', $data); $result = ""; @@ -157,7 +156,7 @@ protected function directiveInclude($data, $required = false) /** * Directive to process mandatory script includes */ - protected function directiveRequire($data) + protected function directiveRequire(string $data): string { return $this->directiveInclude($data, true); } @@ -165,7 +164,7 @@ protected function directiveRequire($data) /** * Directive to define and replace variables */ - protected function directiveDefine($data) + protected function directiveDefine(string $data): string { if (preg_match('@([^\\s]*)\\s+(.*)@', $data, $matches)) { // str_replace($matches[1], $matches[2], $context); diff --git a/src/Assetic/Filter/LessCompiler.php b/src/Assetic/Filter/LessCompiler.php index 1db6ff61d..91951a5ec 100644 --- a/src/Assetic/Filter/LessCompiler.php +++ b/src/Assetic/Filter/LessCompiler.php @@ -38,7 +38,7 @@ public function setPresets(array $presets) /** * filterLoad */ - public function filterLoad(AssetInterface $asset) + public function filterLoad(AssetInterface $asset): void { $parser = new Less_Parser(); @@ -56,7 +56,7 @@ public function filterLoad(AssetInterface $asset) /** * filterDump */ - public function filterDump(AssetInterface $asset) + public function filterDump(AssetInterface $asset): void { } diff --git a/src/Assetic/Filter/LessphpFilter.php b/src/Assetic/Filter/LessphpFilter.php index defb0fae1..15522a887 100644 --- a/src/Assetic/Filter/LessphpFilter.php +++ b/src/Assetic/Filter/LessphpFilter.php @@ -76,7 +76,7 @@ public function setPreserveComments($preserveComments) $this->preserveComments = $preserveComments; } - public function filterLoad(AssetInterface $asset) + public function filterLoad(AssetInterface $asset): void { $lc = new \lessc(); if ($dir = $asset->getSourceDirectory()) { @@ -111,7 +111,7 @@ public function registerFunction($name, $callable) $this->customFunctions[$name] = $callable; } - public function filterDump(AssetInterface $asset) + public function filterDump(AssetInterface $asset): void { } diff --git a/src/Assetic/Filter/ScssCompiler.php b/src/Assetic/Filter/ScssCompiler.php index ded0189bb..a3d32e503 100644 --- a/src/Assetic/Filter/ScssCompiler.php +++ b/src/Assetic/Filter/ScssCompiler.php @@ -49,7 +49,7 @@ public function addVariable($variable) $this->variables[] = $variable; } - public function filterLoad(AssetInterface $asset) + public function filterLoad(AssetInterface $asset): void { parent::setVariables($this->variables); parent::filterLoad($asset); diff --git a/src/Assetic/Filter/ScssphpFilter.php b/src/Assetic/Filter/ScssphpFilter.php index bb2bc3244..d2f682cfe 100644 --- a/src/Assetic/Filter/ScssphpFilter.php +++ b/src/Assetic/Filter/ScssphpFilter.php @@ -20,42 +20,78 @@ */ class ScssphpFilter implements DependencyExtractorInterface { - private $importPaths = []; - private $customFunctions = []; - private $formatter; - private $variables = []; + /** + * @var array importPaths + */ + protected $importPaths = []; + + /** + * @var array customFunctions + */ + protected $customFunctions = []; + + /** + * @var mixed formatter + */ + protected $formatter; + /** + * @var array variables + */ + protected $variables = []; + + /** + * setFormatter + */ public function setFormatter($formatter) { $this->formatter = $formatter; } + /** + * setVariables + */ public function setVariables(array $variables) { $this->variables = $variables; } + /** + * addVariable + */ public function addVariable($variable) { $this->variables[] = $variable; } + /** + * setImportPaths + */ public function setImportPaths(array $paths) { $this->importPaths = $paths; } + /** + * addImportPath + */ public function addImportPath($path) { $this->importPaths[] = $path; } + /** + * registerFunction + */ public function registerFunction($name, $callable) { $this->customFunctions[$name] = $callable; } - public function filterLoad(AssetInterface $asset) + /** + * filterLoad + */ + public function filterLoad(AssetInterface $asset): void { $sc = new Compiler(); @@ -101,10 +137,16 @@ public function filterLoad(AssetInterface $asset) $asset->setContent($result->getCss()); } - public function filterDump(AssetInterface $asset) + /** + * filterDump + */ + public function filterDump(AssetInterface $asset): void { } + /** + * getChildren + */ public function getChildren(AssetFactory $factory, $content, $loadPath = null) { $sc = new Compiler(); diff --git a/src/Assetic/Filter/StylesheetMinify.php b/src/Assetic/Filter/StylesheetMinify.php index c4da45a65..329be65c4 100644 --- a/src/Assetic/Filter/StylesheetMinify.php +++ b/src/Assetic/Filter/StylesheetMinify.php @@ -12,21 +12,27 @@ */ class StylesheetMinify implements FilterInterface { - public function filterLoad(AssetInterface $asset) + /** + * filterLoad + */ + public function filterLoad(AssetInterface $asset): void { } - public function filterDump(AssetInterface $asset) + /** + * filterDump + */ + public function filterDump(AssetInterface $asset): void { $asset->setContent($this->minify($asset->getContent())); } /** - * minify CSS - * @var $css string CSS code to minify. + * Minify CSS + * @param string $css CSS code to minify. * @return string Minified CSS. */ - protected function minify($css) + protected function minify(string $css): string { // Normalize whitespace in a smart way $css = preg_replace('/\s{2,}/', ' ', $css); diff --git a/src/Assetic/FilterManager.php b/src/Assetic/FilterManager.php index 866d28441..cce50ebd7 100644 --- a/src/Assetic/FilterManager.php +++ b/src/Assetic/FilterManager.php @@ -19,7 +19,7 @@ class FilterManager /** * set */ - public function set($alias, FilterInterface $filter) + public function set(string $alias, FilterInterface $filter): void { $this->checkName($alias); @@ -29,7 +29,7 @@ public function set($alias, FilterInterface $filter) /** * get */ - public function get($alias) + public function get(string $alias): FilterInterface { if (!isset($this->filters[$alias])) { throw new InvalidArgumentException(sprintf('There is no "%s" filter.', $alias)); @@ -41,7 +41,7 @@ public function get($alias) /** * has */ - public function has($alias) + public function has(string $alias): bool { return isset($this->filters[$alias]); } @@ -49,7 +49,7 @@ public function has($alias) /** * getNames */ - public function getNames() + public function getNames(): array { return array_keys($this->filters); } @@ -59,7 +59,7 @@ public function getNames() * @param string $name An asset name candidate * @throws InvalidArgumentException If the asset name is invalid */ - protected function checkName($name) + protected function checkName(string $name): void { if (!ctype_alnum(str_replace('_', '', $name))) { throw new InvalidArgumentException(sprintf('The name "%s" is invalid.', $name)); diff --git a/src/Assetic/Util/CssUtils.php b/src/Assetic/Util/CssUtils.php index 3ca73ebbf..2f940666c 100644 --- a/src/Assetic/Util/CssUtils.php +++ b/src/Assetic/Util/CssUtils.php @@ -1,6 +1,5 @@ $vals) { if (!in_array($var, $vars, true)) { continue; @@ -55,7 +54,7 @@ public static function getCombinations(array $vars, array $values) for ($i = array_product($nbValues), $c = $i * 2; $i < $c; $i++) { $k = $i; - $combination = array(); + $combination = []; foreach ($vars as $var) { $combination[$var] = $values[$var][$k % $nbValues[$var]]; diff --git a/src/Combine/Combiner.php b/src/Combine/Combiner.php deleted file mode 100644 index 5754c228d..000000000 --- a/src/Combine/Combiner.php +++ /dev/null @@ -1,50 +0,0 @@ -minify($text); - } - - /** - * compileLess - */ - public function compileLess(array $text, $options = []) - { - return (new LessCompile)->compile($text, $options); - } - - /** - * compileScss - */ - public function compileScss(array $text, $options = []) - { - return (new ScssCompile)->compile($text, $options); - } - - /** - * minifyJs - */ - public function minifyJs(array $text, $options = []) - { - return (new JavascriptMinify)->minify($text); - } - - /** - * compileJs - */ - public function compileJs(array $text, $options = []) - { - return (new JsCompile)->compile($text, $options); - } -} diff --git a/src/Combine/CombinerServiceProvider.php b/src/Combine/CombinerServiceProvider.php deleted file mode 100644 index 3e62b066f..000000000 --- a/src/Combine/CombinerServiceProvider.php +++ /dev/null @@ -1,34 +0,0 @@ -app->singleton('combiner', function ($app) { - return new Combiner; - }); - } - - /** - * provides the returned services. - * @return array - */ - public function provides() - { - return [ - 'combiner', - ]; - } -} diff --git a/src/Combine/JavascriptMinify.php b/src/Combine/JavascriptMinify.php deleted file mode 100644 index fe34957ca..000000000 --- a/src/Combine/JavascriptMinify.php +++ /dev/null @@ -1,30 +0,0 @@ -minify(file_get_contents($path)); - } -} diff --git a/src/Combine/JsCompile.php b/src/Combine/JsCompile.php deleted file mode 100644 index c89125056..000000000 --- a/src/Combine/JsCompile.php +++ /dev/null @@ -1,157 +0,0 @@ - null - ], $options)); - - if (!$basePath) { - throw new Exception('You must specify a base path'); - } - - $this->basePath = $basePath; - $this->includedFiles = []; - - return $this->parse($js); - } - - /** - * compileFile - */ - public function compileFile($path, $options = []) - { - return $this->compile(file_get_contents($path), $options); - } - - /** - * Process JS imports inside a string of javascript - * @param $content string JS code to process. - * @return string Processed JS. - */ - protected function parse($content) - { - $imported = ''; - - // Look for: /* comments */ - if (!preg_match_all('@/\*(.*)\*/@msU', $content, $matches)) { - return $content; - } - - foreach ($matches[1] as $macro) { - // Look for: =include something - if (!preg_match_all('/=([^\\s]*)\\s(.*)\n/', $macro, $matches2)) { - continue; - } - - foreach ($matches2[1] as $index => $macroName) { - $method = 'directive' . ucfirst(strtolower($macroName)); - - if (method_exists($this, $method)) { - $imported .= $this->$method($matches2[2][$index]); - } - } - } - - return $imported . $content; - } - - /** - * directiveInclude to process script includes - */ - protected function directiveInclude($data, $required = false) - { - $require = explode(',', $data); - $result = ""; - - foreach ($require as $script) { - $script = trim($script); - - if (!File::extension($script)) { - $script = $script . '.js'; - } - - $scriptPath = realpath($this->basePath . '/' . $script); - if (!File::isFile($scriptPath)) { - $errorMsg = sprintf("File '%s' not found.", $script); - if ($required) { - throw new RuntimeException($errorMsg); - } - - $result .= '/* ' . $errorMsg . ' */' . PHP_EOL; - continue; - } - - // Exclude duplicates - if (in_array($script, $this->includedFiles)) { - continue; - } - - $this->includedFiles[] = $script; - - // Nested parsing - $oldScriptPath = $this->basePath; - $this->basePath = dirname($scriptPath); - $content = File::get($scriptPath); - $content = $this->parse($content) . PHP_EOL; - $this->basePath = $oldScriptPath; - - // Parse in "magic constants" - $content = str_replace( - ['__DATE__', '__FILE__'], - [date("D M j G:i:s T Y"), $script], - $content - ); - - $result .= $content; - } - - return $result; - } - - /** - * directiveRequire to process mandatory script includes - */ - protected function directiveRequire($data) - { - return $this->directiveInclude($data, true); - } - - /** - * directiveDefine to define and replace variables - */ - protected function directiveDefine($data) - { - if (preg_match('@([^\\s]*)\\s+(.*)@', $data, $matches)) { - // str_replace($matches[1], $matches[2], $context); - $this->definedVars[] = [$matches[1], $matches[2]]; - } - - return ''; - } -} diff --git a/src/Combine/LessCompile.php b/src/Combine/LessCompile.php deleted file mode 100644 index 55a9481a2..000000000 --- a/src/Combine/LessCompile.php +++ /dev/null @@ -1,44 +0,0 @@ - null, - 'compress' => false, - ], $options)); - - $parser = new Less_Parser([ - 'compress' => (bool) $compress - ]); - - $parser->parse($less); - - // Set the LESS variables after parsing to override them - if ($vars) { - $parser->ModifyVars($vars); - } - - return $parser->getCss(); - } - - /** - * compileFile - */ - public function compileFile($path, $options = []) - { - return $this->compile(file_get_contents($path), $options); - } -} diff --git a/src/Combine/ScssCompile.php b/src/Combine/ScssCompile.php deleted file mode 100644 index 8174518be..000000000 --- a/src/Combine/ScssCompile.php +++ /dev/null @@ -1,41 +0,0 @@ - null, - 'compress' => false, // @todo - ], $options)); - - $parser = new Compiler(); - - if ($vars) { - $parser->addVariables($vars); - } - - $result = $parser->compileString($scss); - - return $result->getCss(); - } - - /** - * compileFile - */ - public function compileFile($path, $options = []) - { - return $this->compile(file_get_contents($path), $options); - } -} diff --git a/src/Combine/StylesheetMinify.php b/src/Combine/StylesheetMinify.php deleted file mode 100644 index dcb0d3c9d..000000000 --- a/src/Combine/StylesheetMinify.php +++ /dev/null @@ -1,56 +0,0 @@ -, but not after !*/ - $css = preg_replace('/(,|:|;|\{|}|[^!]\*\/|>) /', '$1', $css); - - // Remove space before , ; { } > - $css = preg_replace('/ (,|;|\{|}|>)/', '$1', $css); - - // Remove newline before } > - $css = preg_replace('/(\r\n|\r|\n)(})/', '$2', $css); - - // Remove trailing zeros from float numbers preceded by : or a white-space - // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px - $css = preg_replace('/((?minify(file_get_contents($path)); - } -} diff --git a/src/Database/Concerns/HasAttributes.php b/src/Database/Concerns/HasAttributes.php index fd5361b21..e875fcd3e 100644 --- a/src/Database/Concerns/HasAttributes.php +++ b/src/Database/Concerns/HasAttributes.php @@ -75,13 +75,36 @@ public function getAttribute($key) return $this->getAttributeValue($key); } + return $this->getRelationValue($key); + } + + /** + * getRelationValue gets a relationship value from a method. + * Overridden from {@link Eloquent} to implement recognition of the relation + * using October Rain's property-based relation definitions. + * @param string $key + * @return mixed + */ + public function getRelationValue($key) + { if ($this->relationLoaded($key)) { return $this->relations[$key]; } - if ($this->hasRelation($key)) { - return $this->getRelationshipFromMethod($key); + // Check both October and Laravel + if (!$this->hasRelation($key) && !$this->isRelation($key)) { + return; + } + + if ($this->attemptToAutoloadRelation($key)) { + return $this->relations[$key]; } + + if ($this->preventsLazyLoading) { + $this->handleLazyLoadingViolation($key); + } + + return $this->getRelationshipFromMethod($key); } /** diff --git a/src/Database/ModelException.php b/src/Database/ModelException.php index cab434d91..4027b139f 100644 --- a/src/Database/ModelException.php +++ b/src/Database/ModelException.php @@ -16,13 +16,13 @@ class ModelException extends ValidationException protected $model; /** - * __construct recevies the troublesome model + * __construct receives the troublesome model */ public function __construct(Model $model) { + parent::__construct($model->errors()); + $this->model = $model; - $this->errors = $model->errors(); - $this->evalErrors(); } /** diff --git a/src/Exception/AjaxException.php b/src/Exception/AjaxException.php index 91478897e..8255c47cd 100644 --- a/src/Exception/AjaxException.php +++ b/src/Exception/AjaxException.php @@ -1,5 +1,7 @@ contents[$key] = $val; } + + /** + * toAjaxData + */ + public function toAjaxData(): array + { + return (array) $this->contents; + } } diff --git a/src/Exception/ValidationException.php b/src/Exception/ValidationException.php index da2644165..764fe4db2 100644 --- a/src/Exception/ValidationException.php +++ b/src/Exception/ValidationException.php @@ -3,6 +3,7 @@ use Validator as ValidatorFacade; use Illuminate\Validation\ValidationException as ValidationExceptionBase; use Illuminate\Validation\Validator; +use Illuminate\Support\MessageBag; use InvalidArgumentException; /** @@ -35,8 +36,6 @@ public function __construct($validation) { parent::__construct($this->resolveToValidator($validation)); - $this->errors = $this->validator->errors(); - $this->evalErrors(); } @@ -55,6 +54,10 @@ protected function resolveToValidator($validation) $validator = ValidatorFacade::make([], []); $validator->errors()->merge($validation); } + elseif ($validation instanceof MessageBag) { + $validator = ValidatorFacade::make([], []); + $validator->errors()->merge($validation->messages()); + } if (!$validator instanceof Validator) { throw new InvalidArgumentException('ValidationException constructor requires instance of Validator or array'); @@ -70,12 +73,12 @@ protected function evalErrors() { $this->fields = []; - foreach ($this->errors->getMessages() as $field => $messages) { + foreach ($this->errors() as $field => $messages) { $fieldName = implode('.', array_merge($this->fieldPrefix, [$field])); $this->fields[$fieldName] = (array) $messages; } - $this->message = $this->errors->first(); + $this->message = $this->getErrors()->first(); } /** @@ -84,11 +87,11 @@ protected function evalErrors() */ public function getErrors() { - return $this->errors; + return $this->validator->errors(); } /** - * getFields returns invalid fields + * @deprecated use ->errors() */ public function getFields() { @@ -103,5 +106,7 @@ public function setFieldPrefix(array $prefix) $this->fieldPrefix = array_filter($prefix, 'strlen'); $this->evalErrors(); + + $this->validator = $this->resolveToValidator($this->fields); } } diff --git a/src/Halcyon/Exception/ModelException.php b/src/Halcyon/Exception/ModelException.php index f1bee9879..8975efd1e 100644 --- a/src/Halcyon/Exception/ModelException.php +++ b/src/Halcyon/Exception/ModelException.php @@ -2,6 +2,8 @@ use October\Rain\Halcyon\Model; use October\Rain\Exception\ValidationException; +use Illuminate\Support\MessageBag; +use Exception; /** * ModelException used when validation fails, contains the invalid model for easy analysis @@ -16,16 +18,41 @@ class ModelException extends ValidationException */ protected $model; + /** + * @var MessageBag validationErrors + */ + protected $validationErrors; + /** * __construct receives the invalid model */ public function __construct(Model $model) { $this->model = $model; - $this->errors = $model->errors(); + $this->validationErrors = $model->errors(); + + // Bypass parent constructor to avoid Validator facade dependency + Exception::__construct($this->validationErrors->first()); + $this->evalErrors(); } + /** + * errors returns validation errors + */ + public function errors(): array + { + return $this->validationErrors->messages(); + } + + /** + * getErrors returns the message bag instance + */ + public function getErrors(): MessageBag + { + return $this->validationErrors; + } + /** * getModel returns the model with invalid attributes */ diff --git a/src/Html/HtmlBuilder.php b/src/Html/HtmlBuilder.php index e83f1bf41..0db1f3125 100644 --- a/src/Html/HtmlBuilder.php +++ b/src/Html/HtmlBuilder.php @@ -444,7 +444,7 @@ public static function limit($html, $maxLength = 100, $end = '...') break; } - if ($tag[0] === '&' || ord($tag) >= 0x80) { + if ($tag[0] === '&' || ord($tag[0]) >= 0x80) { $result .= $tag; $printedLength++; } diff --git a/src/Installer/GatewayClient.php b/src/Installer/GatewayClient.php new file mode 100644 index 000000000..9a29c77e1 --- /dev/null +++ b/src/Installer/GatewayClient.php @@ -0,0 +1,618 @@ +setCredentials('your-api-key', 'your-api-secret'); + * $projects = $client->listProjects(); + * + * // Project-level operations (uses license key or auth.json hash) + * $client = (new GatewayClient)->setProjectHash('your-license-key-or-hash'); + * $updates = $client->checkForUpdates(['plugins' => [...], 'themes' => [...]]); + * + * // Open operations (no auth) + * $client = new GatewayClient; + * $result = $client->validateLicense('XXXX-XXXX-XXXX-XXXX'); + * + */ +class GatewayClient +{ + /** + * @var string API base URL + */ + const API_BASE_URL = 'https://api.octobercms.com'; + + /** + * @var string API version prefix + */ + const API_VERSION = 'v1'; + + /** + * @var string|null apiKey for HMAC authentication + */ + protected $apiKey; + + /** + * @var string|null apiSecret for HMAC authentication + */ + protected $apiSecret; + + /** + * @var string|null projectHash for project-level authentication (license key or auth.json hash) + */ + protected $projectHash; + + /** + * @var int timeout in seconds + */ + protected $timeout = 30; + + /** + * @var array lastResponseHeaders + */ + protected $lastResponseHeaders = []; + + /** + * @var int lastStatusCode + */ + protected $lastStatusCode = 0; + + /** + * @var string|null lastErrorCode from API response + */ + protected $lastErrorCode; + + // ========================================================================= + // CONFIGURATION + // ========================================================================= + + /** + * setCredentials for account-level authentication + */ + public function setCredentials(string $apiKey, string $apiSecret): self + { + $this->apiKey = $apiKey; + $this->apiSecret = $apiSecret; + return $this; + } + + /** + * setProjectHash for project-level authentication + * + * @param string $projectHash License key or auth.json hash (bind code) + */ + public function setProjectHash(string $projectHash): self + { + $this->projectHash = $projectHash; + return $this; + } + + /** + * setTimeout for requests + */ + public function setTimeout(int $seconds): self + { + $this->timeout = $seconds; + return $this; + } + + // ========================================================================= + // ACCOUNT-LEVEL ENDPOINTS (HMAC Auth Required) + // ========================================================================= + + /** + * createProject creates a new project + * + * @param string $name Project name + * @param array $options Optional: description + * @return array Project data including project_id and license_key + * @throws Exception + */ + public function createProject(string $name, array $options = []): array + { + return $this->requestWithHmac('projects/create', array_merge( + ['name' => $name], + $options + )); + } + + /** + * updateProject updates an existing project + * + * @param int $projectId Project ID + * @param array $options Optional: name, description + * @return array Updated project data + * @throws Exception + */ + public function updateProject(int $projectId, array $options = []): array + { + return $this->requestWithHmac('projects/update', array_merge( + ['project_id' => $projectId], + $options + )); + } + + /** + * deleteProject deletes a project + * + * @param int $projectId Project ID + * @return array Confirmation + * @throws Exception + */ + public function deleteProject(int $projectId): array + { + return $this->requestWithHmac('projects/delete', [ + 'project_id' => $projectId, + ]); + } + + /** + * listProjects returns all projects for the account + * + * @param int $limit Results per page (1-100) + * @param string|null $cursor Pagination cursor + * @return array Projects list with next_cursor + * @throws Exception + */ + public function listProjects(int $limit = 50, ?string $cursor = null): array + { + $params = ['limit' => $limit]; + if ($cursor !== null) { + $params['cursor'] = $cursor; + } + return $this->requestWithHmac('projects/list', $params); + } + + /** + * getProject returns detailed information about a project + * + * @param int $projectId Project ID + * @return array Project details + * @throws Exception + */ + public function getProject(int $projectId): array + { + return $this->requestWithHmac('projects/get', [ + 'project_id' => $projectId, + ]); + } + + /** + * lookupByDomain looks up a project by domain name + * + * @param string $domainName Domain to look up + * @return array Project ID + * @throws Exception + */ + public function lookupByDomain(string $domainName): array + { + return $this->requestWithHmac('licenses/domain', [ + 'domain_name' => $domainName, + ]); + } + + /** + * rotateLicense regenerates a license key + * + * @param int $projectId Project ID + * @return array New license_key and license_hash + * @throws Exception + */ + public function rotateLicense(int $projectId): array + { + return $this->requestWithHmac('licenses/rotate', [ + 'project_id' => $projectId, + ]); + } + + /** + * attachPackage attaches a package to a project + * + * @param int $projectId Project ID + * @param string $packageCode Package code (e.g., 'Author.PluginName') + * @param string $type Package type ('plugin' or 'theme') + * @return array Confirmation with attached, package, type + * @throws Exception + */ + public function attachPackage(int $projectId, string $packageCode, string $type): array + { + return $this->requestWithHmac('projects/packages/attach', [ + 'project_id' => $projectId, + 'package' => $packageCode, + 'type' => $type, + ]); + } + + /** + * detachPackage detaches a package from a project + * + * @param int $projectId Project ID + * @param string $packageCode Package code (e.g., 'Author.PluginName') + * @param string $type Package type ('plugin' or 'theme') + * @return array Confirmation with detached, package, type + * @throws Exception + */ + public function detachPackage(int $projectId, string $packageCode, string $type): array + { + return $this->requestWithHmac('projects/packages/detach', [ + 'project_id' => $projectId, + 'package' => $packageCode, + 'type' => $type, + ]); + } + + // ========================================================================= + // OPEN ENDPOINTS (No Auth Required) + // ========================================================================= + + /** + * validateLicense validates a license key (no authentication required) + * + * @param string $licenseKey License key to validate + * @return array License information + * @throws Exception + */ + public function validateLicense(string $licenseKey): array + { + return $this->request('licenses/validate', [ + 'license_key' => $licenseKey, + ]); + } + + /** + * ping performs a health check + * + * @return array Server status + * @throws Exception + */ + public function ping(): array + { + return $this->request('ping', [], 'GET'); + } + + // ========================================================================= + // PROJECT-LEVEL ENDPOINTS + // ========================================================================= + + /** + * getProjectDetail returns project details (project-level auth) + * + * @param string|null $projectHash Optional project hash override + * @return array Project details + * @throws Exception + */ + public function getProjectDetail(?string $projectHash = null): array + { + return $this->requestWithProject('project/detail', [ + 'id' => $projectHash ?? $this->projectHash, + ]); + } + + /** + * checkForUpdates checks for available updates (project-level auth) + * + * @param array $options plugins (assoc array code=>version), themes, version, build + * @return array Available updates + * @throws Exception + */ + public function checkForUpdates(array $options = []): array + { + $params = []; + + if (isset($options['plugins'])) { + $params['plugins'] = base64_encode(json_encode($options['plugins'])); + } + if (isset($options['themes'])) { + $params['themes'] = base64_encode(json_encode($options['themes'])); + } + if (isset($options['version'])) { + $params['version'] = $options['version']; + } + if (isset($options['build'])) { + $params['build'] = $options['build']; + } + + return $this->requestWithProject('project/check', $params); + } + + /** + * getPackages returns multiple package details + * + * @param array $codes Package codes + * @param string $type Package type (plugin or theme) + * @return array Package information + * @throws Exception + */ + public function getPackages(array $codes, string $type = 'plugin'): array + { + return $this->request('package/details', [ + 'names' => $codes, + 'type' => $type, + ]); + } + + /** + * getPackage returns single package details + * + * @param string $code Package code + * @param string $type Package type + * @return array Package information with requirements + * @throws Exception + */ + public function getPackage(string $code, string $type = 'plugin'): array + { + return $this->requestWithProject('package/detail', [ + 'name' => $code, + 'type' => $type, + ]); + } + + /** + * getPackageContent returns package documentation content + * + * @param string $code Package code + * @param string $type Package type + * @return array Package info with HTML content + * @throws Exception + */ + public function getPackageContent(string $code, string $type = 'plugin'): array + { + return $this->requestWithProject('package/content', [ + 'name' => $code, + 'type' => $type, + ]); + } + + /** + * browsePackages returns a paginated list of packages + * + * @param int $page Page number + * @param string $type Package type + * @param int|null $version Compatibility version filter + * @return array Paginated package list + * @throws Exception + */ + public function browsePackages(int $page = 1, string $type = 'plugin', ?int $version = null): array + { + $params = [ + 'page' => $page, + 'type' => $type, + ]; + if ($version !== null) { + $params['version'] = $version; + } + return $this->request('package/browse', $params); + } + + /** + * searchPackages searches for packages + * + * @param string $query Search query + * @param string|null $type Package type filter + * @return array Matching packages + * @throws Exception + */ + public function searchPackages(string $query, ?string $type = null): array + { + $params = ['query' => $query]; + if ($type !== null) { + $params['type'] = $type; + } + return $this->request('package/search', $params); + } + + /** + * getInstallDetail returns installation details + * + * @return array Core and package hashes + * @throws Exception + */ + public function getInstallDetail(): array + { + return $this->requestWithProject('install/detail', []); + } + + // ========================================================================= + // HTTP CLIENT + // ========================================================================= + + /** + * requestWithHmac makes a request with HMAC authentication + */ + protected function requestWithHmac(string $endpoint, array $params): array + { + if (!$this->apiKey || !$this->apiSecret) { + throw new Exception('API credentials required for this operation'); + } + + // Add nonce + $params['nonce'] = (string) round(microtime(true) * 1000); + + // Build query string for signing + $queryString = http_build_query($params, '', '&'); + + // Compute signature + $signature = base64_encode( + hash_hmac('sha512', $queryString, base64_decode($this->apiSecret), true) + ); + + $headers = [ + 'Rest-Key: ' . $this->apiKey, + 'Rest-Sign: ' . $signature, + ]; + + return $this->request($endpoint, $params, 'POST', $headers); + } + + /** + * requestWithProject makes a request with project-level authentication + */ + protected function requestWithProject(string $endpoint, array $params): array + { + $headers = []; + + if ($this->projectHash) { + $headers[] = 'php-auth-pw: ' . $this->projectHash; + $params['project'] = $this->projectHash; + } + + return $this->request($endpoint, $params, 'POST', $headers); + } + + /** + * request makes an HTTP request + * + * @param string $endpoint API endpoint + * @param array $params Request parameters + * @param string $method HTTP method + * @param array $headers Additional headers + * @return array Decoded response + * @throws Exception + */ + protected function request(string $endpoint, array $params = [], string $method = 'POST', array $headers = []): array + { + $url = self::API_BASE_URL . '/' . self::API_VERSION . '/' . ltrim($endpoint, '/'); + + // Default headers + $headers = array_merge([ + 'Content-Type: application/x-www-form-urlencoded', + 'Accept: application/json', + ], $headers); + + // Initialize cURL + $ch = curl_init(); + + if ($method === 'GET') { + if (!empty($params)) { + $url .= '?' . http_build_query($params); + } + } + else { + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params, '', '&')); + } + + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => $this->timeout, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_HEADER => true, + CURLOPT_SSL_VERIFYPEER => true, + ]); + + $response = curl_exec($ch); + + if ($response === false) { + $error = curl_error($ch); + throw new Exception('cURL error: ' . $error); + } + + $this->lastStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + + // Parse headers and body + $headerString = substr($response, 0, $headerSize); + $body = substr($response, $headerSize); + + $this->lastResponseHeaders = $this->parseHeaders($headerString); + $this->lastErrorCode = null; + + // Decode JSON response + $data = json_decode($body, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new Exception('Invalid JSON response: ' . $body); + } + + // Check for error responses + if ($this->lastStatusCode >= 400) { + $this->lastErrorCode = $data['error'] ?? 'unknown_error'; + $message = $data['message'] ?? 'An error occurred'; + throw new Exception($message, $this->lastStatusCode); + } + + return $data; + } + + /** + * parseHeaders extracts headers from response + */ + protected function parseHeaders(string $headerString): array + { + $headers = []; + foreach (explode("\r\n", $headerString) as $line) { + if (strpos($line, ':') !== false) { + list($key, $value) = explode(':', $line, 2); + $headers[trim($key)] = trim($value); + } + } + return $headers; + } + + /** + * getLastResponseSignature returns the last response signature for verification + */ + public function getLastResponseSignature(): ?string + { + return $this->lastResponseHeaders['Rest-Sign'] ?? null; + } + + /** + * getLastStatusCode returns the last HTTP status code + */ + public function getLastStatusCode(): int + { + return $this->lastStatusCode; + } + + /** + * getRetryAfter returns the Retry-After header value (for rate limiting) + */ + public function getRetryAfter(): ?int + { + $value = $this->lastResponseHeaders['Retry-After'] ?? null; + return $value !== null ? (int) $value : null; + } + + /** + * getLastErrorCode returns the API error code from the last failed request + */ + public function getLastErrorCode(): ?string + { + return $this->lastErrorCode; + } + + /** + * isRateLimited checks if the last error was a rate limit error + */ + public function isRateLimited(): bool + { + return $this->lastErrorCode === 'rate_limited' || $this->lastStatusCode === 429; + } + + /** + * isAuthError checks if the last error was an authentication error + */ + public function isAuthError(): bool + { + return $this->lastErrorCode === 'invalid_auth' || $this->lastStatusCode === 401; + } + + /** + * isValidationError checks if the last error was a validation error + */ + public function isValidationError(): bool + { + return $this->lastErrorCode === 'validation_error' || $this->lastStatusCode === 422; + } +} diff --git a/src/Installer/InstallManager.php b/src/Installer/InstallManager.php index 879b89352..54edbc8f8 100644 --- a/src/Installer/InstallManager.php +++ b/src/Installer/InstallManager.php @@ -21,7 +21,7 @@ class InstallManager /** * @var string WANT_VERSION is the default composer version string to use. */ - const WANT_VERSION = '^4.0'; + const WANT_VERSION = '^4.1'; /** * instance creates a new instance of this singleton diff --git a/src/Resize/BrokenImage.php b/src/Resize/BrokenImage.php deleted file mode 100644 index 851c67e04..000000000 --- a/src/Resize/BrokenImage.php +++ /dev/null @@ -1,37 +0,0 @@ -mime = $file->getMimeType(); // Open up the file - $this->image = $this->originalImage = $this->openImage($file); - - // Get width and height of our image - $this->orientation = $this->getOrientation($file); + $this->image = $this->openImage($file); // Get width and height of our image - $this->width = $this->getWidth(); - $this->height = $this->getHeight(); + $this->width = $this->image->width(); + $this->height = $this->image->height(); // Set default options $this->setOptions([]); @@ -105,55 +106,12 @@ public static function open($file): Resizer } /** - * retainImageTransparency manipulates an image resource in order to keep - * transparency for PNG and GIF files. - */ - protected function retainImageTransparency($img) - { - if (!$img) { - return; - } - - if ($this->mime === 'image/gif') { - $alphaColor = ['red' => 0, 'green' => 0, 'blue' => 0]; - $alphaIndex = imagecolortransparent($img); - - if ($alphaIndex >= 0) { - $alphaColor = imagecolorsforindex($img, $alphaIndex); - } - - $alphaIndex = imagecolorallocatealpha($img, $alphaColor['red'], $alphaColor['green'], $alphaColor['blue'], 127); - imagefill($img, 0, 0, $alphaIndex); - imagecolortransparent($img, $alphaIndex); - } - elseif ($this->mime === 'image/png' || $this->mime === 'image/webp') { - imagealphablending($img, false); - imagesavealpha($img, true); - } - } - - /** - * reset the image back to the original. - */ - public function reset(): Resizer - { - $this->image = $this->originalImage; - - return $this; - } - - /** - * setOptions sets resizer options. Available options are: - * - mode: Either exact, portrait, landscape, auto, fit or crop. - * - offset: The offset of the crop = [ left, top ] - * - sharpen: Sharpen image, from 0 - 100 (default: 0) - * - interlace: Interlace image, Boolean: false (disabled: default), true (enabled) - * - quality: Image quality, from 0 - 100 (default: 90) + * setOptions sets resizer options */ - public function setOptions(array $options): Resizer + public function setOptions(array $options): static { $this->options = array_merge([ - 'mode' => 'crop', + 'mode' => 'auto', 'offset' => [0, 0], 'sharpen' => 0, 'interlace' => false, @@ -163,532 +121,93 @@ public function setOptions(array $options): Resizer return $this; } - /** - * setOption sets an individual resizer option - * @param string $option Option name to set - * @param mixed $value Option value to set - */ - protected function setOption($option, $value): Resizer - { - $this->options[$option] = $value; - - return $this; - } - /** * getOption gets an individual resizer option - * @param string $option Option name to get - * @return mixed Depends on the option + * @param string $option */ protected function getOption($option) { - return array_get($this->options, $option); + return $this->options[$option] ?? null; } /** - * getOrientation receives the image's exif orientation + * openImage opens a file, detect its mime-type and create an image resource from it * @param \Symfony\Component\HttpFoundation\File\File $file - * @return int|null + * @return mixed */ - protected function getOrientation($file) + protected function openImage($file): ImageInterface { $filePath = $file->getPathname(); - if ($this->mime !== 'image/jpeg' || !function_exists('exif_read_data')) { - return null; - } - - // Reading the exif data is prone to fail due to bad data - $exif = @exif_read_data($filePath); - if (!isset($exif['Orientation'])) { - return null; - } - - // Only take care of spin orientations, no mirrored - if (!in_array($exif['Orientation'], [1, 3, 6, 8], true)) { - return null; - } - - return $exif['Orientation']; - } - - /** - * getWidth receives the image's width while respecting - * the exif orientation - * @return int - */ - protected function getWidth() - { - switch ($this->orientation) { - case 6: - case 8: - return imagesy($this->image); - - case 1: - case 3: - default: - return imagesx($this->image); - } - } - - /** - * getHeight receives the image's height while respecting - * the exif orientation - * @return int - */ - protected function getHeight() - { - switch ($this->orientation) { - case 6: - case 8: - return imagesx($this->image); - - case 1: - case 3: - default: - return imagesy($this->image); - } - } - - /** - * getRotatedOriginal receives the original but rotated image - * according to exif orientation - * @return \GdImage - */ - protected function getRotatedOriginal() - { - switch ($this->orientation) { - case 6: - $angle = 270.0; - break; - - case 8: - $angle = 90.0; - break; - - case 3: - $angle = 180.0; - break; - - case 1: - default: - return $this->image; - } - - $bgcolor = imagecolorallocate($this->image, 0, 0, 0); - - return imagerotate($this->image, $angle, $bgcolor); - } - - /** - * resize and/or crop an image - * @param int $newWidth The width of the image - * @param int $newHeight The height of the image - * @param array $options A set of resizing options - */ - public function resize($newWidth, $newHeight, $options = []): Resizer - { - $this->setOptions($options); - - /* - * Sanitize input - */ - $newWidth = (int) $newWidth; - $newHeight = (int) $newHeight; - - if (!$newWidth && !$newHeight) { - $newWidth = $this->width; - $newHeight = $this->height; - } - elseif (!$newWidth) { - $newWidth = $this->getSizeByFixedHeight($newHeight); - } - elseif (!$newHeight) { - $newHeight = $this->getSizeByFixedWidth($newWidth); - } - - // Get optimal width and height - based on supplied mode. - list($optimalWidth, $optimalHeight) = $this->getDimensions($newWidth, $newHeight); - - // Get the rotated the original image according to exif orientation - $rotatedOriginal = $this->getRotatedOriginal(); - - if ($this->mime === 'image/gif') { - // Use imagescale() for GIFs, as it produces better results - $imageResized = imagescale($rotatedOriginal, (int) $optimalWidth, (int) $optimalHeight, IMG_NEAREST_NEIGHBOUR); - $this->retainImageTransparency($imageResized); - } - else { - // Resample - create image canvas of x, y size - $imageResized = imagecreatetruecolor((int) $optimalWidth, (int) $optimalHeight); - $this->retainImageTransparency($imageResized); - - // Create the new image - imagecopyresampled( - $imageResized, - $rotatedOriginal, - 0, - 0, - 0, - 0, - (int) $optimalWidth, - (int) $optimalHeight, - (int) $this->width, - (int) $this->height - ); - } - - $this->image = $imageResized; - - // Apply sharpness - if ($sharpen = $this->getOption('sharpen')) { - $this->sharpen($sharpen); - } - - // If mode is crop: find center and use for the cropping. - if ($this->getOption('mode') === 'crop') { - $offset = $this->getOption('offset'); - $cropStartX = ($optimalWidth / 2) - ($newWidth / 2) - $offset[0]; - $cropStartY = ($optimalHeight / 2) - ($newHeight / 2) - $offset[1]; - $this->crop($cropStartX, $cropStartY, $newWidth, $newHeight); - } - - return $this; - } - - /** - * sharpen the image across a scale of 0 - 100 - * @param int $sharpness - */ - public function sharpen($sharpness): Resizer - { - if ($sharpness <= 0 || $sharpness > 100) { - return $this; - } - - $image = $this->image; - - // Normalize sharpening value - $kernelCenter = exp((80 - ((float) $sharpness)) / 18) + 9; - - $matrix = [ - [-1, -1, -1], - [-1, $kernelCenter, -1], - [-1, -1, -1], - ]; - - $divisor = array_sum(array_map('array_sum', $matrix)); - - imageconvolution($image, $matrix, $divisor, 0); + $driver = new \Intervention\Image\Drivers\Gd\Driver; - $this->image = $image; + $manager = new \Intervention\Image\ImageManager($driver); - return $this; + return $manager->read($filePath); } /** - * Crops an image from its center - * @param int $cropStartX Start on X axis - * @param int $cropStartY Start on Y axis - * @param int $newWidth The new width - * @param int $newHeight The new height - * @param int $srcWidth Source area width. - * @param int $srcHeight Source area height. + * reset the image back to the original. */ - public function crop($cropStartX, $cropStartY, $newWidth, $newHeight, $srcWidth = null, $srcHeight = null): Resizer + public function reset(): static { - $image = $this->image; - - if ($srcWidth === null) { - $srcWidth = $newWidth; - } - if ($srcHeight === null) { - $srcHeight = $newHeight; - } - - // Create a new canvas - $imageResized = imagecreatetruecolor((int) $newWidth, (int) $newHeight); - $this->retainImageTransparency($imageResized); - - // Crop the image to the requested size - imagecopyresampled( - $imageResized, - $image, - 0, - 0, - (int) $cropStartX, - (int) $cropStartY, - (int) $newWidth, - (int) $newHeight, - (int) $srcWidth, - (int) $srcHeight - ); - - $this->image = $imageResized; + $this->image = $this->openImage($this->file); return $this; } /** * save the image based on its file type. - * @param string $savePath Where to save the image + * @param string $savePath */ public function save($savePath) { - $image = $this->image; - - $imageQuality = $this->getOption('quality'); - - // Apply boundaries to quality (0-100) - $imageQuality = max(min($imageQuality, 100), 0); - - if ($this->getOption('interlace')) { - imageinterlace($image, true); - } - - // Determine the image type from the destination file - $extension = pathinfo($savePath, PATHINFO_EXTENSION) ?: $this->extension; - - // Create and save an image based on it's extension - switch (strtolower($extension)) { - case 'jpg': - case 'jpeg': - // Check JPG support is enabled - if (imagetypes() & IMG_JPG) { - $width = imagesx($image); - $height = imagesy($image); - - $imageCanvas = imagecreatetruecolor($width, $height); - $white = imagecolorallocate($imageCanvas, 255, 255, 255); - imagefill($imageCanvas, 0, 0, $white); - imagecopy($imageCanvas, $image, 0, 0, 0, 0, $width, $height); - imagejpeg($imageCanvas, $savePath, $imageQuality); - } - break; - - case 'gif': - // Check GIF support is enabled - if (imagetypes() & IMG_GIF) { - imagegif($image, $savePath); - } - break; - - case 'png': - // Scale quality from 0-100 to 0-9 - $scaleQuality = round(($imageQuality / 100) * 9); - - // Invert quality setting as 0 is best, not 9 - $invertScaleQuality = 9 - $scaleQuality; - - // Check PNG support is enabled - if (imagetypes() & IMG_PNG) { - imagepng($image, $savePath, $invertScaleQuality); - } - break; - - case 'webp': - // Check WEBP support is enabled - if (imagetypes() & IMG_WEBP) { - imagewebp($image, $savePath, $imageQuality); - } - break; - - default: - throw new Exception(sprintf( - 'Invalid image type: %s. Accepted types: jpg, gif, png, webp.', - $extension - )); - } - - // Remove the resource for the resized image - imagedestroy($image); - } - - /** - * openImage opens a file, detect its mime-type and create an image resource from it - * @param \Symfony\Component\HttpFoundation\File\File $file File instance - * @return mixed - */ - protected function openImage($file) - { - $filePath = $file->getPathname(); - - switch ($this->mime) { - case 'image/jpeg': - $img = @imagecreatefromjpeg($filePath); - break; - case 'image/gif': - $img = @imagecreatefromgif($filePath); - break; - case 'image/png': - $img = @imagecreatefrompng($filePath); - $this->retainImageTransparency($img); - break; - case 'image/webp': - $img = @imagecreatefromwebp($filePath); - $this->retainImageTransparency($img); - break; - default: - throw new Exception("Invalid mime type: {$this->mime}. Accepted types: image/jpeg, image/gif, image/png, image/webp."); - } - - if ($img === false) { - throw new Exception("Resizer failed opening the file for reading ({$this->mime})."); - } - - return $img; - } - - /** - * getDimensions returns the image dimensions based on the option that was chosen. - * @param int $newWidth The width of the image - * @param int $newHeight The height of the image - * @return array - * @throws Exception Thrown for invalid dimension string - */ - protected function getDimensions($newWidth, $newHeight) - { - $mode = $this->getOption('mode'); - - switch ($mode) { - case 'exact': - return [$newWidth, $newHeight]; - - case 'portrait': - return [$this->getSizeByFixedHeight($newHeight), $newHeight]; - - case 'landscape': - return [$newWidth, $this->getSizeByFixedWidth($newWidth)]; - - case 'auto': - return $this->getSizeByAuto($newWidth, $newHeight); - - case 'crop': - return $this->getOptimalCrop($newWidth, $newHeight); - - case 'fit': - return $this->getSizeByFit($newWidth, $newHeight); - - default: - throw new Exception('Invalid dimension type. Accepted types: exact, portrait, landscape, auto, crop, fit.'); - } + $this->image->save($savePath); } /** - * getSizeByFixedHeight returns the width based on the image height - * @param int $newHeight The height of the image - * @return int + * resize and/or crop an image, specifying the new width and height of the + * destination image. + * @param int $width + * @param int $height + * @param array $options */ - protected function getSizeByFixedHeight($newHeight) + public function resize($width, $height, $options = []): static { - $ratio = $this->width / $this->height; - return $newHeight * $ratio; - } - - /** - * Returns the height based on the image width - * @param int $newWidth The width of the image - * @return int - */ - protected function getSizeByFixedWidth($newWidth) - { - $ratio = $this->height / $this->width; - return $newWidth * $ratio; - } + $this->setOptions($options); - /** - * getSizeByAuto checks to see if an image is portrait or landscape and resizes accordingly. - * @param int $newWidth The width of the image - * @param int $newHeight The height of the image - */ - protected function getSizeByAuto($newWidth, $newHeight): array - { - // Less than 1 pixel height and width? (revert to original) - if ($newWidth <= 1 && $newHeight <= 1) { - $newWidth = $this->width; - $newHeight = $this->height; - } - elseif ($newWidth <= 1) { - $newWidth = $this->getSizeByFixedHeight($newHeight); - } - // Less than 1 pixel height? (portrait) - elseif ($newHeight <= 1) { - $newHeight = $this->getSizeByFixedWidth($newWidth); - } + $mode = $this->options['mode'] ?? 'auto'; - // Image to be resized is wider (landscape) - if ($this->height < $this->width) { - $optimalWidth = $newWidth; - $optimalHeight = $this->getSizeByFixedWidth($newWidth); + if ($mode === 'exact') { + $this->image->resize($width, $height); } - // Image to be resized is taller (portrait) - elseif ($this->height > $this->width) { - $optimalWidth = $this->getSizeByFixedHeight($newHeight); - $optimalHeight = $newHeight; - } - // Image to be resized is a square - else { - if ($newHeight < $newWidth) { - $optimalWidth = $newWidth; - $optimalHeight = $this->getSizeByFixedWidth($newWidth); - } - elseif ($newHeight > $newWidth) { - $optimalWidth = $this->getSizeByFixedHeight($newHeight); - $optimalHeight = $newHeight; + elseif ($mode === 'crop') { + // Backward compatibility + if (!array_key_exists('offset', $options)) { + $this->image->cover($width, $height); } else { - // Square being resized to a square - $optimalWidth = $newWidth; - $optimalHeight = $newHeight; + $this->image->crop( + $width, + $height, + $this->options['offset'][0] ?? 0, + $this->options['offset'][1] ?? 0 + ); } } - - return [$optimalWidth, $optimalHeight]; - } - - /** - * getOptimalCrop attempts to find the best way to crop. Whether crop is based on the - * image being portrait or landscape. - * @param int $newWidth The width of the image - * @param int $newHeight The height of the image - */ - protected function getOptimalCrop($newWidth, $newHeight): array - { - $heightRatio = $this->height / $newHeight; - $widthRatio = $this->width / $newWidth; - - if ($heightRatio < $widthRatio) { - $optimalRatio = $heightRatio; + elseif ($mode === 'cover' || $mode === 'fit') { + $this->image->cover($width, $height); } - else { - $optimalRatio = $widthRatio; + elseif ($mode === 'auto') { + $this->image->scale($width, $height); + } + elseif ($mode === 'portrait') { + $this->image->scale(null, $height); + } + elseif ($mode === 'landscape') { + $this->image->scale($width, null); } - $optimalHeight = round($this->height / $optimalRatio); - $optimalWidth = round($this->width / $optimalRatio); - - return [$optimalWidth, $optimalHeight]; - } - - /** - * getSizeByFit fits the image inside a bounding box using maximum width - * and height constraints. - * @param int $maxWidth The maximum width of the image - * @param int $maxHeight The maximum height of the image - */ - protected function getSizeByFit($maxWidth, $maxHeight): array - { - // Calculate the scaling ratios in order to get the target width and height - $ratioW = $maxWidth / $this->width; - $ratioH = $maxHeight / $this->height; - - // Select the ratio which makes the image fit inside the constraints - $effectiveRatio = min($ratioW, $ratioH); - - // Calculate the final width and height according to this ratio - $optimalWidth = round($this->width * $effectiveRatio); - $optimalHeight = round($this->height * $effectiveRatio); - - return [$optimalWidth, $optimalHeight]; + return $this; } } diff --git a/src/Translation/Translator.php b/src/Translation/Translator.php index cf6be3b46..fd1f3445a 100644 --- a/src/Translation/Translator.php +++ b/src/Translation/Translator.php @@ -45,7 +45,7 @@ public function get($key, array $replace = [], $locale = null, $fallback = true) // Laravel notes that with JSON translations, there is no usage of a fallback language. // The key is the translation. Here we extend the technology to add fallback support. - if ($fallback && $line === null) { + if ($fallback && $line === null && $this->fallback !== null) { $this->load('*', '*', $this->fallback); $line = $this->loaded['*']['*'][$this->fallback][$key] ?? null; } diff --git a/tests/Assetic/MockAsset.php b/tests/Assetic/MockAsset.php index fafc2c386..4a08c194d 100644 --- a/tests/Assetic/MockAsset.php +++ b/tests/Assetic/MockAsset.php @@ -18,69 +18,78 @@ public function __construct(string $content = '') $this->content = $content; } - public function ensureFilter(FilterInterface $filter) + public function ensureFilter(FilterInterface $filter): void { } - public function getFilters() + public function getFilters(): array { + return []; } - public function clearFilters() + public function clearFilters(): void { } - public function load(?FilterInterface $additionalFilter = null) + public function load(?FilterInterface $additionalFilter = null): void { } - public function dump(?FilterInterface $additionalFilter = null) + public function dump(?FilterInterface $additionalFilter = null): string { + return $this->content ?? ''; } - public function getContent() + public function getContent(): ?string { return $this->content; } - public function setContent($content) + public function setContent(?string $content): void { $this->content = $content; } - public function getSourceRoot() + public function getSourceRoot(): ?string { + return null; } - public function getSourcePath() + public function getSourcePath(): ?string { + return null; } - public function getSourceDirectory() + public function getSourceDirectory(): ?string { + return null; } - public function getTargetPath() + public function getTargetPath(): ?string { + return null; } - public function setTargetPath($targetPath) + public function setTargetPath(?string $targetPath): void { } - public function getLastModified() + public function getLastModified(): ?int { + return null; } - public function getVars() + public function getVars(): array { + return []; } - public function setValues(array $values) + public function setValues(array $values): void { } - public function getValues() + public function getValues(): array { + return []; } } diff --git a/tests/Html/HtmlBuilderTest.php b/tests/Html/HtmlBuilderTest.php index 5d7bb9ef3..e432e545f 100644 --- a/tests/Html/HtmlBuilderTest.php +++ b/tests/Html/HtmlBuilderTest.php @@ -21,15 +21,17 @@ public function testLimit() $result = with(new HtmlBuilder)->limit("
The quick brown fox jumped over the lazy dog
The quick brown fox jumped over the lazy dog
", 50); $this->assertEquals('The quick brown fox jumped over the lazy dog
The qu...
', $result); - $result = with(new HtmlBuilder)->limit(trim(" + $input = str_replace("\r\n", "\n", trim("The quick brown fox jumped over the lazy dog
The quick brown fox jumped over the lazy dog
- "), 60); + ")); + $result = with(new HtmlBuilder)->limit($input, 60); - $this->assertEquals(trim(' + $expected = str_replace("\r\n", "\n", trim('The quick brown fox jumped over the lazy dog
The...
- '), $result); + ')); + $this->assertEquals($expected, $result); } public function testClean() diff --git a/tests/Mail/MailerTest.php b/tests/Mail/MailerTest.php index 387046ccc..28ddd2ab8 100644 --- a/tests/Mail/MailerTest.php +++ b/tests/Mail/MailerTest.php @@ -95,7 +95,6 @@ protected static function callProtectedMethod($object, $name, $params = []) $className = get_class($object); $class = new ReflectionClass($className); $method = $class->getMethod($name); - $method->setAccessible(true); return $method->invokeArgs($object, $params); } diff --git a/tests/Parse/MarkdownTest.php b/tests/Parse/MarkdownTest.php index 83d6c7444..72c325fe9 100644 --- a/tests/Parse/MarkdownTest.php +++ b/tests/Parse/MarkdownTest.php @@ -97,7 +97,7 @@ public function testParseNonHtml() $normal = $parser->parse($text); // Only accepting one node per line - $this->assertEquals($expected, $normal); + $this->assertEquals(str_replace("\r\n", "\n", $expected), $normal); } public function testParseMultilineHtml() @@ -149,6 +149,6 @@ public function testParseMultilineHtml() $normal = $parser->parse($text); - $this->assertEquals($expected, $normal); + $this->assertEquals(str_replace("\r\n", "\n", $expected), $normal); } } diff --git a/tests/Parse/SyntaxFieldParserTest.php b/tests/Parse/SyntaxFieldParserTest.php index 79a7c94e9..4787da571 100644 --- a/tests/Parse/SyntaxFieldParserTest.php +++ b/tests/Parse/SyntaxFieldParserTest.php @@ -322,7 +322,6 @@ protected static function callProtectedMethod($object, $name, $params = []) $className = get_class($object); $class = new ReflectionClass($className); $method = $class->getMethod($name); - $method->setAccessible(true); return $method->invokeArgs($object, $params); } @@ -331,7 +330,6 @@ public static function getProtectedProperty($object, $name) $className = get_class($object); $class = new ReflectionClass($className); $property = $class->getProperty($name); - $property->setAccessible(true); return $property->getValue($object); } @@ -340,7 +338,6 @@ public static function setProtectedProperty($object, $name, $value) $className = get_class($object); $class = new ReflectionClass($className); $property = $class->getProperty($name); - $property->setAccessible(true); return $property->setValue($object, $value); } } diff --git a/tests/Scaffold/ScaffoldBaseTest.php b/tests/Scaffold/ScaffoldBaseTest.php index 62975091d..55de50adb 100644 --- a/tests/Scaffold/ScaffoldBaseTest.php +++ b/tests/Scaffold/ScaffoldBaseTest.php @@ -25,7 +25,6 @@ protected static function callProtectedMethod($object, $name, $params = []) $className = get_class($object); $class = new ReflectionClass($className); $method = $class->getMethod($name); - $method->setAccessible(true); return $method->invokeArgs($object, $params); } @@ -34,7 +33,6 @@ public static function getProtectedProperty($object, $name) $className = get_class($object); $class = new ReflectionClass($className); $property = $class->getProperty($name); - $property->setAccessible(true); return $property->getValue($object); } @@ -43,7 +41,6 @@ public static function setProtectedProperty($object, $name, $value) $className = get_class($object); $class = new ReflectionClass($className); $property = $class->getProperty($name); - $property->setAccessible(true); return $property->setValue($object, $value); } diff --git a/tests/TestCase.php b/tests/TestCase.php index 522796664..724c229b7 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -20,7 +20,6 @@ protected static function callProtectedMethod($object, $name, $params = []) $className = get_class($object); $class = new ReflectionClass($className); $method = $class->getMethod($name); - $method->setAccessible(true); return $method->invokeArgs($object, $params); }