diff --git a/CHANGELOG.md b/CHANGELOG.md index df661887..960b9b69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # ImageOptimize Changelog +## 1.2.1 - 2017.09.10 +### Changed +* Fixed an issue that could leave stale image variants around +* Ensure that the optimized image variants are re-created if the image is edited +* Added logging to show the savings for image variants +* Fixed the way the Settings page is rendered +* Updated `README.md` + ## 1.2.0 - 2017.09.08 ### Added * Added `OptimzedImages` Field diff --git a/composer.json b/composer.json index 5bbb7056..3db415f4 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "nystudio107/craft3-imageoptimize", "description": "Automatically create & optimize responsive image transforms", "type": "craft-plugin", - "version": "1.2.0", + "version": "1.2.1", "keywords": [ "craft", "cms", diff --git a/src/ImageOptimize.php b/src/ImageOptimize.php index 1a6caeb5..395c37a0 100644 --- a/src/ImageOptimize.php +++ b/src/ImageOptimize.php @@ -24,12 +24,14 @@ use craft\events\VolumeEvent; use craft\models\FieldLayout; use craft\services\AssetTransforms; -use craft\services\Elements; use craft\services\Fields; use craft\services\Volumes; +use craft\web\Controller; use yii\base\Event; +/** @noinspection MissingPropertyAnnotationsInspection */ + /** * Class ImageOptimize * @@ -168,6 +170,26 @@ function (GenerateTransformEvent $event) { ); } + /** + * @inheritdoc + */ + public function getSettingsResponse() + { + $view = Craft::$app->getView(); + $namespace = $view->getNamespace(); + $view->setNamespace('settings'); + $settingsHtml = $this->settingsHtml(); + $view->setNamespace($namespace); + + /** @var Controller $controller */ + $controller = Craft::$app->controller; + + return $controller->renderTemplate('image-optimize/_settings', [ + 'plugin' => $this, + 'settingsHtml' => $settingsHtml + ]); + } + /** * @inheritdoc */ diff --git a/src/fields/OptimizedImages.php b/src/fields/OptimizedImages.php index c4d338bd..9127d678 100644 --- a/src/fields/OptimizedImages.php +++ b/src/fields/OptimizedImages.php @@ -11,7 +11,6 @@ namespace nystudio107\imageoptimize\fields; use craft\models\AssetTransform; -use nystudio107\imageoptimize\ImageOptimize; use nystudio107\imageoptimize\assetbundles\optimizedimagesfield\OptimizedImagesFieldAsset; use nystudio107\imageoptimize\models\OptimizedImage; @@ -24,6 +23,8 @@ use yii\db\Schema; +/** @noinspection MissingPropertyAnnotationsInspection */ + /** * @author nystudio107 * @package ImageOptimize @@ -124,10 +125,10 @@ public function beforeElementSave(ElementInterface $element, bool $isNew): bool */ public function afterElementSave(ElementInterface $element, bool $isNew) { - // If this is a new element, resave it so that it as an id for our asset transforms - if ($isNew) { - /** @var Asset $element */ - if ($element instanceof Asset) { + /** @var Asset $element */ + if ($element instanceof Asset) { + // If this is a new element, resave it so that it as an id for our asset transforms + if ($isNew) { // Initialize our field with defaults $this->currentAsset = $element; $defaultData = $this->normalizeValue(null, $element); @@ -138,23 +139,19 @@ public function afterElementSave(ElementInterface $element, bool $isNew) $success = Craft::$app->getElements()->saveElement($element, false); Craft::info( - print_r('Re-saved new asset ' . $success, true), + print_r('Re-saved new asset '.$success, true), __METHOD__ ); + } else { + // Otherwise create a dummy model, and populate it, to recreate any asset transforms + $model = new OptimizedImage(); + $this->populateOptimizedImageModel($element, $model); } } parent::afterElementSave($element, $isNew); } - /** - * @inheritdoc - */ - public function getContentColumnType(): string - { - return Schema::TYPE_TEXT; - } - /** * @inheritdoc */ @@ -173,6 +170,50 @@ public function normalizeValue($value, ElementInterface $element = null) return $model; } + /** + * @param Asset $element + * @param OptimizedImage $model + */ + protected function populateOptimizedImageModel(Asset $element, OptimizedImage $model) + { + // Empty our the optimized image URLs + $model->optimizedImageUrls = []; + $model->optimizedWebPImageUrls = []; + + /** @var AssetTransform $transform */ + $transform = new AssetTransform(); + + foreach ($this->variants as $variant) { + // Create the transform based on the variant + $aspectRatio = $variant['aspectRatioX'] / $variant['aspectRatioY']; + $width = $variant['width']; + $transform->width = $width; + $transform->height = intval($width / $aspectRatio); + $transform->quality = $variant['quality']; + $transform->format = $variant['format']; + + // Force generateTransformsBeforePageLoad = true to generate the images now + $generalConfig = Craft::$app->getConfig()->getGeneral(); + $oldSetting = $generalConfig->generateTransformsBeforePageLoad; + $generalConfig->generateTransformsBeforePageLoad = true; + // Generate the URLs to the optimized images + $url = $element->getUrl($transform); + $generalConfig->generateTransformsBeforePageLoad = $oldSetting; + + // Update the model + $model->optimizedImageUrls[$width] = $url; + $model->optimizedWebPImageUrls[$width] = $url.'.webp'; + $model->focalPoint = $element->focalPoint; + $model->originalImageWidth = $element->width; + $model->originalImageHeight = $element->height; + + Craft::info( + 'Created transforms for variant: '.print_r($variant, true), + __METHOD__ + ); + } + } + /** * @inheritdoc */ @@ -181,6 +222,14 @@ public function serializeValue($value, ElementInterface $element = null) return parent::serializeValue($value, $element); } + /** + * @inheritdoc + */ + public function getContentColumnType(): string + { + return Schema::TYPE_TEXT; + } + /** * @inheritdoc */ @@ -192,22 +241,25 @@ public function getSettingsHtml() $namespacedId = Craft::$app->getView()->namespaceInputId($id); $namespacePrefix = Craft::$app->getView()->namespaceInputName($thisId); Craft::$app->getView()->registerJs('new Craft.OptimizedImagesInput('. - '"'. $namespacedId .'", '. - '"'. $namespacePrefix .'"'. + '"'.$namespacedId.'", '. + '"'.$namespacePrefix.'"'. ');'); // Render the settings template return Craft::$app->getView()->renderTemplate( 'image-optimize/_components/fields/OptimizedImages_settings', [ - 'field' => $this, - 'id' => $id, - 'name' => $this->handle, + 'field' => $this, + 'id' => $id, + 'name' => $this->handle, 'namespace' => $namespacedId, ] ); } + // Protected Methods + // ========================================================================= + /** * @inheritdoc */ @@ -218,77 +270,29 @@ public function getInputHtml($value, ElementInterface $element = null): string // Get our id and namespace $id = Craft::$app->getView()->formatInputId($this->handle); - $namespacedId = Craft::$app->getView()->namespaceInputId($id); + $nameSpaceId = Craft::$app->getView()->namespaceInputId($id); // Variables to pass down to our field JavaScript to let it namespace properly $jsonVars = [ - 'id' => $id, - 'name' => $this->handle, - 'namespace' => $namespacedId, - 'prefix' => Craft::$app->getView()->namespaceInputId(''), + 'id' => $id, + 'name' => $this->handle, + 'namespace' => $nameSpaceId, + 'prefix' => Craft::$app->getView()->namespaceInputId(''), ]; $jsonVars = Json::encode($jsonVars); - Craft::$app->getView()->registerJs("$('#{$namespacedId}-field').ImageOptimizeOptimizedImages(" . $jsonVars . ");"); + Craft::$app->getView()->registerJs("$('#{$nameSpaceId}-field').ImageOptimizeOptimizedImages(".$jsonVars.");"); // Render the input template return Craft::$app->getView()->renderTemplate( 'image-optimize/_components/fields/OptimizedImages_input', [ - 'name' => $this->handle, - 'value' => $value, - 'variants' => $this->variants, - 'field' => $this, - 'id' => $id, - 'namespacedId' => $namespacedId, + 'name' => $this->handle, + 'value' => $value, + 'variants' => $this->variants, + 'field' => $this, + 'id' => $id, + 'nameSpaceId' => $nameSpaceId, ] ); } - - // Protected Methods - // ========================================================================= - - /** - * @param Asset $element - * @param OptimizedImage $model - */ - protected function populateOptimizedImageModel(Asset $element, OptimizedImage $model) - { - // Empty our the optimized image URLs - $model->optimizedImageUrls = []; - $model->optimizedWebPImageUrls = []; - - /** @var AssetTransform $transform */ - $transform = new AssetTransform(); - - foreach ($this->variants as $variant) { - // Create the transform based on the variant - $aspectRatio = $variant['aspectRatioX'] / $variant['aspectRatioY']; - $width = $variant['width']; - $transform->width = $width; - $transform->height = intval($width / $aspectRatio); - $transform->quality = $variant['quality']; - $transform->format = $variant['format']; - - // Force generateTransformsBeforePageLoad = true to generate the images now - $generalConfig = Craft::$app->getConfig()->getGeneral(); - $oldSetting = $generalConfig->generateTransformsBeforePageLoad; - $generalConfig->generateTransformsBeforePageLoad = true; - // Generate the URLs to the optimized images - $url = $element->getUrl($transform); - $generalConfig->generateTransformsBeforePageLoad = $oldSetting; - - // Update the model - $model->optimizedImageUrls[$width] = $url; - $model->optimizedWebPImageUrls[$width] = $url . '.webp'; - $model->focalPoint = $element->focalPoint; - $model->originalImageWidth = $element->width; - $model->originalImageHeight = $element->height; - - Craft::info( - 'Created transforms for variant: ' . print_r($variant, true), - __METHOD__ - ); - } - } - } diff --git a/src/models/OptimizedImage.php b/src/models/OptimizedImage.php index 688da866..07943ceb 100644 --- a/src/models/OptimizedImage.php +++ b/src/models/OptimizedImage.php @@ -12,7 +12,6 @@ use nystudio107\imageoptimize\ImageOptimize; -use Craft; use craft\helpers\UrlHelper; use craft\base\Model; use craft\validators\ArrayValidator; diff --git a/src/services/Optimize.php b/src/services/Optimize.php index 78f4241f..38ac12af 100644 --- a/src/services/Optimize.php +++ b/src/services/Optimize.php @@ -10,6 +10,7 @@ namespace nystudio107\imageoptimize\services; +use craft\errors\VolumeException; use nystudio107\imageoptimize\ImageOptimize; use Craft; @@ -25,6 +26,8 @@ use mikehaertl\shellcommand\Command as ShellCommand; +/** @noinspection MissingPropertyAnnotationsInspection */ + /** * @author nystudio107 * @package ImageOptimize @@ -61,21 +64,21 @@ public function handleGenerateTransformEvent(GenerateTransformEvent $event): str $index = $event->transformIndex; Craft::info( pathinfo($index->filename, PATHINFO_FILENAME) - . '.' - . $index->detectedFormat - . ' -> ' - . Craft::t('image-optimize', 'Original') - . ': ' - . $this->humanFileSize($originalFileSize, 1) - . ', ' - . Craft::t('image-optimize', 'Optimized') - . ': ' - . $this->humanFileSize($optimizedFileSize, 1) - . ' -> ' - . Craft::t('image-optimize', 'Savings') - . ': ' - . number_format(abs((1 - ($originalFileSize / $optimizedFileSize)) * 100), 1) - . '%', + .'.' + .$index->detectedFormat + .' -> ' + .Craft::t('image-optimize', 'Original') + .': ' + .$this->humanFileSize($originalFileSize, 1) + .', ' + .Craft::t('image-optimize', 'Optimized') + .': ' + .$this->humanFileSize($optimizedFileSize, 1) + .' -> ' + .Craft::t('image-optimize', 'Savings') + .': ' + .number_format(abs((1 - ($originalFileSize / $optimizedFileSize)) * 100), 1) + .'%', __METHOD__ ); // Create any image variants @@ -98,10 +101,10 @@ public function handleGenerateTransformEvent(GenerateTransformEvent $event): str */ public function saveTransformToTempFile(AssetTransformIndex $index, Image $image): string { - $tempFilename = uniqid(pathinfo($index->filename, PATHINFO_FILENAME), true) . '.' . $index->detectedFormat; - $tempPath = Craft::$app->getPath()->getTempPath() . DIRECTORY_SEPARATOR . $tempFilename; + $tempFilename = uniqid(pathinfo($index->filename, PATHINFO_FILENAME), true).'.'.$index->detectedFormat; + $tempPath = Craft::$app->getPath()->getTempPath().DIRECTORY_SEPARATOR.$tempFilename; $image->saveAs($tempPath); - Craft::info('Transformed image saved to: ' . $tempPath, __METHOD__); + Craft::info('Transformed image saved to: '.$tempPath, __METHOD__); return $tempPath; } @@ -130,127 +133,76 @@ public function optimizeImage(AssetTransformIndex $index, string $tempPath) } /** - * Create any image variants for the image file - * - * @param AssetTransformIndex $index - * @param Asset $asset - * @param string $tempPath + * @param string $tempPath + * @param $thisProcessor */ - public function createImageVariants(AssetTransformIndex $index, Asset $asset, string $tempPath) + protected function executeImageProcessor($thisProcessor, string $tempPath) { - $settings = ImageOptimize::$plugin->getSettings(); - // Get the active image variant creators - $activeImageVariantCreators = $settings['activeImageVariantCreators']; - $fileFormat = $index->detectedFormat; - if (!empty($activeImageVariantCreators[$fileFormat])) { - // Iterate through all of the image variant creators for this format - $imageVariantCreators = $settings['imageVariantCreators']; - foreach ($activeImageVariantCreators[$fileFormat] as $variantCreator) { - if (!empty($imageVariantCreators[$variantCreator])) { - // Create the image variant in a temporary folder - $quality = $index->transform->quality ?: Craft::$app->getConfig()->getGeneral()->defaultImageQuality; - $outputPath = $this->executeVariantCreator( - $imageVariantCreators[$variantCreator], - $tempPath, - $quality - ); - // Copy the image variant into place - $this->copyImageVariantToVolume( - $imageVariantCreators[$variantCreator], - $asset, - $index, - $outputPath - ); - } + // Make sure the command exists + if (file_exists($thisProcessor['commandPath'])) { + // Set any options for the command + $commandOptions = ''; + if (!empty($thisProcessor['commandOptions'])) { + $commandOptions = ' ' + .$thisProcessor['commandOptions'] + .' '; } + // Redirect the command output if necessary for this processor + $outputFileFlag = ''; + if (!empty($thisProcessor['commandOutputFileFlag'])) { + $outputFileFlag = ' ' + .$thisProcessor['commandOutputFileFlag'] + .' ' + .escapeshellarg($tempPath) + .' '; + } + // Build the command to execute + $cmd = + $thisProcessor['commandPath'] + .$commandOptions + .$outputFileFlag + .escapeshellarg($tempPath); + // Execute the command + $shellOutput = $this->executeShellCommand($cmd); + Craft::info($cmd."\n".$shellOutput, __METHOD__); + } else { + Craft::error( + $thisProcessor['commandPath'] + .' ' + .Craft::t('image-optimize', 'does not exist'), + __METHOD__ + ); } } /** - * Return an array of active image processors + * Execute a shell command * - * @return array + * @param string $command + * + * @return string */ - public function getActiveImageProcessors(): array + protected function executeShellCommand(string $command): string { - $result = []; - $settings = ImageOptimize::$plugin->getSettings(); - // Get the active processors for the transform format - $activeImageProcessors = $settings['activeImageProcessors']; - foreach ($activeImageProcessors as $imageFormat => $imageProcessor) { - // Iterate through all of the processors for this format - $imageProcessors = $settings['imageProcessors']; - foreach ($activeImageProcessors[$imageFormat] as $processor) { - if (!empty($imageProcessors[$processor])) { - $thisImageProcessor = $imageProcessors[$processor]; - $result[] = [ - 'format' => $imageFormat, - 'creator' => $processor, - 'command' => $thisImageProcessor['commandPath'] - . ' ' - . $thisImageProcessor['commandOptions'], - 'installed' => file_exists($thisImageProcessor['commandPath']), - ]; - } - } - } + // Create the shell command + $shellCommand = new ShellCommand(); + $shellCommand->setCommand($command); - return $result; - } + // If we don't have proc_open, maybe we've got exec + if (!function_exists('proc_open') && function_exists('exec')) { + $shellCommand->useExec = true; + } - /** - * Return an array of active image variant creators - * - * @return array - */ - public function getActiveVariantCreators(): array - { - $result = []; - $settings = ImageOptimize::$plugin->getSettings(); - // Get the active image variant creators - $activeImageVariantCreators = $settings['activeImageVariantCreators']; - foreach ($activeImageVariantCreators as $imageFormat => $imageCreator) { - // Iterate through all of the image variant creators for this format - $imageVariantCreators = $settings['imageVariantCreators']; - foreach ($activeImageVariantCreators[$imageFormat] as $variantCreator) { - if (!empty($imageVariantCreators[$variantCreator])) { - $thisVariantCreator = $imageVariantCreators[$variantCreator]; - $result[] = [ - 'format' => $imageFormat, - 'creator' => $variantCreator, - 'command' => $thisVariantCreator['commandPath'] - . ' ' - . $thisVariantCreator['commandOptions'], - 'installed' => file_exists($thisVariantCreator['commandPath']), - ]; - } - } + // Return the result of the command's output or error + if ($shellCommand->execute()) { + $result = $shellCommand->getOutput(); + } else { + $result = $shellCommand->getError(); } return $result; } - /** - * Resave all of the Asset elements in the Volume $volume - * - * @param Volume $volume - */ - public function resaveVolumeAssets(Volume $volume) - { - $siteId = Craft::$app->getSites()->getPrimarySite()->id; - - Craft::$app->getQueue()->push(new ResaveElements([ - 'description' => Craft::t('image-optimize', 'Resaving Assets in {name}', ['name' => $volume->name]), - 'elementType' => Asset::class, - 'criteria' => [ - 'siteId' => $siteId, - 'volumeId' => $volume->id, - 'status' => null, - 'enabledForSite' => false, - ], - ])); - } - /** * Translate bytes into something human-readable * @@ -264,52 +216,73 @@ public function humanFileSize($bytes, $decimals = 2): string $sz = 'BKMGTP'; $factor = floor((strlen($bytes) - 1) / 3); - return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[intval($factor)]; + return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)).@$sz[intval($factor)]; } - // Protected Methods - // ========================================================================= - /** - * @param string $tempPath - * @param $thisProcessor + * Create any image variants for the image file + * + * @param AssetTransformIndex $index + * @param Asset $asset + * @param string $tempPath */ - protected function executeImageProcessor($thisProcessor, string $tempPath) + public function createImageVariants(AssetTransformIndex $index, Asset $asset, string $tempPath) { - // Make sure the command exists - if (file_exists($thisProcessor['commandPath'])) { - // Set any options for the command - $commandOptions = ''; - if (!empty($thisProcessor['commandOptions'])) { - $commandOptions = ' ' - . $thisProcessor['commandOptions'] - . ' '; - } - // Redirect the command output if necessary for this processor - $outputFileFlag = ''; - if (!empty($thisProcessor['commandOutputFileFlag'])) { - $outputFileFlag = ' ' - . $thisProcessor['commandOutputFileFlag'] - . ' ' - . escapeshellarg($tempPath) - . ' '; + $settings = ImageOptimize::$plugin->getSettings(); + // Get the active image variant creators + $activeImageVariantCreators = $settings['activeImageVariantCreators']; + $fileFormat = $index->detectedFormat; + if (!empty($activeImageVariantCreators[$fileFormat])) { + // Iterate through all of the image variant creators for this format + $imageVariantCreators = $settings['imageVariantCreators']; + foreach ($activeImageVariantCreators[$fileFormat] as $variantCreator) { + if (!empty($imageVariantCreators[$variantCreator])) { + // Create the image variant in a temporary folder + $generalConfig = Craft::$app->getConfig()->getGeneral(); + $quality = $index->transform->quality ?: $generalConfig->defaultImageQuality; + $outputPath = $this->executeVariantCreator( + $imageVariantCreators[$variantCreator], + $tempPath, + $quality + ); + + // Get info on the original and the created variant + $originalFileSize = filesize($tempPath); + $variantFileSize = filesize($outputPath); + + Craft::info( + pathinfo($tempPath, PATHINFO_FILENAME) + .'.' + .pathinfo($tempPath, PATHINFO_EXTENSION) + .' -> ' + .pathinfo($outputPath, PATHINFO_FILENAME) + .'.' + .pathinfo($outputPath, PATHINFO_EXTENSION) + .' -> ' + .Craft::t('image-optimize', 'Original') + .': ' + .$this->humanFileSize($originalFileSize, 1) + .', ' + .Craft::t('image-optimize', 'Variant') + .': ' + .$this->humanFileSize($variantFileSize, 1) + .' -> ' + .Craft::t('image-optimize', 'Savings') + .': ' + .number_format(abs((1 - ($originalFileSize / $variantFileSize)) * 100), 1) + .'%', + __METHOD__ + ); + + // Copy the image variant into place + $this->copyImageVariantToVolume( + $imageVariantCreators[$variantCreator], + $asset, + $index, + $outputPath + ); + } } - // Build the command to execute - $cmd = - $thisProcessor['commandPath'] - . $commandOptions - . $outputFileFlag - . escapeshellarg($tempPath); - // Execute the command - $shellOutput = $this->executeShellCommand($cmd); - Craft::info($cmd . "\n" . $shellOutput, __METHOD__); - } else { - Craft::error( - $thisProcessor['commandPath'] - . ' ' - . Craft::t('image-optimize', 'does not exist'), - __METHOD__ - ); } } @@ -322,51 +295,51 @@ protected function executeImageProcessor($thisProcessor, string $tempPath) */ protected function executeVariantCreator($variantCreatorCommand, string $tempPath, int $imageQuality): string { - $outputPath = ''; + $outputPath = $tempPath; // Make sure the command exists if (file_exists($variantCreatorCommand['commandPath'])) { // Get the output file for this image variant - $outputPath .= '.' . $variantCreatorCommand['imageVariantExtension']; + $outputPath .= '.'.$variantCreatorCommand['imageVariantExtension']; // Set any options for the command $commandOptions = ''; if (!empty($variantCreatorCommand['commandOptions'])) { $commandOptions = ' ' - . $variantCreatorCommand['commandOptions'] - . ' '; + .$variantCreatorCommand['commandOptions'] + .' '; } // Redirect the command output if necessary for this variantCreator $outputFileFlag = ''; if (!empty($variantCreatorCommand['commandOutputFileFlag'])) { $outputFileFlag = ' ' - . $variantCreatorCommand['commandOutputFileFlag'] - . ' ' - . escapeshellarg($outputPath) - . ' '; + .$variantCreatorCommand['commandOutputFileFlag'] + .' ' + .escapeshellarg($outputPath) + .' '; } // Get the quality setting of this transform $commandQualityFlag = ''; if (!empty($variantCreatorCommand['commandQualityFlag'])) { $commandQualityFlag = ' ' - . $variantCreatorCommand['commandQualityFlag'] - . ' ' - . $imageQuality - . ' '; + .$variantCreatorCommand['commandQualityFlag'] + .' ' + .$imageQuality + .' '; } // Build the command to execute $cmd = $variantCreatorCommand['commandPath'] - . $commandOptions - . $commandQualityFlag - . $outputFileFlag - . escapeshellarg($tempPath); + .$commandOptions + .$commandQualityFlag + .$outputFileFlag + .escapeshellarg($tempPath); // Execute the command $shellOutput = $this->executeShellCommand($cmd); - Craft::info($cmd . "\n" . $shellOutput, __METHOD__); + Craft::info($cmd."\n".$shellOutput, __METHOD__); } else { Craft::error( $variantCreatorCommand['commandPath'] - . ' ' - . Craft::t('image-optimize', 'does not exist'), + .' ' + .Craft::t('image-optimize', 'does not exist'), __METHOD__ ); } @@ -374,6 +347,9 @@ protected function executeVariantCreator($variantCreatorCommand, string $tempPat return $outputPath; } + // Protected Methods + // ========================================================================= + /** * @param $variantCreatorCommand * @param Asset $asset @@ -391,19 +367,22 @@ protected function copyImageVariantToVolume( // Figure out the resulting path for the image variant $volume = $asset->getVolume(); $assetTransforms = Craft::$app->getAssetTransforms(); - $transformPath = $asset->getFolder()->path . $assetTransforms->getTransformSubpath($asset, $index); - $transformPath .= '.' . $variantCreatorCommand['imageVariantExtension']; + $transformPath = $asset->getFolder()->path.$assetTransforms->getTransformSubpath($asset, $index); + $variantPath = $transformPath.'.'.$variantCreatorCommand['imageVariantExtension']; - // No need to create the file if it already exists - if ($volume->fileExists($transformPath)) { - return; + // Delete the variant file in case it is stale + try { + $volume->deleteFile($variantPath); + } catch (VolumeException $e) { + // We're fine with that. } - clearstatcache(true, $outputPath); + clearstatcache(true, $outputPath); $stream = fopen($outputPath, 'rb'); + // Now create it try { - $volume->createFileByStream($transformPath, $stream, []); + $volume->createFileByStream($variantPath, $stream, []); } catch (VolumeObjectExistsException $e) { // We're fine with that. } @@ -412,40 +391,97 @@ protected function copyImageVariantToVolume( } else { Craft::error( Craft::t('image-optimize', 'Failed to create image variant at: ') - . $outputPath, + .$outputPath, __METHOD__ ); } } /** - * Execute a shell command - * - * @param string $command + * Return an array of active image processors * - * @return string + * @return array */ - protected function executeShellCommand(string $command): string + public function getActiveImageProcessors(): array { - // Create the shell command - $shellCommand = new ShellCommand(); - $shellCommand->setCommand($command); - - // If we don't have proc_open, maybe we've got exec - if (!function_exists('proc_open') && function_exists('exec')) { - $shellCommand->useExec = true; + $result = []; + $settings = ImageOptimize::$plugin->getSettings(); + // Get the active processors for the transform format + $activeImageProcessors = $settings['activeImageProcessors']; + foreach ($activeImageProcessors as $imageFormat => $imageProcessor) { + // Iterate through all of the processors for this format + $imageProcessors = $settings['imageProcessors']; + foreach ($activeImageProcessors[$imageFormat] as $processor) { + if (!empty($imageProcessors[$processor])) { + $thisImageProcessor = $imageProcessors[$processor]; + $result[] = [ + 'format' => $imageFormat, + 'creator' => $processor, + 'command' => $thisImageProcessor['commandPath'] + .' ' + .$thisImageProcessor['commandOptions'], + 'installed' => file_exists($thisImageProcessor['commandPath']), + ]; + } + } } - // Return the result of the command's output or error - if ($shellCommand->execute()) { - $result = $shellCommand->getOutput(); - } else { - $result = $shellCommand->getError(); + return $result; + } + + /** + * Return an array of active image variant creators + * + * @return array + */ + public function getActiveVariantCreators(): array + { + $result = []; + $settings = ImageOptimize::$plugin->getSettings(); + // Get the active image variant creators + $activeImageVariantCreators = $settings['activeImageVariantCreators']; + foreach ($activeImageVariantCreators as $imageFormat => $imageCreator) { + // Iterate through all of the image variant creators for this format + $imageVariantCreators = $settings['imageVariantCreators']; + foreach ($activeImageVariantCreators[$imageFormat] as $variantCreator) { + if (!empty($imageVariantCreators[$variantCreator])) { + $thisVariantCreator = $imageVariantCreators[$variantCreator]; + $result[] = [ + 'format' => $imageFormat, + 'creator' => $variantCreator, + 'command' => $thisVariantCreator['commandPath'] + .' ' + .$thisVariantCreator['commandOptions'], + 'installed' => file_exists($thisVariantCreator['commandPath']), + ]; + } + } } return $result; } + /** + * Resave all of the Asset elements in the Volume $volume + * + * @param Volume $volume + */ + public function resaveVolumeAssets(Volume $volume) + { + $siteId = Craft::$app->getSites()->getPrimarySite()->id; + + Craft::$app->getQueue()->push(new ResaveElements([ + 'description' => Craft::t('image-optimize', 'Resaving Assets in {name}', ['name' => $volume->name]), + 'elementType' => Asset::class, + 'criteria' => [ + 'siteId' => $siteId, + 'volumeId' => $volume->id, + 'status' => null, + 'enabledForSite' => false, + ], + ])); + } + /** * @param string $path * @param string $extension @@ -455,9 +491,9 @@ protected function executeShellCommand(string $command): string protected function swapPathExtension(string $path, string $extension): string { $pathParts = pathinfo($path); - $newPath = $pathParts['filename'] . '.' . $extension; + $newPath = $pathParts['filename'].'.'.$extension; if (!empty($pathParts['dirname']) && $pathParts['dirname'] !== '.') { - $newPath = $pathParts['dirname'] . DIRECTORY_SEPARATOR . $newPath; + $newPath = $pathParts['dirname'].DIRECTORY_SEPARATOR.$newPath; $newPath = preg_replace('#/+#', '/', $newPath); } diff --git a/src/templates/_components/fields/OptimizedImages_input.twig b/src/templates/_components/fields/OptimizedImages_input.twig index 3aacc85d..8b3cdaaf 100644 --- a/src/templates/_components/fields/OptimizedImages_input.twig +++ b/src/templates/_components/fields/OptimizedImages_input.twig @@ -71,7 +71,7 @@ refX="0" refY="{{ markerHeight / 2 }}" orient="auto" markerUnits="strokeWidth"> - + {{ variant.width }}w {{ variant.width }}w diff --git a/src/templates/_settings.twig b/src/templates/_settings.twig new file mode 100644 index 00000000..6e335c44 --- /dev/null +++ b/src/templates/_settings.twig @@ -0,0 +1,18 @@ +{% requireAdmin %} + +{% set crumbs = [ + { label: "Settings"|t('app'), url: url('settings') }, + { label: "Plugins"|t('app'), url: url('settings/plugins') } +] %} + +{% set fullPageForm = false %} + +{% extends "_layouts/cp" %} +{% set title = plugin.name %} +{% set docTitle = title ~ ' - ' ~ "Plugins"|t('app') %} + +{% block content %} + {% namespace 'settings' %} + {{ settingsHtml|raw }} + {% endnamespace %} +{% endblock %} diff --git a/src/templates/settings.twig b/src/templates/settings.twig index 8f5cf665..3912dea5 100644 --- a/src/templates/settings.twig +++ b/src/templates/settings.twig @@ -12,16 +12,10 @@ */ #} - {% import "_includes/forms" as forms %} {% do view.registerAssetBundle("nystudio107\\imageoptimize\\assetbundles\\imageoptimize\\ImageOptimizeAsset") %} -{% block body %}
@@ -113,8 +107,6 @@ {% endif %}
-{% endblock %} - {% js %} new Craft.AdminTable({ tableSelector: '#imageProcessors',