diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c293a2ab..627ec728f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. This projects adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/). +## [8.0.3] + +### Added +- Media support for svg and json. +- Optimization service class for better performance. + +### Fixed +- Phpstan config fix for theme and plugin setup +- Project name variable will now be ucfirst on setup. +- Removed .git folder after the setup. + ## [8.0.2] ### Fixed @@ -554,6 +565,7 @@ Init setup [Unreleased]: https://github.com/infinum/eightshift-libs/compare/main...HEAD +[8.0.3]: https://github.com/infinum/eightshift-libs/compare/8.0.2...8.0.3 [8.0.2]: https://github.com/infinum/eightshift-libs/compare/8.0.1...8.0.2 [8.0.1]: https://github.com/infinum/eightshift-libs/compare/8.0.0...8.0.1 [8.0.0]: https://github.com/infinum/eightshift-libs/compare/7.1.2...8.0.0 diff --git a/src/Cli/AbstractCli.php b/src/Cli/AbstractCli.php index e35e289e3..1ea8881fd 100644 --- a/src/Cli/AbstractCli.php +++ b/src/Cli/AbstractCli.php @@ -597,6 +597,10 @@ public function renameUse(array $args = []): self public function renameGeneric(string $keyName, array $args): self { if (isset($args[$keyName])) { + if ($keyName === self::ARG_PROJECT_NAME) { + $args[$keyName] = \ucfirst($args[$keyName]); + } + $this->fileContents = \str_replace( $this->getArgTemplate($keyName), $args[$keyName], @@ -641,6 +645,7 @@ public function cleanUpInitialBoilerplate(string $destination): void { $this->cliLog('--------------------------------------------------', 'C'); $this->cliLog('Removing initial boilerplate setup files', 'C'); + \shell_exec("cd {$destination} && rm -rf .git"); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_shell_exec \shell_exec("cd {$destination} && rm -rf .github"); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_shell_exec \shell_exec("cd {$destination} && rm CODE_OF_CONDUCT.md"); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_shell_exec \shell_exec("cd {$destination} && rm CHANGELOG.md"); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_shell_exec diff --git a/src/Cli/Cli.php b/src/Cli/Cli.php index 1f4f058a3..99c7454b8 100644 --- a/src/Cli/Cli.php +++ b/src/Cli/Cli.php @@ -66,6 +66,7 @@ use EightshiftLibs\Media\RegenerateWebPMediaCli; use EightshiftLibs\Media\UseWebPMediaCli; use EightshiftLibs\Misc\VersionCli; +use EightshiftLibs\ModifyAdminAppearance\OptimizationCli; use EightshiftLibs\Plugin\PluginCli; use EightshiftLibs\Readme\ReadmeCli; use EightshiftLibs\Rest\Routes\LoadMore\LoadMoreRouteCli; @@ -122,6 +123,7 @@ class Cli MediaCli::class, MenuCli::class, ModifyAdminAppearanceCli::class, + OptimizationCli::class, ReadmeCli::class, FieldCli::class, RouteCli::class, diff --git a/src/Helpers/ObjectHelperTrait.php b/src/Helpers/ObjectHelperTrait.php index 0098d856c..41ed95431 100644 --- a/src/Helpers/ObjectHelperTrait.php +++ b/src/Helpers/ObjectHelperTrait.php @@ -28,7 +28,7 @@ trait ObjectHelperTrait * * @return boolean */ - public function isValidXml(string $xml) + public static function isValidXml(string $xml) { \libxml_use_internal_errors(true); $doc = new DOMDocument('1.0', 'utf-8'); diff --git a/src/Init/plugin/.gitignore b/src/Init/plugin/.gitignore new file mode 100644 index 000000000..6ffcec905 --- /dev/null +++ b/src/Init/plugin/.gitignore @@ -0,0 +1,6 @@ +# Project Specific +/public +/vendor +/vendor-prefixed +/node_modules +.DS_Store diff --git a/src/Init/plugin/phpstan.neon.dist b/src/Init/plugin/phpstan.neon.dist index 4544aa75e..1a7ad2080 100644 --- a/src/Init/plugin/phpstan.neon.dist +++ b/src/Init/plugin/phpstan.neon.dist @@ -14,4 +14,4 @@ parameters: - '#^Variable \$renderContent might not be defined\.#' - '#^Variable \$this might not be defined\.#' - '#^Variable \$templatePath might not be defined\.#' - checkGenericClassInNonGenericObjectType: false + - identifier: missingType.generics diff --git a/src/Init/theme/.gitignore b/src/Init/theme/.gitignore new file mode 100644 index 000000000..6ffcec905 --- /dev/null +++ b/src/Init/theme/.gitignore @@ -0,0 +1,6 @@ +# Project Specific +/public +/vendor +/vendor-prefixed +/node_modules +.DS_Store diff --git a/src/Init/theme/phpstan.neon.dist b/src/Init/theme/phpstan.neon.dist index 4544aa75e..1a7ad2080 100644 --- a/src/Init/theme/phpstan.neon.dist +++ b/src/Init/theme/phpstan.neon.dist @@ -14,4 +14,4 @@ parameters: - '#^Variable \$renderContent might not be defined\.#' - '#^Variable \$this might not be defined\.#' - '#^Variable \$templatePath might not be defined\.#' - checkGenericClassInNonGenericObjectType: false + - identifier: missingType.generics diff --git a/src/Media/AbstractMedia.php b/src/Media/AbstractMedia.php index b7bb9e684..afdae4e3b 100644 --- a/src/Media/AbstractMedia.php +++ b/src/Media/AbstractMedia.php @@ -11,6 +11,11 @@ namespace EightshiftLibs\Media; use EightshiftLibs\Services\ServiceInterface; +use EightshiftLibs\Helpers\Helpers; +use Exception; +use SimpleXMLElement; +use WP_Error; +use WP_Post; /** * Abstract class Media class. @@ -73,6 +78,129 @@ public function deleteWebPMedia(int $attachmentId): void $this->deleteWebPMediaAllSizes($attachmentId); } + /** + * Enable additional uploads in media. + * + * @param array $mimes Load all mimes types. + * @return array Return original and updated. + */ + public function enableMimeTypes(array $mimes): array + { + $mimes['svg'] = 'image/svg+xml'; + $mimes['json'] = 'application/json'; + return $mimes; + } + + /** + * Enable SVG preview in Media Library. + * + * @param array $response Array of prepared attachment data. + * @param int|object $attachment Attachment ID or object. + * @return array|false Array of attachment details, or void if the parameter does not correspond to an attachment. + */ + public function enableSvgMediaLibraryPreview($response, $attachment) + { + if ($response['type'] === 'image' && $response['subtype'] === 'svg+xml' && \class_exists('SimpleXMLElement')) { + try { + $path = \get_attached_file($attachment instanceof WP_Post ? $attachment->ID : $attachment); + + if (\file_exists($path)) { + $svgContent = \file($path); + $svgContent = \implode(' ', $svgContent); + + if (!Helpers::isValidXml($svgContent)) { + // Translators: %s represents the filename, eg. demo.json. + new WP_Error(\sprintf(\esc_html__('Error: File invalid: %s', 'eightshift-libs'), $path)); + return false; + } + + $svg = new SimpleXMLElement($svgContent); + $src = $response['url']; + $width = (int) $svg['width']; + $height = (int) $svg['height']; + + // media gallery. + $response['image'] = \compact('src', 'width', 'height'); + $response['thumb'] = \compact('src', 'width', 'height'); + + // media single. + $response['sizes']['full'] = [ + 'height' => $height, + 'width' => $width, + 'url' => $src, + 'orientation' => $height > $width ? 'portrait' : 'landscape', + ]; + } + } catch (Exception $e) { + // Translators: %s represents the error text. + new WP_Error(\sprintf(\esc_html__('Error: %s', 'eightshift-libs'), $e)); + } + } + + return $response; + } + + /** + * Check if SVG is valid on Add New Media Page. + * + * @param array $response Response array. + * @return array + */ + public function validateSvgOnUpload($response) + { + if ($response['type'] !== 'image/svg+xml' && !\class_exists('SimpleXMLElement')) { + return $response; + } + + $path = $response['tmp_name'] ?? ''; + + $svgContent = \file($path); + $svgContent = \implode(' ', $svgContent); + + if (!\file_exists($path) || Helpers::isValidXml($svgContent)) { + return $response; + } + + return [ + 'size' => $response, + 'name' => $response['name'], + ]; + } + + /** + * Enable SVG file upload. + * + * @param array $filetypeExtData Array fot output data. + * @param string $file Full path to the file. + * @param string $filename The name of the file (may differ from $file due to $file being in a tmp directory). + * @return array + */ + public function enableSvgUpload($filetypeExtData, $file, $filename): array + { + if (\substr($filename, -4) === '.svg') { + $filetypeExtData['ext'] = 'svg'; + $filetypeExtData['type'] = 'image/svg+xml'; + } + return $filetypeExtData; + } + + /** + * Enable JSON file upload. + * + * @param array $filetypeExtData Array fot output data. + * @param string $file Full path to the file. + * @param string $filename The name of the file (may differ from $file due to $file being in a tmp directory). + * @return array + */ + public function enableJsonUpload($filetypeExtData, $file, $filename): array + { + if (\substr($filename, -5) === '.json') { + $filetypeExtData['ext'] = 'json'; + $filetypeExtData['type'] = 'application/json'; + } + return $filetypeExtData; + } + /** * WebP Quality compression range 0-100. * diff --git a/src/Media/MediaExample.php b/src/Media/MediaExample.php index aa7d505a8..529e5b78c 100644 --- a/src/Media/MediaExample.php +++ b/src/Media/MediaExample.php @@ -27,6 +27,11 @@ class MediaExample extends AbstractMedia public function register(): void { \add_action('after_setup_theme', [$this, 'addThemeSupport'], 20); + \add_filter('upload_mimes', [$this, 'enableMimeTypes']); + \add_filter('wp_prepare_attachment_for_js', [$this, 'enableSvgMediaLibraryPreview'], 10, 2); + \add_filter('wp_handle_upload_prefilter', [$this, 'validateSvgOnUpload']); + \add_filter('wp_check_filetype_and_ext', [$this, 'enableSvgUpload'], 10, 3); + \add_filter('wp_check_filetype_and_ext', [$this, 'enableJsonUpload'], 10, 3); // WebP. if (\extension_loaded('gd')) { diff --git a/src/Optimization/OptimizationCli.php b/src/Optimization/OptimizationCli.php new file mode 100644 index 000000000..68fe865e4 --- /dev/null +++ b/src/Optimization/OptimizationCli.php @@ -0,0 +1,84 @@ +>|string> + */ + public function getDoc(): array + { + return [ + 'shortdesc' => 'Create optimization class.', + 'longdesc' => $this->prepareLongDesc(" + ## USAGE + + Used to create optimization service class remove unecesery WP core stuff for faster loading. + + ## EXAMPLES + + # Create service class: + $ wp {$this->commandParentName} {$this->getCommandParentName()} {$this->getCommandName()} + + ## RESOURCES + + Service class will be created from this example: + https://github.com/infinum/eightshift-libs/blob/develop/src/Optimization/OptimizationExample.php + "), + ]; + } + + /* @phpstan-ignore-next-line */ + public function __invoke(array $args, array $assocArgs) + { + $assocArgs = $this->prepareArgs($assocArgs); + + $this->getIntroText($assocArgs); + + $className = $this->getClassShortName(); + + // Read the template contents, and replace the placeholders with provided variables. + $this->getExampleTemplate(__DIR__, $className) + ->renameClassName($className) + ->renameGlobals($assocArgs) + ->outputWrite(Helpers::getProjectPaths('srcDestination', 'Optimization'), "{$className}.php", $assocArgs); + } +} diff --git a/src/Optimization/OptimizationExample.php b/src/Optimization/OptimizationExample.php new file mode 100644 index 000000000..142153e00 --- /dev/null +++ b/src/Optimization/OptimizationExample.php @@ -0,0 +1,69 @@ +