Plugin Updates

This commit is contained in:
Tony Volpe
2024-03-19 15:33:31 +00:00
parent ff5b56dc44
commit 3a70a6e4bf
317 changed files with 8178 additions and 2933 deletions

View File

@@ -44,7 +44,7 @@ class AdminBar {
if ( $user->is_free() ) {
$text = esc_html__( 'Upgrade your plan now for more!', 'rocket' ) . '<br>' .
esc_html__( 'From $4.99/month only, keep going with image optimization!', 'rocket' );
esc_html__( 'From $5.99/month only, keep going with image optimization!', 'rocket' );
$button_text = esc_html__( 'Upgrade My Plan', 'rocket' );
$upgrade_link = IMAGIFY_APP_DOMAIN . '/subscription/?utm_source=plugin&utm_medium=notification';
} elseif ( $user->is_growth() ) {

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Imagify\Avif;
use Imagify\WriteFile\AbstractApacheDirConfFile;
/**
* Add and remove contents to the .htaccess file to display AVIF images on the site.
*/
class Apache extends AbstractApacheDirConfFile {
/**
* Name of the tag used as block delemiter.
*
* @var string
*/
const TAG_NAME = 'Imagify: avif file type';
/**
* Get unfiltered new contents to write into the file.
*
* @return string
*/
protected function get_raw_new_contents() {
return trim( '
<IfModule mod_mime.c>
AddType image/avif .avif
</IfModule>' );
}
}

View File

@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace Imagify\Avif;
use Imagify\EventManagement\SubscriberInterface;
use Imagify\Notices\Notices;
use Imagify\WriteFile\WriteFileInterface;
/**
* Display AVIF images on the site using picture tag.
*/
class Display implements SubscriberInterface {
/**
* Server conf object.
*
* @var WriteFileInterface|null
* @since 1.9
*/
protected $server_conf = null;
/**
* Returns an array of events this subscriber listens to
*
* @return array
*/
public static function get_subscribed_events() {
return [
'imagify_settings_on_save' => [ 'maybe_add_rewrite_rules', 12 ],
'imagify_activation' => 'activate',
'imagify_deactivation' => 'deactivate',
];
}
/**
* If display Next-Gen images, add the AVIF type to the .htaccess/etc file.
*
* @since 1.9
*
* @param array $values The option values.
*
* @return array
*/
public function maybe_add_rewrite_rules( $values ) {
if ( ! $this->get_server_conf() ) {
return $values;
}
$enabled = isset( $values['display_nextgen'] ) ? true : false;
$result = false;
if ( $enabled ) {
// Add the AVIF file type.
$result = $this->get_server_conf()->add();
} elseif ( ! $enabled ) {
// Remove the AVIF file type.
$result = $this->get_server_conf()->remove();
}
if ( ! is_wp_error( $result ) ) {
return $values;
}
// Display an error message.
if ( is_multisite() && strpos( wp_get_referer(), network_admin_url( '/' ) ) === 0 ) {
Notices::get_instance()->add_network_temporary_notice( $result->get_error_message() );
return $values;
}
Notices::get_instance()->add_site_temporary_notice( $result->get_error_message() );
return $values;
}
/**
* Add rules on plugin activation.
*
* @since 1.9
*/
public function activate() {
$conf = $this->get_server_conf();
if ( ! $conf ) {
return;
}
if ( ! get_imagify_option( 'display_nextgen' ) ) {
return;
}
if ( is_wp_error( $conf->is_file_writable() ) ) {
return;
}
$conf->add();
}
/**
* Remove rules on plugin deactivation.
*
* @since 1.9
*/
public function deactivate() {
$conf = $this->get_server_conf();
if ( ! $conf ) {
return;
}
$file_path = $conf->get_file_path();
$filesystem = \Imagify_Filesystem::get_instance();
if ( ! $filesystem->exists( $file_path ) ) {
return;
}
if ( ! $filesystem->is_writable( $file_path ) ) {
return;
}
$conf->remove();
}
/**
* Get the path to the directory conf file.
*
* @since 1.9
*
* @param bool $relative True to get a path relative to the sites root.
* @return string|bool The file path. False on failure.
*/
public function get_file_path( $relative = false ) {
if ( ! $this->get_server_conf() ) {
return false;
}
$file_path = $this->get_server_conf()->get_file_path();
if ( $relative ) {
return \Imagify_Filesystem::get_instance()->make_path_relative( $file_path );
}
return $file_path;
}
/**
* Get the server conf instance.
* Note: nothing needed for nginx.
*
* @since 1.9
*
* @return WriteFileInterface
*/
protected function get_server_conf() {
global $is_apache, $is_iis7;
if ( isset( $this->server_conf ) ) {
return $this->server_conf;
}
if ( $is_apache ) {
$this->server_conf = new Apache();
} elseif ( $is_iis7 ) {
$this->server_conf = new IIS();
}
return $this->server_conf;
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Imagify\Avif;
use Imagify\WriteFile\AbstractIISDirConfFile;
/**
* Add and remove contents to the web.config file to display AVIF images on the site.
*/
class IIS extends AbstractIISDirConfFile {
/**
* Name of the tag used as block delemiter.
*
* @var string
*/
const TAG_NAME = 'Imagify: avif file type';
/**
* Get unfiltered new contents to write into the file.
*
* @return array
*/
protected function get_raw_new_contents() {
return trim( '
<!-- @parent /configuration/system.webServer -->
<staticContent name="' . esc_attr( static::TAG_NAME ) . ' 1">
<mimeMap fileExtension=".avif" mimeType="image/avif" />
</staticContent>' );
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Imagify\Avif\RewriteRules;
use Imagify\WriteFile\AbstractApacheDirConfFile;
/**
* Add and remove rewrite rules to the .htaccess file to display AVIF images on the site.
*/
class Apache extends AbstractApacheDirConfFile {
/**
* Name of the tag used as block delimiter.
*
* @var string
*/
const TAG_NAME = 'Imagify: rewrite rules for avif';
/**
* Get unfiltered new contents to write into the file.
*
* @access protected
*
* @return string
*/
protected function get_raw_new_contents() {
$extensions = $this->get_extensions_pattern();
$extensions = str_replace( '|avif', '', $extensions );
$home_root = wp_parse_url( home_url( '/' ) );
$home_root = $home_root['path'];
return trim( '
<IfModule mod_setenvif.c>
# Vary: Accept for all the requests to jpeg, png, and gif.
SetEnvIf Request_URI "\.(' . $extensions . ')$" REQUEST_image
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase ' . $home_root . '
# Check if browser supports AVIF images.
# Update the MIME type accordingly.
RewriteCond %{HTTP_ACCEPT} image/avif
# Check if AVIF replacement image exists.
RewriteCond %{REQUEST_FILENAME}.avif -f
# Serve AVIF image instead.
RewriteRule (.+)\.(' . $extensions . ')$ $1.$2.avif [T=image/avif,NC]
</IfModule>
<IfModule mod_headers.c>
# Update the MIME type accordingly.
Header append Vary Accept env=REQUEST_image
</IfModule>' );
}
}

View File

@@ -0,0 +1,224 @@
<?php
declare(strict_types=1);
namespace Imagify\Avif\RewriteRules;
use Imagify\EventManagement\SubscriberInterface;
use Imagify\Notices\Notices;
use Imagify\WriteFile\WriteFileInterface;
/**
* Display Avif images on the site with rewrite rules.
*/
class Display implements SubscriberInterface {
/**
* Configuration file writer.
*
* @var WriteFileInterface|null
*/
protected $server_conf = null;
/**
* Option value.
*
* @var string
*/
const OPTION_VALUE = 'rewrite';
/**
* Returns an array of events this subscriber listens to
*
* @return array
*/
public static function get_subscribed_events() {
return [
'imagify_settings_on_save' => [ 'maybe_add_rewrite_rules', 11 ],
'imagify_settings_webp_info' => 'maybe_add_avif_info',
'imagify_activation' => 'activate',
'imagify_deactivation' => 'deactivate',
];
}
/**
* If display AVIF images via rewrite rules, add the rules to the .htaccess/etc file.
*
* @since 1.9
*
* @param array $values The option values.
*
* @return array
*/
public function maybe_add_rewrite_rules( $values ) {
$was_enabled = (bool) get_imagify_option( 'display_nextgen' );
$is_enabled = ! empty( $values['display_nextgen'] );
// Which method?
$old_value = get_imagify_option( 'display_nextgen_method' );
$new_value = ! empty( $values['display_nextgen_method'] ) ? $values['display_nextgen_method'] : '';
// Decide when to add or remove rules.
$is_rewrite = self::OPTION_VALUE === $new_value;
$was_rewrite = self::OPTION_VALUE === $old_value;
if ( ! $this->get_server_conf() ) {
return $values;
}
$result = false;
if ( $is_enabled && $is_rewrite && ( ! $was_enabled || ! $was_rewrite ) ) {
// Add the rewrite rules.
$result = $this->get_server_conf()->add();
} elseif ( $was_enabled && $was_rewrite && ( ! $is_enabled || ! $is_rewrite ) ) {
// Remove the rewrite rules.
$result = $this->get_server_conf()->remove();
}
if ( ! is_wp_error( $result ) ) {
return $values;
}
// Display an error message.
if ( is_multisite() && strpos( wp_get_referer(), network_admin_url( '/' ) ) === 0 ) {
Notices::get_instance()->add_network_temporary_notice( $result->get_error_message() );
return $values;
}
Notices::get_instance()->add_site_temporary_notice( $result->get_error_message() );
return $values;
}
/**
* If the conf file is not writable, add a warning.
*/
public function maybe_add_avif_info() {
$conf = $this->get_server_conf();
if ( ! $conf ) {
return;
}
$writable = $conf->is_file_writable();
if ( is_wp_error( $writable ) ) {
$rules = $conf->get_new_contents();
if ( ! $rules ) {
// Uh?
return;
}
printf(
/* translators: %s is a file name. */
esc_html__( 'If you choose to use rewrite rules, you will have to add the following lines manually to the %s file:', 'imagify' ),
'<code>' . $this->get_file_path( true ) . '</code>'
);
echo '<pre class="code">' . esc_html( $rules ) . '</pre>';
}
}
/**
* Add rules on plugin activation.
*/
public function activate() {
$conf = $this->get_server_conf();
if ( ! $conf ) {
return;
}
if ( ! get_imagify_option( 'display_nextgen' ) ) {
return;
}
if ( self::OPTION_VALUE !== get_imagify_option( 'display_nextgen_method' ) ) {
return;
}
if ( is_wp_error( $conf->is_file_writable() ) ) {
return;
}
$conf->add();
}
/**
* Remove rules on plugin deactivation.
*
* @since 1.9
*/
public function deactivate() {
$conf = $this->get_server_conf();
if ( ! $conf ) {
return;
}
if ( ! get_imagify_option( 'display_nextgen' ) ) {
return;
}
if ( self::OPTION_VALUE !== get_imagify_option( 'display_nextgen_method' ) ) {
return;
}
$file_path = $conf->get_file_path();
$filesystem = \Imagify_Filesystem::get_instance();
if ( ! $filesystem->exists( $file_path ) ) {
return;
}
if ( ! $filesystem->is_writable( $file_path ) ) {
return;
}
$conf->remove();
}
/**
* Get the path to the directory conf file.
*
* @param bool $relative True to get a path relative to the sites root.
*
* @return string|bool The file path. False on failure.
*/
public function get_file_path( $relative = false ) {
if ( ! $this->get_server_conf() ) {
return false;
}
$file_path = $this->get_server_conf()->get_file_path();
if ( $relative ) {
return \Imagify_Filesystem::get_instance()->make_path_relative( $file_path );
}
return $file_path;
}
/**
* Get the server conf instance.
*
* @return WriteFileInterface
*/
protected function get_server_conf() {
global $is_apache, $is_iis7, $is_nginx;
if ( isset( $this->server_conf ) ) {
return $this->server_conf;
}
if ( $is_apache ) {
$this->server_conf = new Apache();
} elseif ( $is_iis7 ) {
$this->server_conf = new IIS();
} elseif ( $is_nginx ) {
$this->server_conf = new Nginx();
}
return $this->server_conf;
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Imagify\Avif\RewriteRules;
use Imagify\WriteFile\AbstractIISDirConfFile;
/**
* Add and remove rewrite rules to the web.config file to display AVIF images on the site.
*/
class IIS extends AbstractIISDirConfFile {
/**
* Name of the tag used as block delemiter.
*
* @var string
*/
const TAG_NAME = 'Imagify: rewrite rules for avif';
/**
* Get unfiltered new contents to write into the file.
*
* @source https://github.com/igrigorik/webp-detect/blob/master/iis.config
*
* @return string
*/
protected function get_raw_new_contents() {
$extensions = $this->get_extensions_pattern();
$extensions = str_replace( '|avif', '', $extensions );
$home_root = wp_parse_url( home_url( '/' ) );
$home_root = $home_root['path'];
return trim( '
<!-- @parent /configuration/system.webServer/rewrite/rules -->
<rule name="' . esc_attr( static::TAG_NAME ) . ' 2">
<match url="^(' . $home_root . '.+)\.(' . $extensions . ')$" ignoreCase="true" />
<conditions logicalGrouping="MatchAll">
<add input="{HTTP_ACCEPT}" pattern="image/avif" ignoreCase="false" />
<add input="{DOCUMENT_ROOT}/{R:1}{R:2}.avif" matchType="IsFile" />
</conditions>
<action type="Rewrite" url="{R:1}{R:2}.avif" logRewrittenUrl="true" />
<serverVariables>
<set name="ACCEPTS_AVIF" value="true" />
</serverVariables>
</rule>
<!-- @parent /configuration/system.webServer/rewrite/outboundRules -->
<rule preCondition="IsAvif" name="' . esc_attr( static::TAG_NAME ) . ' 3">
<match serverVariable="RESPONSE_Vary" pattern=".*" />
<action type="Rewrite" value="Accept"/>
</rule>
<preConditions name="' . esc_attr( static::TAG_NAME ) . ' 4">
<preCondition name="IsAvif">
<add input="{ACCEPTS_AVIF}" pattern="true" ignoreCase="false" />
</preCondition>
</preConditions>' );
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Imagify\Avif\RewriteRules;
use Imagify\WriteFile\AbstractNginxDirConfFile;
/**
* Add and remove rewrite rules to the imagify.conf file to display AVIF images on the site.
*/
class Nginx extends AbstractNginxDirConfFile {
/**
* Name of the tag used as block delimiter.
*
* @var string
*/
const TAG_NAME = 'Imagify: rewrite rules for avif';
/**
* Get unfiltered new contents to write into the file.
*
* @access protected
*
* @return string
*/
protected function get_raw_new_contents() {
return '';
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Imagify\Avif;
use Imagify\Dependencies\League\Container\ServiceProvider\AbstractServiceProvider;
use Imagify\Avif\RewriteRules\Display as RewriteRules;
/**
* Service provider for AVIF rewrite rules
*/
class ServiceProvider extends AbstractServiceProvider {
/**
* Services provided by this provider
*
* @var array
*/
protected $provides = [
'avif_display',
'avif_rewrite_rules',
];
/**
* Subscribers provided by this provider
*
* @var array
*/
public $subscribers = [
'avif_display',
'avif_rewrite_rules',
];
/**
* Registers the provided classes
*
* @return void
*/
public function register() {
$this->getContainer()->share( 'avif_display', Display::class );
$this->getContainer()->share( 'avif_rewrite_rules', RewriteRules::class );
}
/**
* Returns the subscribers array
*
* @return array
*/
public function get_subscribers() {
return $this->subscribers;
}
}

View File

@@ -105,13 +105,19 @@ abstract class AbstractBulk implements BulkInterface {
}
/**
* Tell if there are optimized media without WebP versions.
* Tell if there are optimized media without next-gen versions.
*
* @since 1.9
*
* @return int The number of media.
*/
public function has_optimized_media_without_webp() {
return count( $this->get_optimized_media_ids_without_webp()['ids'] );
public function has_optimized_media_without_nextgen() {
$format = 'webp';
if ( get_imagify_option( 'convert_to_avif' ) ) {
$format = 'avif';
}
return count( $this->get_optimized_media_ids_without_format( $format )['ids'] );
}
}

View File

@@ -17,15 +17,15 @@ class Bulk {
*/
public function init() {
add_action( 'imagify_optimize_media', [ $this, 'optimize_media' ], 10, 3 );
add_action( 'imagify_convert_webp', [ $this, 'generate_webp_versions' ], 10, 2 );
add_action( 'imagify_convert_webp_finished', [ $this, 'clear_webp_transients' ], 10, 2 );
add_action( 'imagify_convert_next_gen', [ $this, 'generate_nextgen_versions' ], 10, 2 );
add_action( 'wp_ajax_imagify_bulk_optimize', [ $this, 'bulk_optimize_callback' ] );
add_action( 'wp_ajax_imagify_missing_webp_generation', [ $this, 'missing_webp_callback' ] );
add_action( 'wp_ajax_imagify_missing_nextgen_generation', [ $this, 'missing_nextgen_callback' ] );
add_action( 'wp_ajax_imagify_get_folder_type_data', [ $this, 'get_folder_type_data_callback' ] );
add_action( 'wp_ajax_imagify_bulk_info_seen', [ $this, 'bulk_info_seen_callback' ] );
add_action( 'wp_ajax_imagify_bulk_get_stats', [ $this, 'bulk_get_stats_callback' ] );
add_action( 'imagify_after_optimize', [ $this, 'check_optimization_status' ], 10, 2 );
add_action( 'imagify_deactivation', [ $this, 'delete_transients_data' ] );
add_action( 'update_option_imagify_settings', [ $this, 'maybe_generate_missing_nextgen' ], 10, 2 );
}
/**
@@ -37,7 +37,7 @@ class Bulk {
delete_transient( 'imagify_custom-folders_optimize_running' );
delete_transient( 'imagify_wp_optimize_running' );
delete_transient( 'imagify_bulk_optimization_complete' );
delete_transient( 'imagify_missing_webp_total' );
delete_transient( 'imagify_missing_next_gen_total' );
}
/**
@@ -173,17 +173,31 @@ class Bulk {
'message' => 'over-quota',
];
}
$formats = imagify_nextgen_images_formats();
$media_ids = [
'ids' => [],
'errors' => [
'no_file_path' => [],
'no_backup' => [],
],
];
foreach ( $formats as $format ) {
$result = $this->get_bulk_instance( $context )->get_optimized_media_ids_without_format( $format );
$media_ids['ids'] = array_merge( $media_ids['ids'], $result['ids'] );
}
$get_unoptimized_media_ids = $this->get_bulk_instance( $context )->get_unoptimized_media_ids( $optimization_level );
$media_ids = $this->get_bulk_instance( $context )->get_unoptimized_media_ids( $optimization_level );
$media_ids['ids'] = array_merge( $media_ids['ids'], $get_unoptimized_media_ids );
if ( empty( $media_ids ) ) {
if ( empty( $media_ids['ids'] ) ) {
return [
'success' => false,
'message' => 'no-images',
];
}
$media_ids['ids'] = array_unique( $media_ids['ids'] );
foreach ( $media_ids as $media_id ) {
foreach ( $media_ids['ids'] as $media_id ) {
try {
as_enqueue_async_action(
'imagify_optimize_media',
@@ -213,13 +227,14 @@ class Bulk {
}
/**
* Runs the WebP generation
* Runs the next-gen generation
*
* @param array $contexts An array of contexts (WP/Custom folders).
* @param array $formats An array of format to generate.
*
* @return array
*/
public function run_generate_webp( array $contexts ) {
public function run_generate_nextgen( array $contexts, array $formats ) {
if ( ! $this->can_optimize() ) {
return [
'success' => false,
@@ -227,28 +242,29 @@ class Bulk {
];
}
delete_transient( 'imagify_stat_without_webp' );
delete_transient( 'imagify_stat_without_next_gen' );
$medias = [];
foreach ( $contexts as $context ) {
$media = $this->get_bulk_instance( $context )->get_optimized_media_ids_without_webp();
foreach ( $formats as $format ) {
$media = $this->get_bulk_instance( $context )->get_optimized_media_ids_without_format( $format );
if ( ! $media['ids'] && $media['errors']['no_backup'] ) {
// No backup, no next-gen.
return [
'success' => false,
'message' => 'no-backup',
];
} elseif ( ! $media['ids'] && $media['errors']['no_file_path'] ) {
// Error.
return [
'success' => false,
'message' => __( 'The path to the selected files could not be retrieved.', 'imagify' ),
];
}
if ( ! $media['ids'] && $media['errors']['no_backup'] ) {
// No backup, no WebP.
return [
'success' => false,
'message' => 'no-backup',
];
} elseif ( ! $media['ids'] && $media['errors']['no_file_path'] ) {
// Error.
return [
'success' => false,
'message' => __( 'The path to the selected files could not be retrieved.', 'imagify' ),
];
$medias[ $context ] = $media['ids'];
}
$medias[ $context ] = $media['ids'];
}
if ( empty( $medias ) ) {
@@ -266,12 +282,12 @@ class Bulk {
foreach ( $media_ids as $media_id ) {
try {
as_enqueue_async_action(
'imagify_convert_webp',
'imagify_convert_next_gen',
[
'id' => $media_id,
'context' => $context,
],
"imagify-{$context}-convert-webp"
"imagify-{$context}-convert-nextgen"
);
} catch ( Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
// nothing to do.
@@ -279,7 +295,7 @@ class Bulk {
}
}
set_transient( 'imagify_missing_webp_total', $total, HOUR_IN_SECONDS );
set_transient( 'imagify_missing_next_gen_total', $total, HOUR_IN_SECONDS );
return [
'success' => true,
@@ -310,13 +326,13 @@ class Bulk {
}
/**
* Filter the name of the class to use for bulk process.
*
* @since 1.9
*
* @param int $class_name The class name.
* @param string $context The context name.
*/
* Filter the name of the class to use for bulk process.
*
* @since 1.9
*
* @param int $class_name The class name.
* @param string $context The context name.
*/
$class_name = apply_filters( 'imagify_bulk_class_name', $class_name, $context );
return '\\' . ltrim( $class_name, '\\' );
@@ -374,7 +390,7 @@ class Bulk {
}
/**
* Generate WebP images if they are missing.
* Generate next-gen images if they are missing.
*
* @since 2.1
*
@@ -383,12 +399,12 @@ class Bulk {
*
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
*/
public function generate_webp_versions( int $media_id, string $context ) {
public function generate_nextgen_versions( int $media_id, string $context ) {
if ( ! $this->can_optimize() ) {
return false;
}
return imagify_get_optimization_process( $media_id, $context )->generate_webp_versions();
return imagify_get_optimization_process( $media_id, $context )->generate_nextgen_versions();
}
/**
@@ -451,10 +467,6 @@ class Bulk {
return (int) $level;
}
/** ----------------------------------------------------------------------------------------- */
/** BULK OPTIMIZATION CALLBACKS ============================================================= */
/** ----------------------------------------------------------------------------------------- */
/**
* Launch the bulk optimization action
*
@@ -480,14 +492,14 @@ class Bulk {
}
/**
* Launch the missing WebP versions generation
* Launch the missing Next-gen versions generation
*
* @return void
*/
public function missing_webp_callback() {
public function missing_nextgen_callback() {
imagify_check_nonce( 'imagify-bulk-optimize' );
$contexts = explode( '_', sanitize_key( wp_unslash( $_GET['context'] ) ) );
$contexts = $this->get_contexts();
foreach ( $contexts as $context ) {
if ( ! imagify_get_context( $context )->current_user_can( 'bulk-optimize' ) ) {
@@ -495,8 +507,9 @@ class Bulk {
}
}
$data = $this->run_generate_webp( $contexts );
$formats = imagify_nextgen_images_formats();
$data = $this->run_generate_nextgen( $contexts, $formats );
if ( false === $data['success'] ) {
wp_send_json_error( [ 'message' => $data['message'] ] );
}
@@ -575,4 +588,87 @@ class Bulk {
wp_send_json_success( imagify_get_bulk_stats( array_flip( $folder_types ) ) );
}
/**
* Update Options callback to start bulk optimization.
*
* @since 2.2
*
* @param array $old_value The old option value.
* @param array $value The new option value.
*
* Please note that the convert_to_avif new value is a checkbox,
* so it equals 1 when it's set otherwise it's not set.
* That's why we need to use empty function when checking its value.
*
* @return void
*/
public function maybe_generate_missing_nextgen( $old_value, $value ) {
if ( empty( $old_value['convert_to_avif'] ) === empty( $value['convert_to_avif'] ) ) {
// Old value = new value so do nothing.
return;
}
if ( empty( $value['convert_to_avif'] ) ) {
// new value is disabled, do nothing.
return;
}
$contexts = $this->get_contexts();
$formats = imagify_nextgen_images_formats();
$this->run_generate_nextgen( $contexts, $formats );
}
/**
* Get the context for the bulk optimization page.
*
* @since 2.2
*
* @return array The array of unique contexts ('wp' or 'custom-folders').
*/
public function get_contexts() {
$contexts = [];
$types = [];
// Library: in each site.
if ( ! is_network_admin() ) {
$types['library|wp'] = 1;
}
// Custom folders: in network admin only if network activated, in each site otherwise.
if ( imagify_can_optimize_custom_folders() && ( imagify_is_active_for_network() && is_network_admin() || ! imagify_is_active_for_network() ) ) {
$types['custom-folders|custom-folders'] = 1;
}
/**
* Filter the types to display in the bulk optimization page.
*
* @since 1.7.1
*
* @param array $types The folder types displayed on the page. If a folder type is "library", the context should be suffixed after a pipe character. They are passed as array keys.
*/
$types = apply_filters( 'imagify_bulk_page_types', $types );
$types = array_filter( (array) $types );
if ( isset( $types['library|wp'] ) && ! in_array( 'wp', $contexts, true ) ) {
$contexts[] = 'wp';
}
if ( isset( $types['custom-folders|custom-folders'] ) ) {
$folders_instance = \Imagify_Folders_DB::get_instance();
if ( ! $folders_instance->has_items() ) {
// New Feature!
if ( ! in_array( 'wp', $contexts, true ) ) {
$contexts[] = 'wp';
}
} elseif ( $folders_instance->has_active_folders() && ! in_array( 'custom-folders', $contexts, true ) ) {
$contexts[] = 'custom-folders';
}
}
return $contexts;
}
}

View File

@@ -18,10 +18,11 @@ interface BulkInterface {
public function get_unoptimized_media_ids( $optimization_level );
/**
* Get ids of all optimized media without WebP versions.
* Get ids of all optimized media without Next gen versions.
*
* @since 1.9
* @since 1.9.5 The method doesn't return the IDs directly anymore.
* @since 2.2
*
* @param string $format Format we are looking for. (webp|avif).
*
* @return array {
* @type array $ids A list of media IDs.
@@ -31,16 +32,17 @@ interface BulkInterface {
* }
* }
*/
public function get_optimized_media_ids_without_webp();
public function get_optimized_media_ids_without_format( $format );
/**
* Tell if there are optimized media without WebP versions.
* Tell if there are optimized media without next-gen versions.
*
* @since 1.9
* @since 2.2
*
* @return int The number of media.
*/
public function has_optimized_media_without_webp();
public function has_optimized_media_without_nextgen();
/**
* Get the context data.

View File

@@ -74,10 +74,11 @@ class CustomFolders extends AbstractBulk {
}
/**
* Get ids of all optimized media without WebP versions.
* Get ids of all optimized media without Next gen versions.
*
* @since 1.9
* @since 1.9.5 The method doesn't return the IDs directly anymore.
* @since 2.2
*
* @param string $format Format we are looking for. (webp|avif).
*
* @return array {
* @type array $ids A list of media IDs.
@@ -87,7 +88,7 @@ class CustomFolders extends AbstractBulk {
* }
* }
*/
public function get_optimized_media_ids_without_webp() {
public function get_optimized_media_ids_without_format( $format ) {
global $wpdb;
$this->set_no_time_limit();
@@ -95,9 +96,22 @@ class CustomFolders extends AbstractBulk {
$files_table = Imagify_Files_DB::get_instance()->get_table_name();
$folders_table = Imagify_Folders_DB::get_instance()->get_table_name();
$mime_types = Imagify_DB::get_mime_types( 'image' );
$mime_types = str_replace( ",'image/webp'", '', $mime_types );
$webp_suffix = constant( imagify_get_optimization_process_class_name( 'custom-folders' ) . '::WEBP_SUFFIX' );
$files = $wpdb->get_results( $wpdb->prepare( // WPCS: unprepared SQL ok.
// Remove single quotes and explode string into array.
$mime_types_array = explode( ',', str_replace( "'", '', $mime_types ) );
// Iterate over array and check if string contains input.
foreach ( $mime_types_array as $item ) {
if ( strpos( $item, $format ) !== false ) {
$mime = $item;
break;
}
}
if ( ! isset( $mime ) && empty( $mime ) ) {
$mime = 'image/webp';
}
$mime_types = str_replace( ",'" . $mime . "'", '', $mime_types );
$nextgen_suffix = constant( imagify_get_optimization_process_class_name( 'custom-folders' ) . '::' . strtoupper( $format ) . '_SUFFIX' );
$files = $wpdb->get_results( $wpdb->prepare( // WPCS: unprepared SQL ok.
"
SELECT fi.file_id, fi.path
FROM $files_table as fi
@@ -108,11 +122,11 @@ class CustomFolders extends AbstractBulk {
AND ( fi.status = 'success' OR fi.status = 'already_optimized' )
AND ( fi.data NOT LIKE %s OR fi.data IS NULL )
ORDER BY fi.file_id DESC",
'%' . $wpdb->esc_like( $webp_suffix . '";a:4:{s:7:"success";b:1;' ) . '%'
'%' . $wpdb->esc_like( $nextgen_suffix . '";a:4:{s:7:"success";b:1;' ) . '%'
) );
$wpdb->flush();
unset( $mime_types, $files_table, $folders_table, $webp_suffix );
unset( $mime_types, $files_table, $folders_table, $nextgen_suffix, $mime );
$data = [
'ids' => [],

View File

@@ -20,10 +20,11 @@ class Noop extends AbstractBulk {
}
/**
* Get ids of all optimized media without WebP versions.
* * Get ids of all optimized media without Next gen versions.
*
* @since 1.9
* @since 1.9.5 The method doesn't return the IDs directly anymore.
* @since 2.2
*
* @param string $format Format we are looking for. (webp|avif).
*
* @return array {
* @type array $ids A list of media IDs.
@@ -33,7 +34,7 @@ class Noop extends AbstractBulk {
* }
* }
*/
public function get_optimized_media_ids_without_webp() {
public function get_optimized_media_ids_without_format( $format ) {
return [
'ids' => [],
'errors' => [

View File

@@ -165,10 +165,11 @@ class WP extends AbstractBulk {
}
/**
* Get ids of all optimized media without WebP versions.
* Get ids of all optimized media without Next gen versions.
*
* @since 1.9
* @since 1.9.5 The method doesn't return the IDs directly anymore.
* @since 2.2
*
* @param string $format Format we are looking for. (webp|avif).
*
* @return array {
* @type array $ids A list of media IDs.
@@ -178,45 +179,61 @@ class WP extends AbstractBulk {
* }
* }
*/
public function get_optimized_media_ids_without_webp() {
public function get_optimized_media_ids_without_format( $format ) {
global $wpdb;
$this->set_no_time_limit();
$mime_types = Imagify_DB::get_mime_types( 'image' );
$mime_types = str_replace( ",'image/webp'", '', $mime_types );
// Remove single quotes and explode string into array.
$mime_types_array = explode( ',', str_replace( "'", '', $mime_types ) );
// Iterate over array and check if string contains input.
foreach ( $mime_types_array as $item ) {
if ( strpos( $item, $format ) !== false ) {
$mime = $item;
break;
}
}
if ( ! isset( $mime ) && empty( $mime ) ) {
$mime = 'image/webp';
}
$mime_types = str_replace( ",'" . $mime . "'", '', $mime_types );
$statuses = Imagify_DB::get_post_statuses();
$nodata_join = Imagify_DB::get_required_wp_metadata_join_clause();
$nodata_where = Imagify_DB::get_required_wp_metadata_where_clause( [
'prepared' => true,
] );
$webp_suffix = constant( imagify_get_optimization_process_class_name( 'wp' ) . '::WEBP_SUFFIX' );
$nextgen_suffix = constant( imagify_get_optimization_process_class_name( 'wp' ) . '::' . strtoupper( $format ) . '_SUFFIX' );
$ids = $wpdb->get_col( $wpdb->prepare( // WPCS: unprepared SQL ok.
"
SELECT p.ID
FROM $wpdb->posts AS p
$nodata_join
LEFT JOIN $wpdb->postmeta AS mt1
ON ( p.ID = mt1.post_id AND mt1.meta_key = '_imagify_status' )
LEFT JOIN $wpdb->postmeta AS mt2
SELECT p.ID
FROM $wpdb->posts AS p
$nodata_join
LEFT JOIN $wpdb->postmeta AS mt1
ON ( p.ID = mt1.post_id AND mt1.meta_key = '_imagify_status' )
LEFT JOIN $wpdb->postmeta AS mt2
ON ( p.ID = mt2.post_id AND mt2.meta_key = '_imagify_data' )
WHERE
p.post_mime_type IN ( $mime_types )
AND ( mt1.meta_value = 'success' OR mt1.meta_value = 'already_optimized' )
AND mt2.meta_value NOT LIKE %s
AND p.post_type = 'attachment'
AND p.post_status IN ( $statuses )
$nodata_where
ORDER BY p.ID DESC
LIMIT 0, %d",
'%' . $wpdb->esc_like( $webp_suffix . '";a:4:{s:7:"success";b:1;' ) . '%',
WHERE
p.post_mime_type IN ( $mime_types )
AND (mt1.meta_key IS NULL OR mt1.meta_value = 'success' OR mt1.meta_value = 'already_optimized' )
AND mt2.meta_value NOT LIKE %s
AND p.post_type = 'attachment'
AND p.post_status IN ( $statuses )
$nodata_where
ORDER BY p.ID DESC
LIMIT 0, %d",
'%' . $wpdb->esc_like( $nextgen_suffix . '";a:4:{s:7:"success";b:1;' ) . '%',
imagify_get_unoptimized_attachment_limit()
) );
$wpdb->flush();
unset( $mime_types, $statuses, $webp_suffix );
unset( $mime_types, $statuses, $nextgen_suffix, $mime );
$ids = array_filter( array_map( 'absint', $ids ) );
$data = [
'ids' => [],
'errors' => [
@@ -243,7 +260,7 @@ class WP extends AbstractBulk {
* @param array $metas An array of the data fetched from the database.
* @param string $context The context.
*/
do_action( 'imagify_bulk_generate_webp_before_file_existence_tests', $ids, $metas, 'wp' );
do_action( 'imagify_bulk_generate_nextgen_before_file_existence_tests', $ids, $metas, 'wp' );
foreach ( $ids as $i => $id ) {
if ( empty( $metas['filenames'][ $id ] ) ) {

View File

@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
namespace Imagify\CDN;
use Imagify\EventManagement\SubscriberInterface;
/**
* CDN subscriber
*/
class CDN implements SubscriberInterface {
/**
* Array of events this subscriber listens to
*
* @return array
*/
public static function get_subscribed_events() {
return [
'imagify_cdn_source_url' => 'get_cdn_source',
];
}
/**
* Get the CDN "source".
*
* @since 1.9.3
*
* @param string $option_url An URL to use instead of the one stored in the option. It is used only if no constant/filter.
*
* @return array {
* @type string $source Where does it come from? Possible values are 'constant', 'filter', or 'option'.
* @type string $name Who? Can be a constant name, a plugin name, or an empty string.
* @type string $url The CDN URL, with a trailing slash. An empty string if no URL is set.
* }
*/
public function get_cdn_source( $option_url = '' ) {
if ( defined( 'IMAGIFY_CDN_URL' ) && IMAGIFY_CDN_URL && is_string( IMAGIFY_CDN_URL ) ) {
// Use a constant.
$source = [
'source' => 'constant',
'name' => 'IMAGIFY_CDN_URL',
'url' => IMAGIFY_CDN_URL,
];
} else {
// Maybe use a filter.
$filter_source = [
'name' => null,
'url' => null,
];
/**
* Provide a custom CDN source.
*
* @since 1.9.3
*
* @param array $filter_source {
* @type $name string The name of which provides the URL (plugin name, etc).
* @type $url string The CDN URL.
* }
*/
$filter_source = apply_filters( 'imagify_cdn_source', $filter_source );
if ( ! empty( $filter_source['url'] ) ) {
$source = [
'source' => 'filter',
'name' => ! empty( $filter_source['name'] ) ? $filter_source['name'] : '',
'url' => $filter_source['url'],
];
}
}
if ( empty( $source['url'] ) ) {
// No constant, no filter: use the option.
$source = [
'source' => 'option',
'name' => '',
'url' => $option_url && is_string( $option_url ) ? $option_url : get_imagify_option( 'cdn_url' ),
];
}
if ( empty( $source['url'] ) ) {
// Nothing set.
return [
'source' => 'option',
'name' => '',
'url' => '',
];
}
$source['url'] = $this->sanitize_cdn_url( $source['url'] );
if ( empty( $source['url'] ) ) {
// Not an URL.
return [
'source' => 'option',
'name' => '',
'url' => '',
];
}
return $source;
}
/**
* Sanitize the CDN URL value.
*
* @since 1.9.3
*
* @param string $url The URL to sanitize.
*
* @return string
*/
public function sanitize_cdn_url( $url ) {
$url = sanitize_text_field( $url );
if ( ! $url || ! preg_match( '@^https?://.+\.[^.]+@i', $url ) ) {
// Not an URL.
return '';
}
return trailingslashit( $url );
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Imagify\CDN;
use Imagify\Dependencies\League\Container\ServiceProvider\AbstractServiceProvider;
/**
* Service provider for CDN compatibility
*/
class ServiceProvider extends AbstractServiceProvider {
/**
* Services provided by this provider
*
* @var array
*/
protected $provides = [
'cdn',
];
/**
* Subscribers provided by this provider
*
* @var array
*/
public $subscribers = [
'cdn',
];
/**
* Registers the provided classes
*
* @return void
*/
public function register() {
$this->getContainer()->share( 'cdn', CDN::class );
}
/**
* Returns the subscribers array
*
* @return array
*/
public function get_subscribers() {
return $this->subscribers;
}
}

View File

@@ -6,9 +6,9 @@ namespace Imagify\CLI;
use Imagify\Bulk\Bulk;
/**
* Command class for the missing WebP generation
* Command class for the missing Nextgen generation
*/
class GenerateMissingWebpCommand extends AbstractCommand {
class GenerateMissingNextgenCommand extends AbstractCommand {
/**
* Executes the command.
*
@@ -16,23 +16,23 @@ class GenerateMissingWebpCommand extends AbstractCommand {
* @param array $options Optional arguments.
*/
public function __invoke( $arguments, $options ) {
Bulk::get_instance()->run_generate_webp( $arguments );
Bulk::get_instance()->run_generate_nextgen( $arguments );
\WP_CLI::log( 'Imagify missing WebP generation triggered.' );
\WP_CLI::log( 'Imagify missing next-gen images generation triggered.' );
}
/**
* {@inheritdoc}
*/
protected function get_command_name(): string {
return 'generate-missing-webp';
return 'generate-missing-nextgen';
}
/**
* {@inheritdoc}
*/
public function get_description(): string {
return 'Run the generation of the missing WebP versions';
return 'Run the generation of the missing next-gen images versions';
}
/**
@@ -43,7 +43,7 @@ class GenerateMissingWebpCommand extends AbstractCommand {
[
'type' => 'positional',
'name' => 'contexts',
'description' => 'The context(s) to run the missing WebP generation for. Possible values are wp and custom-folders.',
'description' => 'The context(s) to run the missing next-gen images generation for. Possible values are wp and custom-folders.',
'optional' => false,
'repeating' => true,
],

View File

@@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Argument;
use Imagify\Dependencies\League\Container\ContainerAwareInterface;
use ReflectionFunctionAbstract;
interface ArgumentResolverInterface extends ContainerAwareInterface
{
/**
* Resolve an array of arguments to their concrete implementations.
*
* @param array $arguments
*
* @return array
*/
public function resolveArguments(array $arguments) : array;
/**
* Resolves the correct arguments to be passed to a method.
*
* @param ReflectionFunctionAbstract $method
* @param array $args
*
* @return array
*/
public function reflectArguments(ReflectionFunctionAbstract $method, array $args = []) : array;
}

View File

@@ -0,0 +1,120 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Argument;
use Imagify\Dependencies\League\Container\Container;
use Imagify\Dependencies\League\Container\Exception\{ContainerException, NotFoundException};
use Imagify\Dependencies\League\Container\ReflectionContainer;
use Imagify\Dependencies\Psr\Container\ContainerInterface;
use ReflectionFunctionAbstract;
use ReflectionParameter;
trait ArgumentResolverTrait
{
/**
* {@inheritdoc}
*/
public function resolveArguments(array $arguments) : array
{
return array_map(function ($argument) {
$justStringValue = false;
if ($argument instanceof RawArgumentInterface) {
return $argument->getValue();
} elseif ($argument instanceof ClassNameInterface) {
$id = $argument->getClassName();
} elseif (!is_string($argument)) {
return $argument;
} else {
$justStringValue = true;
$id = $argument;
}
$container = null;
try {
$container = $this->getLeagueContainer();
} catch (ContainerException $e) {
if ($this instanceof ReflectionContainer) {
$container = $this;
}
}
if ($container !== null) {
try {
return $container->get($id);
} catch (NotFoundException $exception) {
if ($argument instanceof ClassNameWithOptionalValue) {
return $argument->getOptionalValue();
}
if ($justStringValue) {
return $id;
}
throw $exception;
}
}
if ($argument instanceof ClassNameWithOptionalValue) {
return $argument->getOptionalValue();
}
// Just a string value.
return $id;
}, $arguments);
}
/**
* {@inheritdoc}
*/
public function reflectArguments(ReflectionFunctionAbstract $method, array $args = []) : array
{
$arguments = array_map(function (ReflectionParameter $param) use ($method, $args) {
$name = $param->getName();
$type = $param->getType();
if (array_key_exists($name, $args)) {
return new RawArgument($args[$name]);
}
if ($type) {
if (PHP_VERSION_ID >= 70100) {
$typeName = $type->getName();
} else {
$typeName = (string) $type;
}
$typeName = ltrim($typeName, '?');
if ($param->isDefaultValueAvailable()) {
return new ClassNameWithOptionalValue($typeName, $param->getDefaultValue());
}
return new ClassName($typeName);
}
if ($param->isDefaultValueAvailable()) {
return new RawArgument($param->getDefaultValue());
}
throw new NotFoundException(sprintf(
'Unable to resolve a value for parameter (%s) in the function/method (%s)',
$name,
$method->getName()
));
}, $method->getParameters());
return $this->resolveArguments($arguments);
}
/**
* @return ContainerInterface
*/
abstract public function getContainer() : ContainerInterface;
/**
* @return Container
*/
abstract public function getLeagueContainer() : Container;
}

View File

@@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Argument;
class ClassName implements ClassNameInterface
{
/**
* @var string
*/
protected $value;
/**
* Construct.
*
* @param string $value
*/
public function __construct(string $value)
{
$this->value = $value;
}
/**
* {@inheritdoc}
*/
public function getClassName() : string
{
return $this->value;
}
}

View File

@@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Argument;
interface ClassNameInterface
{
/**
* Return the class name.
*
* @return string
*/
public function getClassName() : string;
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Imagify\Dependencies\League\Container\Argument;
class ClassNameWithOptionalValue implements ClassNameInterface
{
/**
* @var string
*/
private $className;
/**
* @var mixed
*/
private $optionalValue;
/**
* @param string $className
* @param mixed $optionalValue
*/
public function __construct(string $className, $optionalValue)
{
$this->className = $className;
$this->optionalValue = $optionalValue;
}
/**
* @inheritDoc
*/
public function getClassName(): string
{
return $this->className;
}
public function getOptionalValue()
{
return $this->optionalValue;
}
}

View File

@@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Argument;
class RawArgument implements RawArgumentInterface
{
/**
* @var mixed
*/
protected $value;
/**
* Construct.
*
* @param mixed $value
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* {@inheritdoc}
*/
public function getValue()
{
return $this->value;
}
}

View File

@@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Argument;
interface RawArgumentInterface
{
/**
* Return the value of the raw argument.
*
* @return mixed
*/
public function getValue();
}

View File

@@ -0,0 +1,248 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container;
use Imagify\Dependencies\League\Container\Definition\{DefinitionAggregate, DefinitionInterface, DefinitionAggregateInterface};
use Imagify\Dependencies\League\Container\Exception\{NotFoundException, ContainerException};
use Imagify\Dependencies\League\Container\Inflector\{InflectorAggregate, InflectorInterface, InflectorAggregateInterface};
use Imagify\Dependencies\League\Container\ServiceProvider\{
ServiceProviderAggregate,
ServiceProviderAggregateInterface,
ServiceProviderInterface
};
use Imagify\Dependencies\Psr\Container\ContainerInterface;
class Container implements ContainerInterface
{
/**
* @var boolean
*/
protected $defaultToShared = false;
/**
* @var DefinitionAggregateInterface
*/
protected $definitions;
/**
* @var ServiceProviderAggregateInterface
*/
protected $providers;
/**
* @var InflectorAggregateInterface
*/
protected $inflectors;
/**
* @var ContainerInterface[]
*/
protected $delegates = [];
/**
* Construct.
*
* @param DefinitionAggregateInterface|null $definitions
* @param ServiceProviderAggregateInterface|null $providers
* @param InflectorAggregateInterface|null $inflectors
*/
public function __construct(
DefinitionAggregateInterface $definitions = null,
ServiceProviderAggregateInterface $providers = null,
InflectorAggregateInterface $inflectors = null
) {
$this->definitions = $definitions ?? new DefinitionAggregate;
$this->providers = $providers ?? new ServiceProviderAggregate;
$this->inflectors = $inflectors ?? new InflectorAggregate;
if ($this->definitions instanceof ContainerAwareInterface) {
$this->definitions->setLeagueContainer($this);
}
if ($this->providers instanceof ContainerAwareInterface) {
$this->providers->setLeagueContainer($this);
}
if ($this->inflectors instanceof ContainerAwareInterface) {
$this->inflectors->setLeagueContainer($this);
}
}
/**
* Add an item to the container.
*
* @param string $id
* @param mixed $concrete
* @param boolean $shared
*
* @return DefinitionInterface
*/
public function add(string $id, $concrete = null, bool $shared = null) : DefinitionInterface
{
$concrete = $concrete ?? $id;
$shared = $shared ?? $this->defaultToShared;
return $this->definitions->add($id, $concrete, $shared);
}
/**
* Proxy to add with shared as true.
*
* @param string $id
* @param mixed $concrete
*
* @return DefinitionInterface
*/
public function share(string $id, $concrete = null) : DefinitionInterface
{
return $this->add($id, $concrete, true);
}
/**
* Whether the container should default to defining shared definitions.
*
* @param boolean $shared
*
* @return self
*/
public function defaultToShared(bool $shared = true) : ContainerInterface
{
$this->defaultToShared = $shared;
return $this;
}
/**
* Get a definition to extend.
*
* @param string $id [description]
*
* @return DefinitionInterface
*/
public function extend(string $id) : DefinitionInterface
{
if ($this->providers->provides($id)) {
$this->providers->register($id);
}
if ($this->definitions->has($id)) {
return $this->definitions->getDefinition($id);
}
throw new NotFoundException(
sprintf('Unable to extend alias (%s) as it is not being managed as a definition', $id)
);
}
/**
* Add a service provider.
*
* @param ServiceProviderInterface|string $provider
*
* @return self
*/
public function addServiceProvider($provider) : self
{
$this->providers->add($provider);
return $this;
}
/**
* {@inheritdoc}
*/
public function get($id, bool $new = false)
{
if ($this->definitions->has($id)) {
$resolved = $this->definitions->resolve($id, $new);
return $this->inflectors->inflect($resolved);
}
if ($this->definitions->hasTag($id)) {
$arrayOf = $this->definitions->resolveTagged($id, $new);
array_walk($arrayOf, function (&$resolved) {
$resolved = $this->inflectors->inflect($resolved);
});
return $arrayOf;
}
if ($this->providers->provides($id)) {
$this->providers->register($id);
if (!$this->definitions->has($id) && !$this->definitions->hasTag($id)) {
throw new ContainerException(sprintf('Service provider lied about providing (%s) service', $id));
}
return $this->get($id, $new);
}
foreach ($this->delegates as $delegate) {
if ($delegate->has($id)) {
$resolved = $delegate->get($id);
return $this->inflectors->inflect($resolved);
}
}
throw new NotFoundException(sprintf('Alias (%s) is not being managed by the container or delegates', $id));
}
/**
* {@inheritdoc}
*/
public function has($id)
{
if ($this->definitions->has($id)) {
return true;
}
if ($this->definitions->hasTag($id)) {
return true;
}
if ($this->providers->provides($id)) {
return true;
}
foreach ($this->delegates as $delegate) {
if ($delegate->has($id)) {
return true;
}
}
return false;
}
/**
* Allows for manipulation of specific types on resolution.
*
* @param string $type
* @param callable|null $callback
*
* @return InflectorInterface
*/
public function inflector(string $type, callable $callback = null) : InflectorInterface
{
return $this->inflectors->add($type, $callback);
}
/**
* Delegate a backup container to be checked for services if it
* cannot be resolved via this container.
*
* @param ContainerInterface $container
*
* @return self
*/
public function delegate(ContainerInterface $container) : self
{
$this->delegates[] = $container;
if ($container instanceof ContainerAwareInterface) {
$container->setLeagueContainer($this);
}
return $this;
}
}

View File

@@ -0,0 +1,40 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container;
use Imagify\Dependencies\Psr\Container\ContainerInterface;
interface ContainerAwareInterface
{
/**
* Set a container
*
* @param ContainerInterface $container
*
* @return self
*/
public function setContainer(ContainerInterface $container) : ContainerAwareInterface;
/**
* Get the container
*
* @return ContainerInterface
*/
public function getContainer() : ContainerInterface;
/**
* Set a container. This will be removed in favour of setContainer receiving Container in next major release.
*
* @param Container $container
*
* @return self
*/
public function setLeagueContainer(Container $container) : self;
/**
* Get the container. This will be removed in favour of getContainer returning Container in next major release.
*
* @return Container
*/
public function getLeagueContainer() : Container;
}

View File

@@ -0,0 +1,76 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container;
use Imagify\Dependencies\League\Container\Exception\ContainerException;
use Imagify\Dependencies\Psr\Container\ContainerInterface;
trait ContainerAwareTrait
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var Container
*/
protected $leagueContainer;
/**
* Set a container.
*
* @param ContainerInterface $container
*
* @return ContainerAwareInterface
*/
public function setContainer(ContainerInterface $container) : ContainerAwareInterface
{
$this->container = $container;
return $this;
}
/**
* Get the container.
*
* @return ContainerInterface
*/
public function getContainer() : ContainerInterface
{
if ($this->container instanceof ContainerInterface) {
return $this->container;
}
throw new ContainerException('No container implementation has been set.');
}
/**
* Set a container.
*
* @param Container $container
*
* @return self
*/
public function setLeagueContainer(Container $container) : ContainerAwareInterface
{
$this->container = $container;
$this->leagueContainer = $container;
return $this;
}
/**
* Get the container.
*
* @return Container
*/
public function getLeagueContainer() : Container
{
if ($this->leagueContainer instanceof Container) {
return $this->leagueContainer;
}
throw new ContainerException('No container implementation has been set.');
}
}

View File

@@ -0,0 +1,278 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Definition;
use Imagify\Dependencies\League\Container\Argument\{
ArgumentResolverInterface, ArgumentResolverTrait, ClassNameInterface, RawArgumentInterface
};
use Imagify\Dependencies\League\Container\ContainerAwareTrait;
use ReflectionClass;
use ReflectionException;
class Definition implements ArgumentResolverInterface, DefinitionInterface
{
use ArgumentResolverTrait;
use ContainerAwareTrait;
/**
* @var string
*/
protected $alias;
/**
* @var mixed
*/
protected $concrete;
/**
* @var boolean
*/
protected $shared = false;
/**
* @var array
*/
protected $tags = [];
/**
* @var array
*/
protected $arguments = [];
/**
* @var array
*/
protected $methods = [];
/**
* @var mixed
*/
protected $resolved;
/**
* Constructor.
*
* @param string $id
* @param mixed $concrete
*/
public function __construct(string $id, $concrete = null)
{
$concrete = $concrete ?? $id;
$this->alias = $id;
$this->concrete = $concrete;
}
/**
* {@inheritdoc}
*/
public function addTag(string $tag) : DefinitionInterface
{
$this->tags[$tag] = true;
return $this;
}
/**
* {@inheritdoc}
*/
public function hasTag(string $tag) : bool
{
return isset($this->tags[$tag]);
}
/**
* {@inheritdoc}
*/
public function setAlias(string $id) : DefinitionInterface
{
$this->alias = $id;
return $this;
}
/**
* {@inheritdoc}
*/
public function getAlias() : string
{
return $this->alias;
}
/**
* {@inheritdoc}
*/
public function setShared(bool $shared = true) : DefinitionInterface
{
$this->shared = $shared;
return $this;
}
/**
* {@inheritdoc}
*/
public function isShared() : bool
{
return $this->shared;
}
/**
* {@inheritdoc}
*/
public function getConcrete()
{
return $this->concrete;
}
/**
* {@inheritdoc}
*/
public function setConcrete($concrete) : DefinitionInterface
{
$this->concrete = $concrete;
$this->resolved = null;
return $this;
}
/**
* {@inheritdoc}
*/
public function addArgument($arg) : DefinitionInterface
{
$this->arguments[] = $arg;
return $this;
}
/**
* {@inheritdoc}
*/
public function addArguments(array $args) : DefinitionInterface
{
foreach ($args as $arg) {
$this->addArgument($arg);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function addMethodCall(string $method, array $args = []) : DefinitionInterface
{
$this->methods[] = [
'method' => $method,
'arguments' => $args
];
return $this;
}
/**
* {@inheritdoc}
*/
public function addMethodCalls(array $methods = []) : DefinitionInterface
{
foreach ($methods as $method => $args) {
$this->addMethodCall($method, $args);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function resolve(bool $new = false)
{
$concrete = $this->concrete;
if ($this->isShared() && $this->resolved !== null && $new === false) {
return $this->resolved;
}
if (is_callable($concrete)) {
$concrete = $this->resolveCallable($concrete);
}
if ($concrete instanceof RawArgumentInterface) {
$this->resolved = $concrete->getValue();
return $concrete->getValue();
}
if ($concrete instanceof ClassNameInterface) {
$concrete = $concrete->getClassName();
}
if (is_string($concrete) && class_exists($concrete)) {
$concrete = $this->resolveClass($concrete);
}
if (is_object($concrete)) {
$concrete = $this->invokeMethods($concrete);
}
if (is_string($concrete) && $this->getContainer()->has($concrete)) {
$concrete = $this->getContainer()->get($concrete);
}
$this->resolved = $concrete;
return $concrete;
}
/**
* Resolve a callable.
*
* @param callable $concrete
*
* @return mixed
*/
protected function resolveCallable(callable $concrete)
{
$resolved = $this->resolveArguments($this->arguments);
return call_user_func_array($concrete, $resolved);
}
/**
* Resolve a class.
*
* @param string $concrete
*
* @return object
*
* @throws ReflectionException
*/
protected function resolveClass(string $concrete)
{
$resolved = $this->resolveArguments($this->arguments);
$reflection = new ReflectionClass($concrete);
return $reflection->newInstanceArgs($resolved);
}
/**
* Invoke methods on resolved instance.
*
* @param object $instance
*
* @return object
*/
protected function invokeMethods($instance)
{
foreach ($this->methods as $method) {
$args = $this->resolveArguments($method['arguments']);
/** @var callable $callable */
$callable = [$instance, $method['method']];
call_user_func_array($callable, $args);
}
return $instance;
}
}

View File

@@ -0,0 +1,124 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Definition;
use Generator;
use Imagify\Dependencies\League\Container\ContainerAwareTrait;
use Imagify\Dependencies\League\Container\Exception\NotFoundException;
class DefinitionAggregate implements DefinitionAggregateInterface
{
use ContainerAwareTrait;
/**
* @var DefinitionInterface[]
*/
protected $definitions = [];
/**
* Construct.
*
* @param DefinitionInterface[] $definitions
*/
public function __construct(array $definitions = [])
{
$this->definitions = array_filter($definitions, function ($definition) {
return ($definition instanceof DefinitionInterface);
});
}
/**
* {@inheritdoc}
*/
public function add(string $id, $definition, bool $shared = false) : DefinitionInterface
{
if (!$definition instanceof DefinitionInterface) {
$definition = new Definition($id, $definition);
}
$this->definitions[] = $definition
->setAlias($id)
->setShared($shared)
;
return $definition;
}
/**
* {@inheritdoc}
*/
public function has(string $id) : bool
{
foreach ($this->getIterator() as $definition) {
if ($id === $definition->getAlias()) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function hasTag(string $tag) : bool
{
foreach ($this->getIterator() as $definition) {
if ($definition->hasTag($tag)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function getDefinition(string $id) : DefinitionInterface
{
foreach ($this->getIterator() as $definition) {
if ($id === $definition->getAlias()) {
return $definition->setLeagueContainer($this->getLeagueContainer());
}
}
throw new NotFoundException(sprintf('Alias (%s) is not being handled as a definition.', $id));
}
/**
* {@inheritdoc}
*/
public function resolve(string $id, bool $new = false)
{
return $this->getDefinition($id)->resolve($new);
}
/**
* {@inheritdoc}
*/
public function resolveTagged(string $tag, bool $new = false) : array
{
$arrayOf = [];
foreach ($this->getIterator() as $definition) {
if ($definition->hasTag($tag)) {
$arrayOf[] = $definition->setLeagueContainer($this->getLeagueContainer())->resolve($new);
}
}
return $arrayOf;
}
/**
* {@inheritdoc}
*/
public function getIterator() : Generator
{
$count = count($this->definitions);
for ($i = 0; $i < $count; $i++) {
yield $this->definitions[$i];
}
}
}

View File

@@ -0,0 +1,67 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Definition;
use IteratorAggregate;
use Imagify\Dependencies\League\Container\ContainerAwareInterface;
interface DefinitionAggregateInterface extends ContainerAwareInterface, IteratorAggregate
{
/**
* Add a definition to the aggregate.
*
* @param string $id
* @param mixed $definition
* @param boolean $shared
*
* @return DefinitionInterface
*/
public function add(string $id, $definition, bool $shared = false) : DefinitionInterface;
/**
* Checks whether alias exists as definition.
*
* @param string $id
*
* @return boolean
*/
public function has(string $id) : bool;
/**
* Checks whether tag exists as definition.
*
* @param string $tag
*
* @return boolean
*/
public function hasTag(string $tag) : bool;
/**
* Get the definition to be extended.
*
* @param string $id
*
* @return DefinitionInterface
*/
public function getDefinition(string $id) : DefinitionInterface;
/**
* Resolve and build a concrete value from an id/alias.
*
* @param string $id
* @param boolean $new
*
* @return mixed
*/
public function resolve(string $id, bool $new = false);
/**
* Resolve and build an array of concrete values from a tag.
*
* @param string $tag
* @param boolean $new
*
* @return mixed
*/
public function resolveTagged(string $tag, bool $new = false);
}

View File

@@ -0,0 +1,120 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Definition;
use Imagify\Dependencies\League\Container\ContainerAwareInterface;
interface DefinitionInterface extends ContainerAwareInterface
{
/**
* Add a tag to the definition.
*
* @param string $tag
*
* @return self
*/
public function addTag(string $tag) : DefinitionInterface;
/**
* Does the definition have a tag?
*
* @param string $tag
*
* @return boolean
*/
public function hasTag(string $tag) : bool;
/**
* Set the alias of the definition.
*
* @param string $id
*
* @return DefinitionInterface
*/
public function setAlias(string $id) : DefinitionInterface;
/**
* Get the alias of the definition.
*
* @return string
*/
public function getAlias() : string;
/**
* Set whether this is a shared definition.
*
* @param boolean $shared
*
* @return self
*/
public function setShared(bool $shared) : DefinitionInterface;
/**
* Is this a shared definition?
*
* @return boolean
*/
public function isShared() : bool;
/**
* Get the concrete of the definition.
*
* @return mixed
*/
public function getConcrete();
/**
* Set the concrete of the definition.
*
* @param mixed $concrete
*
* @return DefinitionInterface
*/
public function setConcrete($concrete) : DefinitionInterface;
/**
* Add an argument to be injected.
*
* @param mixed $arg
*
* @return self
*/
public function addArgument($arg) : DefinitionInterface;
/**
* Add multiple arguments to be injected.
*
* @param array $args
*
* @return self
*/
public function addArguments(array $args) : DefinitionInterface;
/**
* Add a method to be invoked
*
* @param string $method
* @param array $args
*
* @return self
*/
public function addMethodCall(string $method, array $args = []) : DefinitionInterface;
/**
* Add multiple methods to be invoked
*
* @param array $methods
*
* @return self
*/
public function addMethodCalls(array $methods = []) : DefinitionInterface;
/**
* Handle instantiation and manipulation of value and return.
*
* @param boolean $new
*
* @return mixed
*/
public function resolve(bool $new = false);
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Imagify\Dependencies\League\Container\Exception;
use Imagify\Dependencies\Psr\Container\ContainerExceptionInterface;
use RuntimeException;
class ContainerException extends RuntimeException implements ContainerExceptionInterface
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Imagify\Dependencies\League\Container\Exception;
use Imagify\Dependencies\Psr\Container\NotFoundExceptionInterface;
use InvalidArgumentException;
class NotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface
{
}

View File

@@ -0,0 +1,123 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Inflector;
use Imagify\Dependencies\League\Container\Argument\ArgumentResolverInterface;
use Imagify\Dependencies\League\Container\Argument\ArgumentResolverTrait;
use Imagify\Dependencies\League\Container\ContainerAwareTrait;
class Inflector implements ArgumentResolverInterface, InflectorInterface
{
use ArgumentResolverTrait;
use ContainerAwareTrait;
/**
* @var string
*/
protected $type;
/**
* @var callable|null
*/
protected $callback;
/**
* @var array
*/
protected $methods = [];
/**
* @var array
*/
protected $properties = [];
/**
* Construct.
*
* @param string $type
* @param callable|null $callback
*/
public function __construct(string $type, callable $callback = null)
{
$this->type = $type;
$this->callback = $callback;
}
/**
* {@inheritdoc}
*/
public function getType() : string
{
return $this->type;
}
/**
* {@inheritdoc}
*/
public function invokeMethod(string $name, array $args) : InflectorInterface
{
$this->methods[$name] = $args;
return $this;
}
/**
* {@inheritdoc}
*/
public function invokeMethods(array $methods) : InflectorInterface
{
foreach ($methods as $name => $args) {
$this->invokeMethod($name, $args);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function setProperty(string $property, $value) : InflectorInterface
{
$this->properties[$property] = $this->resolveArguments([$value])[0];
return $this;
}
/**
* {@inheritdoc}
*/
public function setProperties(array $properties) : InflectorInterface
{
foreach ($properties as $property => $value) {
$this->setProperty($property, $value);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function inflect($object)
{
$properties = $this->resolveArguments(array_values($this->properties));
$properties = array_combine(array_keys($this->properties), $properties);
// array_combine() can technically return false
foreach ($properties ?: [] as $property => $value) {
$object->{$property} = $value;
}
foreach ($this->methods as $method => $args) {
$args = $this->resolveArguments($args);
/** @var callable $callable */
$callable = [$object, $method];
call_user_func_array($callable, $args);
}
if ($this->callback !== null) {
call_user_func($this->callback, $object);
}
}
}

View File

@@ -0,0 +1,58 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Inflector;
use Generator;
use Imagify\Dependencies\League\Container\ContainerAwareTrait;
class InflectorAggregate implements InflectorAggregateInterface
{
use ContainerAwareTrait;
/**
* @var Inflector[]
*/
protected $inflectors = [];
/**
* {@inheritdoc}
*/
public function add(string $type, callable $callback = null) : Inflector
{
$inflector = new Inflector($type, $callback);
$this->inflectors[] = $inflector;
return $inflector;
}
/**
* {@inheritdoc}
*/
public function getIterator() : Generator
{
$count = count($this->inflectors);
for ($i = 0; $i < $count; $i++) {
yield $this->inflectors[$i];
}
}
/**
* {@inheritdoc}
*/
public function inflect($object)
{
foreach ($this->getIterator() as $inflector) {
$type = $inflector->getType();
if (! $object instanceof $type) {
continue;
}
$inflector->setLeagueContainer($this->getLeagueContainer());
$inflector->inflect($object);
}
return $object;
}
}

View File

@@ -0,0 +1,27 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Inflector;
use IteratorAggregate;
use Imagify\Dependencies\League\Container\ContainerAwareInterface;
interface InflectorAggregateInterface extends ContainerAwareInterface, IteratorAggregate
{
/**
* Add an inflector to the aggregate.
*
* @param string $type
* @param callable $callback
*
* @return Inflector
*/
public function add(string $type, callable $callback = null) : Inflector;
/**
* Applies all inflectors to an object.
*
* @param object $object
* @return object
*/
public function inflect($object);
}

View File

@@ -0,0 +1,60 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\Inflector;
interface InflectorInterface
{
/**
* Get the type.
*
* @return string
*/
public function getType() : string;
/**
* Defines a method to be invoked on the subject object.
*
* @param string $name
* @param array $args
*
* @return self
*/
public function invokeMethod(string $name, array $args) : InflectorInterface;
/**
* Defines multiple methods to be invoked on the subject object.
*
* @param array $methods
*
* @return self
*/
public function invokeMethods(array $methods) : InflectorInterface;
/**
* Defines a property to be set on the subject object.
*
* @param string $property
* @param mixed $value
*
* @return self
*/
public function setProperty(string $property, $value) : InflectorInterface;
/**
* Defines multiple properties to be set on the subject object.
*
* @param array $properties
*
* @return self
*/
public function setProperties(array $properties) : InflectorInterface;
/**
* Apply inflections to an object.
*
* @param object $object
*
* @return void
*/
public function inflect($object);
}

View File

@@ -0,0 +1,131 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container;
use Imagify\Dependencies\League\Container\Argument\{ArgumentResolverInterface, ArgumentResolverTrait};
use Imagify\Dependencies\League\Container\Exception\NotFoundException;
use Imagify\Dependencies\Psr\Container\ContainerInterface;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionMethod;
class ReflectionContainer implements ArgumentResolverInterface, ContainerInterface
{
use ArgumentResolverTrait;
use ContainerAwareTrait;
/**
* @var boolean
*/
protected $cacheResolutions = false;
/**
* Cache of resolutions.
*
* @var array
*/
protected $cache = [];
/**
* {@inheritdoc}
*
* @throws ReflectionException
*/
public function get($id, array $args = [])
{
if ($this->cacheResolutions === true && array_key_exists($id, $this->cache)) {
return $this->cache[$id];
}
if (! $this->has($id)) {
throw new NotFoundException(
sprintf('Alias (%s) is not an existing class and therefore cannot be resolved', $id)
);
}
$reflector = new ReflectionClass($id);
$construct = $reflector->getConstructor();
if ($construct && !$construct->isPublic()) {
throw new NotFoundException(
sprintf('Alias (%s) has a non-public constructor and therefore cannot be instantiated', $id)
);
}
$resolution = $construct === null
? new $id
: $resolution = $reflector->newInstanceArgs($this->reflectArguments($construct, $args))
;
if ($this->cacheResolutions === true) {
$this->cache[$id] = $resolution;
}
return $resolution;
}
/**
* {@inheritdoc}
*/
public function has($id)
{
return class_exists($id);
}
/**
* Invoke a callable via the container.
*
* @param callable $callable
* @param array $args
*
* @return mixed
*
* @throws ReflectionException
*/
public function call(callable $callable, array $args = [])
{
if (is_string($callable) && strpos($callable, '::') !== false) {
$callable = explode('::', $callable);
}
if (is_array($callable)) {
if (is_string($callable[0])) {
$callable[0] = $this->getContainer()->get($callable[0]);
}
$reflection = new ReflectionMethod($callable[0], $callable[1]);
if ($reflection->isStatic()) {
$callable[0] = null;
}
return $reflection->invokeArgs($callable[0], $this->reflectArguments($reflection, $args));
}
if (is_object($callable)) {
$reflection = new ReflectionMethod($callable, '__invoke');
return $reflection->invokeArgs($callable, $this->reflectArguments($reflection, $args));
}
$reflection = new ReflectionFunction(\Closure::fromCallable($callable));
return $reflection->invokeArgs($this->reflectArguments($reflection, $args));
}
/**
* Whether the container should default to caching resolutions and returning
* the cache on following calls.
*
* @param boolean $option
*
* @return self
*/
public function cacheResolutions(bool $option = true) : ContainerInterface
{
$this->cacheResolutions = $option;
return $this;
}
}

View File

@@ -0,0 +1,46 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\ServiceProvider;
use Imagify\Dependencies\League\Container\ContainerAwareTrait;
abstract class AbstractServiceProvider implements ServiceProviderInterface
{
use ContainerAwareTrait;
/**
* @var array
*/
protected $provides = [];
/**
* @var string
*/
protected $identifier;
/**
* {@inheritdoc}
*/
public function provides(string $alias) : bool
{
return in_array($alias, $this->provides, true);
}
/**
* {@inheritdoc}
*/
public function setIdentifier(string $id) : ServiceProviderInterface
{
$this->identifier = $id;
return $this;
}
/**
* {@inheritdoc}
*/
public function getIdentifier() : string
{
return $this->identifier ?? get_class($this);
}
}

View File

@@ -0,0 +1,14 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\ServiceProvider;
interface BootableServiceProviderInterface extends ServiceProviderInterface
{
/**
* Method will be invoked on registration of a service provider implementing
* this interface. Provides ability for eager loading of Service Providers.
*
* @return void
*/
public function boot();
}

View File

@@ -0,0 +1,106 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\ServiceProvider;
use Generator;
use Imagify\Dependencies\League\Container\{ContainerAwareInterface, ContainerAwareTrait};
use Imagify\Dependencies\League\Container\Exception\ContainerException;
class ServiceProviderAggregate implements ServiceProviderAggregateInterface
{
use ContainerAwareTrait;
/**
* @var ServiceProviderInterface[]
*/
protected $providers = [];
/**
* @var array
*/
protected $registered = [];
/**
* {@inheritdoc}
*/
public function add($provider) : ServiceProviderAggregateInterface
{
if (is_string($provider) && $this->getContainer()->has($provider)) {
$provider = $this->getContainer()->get($provider);
} elseif (is_string($provider) && class_exists($provider)) {
$provider = new $provider;
}
if (in_array($provider, $this->providers, true)) {
return $this;
}
if ($provider instanceof ContainerAwareInterface) {
$provider->setLeagueContainer($this->getLeagueContainer());
}
if ($provider instanceof BootableServiceProviderInterface) {
$provider->boot();
}
if ($provider instanceof ServiceProviderInterface) {
$this->providers[] = $provider;
return $this;
}
throw new ContainerException(
'A service provider must be a fully qualified class name or instance ' .
'of (\Imagify\Dependencies\League\Container\ServiceProvider\ServiceProviderInterface)'
);
}
/**
* {@inheritdoc}
*/
public function provides(string $service) : bool
{
foreach ($this->getIterator() as $provider) {
if ($provider->provides($service)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function getIterator() : Generator
{
$count = count($this->providers);
for ($i = 0; $i < $count; $i++) {
yield $this->providers[$i];
}
}
/**
* {@inheritdoc}
*/
public function register(string $service)
{
if (false === $this->provides($service)) {
throw new ContainerException(
sprintf('(%s) is not provided by a service provider', $service)
);
}
foreach ($this->getIterator() as $provider) {
if (in_array($provider->getIdentifier(), $this->registered, true)) {
continue;
}
if ($provider->provides($service)) {
$this->registered[] = $provider->getIdentifier();
$provider->register();
}
}
}
}

View File

@@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\ServiceProvider;
use IteratorAggregate;
use Imagify\Dependencies\League\Container\ContainerAwareInterface;
interface ServiceProviderAggregateInterface extends ContainerAwareInterface, IteratorAggregate
{
/**
* Add a service provider to the aggregate.
*
* @param string|ServiceProviderInterface $provider
*
* @return self
*/
public function add($provider) : ServiceProviderAggregateInterface;
/**
* Determines whether a service is provided by the aggregate.
*
* @param string $service
*
* @return boolean
*/
public function provides(string $service) : bool;
/**
* Invokes the register method of a provider that provides a specific service.
*
* @param string $service
*
* @return void
*/
public function register(string $service);
}

View File

@@ -0,0 +1,46 @@
<?php declare(strict_types=1);
namespace Imagify\Dependencies\League\Container\ServiceProvider;
use Imagify\Dependencies\League\Container\ContainerAwareInterface;
interface ServiceProviderInterface extends ContainerAwareInterface
{
/**
* Returns a boolean if checking whether this provider provides a specific
* service or returns an array of provided services if no argument passed.
*
* @param string $service
*
* @return boolean
*/
public function provides(string $service) : bool;
/**
* Use the register method to register items with the container via the
* protected $this->leagueContainer property or the `getLeagueContainer` method
* from the ContainerAwareTrait.
*
* @return void
*/
public function register();
/**
* Set a custom id for the service provider. This enables
* registering the same service provider multiple times.
*
* @param string $id
*
* @return self
*/
public function setIdentifier(string $id) : ServiceProviderInterface;
/**
* The id of the service provider uniquely identifies it, so
* that we can quickly determine if it has already been registered.
* Defaults to get_class($provider).
*
* @return string
*/
public function getIdentifier() : string;
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Imagify\Dependencies\Psr\Container;
use Throwable;
/**
* Base interface representing a generic exception in a container.
*/
interface ContainerExceptionInterface extends Throwable
{
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Imagify\Dependencies\Psr\Container;
/**
* Describes the interface of a container that exposes methods to read its entries.
*/
interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
*
* @return mixed Entry.
*/
public function get(string $id);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has(string $id);
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Imagify\Dependencies\Psr\Container;
/**
* No entry was found in the container.
*/
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}

View File

@@ -174,7 +174,7 @@ class Actions {
}
$remaining = 0;
$total = get_transient( 'imagify_missing_webp_total' );
$total = get_transient( 'imagify_missing_next_gen_total' );
if ( false === $total ) {
return $response;
@@ -182,8 +182,14 @@ class Actions {
$bulk = Bulk::get_instance();
$format = 'webp';
if ( get_imagify_option( 'convert_to_avif' ) ) {
$format = 'avif';
}
foreach ( $data[ $imagifybeat_id ] as $context ) {
$media = $bulk->get_bulk_instance( $context )->get_optimized_media_ids_without_webp();
$media = $bulk->get_bulk_instance( $context )->get_optimized_media_ids_without_format( $format );
$remaining += count( $media['ids'] );
}

View File

@@ -187,8 +187,8 @@ class MediaOptimization extends \Imagify_Abstract_Background_Process {
$item['error'] = $data;
} elseif ( 'already_optimized' === $data['status'] ) {
// Status is "already_optimized", try to create WebP versions only.
$item['sizes'] = array_filter( $item['sizes'], [ $this->optimization_process, 'is_size_webp' ] );
// Status is "already_optimized", try to create next-gen versions only.
$item['sizes'] = array_filter( $item['sizes'], [ $this->optimization_process, 'is_size_next_gen' ] );
} elseif ( 'success' !== $data['status'] ) {
// Don't go further if the full size has not the "success" status.

View File

@@ -113,7 +113,7 @@ class File {
return new \WP_Error(
'not_exists',
sprintf(
/* translators: %s is a file path. */
/* translators: %s is a file path. */
__( 'The file %s does not seem to exist.', 'imagify' ),
'<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
)
@@ -124,7 +124,7 @@ class File {
return new \WP_Error(
'not_a_file',
sprintf(
/* translators: %s is a file path. */
/* translators: %s is a file path. */
__( 'This does not seem to be a file: %s.', 'imagify' ),
'<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
)
@@ -135,7 +135,7 @@ class File {
return new \WP_Error(
'not_writable',
sprintf(
/* translators: %s is a file path. */
/* translators: %s is a file path. */
__( 'The file %s does not seem to be writable.', 'imagify' ),
'<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
)
@@ -148,7 +148,7 @@ class File {
return new \WP_Error(
'folder_not_writable',
sprintf(
/* translators: %s is a file path. */
/* translators: %s is a file path. */
__( 'The folder %s does not seem to be writable.', 'imagify' ),
'<code>' . esc_html( $this->filesystem->make_path_relative( $parent_folder ) ) . '</code>'
)
@@ -197,7 +197,7 @@ class File {
return new \WP_Error(
'not_an_image',
sprintf(
/* translators: %s is a file path. */
/* translators: %s is a file path. */
__( 'The file %s does not seem to be an image, and cannot be resized.', 'imagify' ),
'<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
)
@@ -321,7 +321,7 @@ class File {
return new \WP_Error(
'not_an_image',
sprintf(
/* translators: %s is a file path. */
/* translators: %s is a file path. */
__( 'The file %s does not seem to be an image, and cannot be resized.', 'imagify' ),
'<code>' . esc_html( $this->filesystem->make_path_relative( $this->path ) ) . '</code>'
)
@@ -423,6 +423,22 @@ class File {
) );
}
// Check if a '-scaled' version of the image exists.
$scaled_path = preg_replace( '/(\.)([^\.]+)$/', '-scaled.$2', $backup_source );
if ( $this->filesystem->exists( $scaled_path ) ) {
// Create a backup path for the scaled image.
$scaled_backup_path = preg_replace( '/(\.)([^\.]+)$/', '-scaled.$2', $backup_path );
// Copy the '-scaled' version to the backup.
$this->filesystem->copy( $scaled_path, $scaled_backup_path, $overwrite, FS_CHMOD_FILE );
if ( ! $this->filesystem->exists( $scaled_backup_path ) ) {
return new \WP_Error( 'backup_doesnt_exist', __( 'The file could not be saved.', 'imagify' ), array(
'file_path' => $this->filesystem->make_path_relative( $scaled_path ),
'backup_path' => $this->filesystem->make_path_relative( $scaled_backup_path ),
) );
}
}
return true;
}
@@ -438,7 +454,7 @@ class File {
* @type bool $backup False to prevent backup. True to follow the user's setting. A backup can't be forced.
* @type string $backup_path If a backup must be done, this is the path to use. Default is the backup path used for the WP Media Library.
* @type int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
* @type string $convert Set to 'webp' to convert the image to WebP.
* @type string $convert Set to 'webp' to convert the image to WebP, 'avif' to convert image to AVIF.
* @type string $context The context.
* @type int $original_size The file size, sent to the API.
* }
@@ -474,7 +490,7 @@ class File {
*
* @param string $path Absolute path to the media file.
* @param array $args Arguments passed to the method.
*/
*/
do_action( 'imagify_before_optimize_file', $this->path, $args );
/**
@@ -485,7 +501,7 @@ class File {
*
* @param string $path Absolute path to the image file.
* @param bool $backup True if a backup will be make.
*/
*/
do_action_deprecated( 'before_do_imagify', [ $this->path, $args['backup'] ], '1.9', 'imagify_before_optimize_file' );
if ( $args['backup'] ) {
@@ -509,6 +525,7 @@ class File {
if ( $args['convert'] ) {
$data['convert'] = $args['convert'];
$format = $args['convert'];
}
$response = upload_imagify_image( [
@@ -534,8 +551,12 @@ class File {
$args['convert'] = '';
}
if ( 'webp' === $args['convert'] ) {
$destination_path = $this->get_path_to_webp();
$formats = [
'webp',
'avif',
];
if ( in_array( $args['convert'], $formats, true ) ) {
$destination_path = $this->get_path_to_nextgen( $args['convert'] );
$this->path = $destination_path;
$this->file_type = null;
$this->editor = null;
@@ -557,7 +578,7 @@ class File {
*
* @param string $path Absolute path to the image file.
* @param bool $backup True if a backup has been made.
*/
*/
do_action_deprecated( 'after_do_imagify', [ $this->path, $args['backup'] ], '1.9', 'imagify_before_optimize_file' );
/**
@@ -568,7 +589,7 @@ class File {
*
* @param string $path Absolute path to the media file.
* @param array $args Arguments passed to the method.
*/
*/
do_action( 'imagify_after_optimize_file', $this->path, $args );
return $response;
@@ -603,7 +624,7 @@ class File {
$this->editor = new \WP_Error(
'image_editor',
sprintf(
/* translators: %1$s is an error message, %2$s is a "More info?" link. */
/* translators: %1$s is an error message, %2$s is a "More info?" link. */
__( 'No php extensions are available to edit images on the server. ImageMagick or GD is required. The internal error is: %1$s. %2$s', 'imagify' ),
$this->editor->get_error_message(),
'<a href="' . esc_url( imagify_get_external_url( 'documentation-imagick-gd' ) ) . '" target="_blank">' . __( 'More info?', 'imagify' ) . '</a>'
@@ -765,6 +786,26 @@ class File {
return imagify_path_to_webp( $this->path );
}
/**
* Replace the file extension by its next-gen format extension.
*
* @since 2.2
*
* @param string $format the format we are targeting.
* @return string|bool The file path on success. False if not an image or on failure.
*/
public function get_path_to_nextgen( string $format ) {
if ( ! $this->is_image() ) {
return false;
}
if ( $this->is_webp() || $this->is_avif() ) {
return false;
}
return imagify_path_to_nextgen( $this->path, $format );
}
/**
* Tell if the file is a WebP image.
* Rejects "path/to/.webp" files.
@@ -778,6 +819,18 @@ class File {
return preg_match( '@(?!^|/|\\\)\.webp$@i', $this->path );
}
/**
* Tell if the file is an AVIF image.
* Rejects "path/to/.avif" files.
*
* @since 2.2
*
* @return bool
*/
public function is_avif() {
return preg_match( '@(?!^|/|\\\)\.avif$@i', $this->path );
}
/**
* Get the file mime type + file extension.
*

View File

@@ -1,31 +1,35 @@
<?php
declare(strict_types=1);
namespace Imagify\Optimization\Process;
defined( 'ABSPATH' ) || die( 'Cheatin uh?' );
use WP_Error;
/**
* Fallback class to optimize medias.
*
* @since 1.9
* @author Grégory Viguier
*/
class Noop implements ProcessInterface {
/**
* The suffix used in the thumbnail size name.
*
* @var string
* @since 1.9
* @author Grégory Viguier
* @var string
* @since 1.9
*/
const WEBP_SUFFIX = '@imagify-webp';
/**
* The suffix used in the thumbnail size name.
*
* @var string
* @since 2.2
*/
const AVIF_SUFFIX = '@imagify-avif';
/**
* The suffix used in file name to create a temporary copy of the full size.
*
* @var string
* @since 1.9
* @author Grégory Viguier
* @var string
* @since 1.9
*/
const TMP_SUFFIX = '@imagify-tmp';
@@ -33,9 +37,8 @@ class Noop implements ProcessInterface {
* Used for the name of the transient telling if a media is locked.
* %1$s is the context, %2$s is the media ID.
*
* @var string
* @since 1.9
* @author Grégory Viguier
* @var string
* @since 1.9
*/
const LOCK_NAME = 'imagify_%1$s_%2$s_process_locked';
@@ -43,11 +46,10 @@ class Noop implements ProcessInterface {
* Tell if the given entry can be accepted in the constructor.
* For example it can include `is_numeric( $id )` if the constructor accepts integers.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param mixed $id Whatever.
*
* @param mixed $id Whatever.
* @return bool
*/
public static function constructor_accepts( $id ) {
@@ -57,9 +59,7 @@ class Noop implements ProcessInterface {
/**
* Get the data instance.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return DataInterface|false
*/
@@ -70,9 +70,7 @@ class Noop implements ProcessInterface {
/**
* Get the media instance.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return MediaInterface|false
*/
@@ -83,9 +81,7 @@ class Noop implements ProcessInterface {
/**
* Get the File instance.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return File|false
*/
@@ -96,9 +92,7 @@ class Noop implements ProcessInterface {
/**
* Tell if the current media is valid.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return bool
*/
@@ -109,106 +103,87 @@ class Noop implements ProcessInterface {
/**
* Tell if the current user is allowed to operate Imagify in this context.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param string $describer Capacity describer. See \Imagify\Context\ContextInterface->get_capacity() for possible values. Can also be a "real" user capacity.
*
* @param string $describer Capacity describer. See \Imagify\Context\ContextInterface->get_capacity() for possible values. Can also be a "real" user capacity.
* @return bool
*/
public function current_user_can( $describer ) {
return false;
}
/** ----------------------------------------------------------------------------------------- */
/** OPTIMIZATION ============================================================================ */
/** ----------------------------------------------------------------------------------------- */
/**
* Optimize a media files by pushing tasks into the queue.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
*
* @return bool|WP_Error True if successfully launched. A WP_Error instance on failure.
*/
public function optimize( $optimization_level = null ) {
return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
}
/**
* Re-optimize a media files with a different level.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
*
* @return bool|WP_Error True if successfully launched. A WP_Error instance on failure.
*/
public function reoptimize( $optimization_level = null ) {
return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
}
/**
* Optimize several file sizes by pushing tasks into the queue.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param array $sizes An array of media sizes (strings). Use "full" for the size of the main file.
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
* @param array $sizes An array of media sizes (strings). Use "full" for the size of the main file.
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
*
* @return bool|WP_Error True if successfully launched. A WP_Error instance on failure.
*/
public function optimize_sizes( $sizes, $optimization_level = null ) {
return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
}
/**
* Optimize one file with Imagify directly.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param string $size The media size.
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
* @return array|WP_Error The optimization data. A \WP_Error instance on failure.
* @param string $size The media size.
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
*
* @return array|WP_Error The optimization data. A WP_Error instance on failure.
*/
public function optimize_size( $size, $optimization_level = null ) {
return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
}
/**
* Restore the media files from the backup file.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return bool|WP_Error True on success. A \WP_Error instance on failure.
* @return bool|WP_Error True on success. A WP_Error instance on failure.
*/
public function restore() {
return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
}
/** ----------------------------------------------------------------------------------------- */
/** MISSING THUMBNAILS ====================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the sizes for this media that have not get through optimization.
* No sizes are returned if the file is not optimized, has no backup, or is not an image.
* The 'full' size os never returned.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return array|WP_Error {
* A WP_Error object on failure.
@@ -229,45 +204,30 @@ class Noop implements ProcessInterface {
/**
* Optimize missing thumbnail sizes.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
* @return bool|WP_Error True if successfully launched. A WP_Error instance on failure.
*/
public function optimize_missing_thumbnails() {
return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
}
/** ----------------------------------------------------------------------------------------- */
/** BACKUP FILE ============================================================================= */
/** ----------------------------------------------------------------------------------------- */
/**
* Delete the backup file.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*/
public function delete_backup() {}
/** ----------------------------------------------------------------------------------------- */
/** RESIZE FILE ============================================================================= */
/** ----------------------------------------------------------------------------------------- */
/**
* Maybe resize an image.
*
* @since 1.9
* @access protected
* @author Grégory Viguier
* @since 1.9
*
* @param string $size The size name.
* @param File $file A File instance.
* @return array|WP_Error A \WP_Error instance on failure, an array on success as follow: {
* @param string $size The size name.
* @param File $file A File instance.
*
* @return array|WP_Error A WP_Error instance on failure, an array on success as follow: {
* @type bool $resized True when the image has been resized.
* @type bool $backuped True when the image has been backuped.
* @type int $file_size The file size in bytes.
@@ -281,58 +241,47 @@ class Noop implements ProcessInterface {
];
}
/** ----------------------------------------------------------------------------------------- */
/** WEBP ==================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Generate WebP images if they are missing.
* Generate Nextgen images if they are missing.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
* @return bool|WP_Error True if successfully launched. A WP_Error instance on failure.
*/
public function generate_webp_versions() {
return new \WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
public function generate_nextgen_versions() {
return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
}
/**
* Delete the WebP images.
* Delete the next gen format images.
* This doesn't delete the related optimization data.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 2.2
*
* @param bool $keep_full Set to true to keep the full size.
* @return bool|WP_Error True on success. A WP_Error object on failure.
*/
public function delete_webp_files() {}
/**
* Tell if a thumbnail size is an "Imagify WebP" size.
*
* @since 1.9
* @access public
* @author Grégory Viguier
*
* @param string $size_name The size name.
* @return string|bool The unsuffixed name of the size if WebP. False if not WebP.
*/
public function is_size_webp( $size_name ) {
public function delete_nextgen_files( $keep_full = false ) {
return false;
}
/** ----------------------------------------------------------------------------------------- */
/** PROCESS STATUS ========================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Tell if a thumbnail size is an "Imagify Next-Gen" size.
*
* @since 2.2
*
* @param string $size_name The size name.
*
* @return string|bool The unsuffixed name of the size if Next-Gen. False if not a Next-Gen.
*/
public function is_size_next_gen( $size_name ) {
return false;
}
/**
* Tell if a process is running for this media.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return bool
*/
@@ -343,34 +292,24 @@ class Noop implements ProcessInterface {
/**
* Set the running status to "running" for a period of time.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*/
public function lock() {}
/**
* Delete the running status.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*/
public function unlock() {}
/** ----------------------------------------------------------------------------------------- */
/** DATA ==================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Tell if a size already has optimization data.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param string $size The size name.
*
* @param string $size The size name.
* @return bool
*/
public function size_has_optimization_data( $size ) {
@@ -380,13 +319,12 @@ class Noop implements ProcessInterface {
/**
* Update the optimization data for a size.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param object $response The API response.
* @param string $size The size name.
* @param int $level The optimization level (0=normal, 1=aggressive, 2=ultra).
*
* @param object $response The API response.
* @param string $size The size name.
* @param int $level The optimization level (0=normal, 1=aggressive, 2=ultra).
* @return array {
* The optimization data.
*

View File

@@ -1,13 +1,12 @@
<?php
namespace Imagify\Optimization\Process;
declare(strict_types=1);
defined( 'ABSPATH' ) || die( 'Cheatin uh?' );
namespace Imagify\Optimization\Process;
/**
* Interface to use to optimize medias.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*/
interface ProcessInterface {
@@ -15,11 +14,10 @@ interface ProcessInterface {
* Tell if the given entry can be accepted in the constructor.
* For example it can include `is_numeric( $id )` if the constructor accepts integers.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param mixed $id Whatever.
*
* @param mixed $id Whatever.
* @return bool
*/
public static function constructor_accepts( $id );
@@ -27,9 +25,7 @@ interface ProcessInterface {
/**
* Get the data instance.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return DataInterface|false
*/
@@ -38,9 +34,7 @@ interface ProcessInterface {
/**
* Get the media instance.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return MediaInterface|false
*/
@@ -49,9 +43,7 @@ interface ProcessInterface {
/**
* Get the File instance of the original file.
*
* @since 1.9.8
* @access public
* @author Grégory Viguier
* @since 1.9.8
*
* @return File|false
*/
@@ -60,9 +52,7 @@ interface ProcessInterface {
/**
* Get the File instance of the full size file.
*
* @since 1.9.8
* @access public
* @author Grégory Viguier
* @since 1.9.8
*
* @return File|false
*/
@@ -71,9 +61,7 @@ interface ProcessInterface {
/**
* Tell if the current media is valid.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return bool
*/
@@ -82,94 +70,75 @@ interface ProcessInterface {
/**
* Tell if the current user is allowed to operate Imagify in this context.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param string $describer Capacity describer. See \Imagify\Context\ContextInterface->get_capacity() for possible values. Can also be a "real" user capacity.
*
* @param string $describer Capacity describer. See \Imagify\Context\ContextInterface->get_capacity() for possible values. Can also be a "real" user capacity.
* @return bool
*/
public function current_user_can( $describer );
/** ----------------------------------------------------------------------------------------- */
/** OPTIMIZATION ============================================================================ */
/** ----------------------------------------------------------------------------------------- */
/**
* Optimize a media files by pushing tasks into the queue.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
*
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
*/
public function optimize( $optimization_level = null );
/**
* Re-optimize a media files with a different level.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
*
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
*/
public function reoptimize( $optimization_level = null );
/**
* Optimize several file sizes by pushing tasks into the queue.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param array $sizes An array of media sizes (strings). Use "full" for the size of the main file.
* @param array $sizes An array of media sizes (strings). Use "full" for the size of the main file.
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
*
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
*/
public function optimize_sizes( $sizes, $optimization_level = null );
/**
* Optimize one file with Imagify directly.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param string $size The media size.
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
* @return array|WP_Error The optimization data. A \WP_Error instance on failure.
* @param string $size The media size.
* @param int $optimization_level The optimization level (0=normal, 1=aggressive, 2=ultra).
*
* @return array|WP_Error The optimization data. A \WP_Error instance on failure.
*/
public function optimize_size( $size, $optimization_level = null );
/**
* Restore the media files from the backup file.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return bool|WP_Error True on success. A \WP_Error instance on failure.
*/
public function restore();
/** ----------------------------------------------------------------------------------------- */
/** MISSING THUMBNAILS ====================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the sizes for this media that have not get through optimization.
* No sizes are returned if the file is not optimized, has no backup, or is not an image.
* The 'full' size os never returned.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return array|WP_Error {
* A WP_Error object on failure.
@@ -188,42 +157,27 @@ interface ProcessInterface {
/**
* Optimize missing thumbnail sizes.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
*/
public function optimize_missing_thumbnails();
/** ----------------------------------------------------------------------------------------- */
/** BACKUP FILE ============================================================================= */
/** ----------------------------------------------------------------------------------------- */
/**
* Delete the backup file.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*/
public function delete_backup();
/** ----------------------------------------------------------------------------------------- */
/** RESIZE FILE ============================================================================= */
/** ----------------------------------------------------------------------------------------- */
/**
* Maybe resize an image.
*
* @since 1.9
* @access protected
* @author Grégory Viguier
* @since 1.9
*
* @param string $size The size name.
* @param File $file A File instance.
*
* @param string $size The size name.
* @param File $file A File instance.
* @return array|WP_Error A \WP_Error instance on failure, an array on success as follow: {
* @type bool $resized True when the image has been resized.
* @type bool $backuped True when the image has been backuped.
@@ -232,77 +186,58 @@ interface ProcessInterface {
*/
public function maybe_resize( $size, $file );
/** ----------------------------------------------------------------------------------------- */
/** WEBP ==================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Generate WebP images if they are missing.
* Generate next-gen images if they are missing.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
*/
public function generate_webp_versions();
public function generate_nextgen_versions();
/**
* Delete the WebP images.
* Delete the next gen format images.
* This doesn't delete the related optimization data.
*
* @since 1.9
* @since 1.9.6 Return WP_Error or true.
* @access public
* @author Grégory Viguier
* @since 2.2
*
* @param bool $keep_full Set to true to keep the full size.
* @return bool|\WP_Error True on success. A \WP_Error object on failure.
* @param bool $keep_full Set to true to keep the full size.
*
* @return bool|WP_Error True on success. A \WP_Error object on failure.
*/
public function delete_webp_files( $keep_full = false );
public function delete_nextgen_files( $keep_full = false );
/**
* Tell if a thumbnail size is an "Imagify WebP" size.
* Tell if a thumbnail size is an "Imagify Next-Gen" size.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 2.2
*
* @param string $size_name The size name.
* @return string|bool The unsuffixed name of the size if WebP. False if not WebP.
* @param string $size_name The size name.
*
* @return string|bool The unsuffixed name of the size if next-gen. False if not next-gen.
*/
public function is_size_webp( $size_name );
public function is_size_next_gen( $size_name );
/**
* Tell if the media has all WebP versions.
* Tell if the media has all next-gen versions.
*
* @return bool
*/
public function is_full_webp();
public function is_full_next_gen();
/**
* Tell if the media has WebP versions.
* Tell if the media has a next-gen format.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 2.2
*
* @return bool
*/
public function has_webp();
/** ----------------------------------------------------------------------------------------- */
/** PROCESS STATUS ========================================================================== */
/** ----------------------------------------------------------------------------------------- */
public function has_next_gen();
/**
* Tell if a process is running for this media.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return bool
*/
@@ -311,34 +246,24 @@ interface ProcessInterface {
/**
* Set the running status to "running" for a period of time.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*/
public function lock();
/**
* Delete the running status.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*/
public function unlock();
/** ----------------------------------------------------------------------------------------- */
/** DATA ==================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Tell if a size already has optimization data.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param string $size The size name.
*
* @param string $size The size name.
* @return bool
*/
public function size_has_optimization_data( $size );
@@ -346,13 +271,12 @@ interface ProcessInterface {
/**
* Update the optimization data for a size.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param object $response The API response.
* @param string $size The size name.
* @param int $level The optimization level (0=normal, 1=aggressive, 2=ultra).
*
* @param object $response The API response.
* @param string $size The size name.
* @param int $level The optimization level (0=normal, 1=aggressive, 2=ultra).
* @return array {
* The optimization data.
*

View File

@@ -1,57 +1,50 @@
<?php
namespace Imagify\Webp\Picture;
declare(strict_types=1);
defined( 'ABSPATH' ) || die( 'Cheatin uh?' );
namespace Imagify\Picture;
use Imagify\EventManagement\SubscriberInterface;
use Imagify_Filesystem;
/**
* Display WebP images on the site with <picture> tags.
* Display Next-gen images on the site with <picture> tags.
*
* @since 1.9
* @author Grégory Viguier
*/
class Display {
use \Imagify\Traits\InstanceGetterTrait;
class Display implements SubscriberInterface {
/**
* Option value.
*
* @var string
* @since 1.9
* @author Grégory Viguier
* @var string
*/
const OPTION_VALUE = 'picture';
/**
* Filesystem object.
*
* @var \Imagify_Filesystem
* @since 1.9
* @access protected
* @author Grégory Viguier
* @var Imagify_Filesystem
*/
protected $filesystem;
/**
* Constructor.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @param Imagify_Filesystem $filesystem Filesystem instance.
*/
public function __construct() {
$this->filesystem = \Imagify_Filesystem::get_instance();
public function __construct( Imagify_Filesystem $filesystem ) {
$this->filesystem = $filesystem;
}
/**
* Init.
* Array of events this subscriber listens to
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @return array
*/
public function init() {
add_action( 'template_redirect', [ $this, 'start_content_process' ], -1000 );
add_filter( 'imagify_process_webp_content', [ $this, 'process_content' ] );
public static function get_subscribed_events() {
return [
'template_redirect' => [ 'start_content_process', -1000 ],
'imagify_process_webp_content' => 'process_content',
];
}
/** ----------------------------------------------------------------------------------------- */
@@ -61,28 +54,29 @@ class Display {
/**
* Start buffering the page content.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @return void
*/
public function start_content_process() {
if ( ! get_imagify_option( 'display_webp' ) ) {
if ( ! get_imagify_option( 'display_nextgen' ) ) {
return;
}
if ( self::OPTION_VALUE !== get_imagify_option( 'display_webp_method' ) ) {
if ( self::OPTION_VALUE !== get_imagify_option( 'display_nextgen_method' ) ) {
return;
}
$allow = apply_filters_deprecated( 'imagify_allow_picture_tags_for_webp', [ true ], '2.2', 'imagify_allow_picture_tags_for_nextgen' );
/**
* Prevent the replacement of <img> tags into <picture> tags.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*
* @param bool $allow True to allow the use of <picture> tags (default). False to prevent their use.
*/
$allow = apply_filters( 'imagify_allow_picture_tags_for_webp', true );
$allow = apply_filters( 'imagify_allow_picture_tags_for_nextgen', true );
if ( ! $allow ) {
return;
@@ -94,11 +88,10 @@ class Display {
/**
* Maybe process the page content.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param string $buffer The buffer content.
*
* @param string $buffer The buffer content.
* @return string
*/
public function maybe_process_buffer( $buffer ) {
@@ -116,8 +109,7 @@ class Display {
/**
* Filter the page content after Imagify.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*
* @param string $buffer The page content.
*/
@@ -129,11 +121,10 @@ class Display {
/**
* Process the content.
*
* @since 1.9
* @access public
* @author Grégory Viguier
* @since 1.9
*
* @param string $content The content.
*
* @param string $content The content.
* @return string
*/
public function process_content( $content ) {
@@ -181,13 +172,12 @@ class Display {
/**
* Build the <picture> tag to insert.
*
* @since 1.9
* @see $this->process_image()
* @access protected
* @author Grégory Viguier
* @since 1.9
* @see $this->process_image()
*
* @param array $image An array of data.
* @return string A <picture> tag.
* @param array $image An array of data.
*
* @return string A <picture> tag.
*/
protected function build_picture_tag( $image ) {
$to_remove = [
@@ -210,8 +200,7 @@ class Display {
/**
* Filter the attributes to be added to the <picture> tag.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*
* @param array $attributes A list of attributes to be added to the <picture> tag.
* @param array $data Data built from the originale <img> tag. See $this->process_image().
@@ -233,8 +222,7 @@ class Display {
/**
* Allow to add more <source> tags to the <picture> tag.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*
* @param string $more_source_tags Additional <source> tags.
* @param array $data Data built from the originale <img> tag. See $this->process_image().
@@ -250,33 +238,74 @@ class Display {
/**
* Build the <source> tag to insert in the <picture>.
*
* @since 1.9
* @see $this->process_image()
* @access protected
* @author Grégory Viguier
* @since 1.9
* @see $this->process_image()
*
* @param array $image An array of data.
* @return string A <source> tag.
* @param array $image An array of data.
*
* @return string A <source> tag.
*/
protected function build_source_tag( $image ) {
$source = '';
foreach ( [ 'avif', 'webp' ] as $image_type ) {
$attributes = $this->build_source_attributes( $image, $image_type );
if ( empty( $attributes ) ) {
continue;
}
$source .= '<source' . $this->build_attributes( $attributes ) . "/>\n";
}
return $source;
}
/**
* Build the attribute for the source tag.
*
* @param array $image An array of data.
* @param string $image_type Type of image.
*
* @return array
*/
protected function build_source_attributes( array $image, string $image_type ): array {
$mime_type = '';
$url = '';
switch ( $image_type ) {
case 'webp':
$mime_type = 'image/webp';
$url = 'webp_url';
break;
case 'avif':
$mime_type = 'image/avif';
$url = 'avif_url';
break;
}
$srcset_source = ! empty( $image['srcset_attribute'] ) ? $image['srcset_attribute'] : $image['src_attribute'] . 'set';
$attributes = [
'type' => 'image/webp',
'type' => $mime_type,
$srcset_source => [],
];
if ( ! empty( $image['srcset'] ) ) {
foreach ( $image['srcset'] as $srcset ) {
if ( empty( $srcset['webp_url'] ) ) {
if ( empty( $srcset[ $url ] ) ) {
continue;
}
$attributes[ $srcset_source ][] = $srcset['webp_url'] . ' ' . $srcset['descriptor'];
$attributes[ $srcset_source ][] = $srcset[ $url ] . ' ' . $srcset['descriptor'];
}
}
if ( empty( $attributes[ $srcset_source ] ) && empty( $image['src'][ $url ] ) ) {
return [];
}
if ( empty( $attributes[ $srcset_source ] ) ) {
$attributes[ $srcset_source ][] = $image['src']['webp_url'];
$attributes[ $srcset_source ][] = $image['src'][ $url ];
}
$attributes[ $srcset_source ] = implode( ', ', $attributes[ $srcset_source ] );
@@ -301,27 +330,25 @@ class Display {
/**
* Filter the attributes to be added to the <source> tag.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*
* @param array $attributes A list of attributes to be added to the <source> tag.
* @param array $data Data built from the original <img> tag. See $this->process_image().
*/
$attributes = apply_filters( 'imagify_picture_source_attributes', $attributes, $image );
return '<source' . $this->build_attributes( $attributes ) . "/>\n";
return $attributes;
}
/**
* Build the <img> tag to insert in the <picture>.
*
* @since 1.9
* @see $this->process_image()
* @access protected
* @author Grégory Viguier
* @since 1.9
* @see $this->process_image()
*
* @param array $image An array of data.
* @return string A <img> tag.
* @param array $image An array of data.
*
* @return string A <img> tag.
*/
protected function build_img_tag( $image ) {
/**
@@ -349,8 +376,7 @@ class Display {
/**
* Filter the attributes to be added to the <img> tag.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*
* @param array $attributes A list of attributes to be added to the <img> tag.
* @param array $data Data built from the originale <img> tag. See $this->process_image().
@@ -363,12 +389,11 @@ class Display {
/**
* Create HTML attributes from an array.
*
* @since 1.9
* @access protected
* @author Grégory Viguier
* @since 1.9
*
* @param array $attributes A list of attribute pairs.
* @return string HTML attributes.
* @param array $attributes A list of attribute pairs.
*
* @return string HTML attributes.
*/
protected function build_attributes( $attributes ) {
if ( ! $attributes || ! is_array( $attributes ) ) {
@@ -391,11 +416,10 @@ class Display {
/**
* Get a list of images in a content.
*
* @since 1.9
* @access protected
* @author Grégory Viguier
* @since 1.9
*
* @param string $content The content.
*
* @param string $content The content.
* @return array
*/
protected function get_images( $content ) {
@@ -412,9 +436,8 @@ class Display {
/**
* Filter the images to display with a <picture> tag.
*
* @since 1.9
* @see $this->process_image()
* @author Grégory Viguier
* @since 1.9
* @see $this->process_image()
*
* @param array $images A list of arrays.
* @param string $content The page content.
@@ -426,12 +449,23 @@ class Display {
}
foreach ( $images as $i => $image ) {
if ( empty( $image['src']['webp_exists'] ) || empty( $image['src']['webp_url'] ) ) {
if ( ( empty( $image['src']['webp_exists'] ) || empty( $image['src']['webp_url'] ) ) &&
( empty( $image['src']['avif_exists'] ) || empty( $image['src']['avif_url'] ) ) ) {
unset( $images[ $i ] );
continue;
}
if ( empty( $image['src']['webp_exists'] ) || empty( $image['src']['webp_url'] ) ) {
unset( $images[ $i ]['src']['webp_url'] );
}
if ( empty( $image['src']['avif_exists'] ) || empty( $image['src']['avif_url'] ) ) {
unset( $images[ $i ]['src']['avif_url'] );
}
unset( $images[ $i ]['src']['webp_path'], $images[ $i ]['src']['webp_exists'] );
unset( $images[ $i ]['src']['avif_path'], $images[ $i ]['src']['avif_exists'] );
if ( empty( $image['srcset'] ) || ! is_array( $image['srcset'] ) ) {
unset( $images[ $i ]['srcset'] );
@@ -443,11 +477,22 @@ class Display {
continue;
}
if ( ( empty( $srcset['webp_exists'] ) || empty( $srcset['webp_url'] ) ) &&
( empty( $srcset['avif_exists'] ) || empty( $srcset['avif_url'] ) ) ) {
unset( $images[ $i ]['srcset'][ $j ]['webp_url'] );
unset( $images[ $i ]['srcset'][ $j ]['avif_url'] );
}
if ( empty( $srcset['webp_exists'] ) || empty( $srcset['webp_url'] ) ) {
unset( $images[ $i ]['srcset'][ $j ]['webp_url'] );
}
if ( empty( $srcset['avif_exists'] ) || empty( $srcset['avif_url'] ) ) {
unset( $images[ $i ]['srcset'][ $j ]['avif_url'] );
}
unset( $images[ $i ]['srcset'][ $j ]['webp_path'], $images[ $i ]['srcset'][ $j ]['webp_exists'] );
unset( $images[ $i ]['srcset'][ $j ]['avif_path'], $images[ $i ]['srcset'][ $j ]['avif_exists'] );
}
}
@@ -457,9 +502,7 @@ class Display {
/**
* Process an image tag and get an array containing some data.
*
* @since 1.9
* @access protected
* @author Grégory Viguier
* @since 1.9
*
* @param string $image An image html tag.
* @return array|false {
@@ -527,24 +570,21 @@ class Display {
return false;
}
$webp_url = imagify_path_to_webp( $src['src'] );
$webp_path = $this->url_to_path( $webp_url );
$webp_url .= ! empty( $src['query'] ) ? $src['query'] : '';
$data = [
'tag' => $image,
'attributes' => $attributes,
'src_attribute' => $src_source,
'src' => [
'url' => $attributes[ $src_source ],
'webp_url' => $webp_url,
'webp_path' => $webp_path,
'webp_exists' => $webp_path && $this->filesystem->exists( $webp_path ),
],
'srcset_attribute' => false,
'srcset' => [],
];
foreach ( $this->get_nextgen_image_data_set( $src ) as $key => $value ) {
$data['src'][ $key ] = $value;
}
// Deal with the srcset attribute.
$srcset_source = false;
@@ -556,6 +596,8 @@ class Display {
}
if ( $srcset_source ) {
$srcset_data = [];
$data['srcset_attribute'] = $srcset_source;
$srcset = explode( ',', $attributes[ $srcset_source ] );
@@ -582,25 +624,23 @@ class Display {
continue;
}
$webp_url = imagify_path_to_webp( $src['src'] );
$webp_path = $this->url_to_path( $webp_url );
$webp_url .= ! empty( $src['query'] ) ? $src['query'] : '';
$data['srcset'][] = [
$srcset_data = [
'url' => $srcs[0],
'descriptor' => $srcs[1],
'webp_url' => $webp_url,
'webp_path' => $webp_path,
'webp_exists' => $webp_path && $this->filesystem->exists( $webp_path ),
];
foreach ( $this->get_nextgen_image_data_set( $src ) as $key => $value ) {
$srcset_data[ $key ] = $value;
}
$data['srcset'][] = $srcset_data;
}
}
/**
* Filter a processed image tag.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*
* @param array $data An array of data for this image.
* @param string $image An image html tag.
@@ -618,14 +658,41 @@ class Display {
return $data;
}
/**
* Get the next-gen image(webp & avif) data set.
*
* @param array $src Array of url/path segments.
*
* @return array
*/
protected function get_nextgen_image_data_set( array $src ): array {
$webp_url = imagify_path_to_nextgen( $src['src'], 'webp' );
$webp_path = $this->url_to_path( $webp_url );
$avif_url = imagify_path_to_nextgen( $src['src'], 'avif' );
$avif_path = $this->url_to_path( $avif_url );
$query_string = ! empty( $src['query'] ) ? $src['query'] : '';
return [
// WebP data set.
'webp_url' => $webp_url . $query_string,
'webp_path' => $webp_path,
'webp_exists' => $webp_path && $this->filesystem->exists( $webp_path ),
// Avif data set.
'avif_url' => $avif_url . $query_string,
'avif_path' => $avif_path,
'avif_exists' => $avif_path && $this->filesystem->exists( $avif_path ),
];
}
/**
* Tell if a content is HTML.
*
* @since 1.9
* @access protected
* @author Grégory Viguier
* @since 1.9
*
* @param string $content The content.
*
* @param string $content The content.
* @return bool
*/
protected function is_html( $content ) {
@@ -635,11 +702,10 @@ class Display {
/**
* Convert a file URL to an absolute path.
*
* @since 1.9
* @access protected
* @author Grégory Viguier
* @since 1.9
*
* @param string $url A file URL.
*
* @param string $url A file URL.
* @return string|bool The file path. False on failure.
*/
protected function url_to_path( $url ) {
@@ -658,7 +724,7 @@ class Display {
$uploads_dir = $this->filesystem->get_upload_basedir( true );
$root_url = set_url_scheme( $this->filesystem->get_site_root_url() );
$root_dir = $this->filesystem->get_site_root();
$cdn_url = $this->get_cdn_source();
$cdn_url = apply_filters( 'imagify_cdn_source_url', '' );
$cdn_url = $cdn_url['url'] ? set_url_scheme( $cdn_url['url'] ) : false;
$domain_url = wp_parse_url( $root_url );
@@ -693,108 +759,4 @@ class Display {
return false;
}
/**
* Get the CDN "source".
*
* @since 1.9.3
* @access public
* @author Grégory Viguier
*
* @param string $option_url An URL to use instead of the one stored in the option. It is used only if no constant/filter.
* @return array {
* @type string $source Where does it come from? Possible values are 'constant', 'filter', or 'option'.
* @type string $name Who? Can be a constant name, a plugin name, or an empty string.
* @type string $url The CDN URL, with a trailing slash. An empty string if no URL is set.
* }
*/
public function get_cdn_source( $option_url = '' ) {
if ( defined( 'IMAGIFY_CDN_URL' ) && IMAGIFY_CDN_URL && is_string( IMAGIFY_CDN_URL ) ) {
// Use a constant.
$source = [
'source' => 'constant',
'name' => 'IMAGIFY_CDN_URL',
'url' => IMAGIFY_CDN_URL,
];
} else {
// Maybe use a filter.
$filter_source = [
'name' => null,
'url' => null,
];
/**
* Provide a custom CDN source.
*
* @since 1.9.3
* @author Grégory Viguier
*
* @param array $filter_source {
* @type $name string The name of which provides the URL (plugin name, etc).
* @type $url string The CDN URL.
* }
*/
$filter_source = apply_filters( 'imagify_cdn_source', $filter_source );
if ( ! empty( $filter_source['url'] ) ) {
$source = [
'source' => 'filter',
'name' => ! empty( $filter_source['name'] ) ? $filter_source['name'] : '',
'url' => $filter_source['url'],
];
}
}
if ( empty( $source['url'] ) ) {
// No constant, no filter: use the option.
$source = [
'source' => 'option',
'name' => '',
'url' => $option_url && is_string( $option_url ) ? $option_url : get_imagify_option( 'cdn_url' ),
];
}
if ( empty( $source['url'] ) ) {
// Nothing set.
return [
'source' => 'option',
'name' => '',
'url' => '',
];
}
$source['url'] = $this->sanitize_cdn_url( $source['url'] );
if ( empty( $source['url'] ) ) {
// Not an URL.
return [
'source' => 'option',
'name' => '',
'url' => '',
];
}
return $source;
}
/**
* Sanitize the CDN URL value.
*
* @since 1.9.3
* @access public
* @author Grégory Viguier
*
* @param string $url The URL to sanitize.
* @return string
*/
public function sanitize_cdn_url( $url ) {
$url = sanitize_text_field( $url );
if ( ! $url || ! preg_match( '@^https?://.+\.[^.]+@i', $url ) ) {
// Not an URL.
return '';
}
return trailingslashit( $url );
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Imagify\Picture;
use Imagify\Dependencies\League\Container\ServiceProvider\AbstractServiceProvider;
/**
* Service provider for Picture display
*/
class ServiceProvider extends AbstractServiceProvider {
/**
* Services provided by this provider
*
* @var array
*/
protected $provides = [
'picture_display',
];
/**
* Subscribers provided by this provider
*
* @var array
*/
public $subscribers = [
'picture_display',
];
/**
* Registers the provided classes
*
* @return void
*/
public function register() {
$this->getContainer()->share( 'picture_display', Display::class )
->addArgument( $this->getContainer()->get( 'filesystem' ) );
}
/**
* Returns the subscribers array
*
* @return array
*/
public function get_subscribers() {
return $this->subscribers;
}
}

View File

@@ -3,16 +3,33 @@ declare(strict_types=1);
namespace Imagify;
use Imagify\Bulk\Bulk;
use Imagify\CLI\BulkOptimizeCommand;
use Imagify\CLI\GenerateMissingWebpCommand;
use Imagify\Notices\Notices;
use Imagify\Admin\AdminBar;
use Imagify\Bulk\Bulk;
use Imagify\CLI\{BulkOptimizeCommand, GenerateMissingNextgenCommand};
use Imagify\Dependencies\League\Container\Container;
use Imagify\Dependencies\League\Container\ServiceProvider\ServiceProviderInterface;
use Imagify\EventManagement\{EventManager, SubscriberInterface};
use Imagify\Notices\Notices;
use Imagify_Filesystem;
/**
* Main plugin class.
*/
class Plugin {
/**
* Container instance.
*
* @var Container
*/
private $container;
/**
* Is the plugin loaded
*
* @var boolean
*/
private $loaded = false;
/**
* Absolute path to the plugin (with trailing slash).
*
@@ -25,22 +42,64 @@ class Plugin {
*
* @since 1.9
*
* @param array $plugin_args {
* @param Container $container Instance of the container.
* @param array $plugin_args {
* An array of arguments.
*
* @type string $plugin_path Absolute path to the plugin (with trailing slash).
* }
*/
public function __construct( $plugin_args ) {
public function __construct( Container $container, $plugin_args ) {
$this->container = $container;
$this->plugin_path = $plugin_args['plugin_path'];
add_filter( 'imagify_container', [ $this, 'get_container' ] );
}
/**
* Returns the container instance.
*
* @return Container
*/
public function get_container() {
return $this->container;
}
/**
* Checks if the plugin is loaded
*
* @return boolean
*/
private function is_loaded(): bool {
return $this->loaded;
}
/**
* Plugin init.
*
* @param array $providers Array of service providers.
*
* @since 1.9
*/
public function init() {
public function init( $providers ) {
if ( $this->is_loaded() ) {
return;
}
$this->container->share(
'event_manager',
function () {
return new EventManager();
}
);
$this->container->share(
'filesystem',
function() {
return new Imagify_Filesystem();
}
);
$this->include_files();
class_alias( '\\Imagify\\Traits\\InstanceGetterTrait', '\\Imagify\\Traits\\FakeSingletonTrait' );
@@ -55,7 +114,6 @@ class Plugin {
\Imagify_Cron_Sync_Files::get_instance()->init();
\Imagify\Auth\Basic::get_instance()->init();
\Imagify\Job\MediaOptimization::get_instance()->init();
\Imagify\Stats\OptimizedMediaWithoutWebp::get_instance()->init();
Bulk::get_instance()->init();
AdminBar::get_instance()->init();
@@ -72,15 +130,21 @@ class Plugin {
\Imagify_Assets::get_instance()->init();
}
\Imagify\Webp\Display::get_instance()->init();
add_action( 'init', [ $this, 'maybe_activate' ] );
// Load plugin translations.
imagify_load_translations();
imagify_add_command( new BulkOptimizeCommand() );
imagify_add_command( new GenerateMissingWebpCommand() );
imagify_add_command( new GenerateMissingNextgenCommand() );
foreach ( $providers as $service_provider ) {
$provider_instance = new $service_provider();
$this->container->addServiceProvider( $provider_instance );
// Load each service provider's subscribers if found.
$this->load_subscribers( $provider_instance );
}
/**
* Fires when Imagify is fully loaded.
@@ -91,6 +155,8 @@ class Plugin {
* @param \Imagify_Plugin $plugin Instance of this class.
*/
do_action( 'imagify_loaded', $this );
$this->loaded = true;
}
/**
@@ -171,4 +237,25 @@ class Plugin {
*/
do_action( 'imagify_activation', (int) $user_id );
}
/**
* Load list of event subscribers from service provider.
*
* @param ServiceProviderInterface $service_provider Instance of service provider.
*
* @return void
*/
private function load_subscribers( ServiceProviderInterface $service_provider ) {
if ( empty( $service_provider->get_subscribers() ) ) {
return;
}
foreach ( $service_provider->get_subscribers() as $subscriber ) {
$subscriber_object = $this->container->get( $subscriber );
if ( $subscriber_object instanceof SubscriberInterface ) {
$this->container->get( 'event_manager' )->add_subscriber( $subscriber_object );
}
}
}
}

View File

@@ -1,15 +1,16 @@
<?php
declare(strict_types=1);
namespace Imagify\Stats;
use Imagify\Bulk\Bulk;
use Imagify\EventManagement\SubscriberInterface;
use Imagify\Traits\InstanceGetterTrait;
/**
* Class to get and cache the number of optimized media without WebP versions.
*
* @since 1.9
* Class to get and cache the number of optimized media without next-gen versions.
*/
class OptimizedMediaWithoutWebp implements StatInterface {
class OptimizedMediaWithoutNextGen implements StatInterface, SubscriberInterface {
use InstanceGetterTrait;
/**
@@ -17,28 +18,26 @@ class OptimizedMediaWithoutWebp implements StatInterface {
*
* @var string
*/
const NAME = 'imagify_stat_without_webp';
const NAME = 'imagify_stat_without_next_gen';
/**
* Launch hooks.
* Array of events this subscriber listens to
*
* @since 1.9
* @return array
*/
public function init() {
add_action( 'imagify_after_optimize', [ $this, 'maybe_clear_cache_after_optimization' ], 10, 2 );
add_action( 'imagify_after_restore_media', [ $this, 'maybe_clear_cache_after_restoration' ], 10, 4 );
add_action( 'imagify_delete_media', [ $this, 'maybe_clear_cache_on_deletion' ] );
public static function get_subscribed_events() {
return [
'imagify_after_optimize' => [ 'maybe_clear_cache_after_optimization', 10, 2 ],
'imagify_after_restore_media' => [ 'maybe_clear_cache_after_restoration', 10, 4 ],
'imagify_delete_media' => 'maybe_clear_cache_on_deletion',
'update_option_imagify_settings' => [ 'maybe_clear_stat_cache', 9, 2 ],
];
}
/** ----------------------------------------------------------------------------------------- */
/** GET/CACHE THE STAT ====================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the number of optimized media without WebP versions.
* Get the number of optimized media without next-gen versions.
*
* @since 1.9
* @since 2.2
*
* @return int
*/
@@ -48,16 +47,16 @@ class OptimizedMediaWithoutWebp implements StatInterface {
// Sum the counts of each context.
foreach ( imagify_get_context_names() as $context ) {
$stat += $bulk->get_bulk_instance( $context )->has_optimized_media_without_webp();
$stat += $bulk->get_bulk_instance( $context )->has_optimized_media_without_nextgen();
}
return $stat;
}
/**
* Get and cache the number of optimized media without WebP versions.
* Get and cache the number of optimized media without next-gen versions.
*
* @since 1.9
* @since 2.2
*
* @return int
*/
@@ -83,21 +82,16 @@ class OptimizedMediaWithoutWebp implements StatInterface {
/**
* Clear the stat cache.
*
* @since 1.9
* @since 2.2
*/
public function clear_cache() {
delete_transient( static::NAME );
}
/** ----------------------------------------------------------------------------------------- */
/** HOOKS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Clear cache after optimizing a media.
*
* @since 1.9
* @since 2.2
*
* @param ProcessInterface $process The optimization process.
* @param array $item The item being processed.
@@ -113,11 +107,15 @@ class OptimizedMediaWithoutWebp implements StatInterface {
$new_sizes = array_intersect_key( $sizes, $new_sizes );
$size_name = 'full' . $process::WEBP_SUFFIX;
if ( get_imagify_option( 'convert_to_avif' ) ) {
$size_name = 'full' . $process::AVIF_SUFFIX;
}
if ( ! isset( $new_sizes['full'] ) && ! empty( $new_sizes[ $size_name ]['success'] ) ) {
/**
* We just successfully generated the WebP version of the full size.
* We just successfully generated the next-gen version of the full size.
* The full size was not optimized at the same time, that means it was optimized previously.
* Meaning: we just added a WebP version to a media that was previously optimized, so there is one less optimized media without WebP.
* Meaning: we just added a next-gen version to a media that was previously optimized, so there is one less optimized media without next-gen.
*/
$this->clear_cache();
return;
@@ -125,7 +123,7 @@ class OptimizedMediaWithoutWebp implements StatInterface {
if ( ! empty( $new_sizes['full']['success'] ) && empty( $new_sizes[ $size_name ]['success'] ) ) {
/**
* We now have a new optimized media without WebP.
* We now have a new optimized media without next-gen.
*/
$this->clear_cache();
}
@@ -134,7 +132,7 @@ class OptimizedMediaWithoutWebp implements StatInterface {
/**
* Clear cache after restoring a media.
*
* @since 1.9
* @since 2.2
*
* @param ProcessInterface $process The optimization process.
* @param bool|WP_Error $response The result of the operation: true on success, a WP_Error object on failure.
@@ -149,9 +147,13 @@ class OptimizedMediaWithoutWebp implements StatInterface {
$sizes = isset( $data['sizes'] ) ? (array) $data['sizes'] : [];
$size_name = 'full' . $process::WEBP_SUFFIX;
if ( get_imagify_option( 'convert_to_avif' ) ) {
$size_name = 'full' . $process::AVIF_SUFFIX;
}
if ( ! empty( $sizes['full']['success'] ) && empty( $sizes[ $size_name ]['success'] ) ) {
/**
* This media had no WebP versions.
* This media had no next-gen versions.
*/
$this->clear_cache();
}
@@ -160,7 +162,7 @@ class OptimizedMediaWithoutWebp implements StatInterface {
/**
* Clear cache on media deletion.
*
* @since 1.9
* @since 2.2
*
* @param ProcessInterface $process An optimization process.
*/
@@ -173,11 +175,37 @@ class OptimizedMediaWithoutWebp implements StatInterface {
$sizes = isset( $data['sizes'] ) ? (array) $data['sizes'] : [];
$size_name = 'full' . $process::WEBP_SUFFIX;
if ( get_imagify_option( 'convert_to_avif' ) ) {
$size_name = 'full' . $process::AVIF_SUFFIX;
}
if ( ! empty( $sizes['full']['success'] ) && empty( $sizes[ $size_name ]['success'] ) ) {
/**
* This media had no WebP versions.
* This media had no next-gen versions.
*/
$this->clear_cache();
}
}
/**
* Maybe clear the stat cache on option change
*
* @since 2.2
*
* @param array $old_value The old option value.
* @param array $value The new option value.
*
* @return void
*/
public function maybe_clear_stat_cache( $old_value, $value ) {
if ( isset( $old_value['convert_to_avif'] ) && isset( $value['convert_to_avif'] ) ) {
return;
}
if ( ! isset( $old_value['convert_to_avif'] ) && ! isset( $value['convert_to_avif'] ) ) {
return;
}
$this->clear_cache();
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Imagify\Stats;
use Imagify\Dependencies\League\Container\ServiceProvider\AbstractServiceProvider;
/**
* Service provider for Stats
*/
class ServiceProvider extends AbstractServiceProvider {
/**
* Services provided by this provider
*
* @var array
*/
protected $provides = [
'optimized_media_without_next_gen',
];
/**
* Subscribers provided by this provider
*
* @var array
*/
public $subscribers = [
'optimized_media_without_next_gen',
];
/**
* Registers the provided classes
*
* @return void
*/
public function register() {
$this->getContainer()->share( 'optimized_media_without_next_gen', OptimizedMediaWithoutNextGen::class );
}
/**
* Returns the subscribers array
*
* @return array
*/
public function get_subscribers() {
return $this->subscribers;
}
}

View File

@@ -1,31 +1,29 @@
<?php
declare(strict_types=1);
namespace Imagify\Webp;
defined( 'ABSPATH' ) || die( 'Cheatin uh?' );
use Imagify\WriteFile\AbstractApacheDirConfFile;
/**
* Add and remove contents to the .htaccess file to display WebP images on the site.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*/
class Apache extends \Imagify\WriteFile\AbstractApacheDirConfFile {
class Apache extends AbstractApacheDirConfFile {
/**
* Name of the tag used as block delemiter.
*
* @var string
* @since 1.9
* @author Grégory Viguier
* @var string
* @since 1.9
*/
const TAG_NAME = 'Imagify: webp file type';
/**
* Get unfiltered new contents to write into the file.
*
* @since 1.9
* @access protected
* @author Grégory Viguier
* @since 1.9
*
* @return string
*/

View File

@@ -1,70 +1,64 @@
<?php
declare(strict_types=1);
namespace Imagify\Webp;
use Imagify\EventManagement\SubscriberInterface;
use Imagify\Notices\Notices;
use Imagify\Traits\InstanceGetterTrait;
use Imagify\WriteFile\WriteFileInterface;
/**
* Display WebP images on the site.
* Display WebP images on the site using picture tag.
*
* @since 1.9
*/
class Display {
class Display implements SubscriberInterface {
use InstanceGetterTrait;
/**
* Server conf object.
*
* @var \Imagify\WriteFile\WriteFileInterface
* @var WriteFileInterface|null
* @since 1.9
*/
protected $server_conf;
protected $server_conf = null;
/**
* Init.
* Returns an array of events this subscriber listens to
*
* @since 1.9
* @return array
*/
public function init() {
add_filter( 'imagify_settings_on_save', [ $this, 'maybe_add_rewrite_rules' ] );
add_action( 'imagify_settings_webp_info', [ $this, 'maybe_add_webp_info' ] );
add_action( 'imagify_activation', [ $this, 'activate' ] );
add_action( 'imagify_deactivation', [ $this, 'deactivate' ] );
Picture\Display::get_instance()->init();
RewriteRules\Display::get_instance()->init();
public static function get_subscribed_events() {
return [
'imagify_settings_on_save' => [ 'maybe_add_rewrite_rules', 13 ],
'imagify_settings_webp_info' => 'maybe_add_webp_info',
'imagify_activation' => 'activate',
'imagify_deactivation' => 'deactivate',
];
}
/** ----------------------------------------------------------------------------------------- */
/** HOOKS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* If display WebP images, add the WebP type to the .htaccess/etc file.
*
* @since 1.9
*
* @param array $values The option values.
* @param array $values The option values.
*
* @return array
*/
public function maybe_add_rewrite_rules( $values ) {
$old_value = (bool) get_imagify_option( 'display_webp' );
// See \Imagify_Options->validate_values_on_update() for why we use 'convert_to_webp' here.
$new_value = ! empty( $values['display_webp'] ) && ! empty( $values['convert_to_webp'] );
if ( $old_value === $new_value ) {
// No changes.
return $values;
}
if ( ! $this->get_server_conf() ) {
return $values;
}
if ( $new_value ) {
$enabled = isset( $values['display_nextgen'] ) ? true : false;
$result = false;
if ( $enabled ) {
// Add the WebP file type.
$result = $this->get_server_conf()->add();
} else {
} elseif ( ! $enabled ) {
// Remove the WebP file type.
$result = $this->get_server_conf()->remove();
}
@@ -76,10 +70,12 @@ class Display {
// Display an error message.
if ( is_multisite() && strpos( wp_get_referer(), network_admin_url( '/' ) ) === 0 ) {
Notices::get_instance()->add_network_temporary_notice( $result->get_error_message() );
} else {
Notices::get_instance()->add_site_temporary_notice( $result->get_error_message() );
return $values;
}
Notices::get_instance()->add_site_temporary_notice( $result->get_error_message() );
return $values;
}
@@ -130,9 +126,11 @@ class Display {
if ( ! $conf ) {
return;
}
if ( ! get_imagify_option( 'display_webp' ) ) {
if ( ! get_imagify_option( 'display_nextgen' ) ) {
return;
}
if ( is_wp_error( $conf->is_file_writable() ) ) {
return;
}
@@ -151,9 +149,6 @@ class Display {
if ( ! $conf ) {
return;
}
if ( ! get_imagify_option( 'display_webp' ) ) {
return;
}
$file_path = $conf->get_file_path();
$filesystem = \Imagify_Filesystem::get_instance();
@@ -168,10 +163,6 @@ class Display {
$conf->remove();
}
/** ----------------------------------------------------------------------------------------- */
/** TOOLS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the path to the directory conf file.
*
@@ -194,30 +185,13 @@ class Display {
return $file_path;
}
/**
* Get the WebP display method by validating the given value.
*
* @since 1.9
*
* @param array $values The option values.
* @return string 'picture' or 'rewrite'.
*/
public function get_display_webp_method( $values ) {
$options = \Imagify_Options::get_instance();
$default = $options->get_default_values();
$default = $default['display_webp_method'];
$method = ! empty( $values['display_webp_method'] ) ? $values['display_webp_method'] : '';
return $options->sanitize_and_validate( 'display_webp_method', $method, $default );
}
/**
* Get the server conf instance.
* Note: nothing needed for nginx.
*
* @since 1.9
*
* @return \Imagify\WriteFile\WriteFileInterface
* @return WriteFileInterface
*/
protected function get_server_conf() {
global $is_apache, $is_iis7;
@@ -230,8 +204,6 @@ class Display {
$this->server_conf = new Apache();
} elseif ( $is_iis7 ) {
$this->server_conf = new IIS();
} else {
$this->server_conf = false;
}
return $this->server_conf;

View File

@@ -1,31 +1,29 @@
<?php
declare(strict_types=1);
namespace Imagify\Webp;
defined( 'ABSPATH' ) || die( 'Cheatin uh?' );
use Imagify\WriteFile\AbstractIISDirConfFile;
/**
* Add and remove contents to the web.config file to display WebP images on the site.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*/
class IIS extends \Imagify\WriteFile\AbstractIISDirConfFile {
class IIS extends AbstractIISDirConfFile {
/**
* Name of the tag used as block delemiter.
*
* @var string
* @since 1.9
* @author Grégory Viguier
* @var string
* @since 1.9
*/
const TAG_NAME = 'Imagify: webp file type';
/**
* Get unfiltered new contents to write into the file.
*
* @since 1.9
* @access protected
* @author Grégory Viguier
* @since 1.9
*
* @return array
*/

View File

@@ -1,22 +1,22 @@
<?php
declare(strict_types=1);
namespace Imagify\Webp\RewriteRules;
defined( 'ABSPATH' ) || die( 'Cheatin uh?' );
use Imagify\WriteFile\AbstractApacheDirConfFile;
/**
* Add and remove rewrite rules to the .htaccess file to display WebP images on the site.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*/
class Apache extends \Imagify\WriteFile\AbstractApacheDirConfFile {
class Apache extends AbstractApacheDirConfFile {
/**
* Name of the tag used as block delemiter.
*
* @var string
* @since 1.9
* @author Grégory Viguier
* @var string
* @since 1.9
*/
const TAG_NAME = 'Imagify: rewrite rules for webp';
@@ -24,14 +24,13 @@ class Apache extends \Imagify\WriteFile\AbstractApacheDirConfFile {
* Get unfiltered new contents to write into the file.
*
* @since 1.9
* @access protected
* @source https://github.com/vincentorback/WebP-images-with-htaccess
* @author Grégory Viguier
*
* @return string
*/
protected function get_raw_new_contents() {
$extensions = $this->get_extensions_pattern();
$extensions = str_replace( '|webp', '', $extensions );
$home_root = wp_parse_url( home_url( '/' ) );
$home_root = $home_root['path'];

View File

@@ -1,100 +1,80 @@
<?php
declare(strict_types=1);
namespace Imagify\Webp\RewriteRules;
use Imagify\EventManagement\SubscriberInterface;
use Imagify\Notices\Notices;
use Imagify\Traits\InstanceGetterTrait;
use Imagify\WriteFile\AbstractWriteDirConfFile;
use Imagify\WriteFile\WriteFileInterface;
/**
* Display WebP images on the site with rewrite rules.
*
* @since 1.9
*/
class Display {
use InstanceGetterTrait;
class Display implements SubscriberInterface {
/**
* Configuration file writer.
*
* @var AbstractWriteDirConfFile
* @var WriteFileInterface|null
*/
protected $server_conf;
protected $server_conf = null;
/**
* Option value.
*
* @var string
* @var string
* @since 1.9
*/
const OPTION_VALUE = 'rewrite';
/**
* Init.
* Returns an array of events this subscriber listens to
*
* @since 1.9
* @return array
*/
public function init() {
add_filter( 'imagify_settings_on_save', [ $this, 'maybe_add_rewrite_rules' ] );
add_action( 'imagify_settings_webp_info', [ $this, 'maybe_add_webp_info' ] );
add_action( 'imagify_activation', [ $this, 'activate' ] );
add_action( 'imagify_deactivation', [ $this, 'deactivate' ] );
public static function get_subscribed_events() {
return [
'imagify_settings_on_save' => [ 'maybe_add_rewrite_rules', 10 ],
'imagify_settings_webp_info' => 'maybe_add_webp_info',
'imagify_activation' => 'activate',
'imagify_deactivation' => 'deactivate',
];
}
/** ----------------------------------------------------------------------------------------- */
/** HOOKS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* If display WebP images via rewrite rules, add the rules to the .htaccess/etc file.
*
* @since 1.9
*
* @param array $values The option values.
* @param array $values The option values.
*
* @return array
*/
public function maybe_add_rewrite_rules( $values ) {
global $is_apache, $is_iis7, $is_nginx;
// Display WebP?
$was_enabled = (bool) get_imagify_option( 'display_webp' );
// See \Imagify_Options->validate_values_on_update() for why we use 'convert_to_webp' here.
$is_enabled = ! empty( $values['display_webp'] ) && ! empty( $values['convert_to_webp'] );
$was_enabled = (bool) get_imagify_option( 'display_nextgen' );
$is_enabled = ! empty( $values['display_nextgen'] );
// Which method?
$old_value = get_imagify_option( 'display_webp_method' );
$new_value = ! empty( $values['display_webp_method'] ) ? $values['display_webp_method'] : '';
$old_value = get_imagify_option( 'display_nextgen_method' );
$new_value = ! empty( $values['display_nextgen_method'] ) ? $values['display_nextgen_method'] : '';
// Decide when to add or remove rules.
$is_rewrite = self::OPTION_VALUE === $new_value;
$was_rewrite = self::OPTION_VALUE === $old_value;
$add_or_remove = false;
$is_rewrite = self::OPTION_VALUE === $new_value;
$was_rewrite = self::OPTION_VALUE === $old_value;
if ( ! $this->get_server_conf() ) {
return $values;
}
$result = false;
if ( $is_enabled && $is_rewrite && ( ! $was_enabled || ! $was_rewrite ) ) {
// Display WebP & use rewrite method, but only if one of the values changed: add rules.
$add_or_remove = 'add';
} elseif ( $was_enabled && $was_rewrite && ( ! $is_enabled || ! $is_rewrite ) ) {
// Was displaying WebP & was using rewrite method, but only if one of the values changed: remove rules.
$add_or_remove = 'remove';
} else {
return $values;
}
if ( $is_apache ) {
$rules = new Apache();
} elseif ( $is_iis7 ) {
$rules = new IIS();
} elseif ( $is_nginx ) {
$rules = new Nginx();
} else {
return $values;
}
if ( 'add' === $add_or_remove ) {
// Add the rewrite rules.
$result = $rules->add();
} else {
$result = $this->get_server_conf()->add();
} elseif ( $was_enabled && $was_rewrite && ( ! $is_enabled || ! $is_rewrite ) ) {
// Remove the rewrite rules.
$result = $rules->remove();
$result = $this->get_server_conf()->remove();
}
if ( ! is_wp_error( $result ) ) {
@@ -104,10 +84,12 @@ class Display {
// Display an error message.
if ( is_multisite() && strpos( wp_get_referer(), network_admin_url( '/' ) ) === 0 ) {
Notices::get_instance()->add_network_temporary_notice( $result->get_error_message() );
} else {
Notices::get_instance()->add_site_temporary_notice( $result->get_error_message() );
return $values;
}
Notices::get_instance()->add_site_temporary_notice( $result->get_error_message() );
return $values;
}
@@ -162,10 +144,11 @@ class Display {
if ( ! $conf ) {
return;
}
if ( ! get_imagify_option( 'display_webp' ) ) {
if ( ! get_imagify_option( 'display_nextgen' ) ) {
return;
}
if ( self::OPTION_VALUE !== get_imagify_option( 'display_webp_method' ) ) {
if ( self::OPTION_VALUE !== get_imagify_option( 'display_nextgen_method' ) ) {
return;
}
if ( is_wp_error( $conf->is_file_writable() ) ) {
@@ -186,10 +169,10 @@ class Display {
if ( ! $conf ) {
return;
}
if ( ! get_imagify_option( 'display_webp' ) ) {
if ( ! get_imagify_option( 'display_nextgen' ) ) {
return;
}
if ( self::OPTION_VALUE !== get_imagify_option( 'display_webp_method' ) ) {
if ( self::OPTION_VALUE !== get_imagify_option( 'display_nextgen_method' ) ) {
return;
}
@@ -206,17 +189,14 @@ class Display {
$conf->remove();
}
/** ----------------------------------------------------------------------------------------- */
/** TOOLS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the path to the directory conf file.
*
* @since 1.9
*
* @param bool $relative True to get a path relative to the sites root.
* @return string|bool The file path. False on failure.
*
* @return string|bool The file path. False on failure.
*/
public function get_file_path( $relative = false ) {
if ( ! $this->get_server_conf() ) {
@@ -237,7 +217,7 @@ class Display {
*
* @since 1.9
*
* @return \Imagify\WriteFile\WriteFileInterface
* @return WriteFileInterface
*/
protected function get_server_conf() {
global $is_apache, $is_iis7, $is_nginx;
@@ -252,8 +232,6 @@ class Display {
$this->server_conf = new IIS();
} elseif ( $is_nginx ) {
$this->server_conf = new Nginx();
} else {
$this->server_conf = false;
}
return $this->server_conf;

View File

@@ -1,37 +1,36 @@
<?php
declare(strict_types=1);
namespace Imagify\Webp\RewriteRules;
defined( 'ABSPATH' ) || die( 'Cheatin uh?' );
use Imagify\WriteFile\AbstractIISDirConfFile;
/**
* Add and remove rewrite rules to the web.config file to display WebP images on the site.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*/
class IIS extends \Imagify\WriteFile\AbstractIISDirConfFile {
class IIS extends AbstractIISDirConfFile {
/**
* Name of the tag used as block delemiter.
*
* @var string
* @since 1.9
* @author Grégory Viguier
* @var string
* @since 1.9
*/
const TAG_NAME = 'Imagify: rewrite rules for webp';
/**
* Get unfiltered new contents to write into the file.
*
* @since 1.9
* @access protected
* @since 1.9
* @source https://github.com/igrigorik/webp-detect/blob/master/iis.config
* @author Grégory Viguier
*
* @return string
*/
protected function get_raw_new_contents() {
$extensions = $this->get_extensions_pattern();
$extensions = str_replace( '|webp', '', $extensions );
$home_root = wp_parse_url( home_url( '/' ) );
$home_root = $home_root['path'];

View File

@@ -1,52 +1,70 @@
<?php
declare(strict_types=1);
namespace Imagify\Webp\RewriteRules;
defined( 'ABSPATH' ) || die( 'Cheatin uh?' );
use Imagify\WriteFile\AbstractNginxDirConfFile;
/**
* Add and remove rewrite rules to the imagify.conf file to display WebP images on the site.
*
* @since 1.9
* @author Grégory Viguier
* @since 1.9
*/
class Nginx extends \Imagify\WriteFile\AbstractNginxDirConfFile {
class Nginx extends AbstractNginxDirConfFile {
/**
* Name of the tag used as block delemiter.
*
* @var string
* @since 1.9
* @author Grégory Viguier
* @var string
* @since 1.9
*/
const TAG_NAME = 'Imagify: rewrite rules for webp';
/**
* Get unfiltered new contents to write into the file.
*
* @since 1.9
* @access protected
* @author Grégory Viguier
* @since 1.9
*
* @return string
*/
protected function get_raw_new_contents() {
$extensions = $this->get_extensions_pattern();
$extensions = $this->get_extensions_pattern() . '|avif';
$home_root = wp_parse_url( home_url( '/' ) );
$home_root = $home_root['path'];
return trim( '
location ~* ^(' . $home_root . '.+)\.(' . $extensions . ')$ {
add_header Vary Accept;
add_header Vary Accept;
if ($http_accept ~* "webp"){
set $imwebp A;
}
if (-f $request_filename.webp) {
set $imwebp "${imwebp}B";
}
if ($imwebp = AB) {
rewrite ^(.*) $1.webp;
}
set $canavif 1;
if ($http_accept !~* "avif"){
set $canavif 0;
}
if (!-f $request_filename.avif) {
set $canavif 0;
}
if ($canavif = 1){
rewrite ^(.*) $1.avif;
break;
}
set $canwebp 1;
if ($http_accept !~* "webp"){
set $canwebp 0;
}
if (!-f $request_filename.webp) {
set $canwebp 0;
}
if ($canwebp = 1){
rewrite ^(.*) $1.webp;
break;
}
}' );
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Imagify\Webp;
use Imagify\Dependencies\League\Container\ServiceProvider\AbstractServiceProvider;
use Imagify\Webp\RewriteRules\Display as RewriteRules;
/**
* Service provider for WebP rewrite rules
*/
class ServiceProvider extends AbstractServiceProvider {
/**
* Services provided by this provider
*
* @var array
*/
protected $provides = [
'webp_display',
'webp_rewrite_rules',
];
/**
* Subscribers provided by this provider
*
* @var array
*/
public $subscribers = [
'webp_display',
'webp_rewrite_rules',
];
/**
* Registers the provided classes
*
* @return void
*/
public function register() {
$this->getContainer()->share( 'webp_display', Display::class );
$this->getContainer()->share( 'webp_rewrite_rules', RewriteRules::class );
}
/**
* Returns the subscribers array
*
* @return array
*/
public function get_subscribers() {
return $this->subscribers;
}
}