PluginProbe
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization / 4.2.16
Optimole – Optimize Images | Convert WebP & AVIF | CDN & Lazy Load | Image Optimization v4.2.16
4.2.16 4.2.15 4.2.14 4.2.13 4.2.12 4.2.11 4.2.10 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 2.5.5 2.5.6 2.5.7 3.0.0 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.11.0 3.11.1 3.11.2 All 137 releases
← All changes | inc/admin.php +2127 -381 2.5.5 → 4.2.16 View file →
@@ -9,13 +9,25 @@
9 9 * @package \Optimole\Inc
10 10 * @author Optimole <[email protected]>
11 11 */
12 12
13 +use enshrined\svgSanitize\Sanitizer;
14 +use OptimoleWP\BgOptimizer\Lazyload;
15 +use OptimoleWP\PageProfiler\Profile;
13 16 /**
14 17 * Class Optml_Admin
15 18 */
16 19 class Optml_Admin {
20 + use Optml_Normalizer;
17 21
22 + const IMAGE_DATA_COLLECTED = 'optml_image_data_collected';
23 +
24 + const IMAGE_DATA_COLLECTED_BATCH = 100;
25 +
26 + const BF_PROMO_DISMISS_KEY = 'optml_bf25_notice_dismiss';
27 +
28 + const SPC_BANNER_DISMISS_KEY = 'optml_spc_banner_dismiss';
29 +
18 30 /**
19 31 * Hold the settings object.
20 32 *
21 33 * @var Optml_Settings Settings object.
@@ -22,144 +34,529 @@
22 34 */
23 35 public $settings;
24 36
25 37 /**
38 + * Hold the plugin conflict object.
39 + *
40 + * @var Optml_Conflicting_Plugins Settings object.
41 + */
42 + public $conflicting_plugins;
43 +
44 + const NEW_USER_DEFAULTS_UPDATED = 'optml_defaults_updated';
45 + const OLD_USER_ENABLED_LD = 'optml_enabled_limit_dimensions';
46 + const OLD_USER_ENABLED_CL = 'optml_enabled_cloud_sites';
47 +
48 + const SYNC_CRON = 'optml_daily_sync';
49 + const ENRICH_CRON = 'optml_pull_image_data';
50 + /**
26 51 * Optml_Admin constructor.
27 52 */
28 53 public function __construct() {
29 - $this->settings = new Optml_Settings();
30 - add_action( 'plugin_action_links_' . plugin_basename( OPTML_BASEFILE ), array( $this, 'add_action_links' ) );
31 - add_action( 'admin_menu', array( $this, 'add_dashboard_page' ) );
32 - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ), PHP_INT_MIN );
33 - add_action( 'admin_notices', array( $this, 'add_notice' ) );
34 - add_action( 'admin_notices', array( $this, 'add_notice_upgrade' ) );
35 - add_filter( 'admin_body_class', array( $this, 'add_body_class' ) );
36 - add_action( 'optml_daily_sync', array( $this, 'daily_sync' ) );
37 - add_action( 'admin_init', array( $this, 'maybe_redirect' ) );
38 - if ( ! is_admin() && $this->settings->is_connected() && ! wp_next_scheduled( 'optml_daily_sync' ) ) {
39 - wp_schedule_event( time() + 10, 'daily', 'optml_daily_sync', array() );
54 + $this->settings = new Optml_Settings();
55 + $this->conflicting_plugins = new Optml_Conflicting_Plugins();
56 +
57 + $media_rename = new Optml_Attachment_Edit();
58 + $media_rename->init();
59 +
60 + $dashboard_widget = new Optml_Dashboard_Widget();
61 + $dashboard_widget->init();
62 +
63 + add_filter( 'plugin_action_links_' . plugin_basename( OPTML_BASEFILE ), [ $this, 'add_action_links' ] );
64 + add_action( 'admin_menu', [ $this, 'add_dashboard_page' ] );
65 + add_action( 'admin_menu', [ $this, 'add_settings_subpage' ], 99 );
66 + add_action( 'admin_head', [ $this, 'menu_icon_style' ] );
67 + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ], PHP_INT_MIN );
68 + add_action( 'admin_notices', [ $this, 'add_notice' ] );
69 + add_action( 'admin_notices', [ $this, 'add_notice_upgrade' ] );
70 + add_action( 'admin_notices', [ $this, 'add_notice_conflicts' ] );
71 + add_action( self::SYNC_CRON, [ $this, 'daily_sync' ] );
72 + add_action( 'admin_init', [ $this, 'redirect_old_dashboard' ] );
73 + add_action( 'optml_purge_image_cache', [ $this, 'purge_image_cache' ] );
74 + if ( $this->settings->is_connected() ) {
75 + add_action( 'init', [ $this, 'check_domain_change' ] );
76 + add_action( self::ENRICH_CRON, [ $this, 'pull_image_data' ] );
77 +
78 + // Backwards compatibility for older versions of WordPress < 6.0.0 requiring 3 parameters for this specific filter.
79 + $below_6_0_0 = version_compare( get_bloginfo( 'version' ), '6.0.0', '<' );
80 + if ( $below_6_0_0 ) {
81 + add_filter( 'wp_insert_attachment_data', [ $this, 'legacy_detect_image_title_changes' ], 10, 3 );
82 + } else {
83 + add_filter( 'wp_insert_attachment_data', [ $this, 'detect_image_title_changes' ], 10, 4 );
84 + }
85 +
86 + add_action( 'updated_post_meta', [ $this, 'detect_image_alt_change' ], 10, 4 );
87 + add_action( 'added_post_meta', [ $this, 'detect_image_alt_change' ], 10, 4 );
88 + add_filter( 'update_attached_file', [ $this, 'listen_update_file' ], 999, 2 );
89 + if ( ! wp_next_scheduled( self::ENRICH_CRON ) ) {
90 + wp_schedule_event( time() + 10, 'hourly', self::ENRICH_CRON );
91 + }
40 92 }
41 - add_action( 'optml_after_setup', array( $this, 'register_public_actions' ), 999999 );
93 + add_action( 'init', [ $this, 'update_default_settings' ] );
94 + add_action( 'init', [ $this, 'update_limit_dimensions' ] );
95 + add_action( 'init', [ $this, 'update_cloud_sites_default' ] );
96 + add_action( 'admin_init', [ $this, 'maybe_redirect' ] );
97 + add_action( 'admin_init', [ $this, 'init_no_script' ] );
98 + add_action( 'plugins_loaded', [ $this,'add_permission_policy' ] );
99 + if ( ! is_admin() && $this->settings->is_connected() && ! wp_next_scheduled( self::SYNC_CRON ) ) {
100 + wp_schedule_event( time() + 10, 'twicedaily', self::SYNC_CRON, [] );
101 + }
102 + add_action( 'optml_after_setup', [ $this, 'register_public_actions' ], 999999 );
42 103
104 + if ( ! function_exists( 'is_wpcom_vip' ) ) {
105 + add_filter(
106 + 'upload_mimes',
107 + [
108 + $this,
109 + 'allow_svg',
110 + ]
111 + ); // phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.upload_mimes
112 + add_filter( 'wp_handle_upload_prefilter', [ $this, 'check_svg_and_sanitize' ] );
113 + add_filter( 'wp_handle_sideload_prefilter', [ $this, 'sanitize_sideloaded_svg' ] );
114 + }
115 +
116 + add_filter( 'themeisle-sdk/survey/' . OPTML_PRODUCT_SLUG, [ $this, 'get_survey_metadata' ], 10, 2 );
117 + add_action( 'admin_init', [ $this, 'mark_user_with_offload' ] );
43 118 }
44 119
45 120 /**
46 - * Adds Optimole tag to admin bar
121 + * Add the permission policy for CH.
122 + *
123 + * @return void
47 124 */
48 - public function add_report_menu() {
49 - global $wp_admin_bar;
125 + public function add_permission_policy() {
126 + if ( ! $this->settings->is_connected() ) {
127 + return;
128 + }
129 + if ( ! $this->settings->is_enabled() ) {
130 + return;
131 + }
132 + if ( headers_sent() ) {
133 + return;
134 + }
135 + $policy = $this->get_permissions_policy();
136 + if ( empty( $policy ) ) {
137 + return;
138 + }
139 + header( sprintf( 'Permissions-Policy: %s', $policy ), false );
140 + }
50 141
51 - $wp_admin_bar->add_node(
52 - array(
53 - 'id' => 'optml_report_script',
54 - 'href' => '#',
55 - 'title' => '<span class="ab-icon"></span>Optimole ' . __( 'debugger', 'optimole-wp' ),
56 - )
57 - );
58 - $wp_admin_bar->add_menu(
59 - array(
60 - 'id' => 'optml_status',
61 - 'title' => __( 'Troubleshoot this page', 'optimole-wp' ),
62 - 'parent' => 'optml_report_script',
63 - )
64 - );
142 + /**
143 + * Build the Permissions-Policy header value based on active settings.
144 + *
145 + * @return string Comma-separated policy directives, or empty string when none apply.
146 + */
147 + public function get_permissions_policy(): string {
148 + $parts = [];
149 + $service_url = esc_url( Optml_Config::$service_url );
150 +
151 + if ( $this->settings->is_scale_enabled() ) {
152 + $parts[] = sprintf( 'ch-viewport-width=(self "%s")', $service_url );
153 + }
154 + if ( $this->settings->get( 'network_optimization' ) === 'enabled' ) {
155 + $parts[] = sprintf( 'ch-ect=(self "%s")', $service_url );
156 + }
157 + if ( $this->settings->get( 'retina_images' ) === 'enabled' ) {
158 + $parts[] = sprintf( 'ch-dpr=(self "%s")', $service_url );
159 + }
160 +
161 + return implode( ', ', $parts );
65 162 }
163 + /**
164 + * Function that purges the image cache for a specific file.
165 + *
166 + * @param string $file Path or url of the file to clear.
167 + *
168 + * @return void
169 + */
170 + public function purge_image_cache( $file ) {
171 + $basename = wp_basename( $file );
172 + $settings = new Optml_Settings();
173 + $settings->clear_cache( $basename );
174 + }
175 + /**
176 + * Listen when the file is updated and clear the cache for the file.
177 + *
178 + * @param string $file The file path.
179 + * @param int $post_id The post ID.
180 + *
181 + * @return string The file path.
182 + */
183 + public function listen_update_file( $file, $post_id ) {
184 + // Purge the image cache.
185 + do_action( 'optml_purge_image_cache', $file );
186 + return $file;
187 + }
188 + /**
189 + * Check if the file is an SVG, if so handle appropriately
190 + *
191 + * @param array $file An array of data for a single file.
192 + *
193 + * @return mixed
194 + */
195 + public function check_svg_and_sanitize( $file ) {
196 + if ( ! is_array( $file ) || ! $this->is_svg_file( $file ) ) {
197 + return $file;
198 + }
66 199
200 + if ( ! current_user_can( 'upload_files' ) ) {
201 + $file['error'] = 'Invalid';
202 + return $file;
203 + }
204 +
205 + return $this->sanitize_svg_file( $file );
206 + }
207 +
67 208 /**
68 - * Adds Optimole css to admin bar
209 + * Sanitize an SVG stored through wp_handle_sideload().
210 + *
211 + * Sideload callers authorize themselves (the REST media endpoint checks upload_files) and
212 + * background restores run without a user, so only sanitization is applied here.
213 + *
214 + * @param array<string, mixed> $file An array of data for a single file.
215 + *
216 + * @return array<string, mixed>
69 217 */
70 - public function print_report_css() {
71 - ?>
72 - <style type="text/css">
73 - li#wp-admin-bar-optml_report_script > div :hover {
74 - cursor: pointer;
75 - color: #00b9eb !important;
76 - text-decoration: underline;
218 + public function sanitize_sideloaded_svg( array $file ): array {
219 + if ( ! $this->is_svg_file( $file ) ) {
220 + return $file;
221 + }
222 +
223 + return $this->sanitize_svg_file( $file );
224 + }
225 +
226 + /**
227 + * Check whether an upload/sideload file array is an SVG.
228 + *
229 + * @param array<string, mixed> $file An array of data for a single file.
230 + *
231 + * @return bool
232 + */
233 + private function is_svg_file( array $file ): bool {
234 + // Ensure we have a proper file path before processing.
235 + if ( ! isset( $file['tmp_name'] ) ) {
236 + return false;
237 + }
238 +
239 + $file_name = isset( $file['name'] ) ? $file['name'] : '';
240 + $wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file_name );
241 +
242 + return ! empty( $wp_filetype['type'] ) && 'image/svg+xml' === $wp_filetype['type'];
243 + }
244 +
245 + /**
246 + * Sanitize the SVG temp file in place, flagging the upload when it cannot be sanitized.
247 + *
248 + * @param array<string, mixed> $file An array of data for a single file.
249 + *
250 + * @return array<string, mixed>
251 + */
252 + private function sanitize_svg_file( array $file ): array {
253 + if ( ! $this->sanitize_svg( $file['tmp_name'] ) ) {
254 + $file['error'] = 'Invalid';
255 + }
256 +
257 + return $file;
258 + }
259 +
260 + /**
261 + * Sanitize the SVG
262 + *
263 + * @param string $file Temp file path.
264 + *
265 + * @return bool|int
266 + */
267 + public function sanitize_svg( $file ) {
268 + // We can ignore the phpcs warning here as we're reading and writing to the Temp file.
269 + $dirty = file_get_contents( $file ); // phpcs:ignore
270 +
271 + // Is the SVG gzipped? If so we try and decode the string.
272 + $is_zipped = $this->is_gzipped( $dirty );
273 + if ( $is_zipped && ( ! function_exists( 'gzdecode' ) || ! function_exists( 'gzencode' ) ) ) {
274 + return false;
275 + }
276 +
277 + if ( $is_zipped ) {
278 + $dirty = gzdecode( $dirty );
279 +
280 + // If decoding fails, bail as we're not secure.
281 + if ( false === $dirty ) {
282 + return false;
77 283 }
284 + }
78 285
79 - #wpadminbar #wp-admin-bar-optml_report_script .ab-icon:before {
80 - content: "\f227";
81 - top: 3px;
286 + $sanitizer = new Sanitizer();
287 + $clean = $sanitizer->sanitize( $dirty );
288 +
289 + if ( false === $clean ) {
290 + return false;
291 + }
292 +
293 + // If we were gzipped, we need to re-zip.
294 + if ( $is_zipped ) {
295 + $clean = gzencode( $clean );
296 + }
297 +
298 + // We handle the write result below; silence the warning on I/O failure. Reading/writing the temp file is intended.
299 + $written = @file_put_contents( $file, $clean ); // phpcs:ignore WordPress.WP.AlternativeFunctions, WordPress.PHP.NoSilencedErrors
300 +
301 + // A failed write leaves the dirty upload in place, so report it as unsanitized.
302 + return is_string( $clean ) && strlen( $clean ) === $written;
303 + }
304 +
305 + /**
306 + * Check if the contents are gzipped
307 + *
308 + * @see http://www.gzip.org/zlib/rfc-gzip.html#member-format
309 + *
310 + * @param string $contents Content to check.
311 + *
312 + * @return bool
313 + */
314 + protected function is_gzipped( $contents ) {
315 + // phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found
316 + if ( function_exists( 'mb_strpos' ) ) {
317 + return 0 === mb_strpos( $contents, "\x1f" . "\x8b" . "\x08" );
318 + } else {
319 + return 0 === strpos( $contents, "\x1f" . "\x8b" . "\x08" );
320 + }
321 + // phpcs:enable
322 + }
323 +
324 + /**
325 + * Query the database for images and extract the alt/title to send them to the API
326 + *
327 + * @uses action: optml_pull_image_data
328 + */
329 + public function pull_image_data() {
330 + // Get all image attachments that are not processed
331 + $args = [
332 + 'post_type' => 'attachment',
333 + 'post_mime_type' => 'image',
334 + 'posts_per_page' => self::IMAGE_DATA_COLLECTED_BATCH,
335 + 'meta_query' => [
336 + 'relation' => 'AND',
337 + [
338 + 'key' => self::IMAGE_DATA_COLLECTED,
339 + 'compare' => 'NOT EXISTS',
340 + ],
341 + ],
342 + ];
343 + $attachments = get_posts( $args );
344 +
345 + $image_data = [];
346 +
347 + foreach ( $attachments as $attachment ) {
348 + // Get image URL, alt, and title
349 + $image_url = wp_get_attachment_url( $attachment->ID );
350 + $image_alt = get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true );
351 + $image_title = $attachment->post_title;
352 + $image_data[ $image_url ] = [];
353 +
354 + if ( ! empty( $image_alt ) ) {
355 + $image_data[ $image_url ]['alt'] = $image_alt;
82 356 }
357 + if ( ! empty( $image_title ) ) {
358 + $image_data[ $image_url ]['title'] = $image_title;
359 + }
360 + if ( empty( $image_data[ $image_url ] ) ) {
361 + unset( $image_data[ $image_url ] );
362 + }
83 363
84 - /* The Modal (background) */
85 - .optml-modal {
86 - display: none; /* Hidden by default */
87 - position: fixed; /* Stay in place */
88 - z-index: 2147483641; /* Sit on top */
89 - padding-top: 100px; /* Location of the box */
90 - left: 0;
91 - top: 0;
92 - width: 100%; /* Full width */
93 - height: 100%; /* Full height */
94 - overflow: auto; /* Enable scroll if needed */
95 - background-color: rgb(0, 0, 0); /* Fallback color */
96 - background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
364 + // Mark the image as processed
365 + update_post_meta( $attachment->ID, self::IMAGE_DATA_COLLECTED, 'yes' );
366 + }
367 +
368 + if ( ! empty( $image_data ) ) {
369 + $api = new Optml_Api();
370 + $api->call_data_enrich_api( $image_data );
371 + }
372 + }
373 +
374 + /**
375 + * Delete the processed meta from an image when the alt text is changed
376 + *
377 + * @uses action: updated_post_meta, added_post_meta
378 + */
379 + public function detect_image_alt_change( $meta_id, $post_id, $meta_key, $meta_value ) {
380 +
381 + // Check if the updated metadata is for alt text and it's an attachment
382 + if ( $meta_key === '_wp_attachment_image_alt' && get_post_type( $post_id ) === 'attachment' ) {
383 + delete_post_meta( $post_id, self::IMAGE_DATA_COLLECTED );
384 + }
385 + }
386 +
387 + /**
388 + * Delete the processed meta from an image when the title is changed
389 + *
390 + * @uses filter: wp_insert_attachment_data
391 + */
392 + public function detect_image_title_changes( $data, $postarr, $unsanitized_postarr, $update ) {
393 +
394 + // Check if it's an attachment being updated
395 + if ( $data['post_type'] !== 'attachment' ) {
396 + return $data;
397 + }
398 + if ( ! isset( $postarr['ID'] ) ) {
399 + return $data;
400 + }
401 + if ( isset( $postarr['post_title'] ) && isset( $postarr['original_post_title'] ) && $postarr['post_title'] !== $postarr['original_post_title'] ) {
402 + delete_post_meta( $postarr['ID'], self::IMAGE_DATA_COLLECTED );
403 + }
404 +
405 + return $data;
406 + }
407 +
408 + /**
409 + * Delete the processed meta from an image when the title is changed
410 + *
411 + * @uses filter: wp_insert_attachment_data
412 + */
413 + public function legacy_detect_image_title_changes( $data, $postarr, $unsanitized_postarr ) {
414 + return $this->detect_image_title_changes( $data, $postarr, $unsanitized_postarr, true );
415 + }
416 +
417 + /**
418 + * Init no_script setup value based on whether the user is connected or not.
419 + */
420 + public function init_no_script() {
421 + if ( $this->settings->is_connected() ) {
422 + $raw_settings = $this->settings->get_raw_settings();
423 + if ( ! isset( $raw_settings['no_script'] ) ) {
424 + $this->settings->update( 'no_script', 'enabled' );
97 425 }
426 + }
427 + }
98 428
99 - /* Modal Content */
100 - .optml-modal-content {
101 - background-color: #fefefe;
102 - margin: auto;
103 - padding: 20px;
104 - border: 1px solid #888;
105 - width: 80%;
429 + /**
430 + * Checks if domain has changed
431 + */
432 + public function check_domain_change() {
433 + $previous_domain = get_option( 'optml_current_domain', 0 );
434 + $site_url = $this->to_domain_hash( get_home_url() );
435 +
436 + if ( $site_url !== $previous_domain ) {
437 + update_option( 'optml_current_domain', $site_url );
438 + if ( $previous_domain !== 0 ) {
439 + $this->daily_sync();
106 440 }
441 + }
442 + }
107 443
108 - /* The Close Button */
109 - .optml-close {
110 - color: #aaaaaa;
111 - float: right;
112 - font-size: 28px;
113 - font-weight: bold;
114 - }
444 + /**
445 + * Update the limit dimensions setting to enabled if the user is new.
446 + */
447 + public function update_default_settings() {
448 + if ( get_option( self::NEW_USER_DEFAULTS_UPDATED ) === 'yes' ) {
449 + return;
450 + }
115 451
116 - .optml-modal-content ul {
117 - list-style: none;
118 - font-size: 80%;
119 - margin-top: 50px;
120 - }
452 + if ( $this->settings->is_connected() ) {
453 + update_option( self::NEW_USER_DEFAULTS_UPDATED, 'yes' );
121 454
122 - .optml-close:hover,
123 - .optml-close:focus {
124 - color: #000;
125 - text-decoration: none;
126 - cursor: pointer;
127 - }
128 - </style>
129 - <?php
455 + return;
456 + }
457 +
458 + $this->settings->update( 'limit_dimensions', 'enabled' );
459 + $this->settings->update( 'lazyload', 'enabled' );
460 + $this->settings->update( 'skip_lazyload_images', '2' );
461 + $this->settings->update( 'compression_mode', 'speed_optimized' );
462 +
463 + update_option( self::NEW_USER_DEFAULTS_UPDATED, 'yes' );
130 464 }
131 465
132 466 /**
467 + * Enable limit dimensions for old users after removing the Resize option.
468 + *
469 + * @return void
470 + */
471 + public function update_limit_dimensions() {
472 + if ( get_option( self::OLD_USER_ENABLED_LD ) === 'yes' ) {
473 + return;
474 + }
475 +
476 + // New users already have this enabled as we changed defaults.
477 + if ( ! $this->settings->is_connected() ) {
478 + return;
479 + }
480 +
481 + $this->settings->update( 'limit_dimensions', 'enabled' );
482 +
483 + update_option( self::OLD_USER_ENABLED_LD, 'yes' );
484 + }
485 +
486 + /**
487 + * Enable cloud sites and add the current site to the whitelist.
488 + *
489 + * @return void
490 + */
491 + public function update_cloud_sites_default() {
492 + if ( get_option( self::OLD_USER_ENABLED_CL ) === 'yes' ) {
493 + return;
494 + }
495 +
496 + if ( ! $this->settings->is_connected() ) {
497 + return;
498 + }
499 +
500 + // This is already enabled. Don't alter it.
501 + if ( $this->settings->get( 'cloud_images' ) === 'enabled' ) {
502 + update_option( self::OLD_USER_ENABLED_CL, 'yes' );
503 +
504 + return;
505 + }
506 +
507 + $this->settings->update( 'cloud_images', 'enabled' );
508 + $this->settings->update( 'cloud_sites', $this->settings->get_cloud_sites_whitelist_default() );
509 +
510 + update_option( self::OLD_USER_ENABLED_CL, 'yes' );
511 + }
512 +
513 + /**
133 514 * Register public actions.
134 515 */
135 516 public function register_public_actions() {
136 - add_action( 'wp_head', array( $this, 'generator' ) );
137 - add_filter( 'wp_resource_hints', array( $this, 'add_dns_prefetch' ), 10, 2 );
517 + add_action( 'wp_head', [ $this, 'generator' ] );
518 + add_filter( 'wp_resource_hints', [ $this, 'add_dns_prefetch' ], 10, 2 );
138 519
139 520 if ( Optml_Manager::should_ignore_image_tags() ) {
140 521 return;
141 522 }
142 - if ( ! is_admin() && $this->settings->get( 'report_script' ) === 'enabled' && current_user_can( 'manage_options' ) ) {
143 523
144 - add_action( 'wp_head', array( $this, 'print_report_css' ) );
145 - add_action( 'wp_before_admin_bar_render', array( $this, 'add_report_menu' ) );
146 - add_action( 'wp_enqueue_scripts', array( $this, 'add_diagnosis_script' ) );
147 - }
148 - if ( ! $this->settings->use_lazyload() ) {
524 + if ( ! $this->settings->use_lazyload()
525 + || ( $this->settings->get( 'native_lazyload' ) === 'enabled'
526 + && $this->settings->get( 'video_lazyload' ) === 'disabled'
527 + && $this->settings->get( 'bg_replacer' ) === 'disabled' ) ) {
149 528 return;
150 529 }
151 - add_action( 'wp_enqueue_scripts', array( $this, 'frontend_scripts' ) );
152 - add_action( 'wp_head', array( $this, 'inline_bootstrap_script' ) );
530 + add_action( 'wp_enqueue_scripts', [ $this, 'frontend_scripts' ] );
531 + add_action( 'wp_head', [ $this, 'inline_bootstrap_script' ] );
153 532
533 + add_filter( 'optml_additional_html_classes', [ $this, 'add_no_js_class_to_html_tag' ], 10 );
154 534 }
155 535
156 536 /**
537 + * Use filter to add additional class to html tag.
538 + *
539 + * @param array $classes The classes to be added.
540 + *
541 + * @return array
542 + */
543 + public function add_no_js_class_to_html_tag( $classes ) {
544 + // we need the space padding since some plugins might not target correctly with js there own classes
545 + // this causes some issues if they concat directly, this way we can protect against that since no matter what
546 + // there will be an extra space padding so that classes can be easily identified
547 + return array_merge( $classes, [ ' optml_no_js ' ] );
548 + }
549 +
550 + /**
157 551 * Adds script for lazyload/js replacement.
158 552 */
159 553 public function inline_bootstrap_script() {
554 + $domain = 'https://' . $this->settings->get_cdn_url() . '/js-lib';
160 555
161 - $domain = 'https://' . OPTML_JS_CDN;
556 + if ( defined( 'OPTML_JS_CDN' ) && constant( 'OPTML_JS_CDN' ) ) {
557 + $domain = 'https://' . constant( 'OPTML_JS_CDN' ) . '/js-lib';
558 + }
162 559
163 560 $min = ! OPTML_DEBUG ? '.min' : '';
164 561 $bgclasses = Optml_Lazyload_Replacer::get_lazyload_bg_classes();
165 562 $watcher_classes = Optml_Lazyload_Replacer::get_watcher_lz_classes();
@@ -170,9 +567,12 @@
170 567 $lazyload_bg_selectors = empty( $lazyload_bg_selectors ) ? '' : sprintf( '%s', implode( ', ', (array) $lazyload_bg_selectors ) );
171 568 $bgclasses = empty( $bgclasses ) ? '' : sprintf( '"%s"', implode( '","', (array) $bgclasses ) );
172 569 $watcher_classes = empty( $watcher_classes ) ? '' : sprintf( '"%s"', implode( '","', (array) $watcher_classes ) );
173 570 $default_network = ( $this->settings->get( 'network_optimization' ) === 'enabled' );
174 - $retina_ready = ! ( $this->settings->get( 'retina_images' ) === 'enabled' );
571 + $limit_dimensions = $this->settings->get( 'limit_dimensions' ) === 'enabled';
572 + $limit_width = $limit_dimensions ? $this->settings->get( 'limit_width' ) : 0;
573 + $limit_height = $limit_dimensions ? $this->settings->get( 'limit_height' ) : 0;
574 + $retina_ready = ( $this->settings->get( 'retina_images' ) !== 'enabled' );
175 575 $scale_is_disabled = ( $this->settings->get( 'scale' ) === 'enabled' );
176 576 $native_lazy_enabled = ( $this->settings->get( 'native_lazyload' ) === 'enabled' );
177 577 $output = sprintf(
178 578 '
@@ -183,29 +583,22 @@
183 583 -moz-transition: .2s filter linear, .2s opacity linear, .2s border-radius linear;
184 584 -o-transition: .2s filter linear, .2s opacity linear, .2s border-radius linear;
185 585 }
186 586 img[data-opt-src]:not([data-opt-lazy-loaded]) {
187 - opacity: .75;
188 - -webkit-filter: blur(8px);
189 - -moz-filter: blur(8px);
190 - -o-filter: blur(8px);
191 - -ms-filter: blur(8px);
192 - filter: blur(8px);
193 - transform: scale(1.04);
194 - animation: 0.1s ease-in;
195 - -webkit-transform: translate3d(0, 0, 0);
587 + opacity: .75;
588 + -webkit-filter: blur(8px);
589 + -moz-filter: blur(8px);
590 + -o-filter: blur(8px);
591 + -ms-filter: blur(8px);
592 + filter: blur(8px);
593 + transform: scale(1.04);
594 + animation: 0.1s ease-in;
595 + -webkit-transform: translate3d(0, 0, 0);
196 596 }
197 - iframe[data-opt-src]:not([data-opt-lazy-loaded]) {
198 - background-color: #ffffff;
199 - background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHdpZHRoPSI0MHB4IiBoZWlnaHQ9IjQwcHgiIHZpZXdCb3g9IjAgMCA0MCA0MCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWw6c3BhY2U9InByZXNlcnZlIiBzdHlsZT0iZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjE7IiB4PSIwcHgiIHk9IjBweCI+CiAgICA8ZGVmcz4KICAgICAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWwogICAgICAgICAgICBALXdlYmtpdC1rZXlmcmFtZXMgc3BpbiB7CiAgICAgICAgICAgICAgZnJvbSB7CiAgICAgICAgICAgICAgICAtd2Via2l0LXRyYW5zZm9ybTogcm90YXRlKDBkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIHRvIHsKICAgICAgICAgICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoLTM1OWRlZykKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgQGtleWZyYW1lcyBzcGluIHsKICAgICAgICAgICAgICBmcm9tIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKDBkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIHRvIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogcm90YXRlKC0zNTlkZWcpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIHN2ZyB7CiAgICAgICAgICAgICAgICAtd2Via2l0LXRyYW5zZm9ybS1vcmlnaW46IDUwJSA1MCU7CiAgICAgICAgICAgICAgICAtd2Via2l0LWFuaW1hdGlvbjogc3BpbiAxLjVzIGxpbmVhciBpbmZpbml0ZTsKICAgICAgICAgICAgICAgIC13ZWJraXQtYmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuOwogICAgICAgICAgICAgICAgYW5pbWF0aW9uOiBzcGluIDEuNXMgbGluZWFyIGluZmluaXRlOwogICAgICAgICAgICB9CiAgICAgICAgXV0+PC9zdHlsZT4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSJvdXRlciI+CiAgICAgICAgPGc+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMCwwQzIyLjIwNTgsMCAyMy45OTM5LDEuNzg4MTMgMjMuOTkzOSwzLjk5MzlDMjMuOTkzOSw2LjE5OTY4IDIyLjIwNTgsNy45ODc4MSAyMCw3Ljk4NzgxQzE3Ljc5NDIsNy45ODc4MSAxNi4wMDYxLDYuMTk5NjggMTYuMDA2MSwzLjk5MzlDMTYuMDA2MSwxLjc4ODEzIDE3Ljc5NDIsMCAyMCwwWiIgc3R5bGU9ImZpbGw6YmxhY2s7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNNS44NTc4Niw1Ljg1Nzg2QzcuNDE3NTgsNC4yOTgxNSA5Ljk0NjM4LDQuMjk4MTUgMTEuNTA2MSw1Ljg1Nzg2QzEzLjA2NTgsNy40MTc1OCAxMy4wNjU4LDkuOTQ2MzggMTEuNTA2MSwxMS41MDYxQzkuOTQ2MzgsMTMuMDY1OCA3LjQxNzU4LDEzLjA2NTggNS44NTc4NiwxMS41MDYxQzQuMjk4MTUsOS45NDYzOCA0LjI5ODE1LDcuNDE3NTggNS44NTc4Niw1Ljg1Nzg2WiIgc3R5bGU9ImZpbGw6cmdiKDIxMCwyMTAsMjEwKTsiLz4KICAgICAgICA8L2c+CiAgICAgICAgPGc+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0yMCwzMi4wMTIyQzIyLjIwNTgsMzIuMDEyMiAyMy45OTM5LDMzLjgwMDMgMjMuOTkzOSwzNi4wMDYxQzIzLjk5MzksMzguMjExOSAyMi4yMDU4LDQwIDIwLDQwQzE3Ljc5NDIsNDAgMTYuMDA2MSwzOC4yMTE5IDE2LjAwNjEsMzYuMDA2MUMxNi4wMDYxLDMzLjgwMDMgMTcuNzk0MiwzMi4wMTIyIDIwLDMyLjAxMjJaIiBzdHlsZT0iZmlsbDpyZ2IoMTMwLDEzMCwxMzApOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTI4LjQ5MzksMjguNDkzOUMzMC4wNTM2LDI2LjkzNDIgMzIuNTgyNCwyNi45MzQyIDM0LjE0MjEsMjguNDkzOUMzNS43MDE5LDMwLjA1MzYgMzUuNzAxOSwzMi41ODI0IDM0LjE0MjEsMzQuMTQyMUMzMi41ODI0LDM1LjcwMTkgMzAuMDUzNiwzNS43MDE5IDI4LjQ5MzksMzQuMTQyMUMyNi45MzQyLDMyLjU4MjQgMjYuOTM0MiwzMC4wNTM2IDI4LjQ5MzksMjguNDkzOVoiIHN0eWxlPSJmaWxsOnJnYigxMDEsMTAxLDEwMSk7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNMy45OTM5LDE2LjAwNjFDNi4xOTk2OCwxNi4wMDYxIDcuOTg3ODEsMTcuNzk0MiA3Ljk4NzgxLDIwQzcuOTg3ODEsMjIuMjA1OCA2LjE5OTY4LDIzLjk5MzkgMy45OTM5LDIzLjk5MzlDMS43ODgxMywyMy45OTM5IDAsMjIuMjA1OCAwLDIwQzAsMTcuNzk0MiAxLjc4ODEzLDE2LjAwNjEgMy45OTM5LDE2LjAwNjFaIiBzdHlsZT0iZmlsbDpyZ2IoMTg3LDE4NywxODcpOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTUuODU3ODYsMjguNDkzOUM3LjQxNzU4LDI2LjkzNDIgOS45NDYzOCwyNi45MzQyIDExLjUwNjEsMjguNDkzOUMxMy4wNjU4LDMwLjA1MzYgMTMuMDY1OCwzMi41ODI0IDExLjUwNjEsMzQuMTQyMUM5Ljk0NjM4LDM1LjcwMTkgNy40MTc1OCwzNS43MDE5IDUuODU3ODYsMzQuMTQyMUM0LjI5ODE1LDMyLjU4MjQgNC4yOTgxNSwzMC4wNTM2IDUuODU3ODYsMjguNDkzOVoiIHN0eWxlPSJmaWxsOnJnYigxNjQsMTY0LDE2NCk7Ii8+CiAgICAgICAgPC9nPgogICAgICAgIDxnPgogICAgICAgICAgICA8cGF0aCBkPSJNMzYuMDA2MSwxNi4wMDYxQzM4LjIxMTksMTYuMDA2MSA0MCwxNy43OTQyIDQwLDIwQzQwLDIyLjIwNTggMzguMjExOSwyMy45OTM5IDM2LjAwNjEsMjMuOTkzOUMzMy44MDAzLDIzLjk5MzkgMzIuMDEyMiwyMi4yMDU4IDMyLjAxMjIsMjBDMzIuMDEyMiwxNy43OTQyIDMzLjgwMDMsMTYuMDA2MSAzNi4wMDYxLDE2LjAwNjFaIiBzdHlsZT0iZmlsbDpyZ2IoNzQsNzQsNzQpOyIvPgogICAgICAgIDwvZz4KICAgICAgICA8Zz4KICAgICAgICAgICAgPHBhdGggZD0iTTI4LjQ5MzksNS44NTc4NkMzMC4wNTM2LDQuMjk4MTUgMzIuNTgyNCw0LjI5ODE1IDM0LjE0MjEsNS44NTc4NkMzNS43MDE5LDcuNDE3NTggMzUuNzAxOSw5Ljk0NjM4IDM0LjE0MjEsMTEuNTA2MUMzMi41ODI0LDEzLjA2NTggMzAuMDUzNiwxMy4wNjU4IDI4LjQ5MzksMTEuNTA2MUMyNi45MzQyLDkuOTQ2MzggMjYuOTM0Miw3LjQxNzU4IDI4LjQ5MzksNS44NTc4NloiIHN0eWxlPSJmaWxsOnJnYig1MCw1MCw1MCk7Ii8+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4K");
200 - background-repeat: no-repeat;
201 - background-position: 50%% 50%%;
202 - }
203 -
204 -
597 + %s
205 598 </style>
206 599 <script type="application/javascript">
207 - document.documentElement.className += " optimole_has_js";
600 + document.documentElement.className = document.documentElement.className.replace(/\boptml_no_js\b/g, "");
208 601 (function(w, d){
209 602 var b = d.getElementsByTagName("head")[0];
210 603 var s = d.createElement("script");
211 604 var v = ("IntersectionObserver" in w && "isIntersecting" in w.IntersectionObserverEntry.prototype) ? "_no_poly" : "";
@@ -220,129 +613,79 @@
220 613 watchClasses: [%s],
221 614 backgroundLazySelectors: "%s",
222 615 network_optimizations: %s,
223 616 ignoreDpr: %s,
224 - quality: %d
617 + quality: %d,
618 + maxWidth: %d,
619 + maxHeight: %d,
225 620 }
226 621 }(window, document));
227 - document.addEventListener( "DOMContentLoaded", function() {
228 -
229 - if ( "loading" in HTMLImageElement.prototype && Object.prototype.hasOwnProperty.call( optimoleData, "nativeLazyload" ) && optimoleData.nativeLazyload === true ) {
230 - const images = document.querySelectorAll(\'img[loading="lazy"]\');
231 - images.forEach( function (img) {
232 - if ( !img.dataset.optSrc) {
233 - return;
234 - }
235 - img.src = img.dataset.optSrc;
236 - delete img.dataset.optSrc;
237 - });
238 - }
239 - } );
240 622 </script>',
623 + Optml_Lazyload_Replacer::IFRAME_TEMP_COMMENT,
241 624 esc_url( $domain ),
242 625 $min,
243 - $bgclasses,
626 + wp_strip_all_tags( $bgclasses ),
244 627 $native_lazy_enabled ? 'true' : 'false',
245 628 $scale_is_disabled ? 'true' : 'false',
246 - $watcher_classes,
247 - addcslashes( $lazyload_bg_selectors, '"' ),
629 + wp_strip_all_tags( $watcher_classes ),
630 + addcslashes( wp_strip_all_tags( $lazyload_bg_selectors ), '"' ),
248 631 defined( 'OPTML_NETWORK_ON' ) && constant( 'OPTML_NETWORK_ON' ) ? ( OPTML_NETWORK_ON ? 'true' : 'false' ) : ( $default_network ? 'true' : 'false' ),
249 632 $retina_ready ? 'true' : 'false',
250 - $this->settings->get_numeric_quality()
633 + $this->settings->get_numeric_quality(),
634 + $limit_width,
635 + $limit_height
251 636 );
252 637 echo $output;
253 638 }
254 639
255 640 /**
256 - * Adds script for lazyload/js replacement.
257 - */
258 - public function add_diagnosis_script() {
259 -
260 - wp_enqueue_script( 'optml-report', OPTML_URL . 'assets/js/report_script.js' );
261 - $ignoredDomains = [ 'gravatar.com', 'instagram.com', 'fbcdn' ];
262 - $report_script = array(
263 - 'optmlCdn' => $this->settings->get_cdn_url(),
264 - 'restUrl' => untrailingslashit( rest_url( OPTML_NAMESPACE . '/v1' ) ) . '/check_redirects',
265 - 'nonce' => wp_create_nonce( 'wp_rest' ),
266 - 'ignoredDomains' => $ignoredDomains,
267 - 'wait' => __( 'We are checking the current page for any issues with optimized images ...', 'optimole-wp' ),
268 - 'description' => __( 'Optimole page analyzer', 'optimole-wp' ),
269 - );
270 - wp_localize_script( 'optml-report', 'reportScript', $report_script );
271 - }
272 -
273 - /**
274 641 * Add settings links in the plugin listing page.
275 642 *
276 - * @param array $links Old plugin links.
643 + * @param string[]|mixed $links Old plugin links.
277 644 *
278 - * @return array Altered links.
645 + * @return string[]|mixed Altered links.
279 646 */
280 - function add_action_links( $links ) {
647 + public function add_action_links( $links ) {
281 648 if ( ! is_array( $links ) ) {
282 649 return $links;
283 650 }
284 -
285 651 return array_merge(
286 652 $links,
287 - array(
288 - '<a href="' . admin_url( 'upload.php?page=optimole' ) . '">' . __( 'Settings', 'optimole-wp' ) . '</a>',
289 - )
653 + [
654 + '<a href="' . admin_url( 'admin.php?page=optimole' ) . '">' . __( 'Settings', 'optimole-wp' ) . '</a>',
655 + ]
290 656 );
291 657 }
292 658
293 659 /**
294 - * Adds optimole optin class.
295 - *
296 - * @return string Optimole class.
297 - */
298 - public function add_body_class( $classes ) {
299 -
300 - if ( ! $this->should_show_notice() ) {
301 - return $classes;
302 - }
303 -
304 - return $classes . ' optimole-optin-show ';
305 - }
306 -
307 - /**
308 660 * Check if we should show the notice.
309 661 *
310 662 * @return bool Should show?
311 663 */
312 664 public function should_show_notice() {
665 + if ( wp_doing_ajax() ) {
666 + return false;
667 + }
313 668
314 - $current_screen = get_current_screen();
315 - if ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ||
316 - is_network_admin() ||
317 - $this->settings->is_connected() ||
318 - empty( $current_screen )
319 - ) {
669 + if ( is_network_admin() ) {
320 670 return false;
321 671 }
322 672
323 - static $allowed_base = array(
324 - 'plugins' => true,
325 - 'upload' => true,
326 - 'media' => true,
327 - 'themes' => true,
328 - 'appearance_page_tgmpa-install-plugins' => true,
329 - );
673 + if ( ! current_user_can( 'manage_options' ) ) {
674 + return false;
675 + }
330 676
331 - $screen_slug = '';
332 - if ( isset( $current_screen->base ) ) {
333 - $screen_slug = $current_screen->base;
677 + if ( $this->settings->is_connected() ) {
678 + return false;
334 679 }
335 680
336 - if ( isset( $current_screen->parent_base ) ) {
337 - $screen_slug = $current_screen->parent_base;
681 + $current_screen = get_current_screen();
682 +
683 + if ( empty( $current_screen ) ) {
684 + return false;
338 685 }
339 686
340 - if ( empty( $screen_slug ) ||
341 - ( ! isset( $allowed_base[ $screen_slug ] ) ) ||
342 - ! current_user_can( 'manage_options' ) ||
343 - ( get_option( 'optml_notice_optin', 'no' ) === 'yes' )
344 - ) {
687 + if ( ( get_option( 'optml_notice_optin', 'no' ) === 'yes' ) ) {
345 688 return false;
346 689 }
347 690
348 691 return true;
@@ -355,17 +698,32 @@
355 698 if ( ! $this->should_show_upgrade() ) {
356 699 return;
357 700 }
358 701 ?>
359 - <div class="notice notice-warning optml-notice-optin">
360 - <p> <?php printf( __( 'It seems your are close to the %1$s5.0000%2$s visits limit with %3$sOptiMole%4$s for this month. You might want to check the upgrade plans for a larger quota. %5$s %6$s What happens if i exceed the quota ?%7$s We will need to deliver back your original %8$sun-optimized%9$s images which might decrease your site speed perfomance.', 'optimole-wp' ), '<strong>', '</strong>', '<strong>', '</strong>', '<br/><br/>', '<i>', '</i >', '<strong>', '</strong>' ); ?></p>
361 - <p>
362 - <a href="https://optimole.com/pricing" target="_blank" class="button button-primary"><span
363 - class="dashicons dashicons-external"></span><?php _e( 'Check upgrade plans', 'optimole-wp' ); ?>
364 - </a>
365 - <a class="button"
366 - href="<?php echo wp_nonce_url( add_query_arg( array( 'optml_hide_upg' => 'yes' ) ), 'hide_nonce', 'optml_nonce' ); ?>"><?php _e( 'I\'ve done this', 'optimole-wp' ); ?></a>
367 - </p>
702 + <div class="notice optml-notice-optin"
703 + style="background-color: #577BF9; color:white; border: none !important; display: flex; align-items: center;">
704 + <div style="margin: 1% 2%;">
705 + <img style="max-width: 100px;" src='<?php echo OPTML_URL . 'assets/img/upgrade_icon.png'; ?>'>
706 + </div>
707 + <div style="display: grid; gap: 10px;">
708 + <p style="font-size: 16px !important;">
709 + <?php
710 + printf(
711 + /* translators: 1 - visits limit */
712 + __( 'You\'re nearing your %1$s-visit monthly cap on Optimole. If you exceed it, we\'ll serve original (unoptimized) images - expect slower pages.', 'optimole-wp' ),
713 + '<strong>' . number_format_i18n( 2000 ) . '</strong>'
714 + );
715 + ?>
716 + </p>
717 + <p style="margin: 1.5% 0;">
718 + <a href="<?php echo esc_url( tsdk_translate_link( self::get_upgrade_base_link() ) ); ?>"
719 + target="_blank"
720 + style="border-radius: 4px;padding: 9px 10px;border: 2px solid #FFF;color: white;text-decoration: none;"><?php _e( 'Check upgrade plans', 'optimole-wp' ); ?>
721 + </a>
722 + <a style="padding: 2%; color: white;"
723 + href="<?php echo wp_nonce_url( add_query_arg( [ 'optml_hide_upg' => 'yes' ] ), 'hide_nonce', 'optml_nonce' ); ?>"><?php _e( 'I have already done this', 'optimole-wp' ); ?></a>
724 + </p>
725 + </div>
368 726 </div>
369 727 <?php
370 728 }
371 729
@@ -375,13 +733,13 @@
375 733 * @return bool Should we show it?
376 734 */
377 735 public function should_show_upgrade() {
378 736 $current_screen = get_current_screen();
379 - if ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ||
380 - is_network_admin() ||
381 - ! current_user_can( 'manage_options' ) ||
382 - ! $this->settings->is_connected() ||
383 - empty( $current_screen )
737 + if ( wp_doing_ajax() ||
738 + is_network_admin() ||
739 + ! current_user_can( 'manage_options' ) ||
740 + ! $this->settings->is_connected() ||
741 + empty( $current_screen )
384 742 ) {
385 743 return false;
386 744 }
387 745 if ( get_option( 'optml_notice_hide_upg', 'no' ) === 'yes' ) {
@@ -386,9 +744,9 @@
386 744 }
387 745 if ( get_option( 'optml_notice_hide_upg', 'no' ) === 'yes' ) {
388 746 return false;
389 747 }
390 - if ( isset( $current_screen->base ) && $current_screen->base !== 'upload' ) {
748 + if ( $current_screen->base !== 'upload' ) {
391 749 return false;
392 750 }
393 751 $service_data = $this->settings->get( 'service_data' );
394 752 if ( ! isset( $service_data['plan'] ) ) {
@@ -401,9 +759,9 @@
401 759 $visitors_left = isset( $service_data['visitors_left'] ) ? (int) $service_data['visitors_left'] : 0;
402 760 if ( $visitors_limit === 0 ) {
403 761 return false;
404 762 }
405 - if ( $visitors_left > 2000 ) {
763 + if ( $visitors_left > 300 ) {
406 764 return false;
407 765 }
408 766
409 767 return true;
@@ -409,8 +767,134 @@
409 767 return true;
410 768 }
411 769
412 770 /**
771 + * Build upgrade URL with current user's encoded email.
772 + *
773 + * @return string Upgrade URL.
774 + */
775 + public static function get_upgrade_base_link() {
776 + $base_url = 'https://optimole.com/upgrade';
777 + $base_url = tsdk_translate_link( $base_url );
778 + $email = self::get_account_email();
779 +
780 + if ( ! empty( $email ) ) {
781 + $base_url = add_query_arg( 'email', base64_encode( $email ), $base_url );
782 + }
783 +
784 + return $base_url;
785 + }
786 +
787 + /**
788 + * Get contact URL.
789 + *
790 + * @return string Contact URL.
791 + */
792 + public static function get_contact_base_link() {
793 + return tsdk_translate_link( 'https://optimole.com/contact' );
794 + }
795 +
796 + /**
797 + * Retrieve Optimole account email.
798 + *
799 + * @return string
800 + */
801 + private static function get_account_email() {
802 + $settings = new Optml_Settings();
803 + $service_data = $settings->get( 'service_data' );
804 +
805 + if ( isset( $service_data['user_email'] ) && is_string( $service_data['user_email'] ) ) {
806 + $email = sanitize_email( $service_data['user_email'] );
807 + if ( ! empty( $email ) ) {
808 + return $email;
809 + }
810 + }
811 +
812 + return '';
813 + }
814 +
815 + /**
816 + * CSS styles for Notice.
817 + */
818 + public static function notice_styles() {
819 + ?>
820 + <style>
821 + .optml-notice-optin:not(.has-dismiss) {
822 + background: url(" <?php echo esc_attr( OPTML_URL . '/assets/img/disconnected.svg' ); ?> ") #fff 100% 0 no-repeat;
823 + position: relative;
824 + padding: 0;
825 + }
826 +
827 + .optml-notice-optin.has-dismiss {
828 + position: relative;
829 + }
830 +
831 + .optml-notice-optin .content {
832 + background: rgba(255, 255, 255, 0.75);
833 + display: flex;
834 + align-items: center;
835 + padding: 20px;
836 + }
837 +
838 + .optml-notice-optin img {
839 + max-width: 100px;
840 + margin-right: 20px;
841 + display: none;
842 + }
843 +
844 + .optml-notice-optin .description {
845 + font-size: 14px;
846 + margin-bottom: 20px;
847 + color: #000;
848 + }
849 +
850 + .optml-notice-optin .actions {
851 + margin-top: auto;
852 + display: flex;
853 + gap: 20px;
854 + }
855 +
856 + @media screen and (min-width: 768px) {
857 + .optml-notice-optin img {
858 + display: block;
859 + }
860 + }
861 + </style>
862 + <?php
863 + }
864 +
865 + /**
866 + * JS for Notice.
867 + */
868 + public static function notice_js( $action ) {
869 + ?>
870 + <script>
871 + jQuery(document).ready(function ($) {
872 + // AJAX request to update the option value
873 + $('.optml-notice-optin button.notice-dismiss').click(function (e) {
874 + e.preventDefault();
875 +
876 + var notice = $(this).closest('.optml-notice-optin');
877 + var nonce = '<?php echo esc_attr( wp_create_nonce( $action ) ); ?>';
878 +
879 + $.ajax({
880 + url: window.ajaxurl,
881 + type: 'POST',
882 + data: {
883 + action: '<?php echo esc_attr( $action ); ?>',
884 + nonce
885 + },
886 + complete() {
887 + notice.remove();
888 + }
889 + });
890 + });
891 + });
892 + </script>
893 + <?php
894 + }
895 +
896 + /**
413 897 * Adds opt in notice.
414 898 */
415 899 public function add_notice() {
416 900 if ( ! $this->should_show_notice() ) {
@@ -415,22 +899,101 @@
415 899 public function add_notice() {
416 900 if ( ! $this->should_show_notice() ) {
417 901 return;
418 902 }
903 +
904 + self::notice_styles();
419 905 ?>
420 - <div class="notice notice-success optml-notice-optin">
421 - <p> <?php printf( __( 'Welcome to %1$sOptiMole%2$s, the easiest way to optimize your website images. Your users will enjoy a %3$sfaster%4$s website after you connect it with our service.', 'optimole-wp' ), '<strong>', '</strong>', '<strong>', '</strong>' ); ?></p>
422 - <p>
423 - <a href="<?php echo esc_url( admin_url( 'upload.php?page=optimole' ) ); ?>"
424 - class="button button-primary"><?php _e( 'Connect to OptiMole', 'optimole-wp' ); ?></a>
425 - <a class="button"
426 - href="<?php echo wp_nonce_url( add_query_arg( array( 'optml_hide_optin' => 'yes' ) ), 'hide_nonce', 'optml_nonce' ); ?>"><?php _e( 'I will do it later', 'optimole-wp' ); ?></a>
427 - </p>
906 + <div class="notice notice-info optml-notice-optin">
907 + <div class="content">
908 + <img src="<?php echo OPTML_URL . '/assets/img/logo.svg'; ?>"
909 + alt="<?php echo esc_attr__( 'Logo', 'optimole-wp' ); ?>"/>
910 +
911 + <div>
912 + <p class="notice-title"> <?php echo esc_html__( 'Finish setting up!', 'optimole-wp' ); ?></p>
913 + <p class="description">
914 + <?php
915 + printf(
916 + /* translators: 1 - opening strong tag, 2 - closing strong tag, 3 - opening strong tag, 4 - closing strong tag */
917 + __( 'Welcome to %1$sOptimole%2$s, the easiest way to optimize your website images. Your users will enjoy a %3$sfaster%4$s website after you connect it with our service.', 'optimole-wp' ),
918 + '<strong>',
919 + '</strong>',
920 + '<strong>',
921 + '</strong>'
922 + );
923 + ?>
924 + </p>
925 + <div class="actions">
926 + <a href="<?php echo esc_url( admin_url( 'admin.php?page=optimole' ) ); ?>"
927 + class="button button-primary button-hero"><?php _e( 'Connect to Optimole', 'optimole-wp' ); ?>
928 + </a>
929 + <a class="button button-secondary button-hero"
930 + href="<?php echo wp_nonce_url( add_query_arg( [ 'optml_hide_optin' => 'yes' ] ), 'hide_nonce', 'optml_nonce' ); ?>"><?php _e( 'I will do it later', 'optimole-wp' ); ?>
931 + </a>
932 + </div>
933 + </div>
934 + </div>
428 935 </div>
429 936 <?php
430 937 }
431 938
432 939 /**
940 + * Adds conflicts notice.
941 + */
942 + public function add_notice_conflicts() {
943 + if ( ! $this->conflicting_plugins->should_show_notice() ) {
944 + return;
945 + }
946 +
947 + $plugins = $this->conflicting_plugins->get_conflicting_plugins();
948 + $names = [];
949 +
950 + foreach ( $plugins as $plugin ) {
951 + $plugin_data = get_plugin_data( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin );
952 + $names[] = $plugin_data['Name'];
953 + }
954 +
955 + $names = implode( ', ', $names );
956 +
957 + self::notice_styles();
958 + self::notice_js( 'optml_dismiss_conflict_notice' );
959 + ?>
960 + <div class="notice notice-info optml-notice-optin has-dismiss">
961 + <div class="content">
962 + <img src="<?php echo OPTML_URL . '/assets/img/logo.svg'; ?>"
963 + alt="<?php echo esc_attr__( 'Logo', 'optimole-wp' ); ?>"/>
964 +
965 + <div>
966 + <p class="notice-title">
967 + <strong><?php echo esc_html__( 'Oops... Multiple image optimization plugins active', 'optimole-wp' ); ?></strong>
968 + </p>
969 + <p class="description">
970 + <?php
971 + printf(
972 + /* translators: 1 - plugin name, 2 - opening bold tag, 3 - closing bold tag */
973 + __( 'We noticed multiple image optimization plugins active on your site, which may cause issues in Optimole. We recommend using only one image optimization plugin on your site for the best results. The following plugins may cause issues in Optimole: %2$s%1$s%3$s.', 'optimole-wp' ),
974 + $names,
975 + '<strong>',
976 + '</strong>'
977 + );
978 + ?>
979 + </p>
980 + <div class="actions">
981 + <a href="<?php echo esc_url( admin_url( 'plugins.php?optimole_conflicts' ) ); ?>"
982 + class="button button-primary button-hero"><?php _e( 'Manage Plugins', 'optimole-wp' ); ?>
983 + </a>
984 + </div>
985 + </div>
986 + </div>
987 +
988 + <button type="button" class="notice-dismiss">
989 + <span class="screen-reader-text"><?php _e( 'Dismiss this notice.', 'optimole-wp' ); ?></span>
990 + </button>
991 + </div>
992 + <?php
993 + }
994 +
995 + /**
433 996 * Add style classes for lazy loading background images.
434 997 */
435 998 protected function get_background_lazy_css() {
436 999
@@ -437,16 +1000,17 @@
437 1000 $watchers = Optml_Lazyload_Replacer::get_background_lazyload_selectors();
438 1001
439 1002 $css = [];
440 1003 foreach ( $watchers as $selector ) {
441 - $css[] = 'html.optimole_has_js ' . $selector . ':not(.optml-bg-lazyloaded)';
1004 + $css[] = 'html ' . $selector . ':not(.optml-bg-lazyloaded)';
442 1005 }
443 1006 if ( empty( $css ) ) {
444 1007 return '';
445 1008 }
446 - $css = implode( ",\n", $css ) . ' { background-image: none !important; } ';
1009 + $css = implode( ",\n", $css ) . ' { background-image: none !important; }';
447 1010
448 - return strip_tags( $css );
1011 + $css = Lazyload::MARKER . "\n" . strip_tags( $css ) . "\n" . Lazyload::MARKER;
1012 + return $css;
449 1013 }
450 1014
451 1015 /**
452 1016 * Enqueue frontend scripts.
@@ -456,16 +1020,82 @@
456 1020 $bg_css = $this->get_background_lazy_css();
457 1021
458 1022 wp_register_style( 'optm_lazyload_noscript_style', false );
459 1023 wp_enqueue_style( 'optm_lazyload_noscript_style' );
460 - wp_add_inline_style( 'optm_lazyload_noscript_style', ":not(.optimole_has_js) img[data-opt-src] { display: none !important; } \n " . $bg_css );
1024 + wp_add_inline_style( 'optm_lazyload_noscript_style', "html.optml_no_js img[data-opt-src] { display: none !important; } \n " . $bg_css );
1025 +
1026 + if ( $this->settings->use_lazyload() === true ) {
1027 + wp_register_script( 'optml-print', false );
1028 + wp_enqueue_script( 'optml-print' );
1029 + $script = '
1030 + (function(w, d){
1031 + w.addEventListener("beforeprint", function(){
1032 + let images = d.getElementsByTagName( "img" );
1033 + for (let img of images) {
1034 + if ( !img.dataset.optSrc) {
1035 + continue;
1036 + }
1037 + img.src = img.dataset.optSrc;
1038 + delete img.dataset.optSrc;
1039 + }
1040 + });
1041 +
1042 + }(window, document));
1043 + ';
1044 + wp_add_inline_script( 'optml-print', $script );
1045 + }
1046 + if ( Optml_Manager::should_load_profiler() ) {
1047 + add_action(
1048 + 'wp_footer',
1049 + function () {
1050 +
1051 + print ( self::get_optimizer_script() );
1052 + }
1053 + );
1054 + }
1055 + print ( self::get_preload_links_placeholder() );
461 1056 }
1057 + /**
1058 + * Get the optimizer script.
1059 + *
1060 + * @param bool $placeholder Whether to return the placeholder or the actual script.
1061 + *
1062 + * @return string The optimizer script.
1063 + */
1064 + public static function get_optimizer_script( $placeholder = true ) {
462 1065
1066 + if ( $placeholder ) {
1067 + return '<script id=optmloptimizer></script>';
1068 + }
1069 +
1070 + return '<script async src="' . add_query_arg( 'v', OPTML_VERSION, OPTML_URL . 'assets/build/optimizer/optimizer.js' ) . '"></script><script id="optmloptimizer" >
1071 + var optimoleDataOptimizer = ' . wp_json_encode(
1072 + [
1073 + 'restUrl' => esc_js( untrailingslashit( rest_url( OPTML_NAMESPACE . '/v1' ) ) ),
1074 + 'nonce' => esc_js( wp_create_nonce( 'wp_rest' ) ),
1075 + 'missingDevices' => esc_js( Profile::PLACEHOLDER_MISSING ),
1076 + 'pageProfileId' => esc_js( Profile::PLACEHOLDER ),
1077 + 'pageProfileUrl' => esc_js( Profile::PLACEHOLDER_URL ),
1078 + '_t' => esc_js( Profile::PLACEHOLDER_TIME ),
1079 + 'hmac' => esc_js( Profile::PLACEHOLDER_HMAC ),
1080 + 'bgSelectors' => array_values( Optml_Lazyload_Replacer::get_background_lazyload_selectors() ),
1081 + ]
1082 + ) . ';
1083 + </script>';
1084 + }
1085 +
463 1086 /**
1087 + * Get the preload links placeholder.
1088 + *
1089 + * @return string The preload links placeholder.
1090 + */
1091 + public static function get_preload_links_placeholder(): string {
1092 + return '<script id=optmlpreload></script>';
1093 + }
1094 + /**
464 1095 * Maybe redirect to dashboard page.
465 1096 */
466 1097 public function maybe_redirect() {
467 -
468 1098 if ( isset( $_GET['optml_nonce'] ) && isset( $_GET['optml_hide_optin'] ) && $_GET['optml_hide_optin'] === 'yes' && wp_verify_nonce( $_GET['optml_nonce'], 'hide_nonce' ) ) {
469 1099 update_option( 'optml_notice_optin', 'yes' );
470 1100 }
471 1101
@@ -476,9 +1106,9 @@
476 1106 if ( ! get_transient( 'optml_fresh_install' ) ) {
477 1107 return;
478 1108 }
479 1109
480 - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
1110 + if ( wp_doing_ajax() ) {
481 1111 return;
482 1112 }
483 1113
484 1114 delete_transient( 'optml_fresh_install' );
@@ -489,9 +1119,10 @@
489 1119
490 1120 if ( $this->settings->is_connected() ) {
491 1121 return;
492 1122 }
493 - wp_safe_redirect( admin_url( 'upload.php?page=optimole' ) );
1123 +
1124 + wp_safe_redirect( admin_url( 'admin.php?page=optimole' ) );
494 1125 exit;
495 1126 }
496 1127
497 1128 /**
@@ -503,29 +1134,102 @@
503 1134 }
504 1135 if ( ! $this->settings->is_enabled() ) {
505 1136 return;
506 1137 }
507 - echo '<meta name="generator" content="Optimole ' . esc_attr( OPTML_VERSION ) . '">';
1138 +
1139 + $hints = $this->get_accept_ch_hints();
1140 + if ( empty( $hints ) ) {
1141 + return;
1142 + }
1143 + echo sprintf( '<meta http-equiv="Accept-CH" content="%s" />', esc_attr( $hints ) );
508 1144 }
509 1145
510 1146 /**
1147 + * Build the Accept-CH meta content value based on active settings.
1148 + *
1149 + * Mirrors the directives used in get_permissions_policy() so both
1150 + * the Permissions-Policy header and the Accept-CH meta stay in sync.
1151 + *
1152 + * @return string Comma-separated hint tokens, or empty string when none apply.
1153 + */
1154 + public function get_accept_ch_hints(): string {
1155 + $hints = [];
1156 +
1157 + if ( $this->settings->is_scale_enabled() ) {
1158 + $hints[] = 'Viewport-Width';
1159 + }
1160 + if ( $this->settings->get( 'network_optimization' ) === 'enabled' ) {
1161 + $hints[] = 'ECT';
1162 + }
1163 + if ( $this->settings->get( 'retina_images' ) === 'enabled' ) {
1164 + $hints[] = 'DPR';
1165 + }
1166 +
1167 + return implode( ', ', $hints );
1168 + }
1169 +
1170 + /**
511 1171 * Update daily the quota routine.
512 1172 */
513 - function daily_sync() {
1173 + public function daily_sync() {
514 1174
515 - $api_key = $this->settings->get( 'api_key' );
1175 + $api_key = $this->settings->get( 'api_key' );
1176 + $service_data = $this->settings->get( 'service_data' );
1177 + $application = '';
1178 +
1179 + if ( isset( $service_data['cdn_key'] ) ) {
1180 + $application = $service_data['cdn_key'];
1181 + }
1182 +
516 1183 if ( empty( $api_key ) ) {
517 1184 return;
518 1185 }
519 1186
520 1187 $request = new Optml_Api();
521 - $data = $request->get_user_data( $api_key );
1188 + $data = $request->get_user_data( $api_key, $application );
522 1189 if ( $data === false || is_wp_error( $data ) ) {
523 1190 return;
524 1191 }
1192 + if ( $data === 'disconnect' ) {
1193 + $settings = new Optml_Settings();
1194 + $settings->reset();
525 1195
1196 + return;
1197 + }
1198 +
1199 + add_filter( 'optml_dont_trigger_settings_updated', '__return_true' );
526 1200 $this->settings->update( 'service_data', $data );
527 1201
1202 + if ( isset( $data['extra_visits'] ) ) {
1203 + $this->settings->update_frontend_banner_from_remote( $data['extra_visits'] );
1204 + }
1205 +
1206 + // Here the account got deactivated, in this case we check if the user is using offloaded images and we roll them back.
1207 + $should_revert_offloading = isset( $data['status'] ) && $data['status'] === 'inactive';
1208 +
1209 + // The user is now on a plan without offloading and the grace period is over.
1210 + if ( isset( $data['should_revert_offload'] ) && (bool) $data['should_revert_offload'] ) {
1211 + $should_revert_offloading = true;
1212 + }
1213 +
1214 + if ( $should_revert_offloading ) {
1215 + // We check if the user has images offloaded.
1216 + if ( $this->settings->get( 'offload_media' ) === 'disabled' ) {
1217 + return;
1218 + }
1219 +
1220 + $in_progress = $this->settings->get( 'offloading_status' ) !== 'disabled';
1221 + // We check if there is an in progress transfer, we stop it.
1222 + if ( $in_progress ) {
1223 + $this->settings->update( 'offloading_status', 'disabled' );
1224 + }
1225 + $this->settings->update( 'rollback_status', 'enabled' );
1226 + // We start the rollback process.
1227 + Optml_Logger::instance()->add_log( 'rollback_images', 'Account deactivated, starting rollback.' );
1228 + Optml_Media_Offload::move_images( 'rollback_images', false );
1229 + $this->settings->update( 'offload_media', 'disabled' );
1230 + }
1231 + remove_filter( 'optml_dont_trigger_settings_updated', '__return_true' );
528 1232 }
529 1233
530 1234 /**
531 1235 * Adds cdn url for prefetch.
@@ -536,9 +1240,9 @@
536 1240 * @return array Altered hints array.
537 1241 */
538 1242 public function add_dns_prefetch( $hints, $relation_type ) {
539 1243 if ( 'dns-prefetch' !== $relation_type &&
540 - 'preconnect' !== $relation_type
1244 + 'preconnect' !== $relation_type
541 1245 ) {
542 1246 return $hints;
543 1247 }
544 1248 if ( ! $this->settings->is_connected() ) {
@@ -548,12 +1252,8 @@
548 1252 return $hints;
549 1253 }
550 1254 $hints[] = sprintf( 'https://%s', $this->settings->get_cdn_url() );
551 1255
552 - if ( ! $this->settings->use_lazyload() && ! Optml_Manager::should_ignore_image_tags() ) {
553 - $hints[] = sprintf( 'https://%s', OPTML_JS_CDN );
554 - }
555 -
556 1256 return $hints;
557 1257 }
558 1258
559 1259 /**
@@ -562,12 +1262,106 @@
562 1262 public function add_dashboard_page() {
563 1263 if ( defined( 'OPTIOMLE_HIDE_ADMIN_AREA' ) && OPTIOMLE_HIDE_ADMIN_AREA ) {
564 1264 return;
565 1265 }
566 - add_media_page( 'Optimole', 'Optimole', 'manage_options', 'optimole', array( $this, 'render_dashboard_page' ) );
1266 +
1267 + add_menu_page(
1268 + 'Optimole',
1269 + 'Optimole',
1270 + 'manage_options',
1271 + 'optimole',
1272 + [ $this, 'render_dashboard_page' ],
1273 + OPTML_URL . 'assets/img/logo.svg',
1274 + 11
1275 + );
567 1276 }
568 1277
569 1278 /**
1279 + * Add menu icon style.
1280 + *
1281 + * @return void
1282 + */
1283 + public function menu_icon_style() {
1284 + $conflicts_count = $this->get_active_notices_count();
1285 + $badge_html = '';
1286 +
1287 + if ( $conflicts_count > 0 ) {
1288 + $badge_html = sprintf(
1289 + '<style>
1290 + #toplevel_page_optimole .wp-menu-name::after {
1291 + content: "%d";
1292 + background: #d63638;
1293 + border-radius: 10px;
1294 + padding: 1.5px 1px;
1295 + font-size: 11px;
1296 + margin-left: 5px;
1297 + min-width: 18px;
1298 + display: inline-block;
1299 + text-align: center;
1300 + }
1301 + </style>',
1302 + $conflicts_count
1303 + );
1304 + }
1305 +
1306 + echo '<style>#toplevel_page_optimole img{ max-width:22px;padding-top:6px!important;opacity:.9!important;} #toplevel_page_optimole li.wp-first-item{ display:none }</style>' . $badge_html;
1307 + }
1308 +
1309 + /**
1310 + * Add the settings page.
1311 + *
1312 + * We do it with another priority as it goes above the cloud library otherwise.
1313 + */
1314 + public function add_settings_subpage() {
1315 + if ( defined( 'OPTIOMLE_HIDE_ADMIN_AREA' ) && OPTIOMLE_HIDE_ADMIN_AREA ) {
1316 + return;
1317 + }
1318 +
1319 + if ( ! $this->settings->is_connected() ) {
1320 + return;
1321 + }
1322 +
1323 + add_submenu_page(
1324 + 'optimole',
1325 + __( 'Settings', 'optimole-wp' ),
1326 + __( 'Settings', 'optimole-wp' ),
1327 + 'manage_options',
1328 + 'optimole#settings',
1329 + [ $this, 'render_dashboard_page' ]
1330 + );
1331 + }
1332 +
1333 +
1334 + /**
1335 + * Redirect old dashboard.
1336 + *
1337 + * @return void
1338 + */
1339 + public function redirect_old_dashboard() {
1340 + if ( ! is_admin() ) {
1341 + return;
1342 + }
1343 +
1344 + global $pagenow;
1345 +
1346 + if ( $pagenow !== 'upload.php' ) {
1347 + return;
1348 + }
1349 +
1350 + if ( ! isset( $_GET['page'] ) ) {
1351 + return;
1352 + }
1353 +
1354 + if ( $_GET['page'] !== 'optimole' ) {
1355 + return;
1356 + }
1357 +
1358 + wp_safe_redirect( admin_url( 'admin.php?page=optimole' ) );
1359 +
1360 + exit;
1361 + }
1362 +
1363 + /**
570 1364 * Render dashboard page.
571 1365 */
572 1366 public function render_dashboard_page() {
573 1367 if ( get_option( 'optml_notice_optin', 'no' ) !== 'yes' ) {
@@ -573,12 +1367,14 @@
573 1367 if ( get_option( 'optml_notice_optin', 'no' ) !== 'yes' ) {
574 1368 update_option( 'optml_notice_optin', 'yes' );
575 1369 }
576 1370 ?>
577 -
578 - <div id="optimole-app">
579 - <app></app>
580 - </div>
1371 + <style>
1372 + .notice:not(.optml-notice-optin) {
1373 + display: none !important;
1374 + }
1375 + </style>
1376 + <div id="optimole-app"></div>
581 1377 <?php
582 1378 }
583 1379
584 1380 /**
@@ -591,14 +1387,61 @@
591 1387 $current_screen = get_current_screen();
592 1388 if ( ! isset( $current_screen->id ) ) {
593 1389 return;
594 1390 }
595 - if ( $current_screen->id != 'media_page_optimole' ) {
1391 + if ( $current_screen->id === 'upload' ) {
1392 + wp_enqueue_script( OPTML_NAMESPACE . '-media-admin', OPTML_URL . 'assets/js/media.js', [], OPTML_VERSION );
1393 + wp_localize_script(
1394 + OPTML_NAMESPACE . '-media-admin',
1395 + 'optimoleMediaListing',
1396 + [
1397 + 'rest_url' => rest_url( OPTML_NAMESPACE . '/v1/move_image' ),
1398 + 'nonce' => wp_create_nonce( 'wp_rest' ),
1399 + ]
1400 + );
1401 +
1402 + wp_register_style( 'optml_media_admin_style', false );
1403 + wp_add_inline_style(
1404 + 'optml_media_admin_style',
1405 + '
1406 + .is-loading {
1407 + pointer-events: none;
1408 + opacity: 0.5;
1409 + display: inline-block;
1410 + }
1411 + .spinner {
1412 + margin-top: 0px;
1413 + }
1414 + '
1415 + );
1416 + wp_enqueue_style( 'optml_media_admin_style' );
1417 + }
1418 + if ( $current_screen->id !== 'toplevel_page_optimole' ) {
596 1419 return;
597 1420 }
598 - wp_register_script( OPTML_NAMESPACE . '-admin', OPTML_URL . 'assets/js/bundle' . ( ! OPTML_DEBUG ? '.min' : '' ) . '.js', array(), OPTML_VERSION );
1421 +
1422 + $asset_file = include OPTML_PATH . 'assets/build/dashboard/index.asset.php';
1423 + wp_register_script(
1424 + OPTML_NAMESPACE . '-admin',
1425 + OPTML_URL . 'assets/build/dashboard/index.js',
1426 + array_merge( $asset_file['dependencies'], [ 'updates' ] ),
1427 + $asset_file['version'],
1428 + true
1429 + );
1430 +
599 1431 wp_localize_script( OPTML_NAMESPACE . '-admin', 'optimoleDashboardApp', $this->localize_dashboard_app() );
600 1432 wp_enqueue_script( OPTML_NAMESPACE . '-admin' );
1433 +
1434 + wp_enqueue_style(
1435 + OPTML_NAMESPACE . '-admin',
1436 + OPTML_URL . 'assets/build/dashboard/style-index.css',
1437 + [
1438 + 'wp-components',
1439 + ],
1440 + $asset_file['version']
1441 + );
1442 +
1443 + do_action( 'themeisle_internal_page', OPTML_PRODUCT_SLUG, 'dashboard' );
601 1444 }
602 1445
603 1446 /**
604 1447 * Localize the dashboard app.
@@ -606,31 +1449,284 @@
606 1449 * @codeCoverageIgnore
607 1450 * @return array
608 1451 */
609 1452 private function localize_dashboard_app() {
610 - $api_key = $this->settings->get( 'api_key' );
611 - $service_data = $this->settings->get( 'service_data' );
612 - $user = get_userdata( get_current_user_id() );
613 1453
614 - return array(
615 - 'strings' => $this->get_dashboard_strings(),
616 - 'assets_url' => OPTML_URL . 'assets/',
617 - 'connection_status' => empty( $service_data ) ? 'no' : 'yes',
618 - 'user_status' => isset( $service_data['status'] ) && $service_data['status'] === 'inactive' ? 'inactive' : 'active',
619 - 'api_key' => $api_key,
620 - 'root' => untrailingslashit( rest_url( OPTML_NAMESPACE . '/v1' ) ),
621 - 'nonce' => wp_create_nonce( 'wp_rest' ),
622 - 'user_data' => $service_data,
623 - 'remove_latest_images' => defined( 'OPTML_REMOVE_LATEST_IMAGES' ) && constant( 'OPTML_REMOVE_LATEST_IMAGES' ) ? ( OPTML_REMOVE_LATEST_IMAGES ? 'yes' : 'no' ) : 'no',
624 - 'current_user' => array(
1454 + global $wp_version;
1455 + $is_offload_media_available = 'no';
1456 + if ( version_compare( $wp_version, '5.3', '>=' ) ) {
1457 + $is_offload_media_available = 'yes';
1458 + }
1459 + $api_key = $this->settings->get( 'api_key' );
1460 + $service_data = $this->settings->get( 'service_data' );
1461 + if ( ! is_array( $service_data ) ) {
1462 + $service_data = [];
1463 + }
1464 + $user = get_userdata( get_current_user_id() );
1465 + $user_status = 'inactive';
1466 + $auto_connect = get_option( Optml_Settings::OPTML_USER_EMAIL, 'no' );
1467 + $available_apps = isset( $service_data['available_apps'] ) ? $service_data['available_apps'] : null;
1468 + if ( isset( $service_data['cdn_key'] ) && $available_apps !== null ) {
1469 + foreach ( $service_data['available_apps'] as $app ) {
1470 + if ( isset( $app['key'] ) && $app['key'] === $service_data['cdn_key'] && isset( $app['status'] ) && $app['status'] === 'active' ) {
1471 + $user_status = 'active';
1472 + }
1473 + }
1474 + }
1475 + $routes = [];
1476 + foreach ( Optml_Rest::$rest_routes as $route_type ) {
1477 + foreach ( $route_type as $route => $details ) {
1478 + $routes[ $route ] = OPTML_NAMESPACE . '/v1/' . $route;
1479 + }
1480 + }
1481 + // Disable this for now, we need a better way to detect it.
1482 + // $cron_disabled = apply_filters( 'optml_offload_wp_cron_disabled', defined( 'DISABLE_WP_CRON' ) && constant( 'DISABLE_WP_CRON' ) === true );
1483 +
1484 + $language = get_user_locale();
1485 + $available_languages = [
1486 + 'de_DE' => 'de',
1487 + 'de_DE_formal' => 'de',
1488 + ];
1489 + $lang_code = isset( $available_languages[ $language ] ) ? 'de' : 'en';
1490 +
1491 + $site_settings = $this->settings->get_site_settings();
1492 + if ( ! empty( $service_data ) ) {
1493 + $service_data['domain_dns'] = $this->settings->get_cdn_url();
1494 + }
1495 + if ( isset( $service_data['renews_on'] ) ) {
1496 + $service_data['renews_on_formatted'] = date_i18n( get_option( 'date_format' ), $service_data['renews_on'] );
1497 +
1498 + }
1499 +
1500 + $user_plan = isset( $service_data['plan'] ) ? $service_data['plan'] : 'free';
1501 +
1502 + return [
1503 + 'strings' => $this->get_dashboard_strings(),
1504 + 'assets_url' => OPTML_URL . 'assets/',
1505 + 'dam_url' => 'admin.php?page=optimole-dam',
1506 + 'connection_status' => empty( $service_data ) ? 'no' : 'yes',
1507 + 'has_application' => isset( $service_data['app_count'] ) && $service_data['app_count'] >= 1 ? 'yes' : 'no',
1508 + 'user_status' => $user_status,
1509 + 'available_apps' => $available_apps,
1510 + 'api_key' => $api_key,
1511 + 'routes' => $routes,
1512 + 'language' => $lang_code,
1513 + 'nonce' => wp_create_nonce( 'wp_rest' ),
1514 + 'user_data' => $service_data,
1515 + 'remove_latest_images' => defined( 'OPTML_REMOVE_LATEST_IMAGES' ) && constant( 'OPTML_REMOVE_LATEST_IMAGES' ) ? ( OPTML_REMOVE_LATEST_IMAGES ? 'yes' : 'no' ) : 'no',
1516 + 'current_user' => [
625 1517 'email' => $user->user_email,
1518 + ],
1519 + 'site_settings' => $site_settings,
1520 + 'offload_limit' => $this->settings->get( 'offload_limit' ),
1521 + 'home_url' => home_url(),
1522 + 'optimoleHome' => tsdk_translate_link( 'https://optimole.com/' ),
1523 + 'optimoleDashHome' => tsdk_translate_link( 'https://dashboard.optimole.com/', 'query' ),
1524 + 'optimoleDashBilling' => tsdk_translate_link( 'https://dashboard.optimole.com/settings/billing', 'query' ),
1525 + 'optimoleDashMetrics' => tsdk_translate_link( tsdk_utmify( 'https://dashboard.optimole.com/metrics', 'wp-plugin', 'shortcut' ) ),
1526 + 'offload_upgrade_url' => tsdk_translate_link( tsdk_utmify( 'https://optimole.com/pricing/', 'offload' ) ),
1527 + 'days_since_install' => round( ( time() - get_option( 'optimole_wp_install', 0 ) ) / DAY_IN_SECONDS ),
1528 + 'is_offload_media_available' => $is_offload_media_available,
1529 + 'auto_connect' => $auto_connect,
1530 + 'cron_disabled' => false, // $cron_disabled && ! function_exists( 'as_schedule_single_action' ),
1531 + 'submenu_links' => [
1532 + [
1533 + 'href' => 'admin.php?page=optimole#settings',
1534 + 'text' => __( 'Settings', 'optimole-wp' ),
1535 + 'hash' => '#settings',
1536 + ],
1537 + ],
1538 + 'bf_notices' => $this->get_bf_notices( $user_plan ),
1539 + 'spc_banner' => $this->get_spc_banner(),
1540 + 'show_exceed_plan_quota_notice' => $this->should_show_exceed_quota_warning(),
1541 + 'show_free_user_with_offload_notice' => get_option( 'optml_has_offloading_enabled_on_upgrade', 'no' ),
1542 + 'report_issue_url' => add_query_arg(
1543 + [
1544 + 'utm_source' => 'plugin',
1545 + 'utm_medium' => 'wp_dashboard',
1546 + 'utm_campaign' => 'report_issue',
1547 + 'contact_website' => home_url(),
1548 + ],
1549 + self::get_contact_base_link()
626 1550 ),
627 - 'site_settings' => $this->settings->get_site_settings(),
628 - 'home_url' => home_url(),
1551 + 'upgrade_url' => esc_url( self::get_upgrade_base_link() ),
1552 + ];
1553 + }
1554 +
1555 + /**
1556 + * Get the SPC banner data.
1557 + *
1558 + * @return array|null
1559 + */
1560 + private function get_spc_banner() {
1561 + // User can't dismiss notice or install plugins.
1562 + if ( ! current_user_can( 'manage_options' ) || ! current_user_can( 'install_plugins' ) ) {
1563 + return null;
1564 + }
1565 +
1566 + // User has dismissed the notice.
1567 + if ( get_option( self::SPC_BANNER_DISMISS_KEY ) === 'yes' ) {
1568 + return null;
1569 + }
1570 +
1571 + // User has installed the pro plugin.
1572 + if ( defined( 'SPC_PRO_PATH' ) ) {
1573 + return null;
1574 + }
1575 +
1576 + // User has installed the plugin.
1577 + if ( defined( 'SPC_PATH' ) ) {
1578 + return null;
1579 + }
1580 +
1581 + return [
1582 + 'activate_url' => $this->get_spc_activate_url(),
1583 + 'status' => $this->get_spc_status(),
1584 + 'banner_dismiss_key' => self::SPC_BANNER_DISMISS_KEY,
1585 + 'i18n' => [
1586 + 'dismiss' => __( 'Dismiss', 'optimole-wp' ),
1587 + 'title' => __( 'Pair Optimole with Super Page Cache', 'optimole-wp' ),
1588 + 'byline' => __( 'Improve your Core Web Vitals with our recommended caching solution', 'optimole-wp' ),
1589 + 'features' => [
1590 + __( 'Edge Caching (with Cloudflare free plan)', 'optimole-wp' ),
1591 + __( 'Works with Optimole optimization', 'optimole-wp' ),
1592 + __( 'Lightning Speed Disk Caching', 'optimole-wp' ),
1593 + ],
1594 + 'cta' => __( 'Get Super Page Cache', 'optimole-wp' ),
1595 + 'activate' => __( 'Activate Super Page Cache', 'optimole-wp' ),
1596 + 'installing' => __( 'Installing Super Page Cache...', 'optimole-wp' ),
1597 + 'activating' => __( 'Activating Super Page Cache...', 'optimole-wp' ),
1598 + 'activated' => __( 'Super Page Cache is active!', 'optimole-wp' ),
1599 + 'error' => __( 'Something went wrong. Please refresh the page and try again.', 'optimole-wp' ),
1600 + ],
1601 + ];
1602 + }
1603 +
1604 + /**
1605 + * Get the SPC activate URL.
1606 + *
1607 + * @return string
1608 + */
1609 + private function get_spc_activate_url() {
1610 + return add_query_arg(
1611 + [
1612 + 'plugin_status' => 'all',
1613 + 'paged' => 1,
1614 + 'action' => 'activate',
1615 + 'plugin' => rawurlencode( 'wp-cloudflare-page-cache/wp-cloudflare-super-page-cache.php' ),
1616 + '_wpnonce' => wp_create_nonce( 'activate-plugin_wp-cloudflare-page-cache/wp-cloudflare-super-page-cache.php' ),
1617 + ],
1618 + admin_url( 'plugins.php' )
629 1619 );
630 1620 }
631 1621
632 1622 /**
1623 + * Get the SPC status.
1624 + *
1625 + * @return string
1626 + */
1627 + private function get_spc_status() {
1628 + if ( is_plugin_active( 'wp-cloudflare-page-cache/wp-cloudflare-super-page-cache.php' ) ) {
1629 + return 'active';
1630 + }
1631 +
1632 + if ( file_exists( WP_PLUGIN_DIR . '/wp-cloudflare-page-cache/wp-cloudflare-super-page-cache.php' ) ) {
1633 + return 'installed';
1634 + }
1635 +
1636 + return 'not-installed';
1637 + }
1638 +
1639 + /**
1640 + * Get the black friday notices.
1641 + *
1642 + * @param non-empty-string $user_plan User plan.
1643 + *
1644 + * @return array<string, mixed> Notice data.
1645 + */
1646 + public function get_bf_notices( $user_plan ) {
1647 +
1648 + $notices = [];
1649 + $has_free_plan = 'free' === $user_plan;
1650 +
1651 + if ( ! $has_free_plan ) {
1652 + return $notices;
1653 + }
1654 +
1655 + /**
1656 + * The black friday sale period flag.
1657 + *
1658 + * @var bool $is_black_friday Whether is black friday sale period or not.
1659 + */
1660 + $is_black_friday = apply_filters( 'themeisle_sdk_is_black_friday_sale', false );
1661 +
1662 + if ( ! $is_black_friday ) {
1663 + return $notices;
1664 + }
1665 +
1666 + /**
1667 + * The current date.
1668 + *
1669 + * @var DateTime $now Current date.
1670 + */
1671 + $now = apply_filters( 'themeisle_sdk_current_date', new \DateTime( 'now' ) );
1672 + $current_year = $now->format( 'Y' );
1673 +
1674 + $black_friday_day = new \DateTime( "last Friday of November $current_year" );
1675 + $sale_start = clone $black_friday_day;
1676 + $sale_start->modify( 'monday this week' );
1677 + $sale_start->setTime( 0, 0 );
1678 +
1679 + $end = clone $sale_start;
1680 + $end->modify( '+7 days' );
1681 + $end->setTime( 23, 59, 59 );
1682 +
1683 + $promo_code = 'BFCM2525';
1684 +
1685 + $notices['sidebar'] = [
1686 + 'title' => sprintf(
1687 + '<span class="text-promo-orange">%1$s:</span> %2$s - %3$s',
1688 + __( 'Private Sale', 'optimole-wp' ),
1689 + wp_date( 'j M', $sale_start->getTimestamp() ),
1690 + wp_date( 'j M', $end->getTimestamp() )
1691 + ),
1692 + 'subtitle' => sprintf(
1693 + /* translators: %1$s is the promo code, %2$s is the discount amount ('25 off') */
1694 + __( 'Use code %1$s for %2$s on yearly plans.', 'optimole-wp' ),
1695 + '<span class="border-b border-0 border-white border-dashed text-promo-orange">' . $promo_code . '</span>',
1696 + '<span class="text-promo-orange uppercase">' . __( '25% discount', 'optimole-wp' ) . '</span>'
1697 + ),
1698 + 'cta_link' => esc_url_raw( tsdk_utmify( tsdk_translate_link( self::get_upgrade_base_link() ), 'bfcm25', 'sidebarnotice' ) ),
1699 + ];
1700 +
1701 + if ( get_option( self::BF_PROMO_DISMISS_KEY ) === 'yes' ) {
1702 + return $notices;
1703 + }
1704 +
1705 + $message = sprintf(
1706 + // translators: %1$s is the promo code, %2$s is the discount amount ('25% off')
1707 + __( 'Use coupon code %1$s for an instant %2$s on Optimole yearly plans', 'optimole-wp' ),
1708 + '<span class="border-b border-0 border-white border-dashed text-promo-orange">' . $promo_code . '</span>',
1709 + '<span class="text-promo-orange uppercase">' . __( '25% discount', 'optimole-wp' ) . '</span>'
1710 + );
1711 + $cta_link = esc_url_raw( tsdk_utmify( self::get_upgrade_base_link(), 'bfcm25', 'notice' ) );
1712 + $cta_text = __( 'Claim now', 'optimole-wp' );
1713 +
1714 + $notices['banner'] = [
1715 + /* translators: number of days left */
1716 + 'urgency' => sprintf( __( 'Hurry up! only %s left', 'optimole-wp' ), human_time_diff( $end->getTimestamp(), $now->getTimestamp() ) ),
1717 + /* translators: private sale */
1718 + 'title' => sprintf( __( 'Black Friday %s', 'optimole-wp' ), '<span class="text-promo-orange">' . __( 'private sale', 'optimole-wp' ) . '</span>' ),
1719 + 'subtitle' => $message,
1720 + 'cta_text' => $cta_text,
1721 + 'cta_link' => $cta_link,
1722 + 'dismiss_key' => self::BF_PROMO_DISMISS_KEY,
1723 + ];
1724 +
1725 + return $notices;
1726 + }
1727 +
1728 + /**
633 1729 * Get all dashboard strings.
634 1730 *
635 1731 * @codeCoverageIgnore
636 1732 * @return array
@@ -635,9 +1731,9 @@
635 1731 * @codeCoverageIgnore
636 1732 * @return array
637 1733 */
638 1734 private function get_dashboard_strings() {
639 - return array(
1735 + return [
640 1736 'optimole' => 'Optimole',
641 1737 'version' => OPTML_VERSION,
642 1738 'terms_menu' => __( 'Terms', 'optimole-wp' ),
643 1739 'privacy_menu' => __( 'Privacy', 'optimole-wp' ),
@@ -642,56 +1738,127 @@
642 1738 'terms_menu' => __( 'Terms', 'optimole-wp' ),
643 1739 'privacy_menu' => __( 'Privacy', 'optimole-wp' ),
644 1740 'testdrive_menu' => __( 'Test Optimole', 'optimole-wp' ),
645 1741 'service_details' => __( 'Image optimization service', 'optimole-wp' ),
646 - 'connect_btn' => __( 'Connect to OptiMole Service', 'optimole-wp' ),
1742 + 'dashboard_title' => __( 'Image Optimization Overview', 'optimole-wp' ),
1743 + 'banner_title' => __( 'All images are automatically optimized!', 'optimole-wp' ),
1744 + 'banner_description' => __( 'Optimole is handling all your images in real-time with our CloudFront CDN (450+ locations worldwide)', 'optimole-wp' ),
1745 + 'quick_action_title' => __( 'Quick Actions', 'optimole-wp' ),
1746 + 'connect_btn' => __( 'Connect to Optimole', 'optimole-wp' ),
647 1747 'disconnect_btn' => __( 'Disconnect', 'optimole-wp' ),
648 - 'refresh_stats_cta' => __( 'Refresh stats', 'optimole-wp' ),
649 - 'updating_stats_cta' => __( 'Updating stats', 'optimole-wp' ),
1748 + 'select' => __( 'Select', 'optimole-wp' ),
1749 + 'your_domain' => __( 'your domain', 'optimole-wp' ),
1750 + 'add_api' => __( 'Add your API Key', 'optimole-wp' ),
1751 + 'your_api_key' => __( 'Your API Key', 'optimole-wp' ),
1752 + 'looking_for_api_key' => __( 'LOOKING FOR YOUR API KEY?', 'optimole-wp' ),
1753 + 'refresh_stats_cta' => __( 'Refresh Stats', 'optimole-wp' ),
1754 + 'updating_stats_cta' => __( 'UPDATING STATS', 'optimole-wp' ),
650 1755 'api_key_placeholder' => __( 'API Key', 'optimole-wp' ),
651 - 'account_needed_heading' => __( 'Sign-up for API key', 'optimole-wp' ),
1756 + 'account_needed_heading' => __( 'Supercharge Your WordPress Images in 60 Seconds', 'optimole-wp' ),
1757 + 'account_needed_sub_heading' => __( 'Stop sacrificing image quality for page speed. Optimole delivers both.', 'optimole-wp' ),
1758 + 'account_needed_trust_badge' => __( 'TRUSTED BY 200,000+ HAPPY USERS', 'optimole-wp' ),
1759 + 'account_needed_setup_time' => __( 'Setup is instant - just click connect', 'optimole-wp' ),
1760 + 'account_needed_benefits_toggle' => __( 'See what you\'ll get', 'optimole-wp' ),
652 1761 'invalid_key' => __( 'Invalid API Key', 'optimole-wp' ),
653 - 'status' => __( 'Status', 'optimole-wp' ),
1762 + 'keep_connected' => __( 'Ok, keep me connected', 'optimole-wp' ),
1763 + 'cloud_library' => __( 'Cloud Library', 'optimole-wp' ),
1764 + 'image_storage' => __( 'Image Storage', 'optimole-wp' ),
1765 + 'word' => __( 'word', 'optimole-wp' ),
1766 + 'path' => __( 'path', 'optimole-wp' ),
1767 + 'disconnect_title' => __( 'You are about to disconnect from the Optimole API', 'optimole-wp' ),
1768 + 'disconnect_desc' => __(
1769 + 'Please note that disconnecting your site from the Optimole API will impact your website performance.
1770 +If you still want to disconnect click the button below.',
1771 + 'optimole-wp'
1772 + ),
654 1773 'email_address_label' => __( 'Your email address', 'optimole-wp' ),
655 - 'register_btn' => __( 'Register & Email API key', 'optimole-wp' ),
1774 + 'steps_connect_api_title' => __( 'Connect your account', 'optimole-wp' ),
1775 + 'register_btn' => __( 'Create & connect your account', 'optimole-wp' ),
1776 + 'secure_connection' => __( 'Secure connection - your data is protected', 'optimole-wp' ),
656 1777 'step_one_api_title' => __( 'Enter your API key.', 'optimole-wp' ),
657 - 'step_one_api_desc' => sprintf( __( 'Copy the API key you have received via email or you can get it from %1$s Optimole dashboard%2$s. <br/>', 'optimole-wp' ), '<a href="https://dashboard.optimole.com/" target="_blank"> ', '</a>' ),
658 - 'step_two_api_title' => __( 'Connect to Optimole.', 'optimole-wp' ),
659 - 'step_two_api_desc' => __( ' Fill in the upper API key field and connect to Optimole service.', 'optimole-wp' ),
1778 + 'optml_dashboard' => sprintf(
1779 + /* translators: 1 is the opening anchor tag, 2 is the closing anchor tag */
1780 + __( 'Get it from the %1$s Optimole Dashboard%2$s.', 'optimole-wp' ),
1781 + '<a style="white-space:nowrap; text-decoration: underline !important;" href="' . esc_url( tsdk_translate_link( 'https://dashboard.optimole.com/', 'query' ) ) . '" target="_blank"> ',
1782 + '<span style="text-decoration:none; font-size:15px; margin-top:2px;" class="dashicons dashicons-external"></span></a>'
1783 + ),
1784 + 'steps_connect_api_desc' => sprintf(
1785 + /* translators: 1 is the opening anchor tag, 2 is the closing anchor tag */
1786 + __( 'Copy the API Key you have received via email or you can get it from %1$s Optimole dashboard%2$s. If your account has multiple domains select the one you want to use. <br/>', 'optimole-wp' ),
1787 + '<a href="' . esc_url( tsdk_translate_link( 'https://dashboard.optimole.com/', 'query' ) ) . '" target="_blank"> ',
1788 + '</a>'
1789 + ),
660 1790 'api_exists' => __( 'I already have an API key.', 'optimole-wp' ),
661 1791 'back_to_register' => __( 'Register account', 'optimole-wp' ),
662 - 'back_to_connect' => __( 'Connect account', 'optimole-wp' ),
663 - 'error_register' => sprintf( __( 'Error registering account. You can try again %1$shere%2$s ', 'optimole-wp' ), '<a href="https://dashboard.optimole.com/register" target="_blank"> ', '</a>' ),
664 - 'connected' => __( 'Connected', 'optimole-wp' ),
665 - 'not_connected' => __( 'Not connected', 'optimole-wp' ),
1792 + 'back_to_connect' => __( 'Go to previous step', 'optimole-wp' ),
1793 + /* translators: 1 is starting anchor tag, 2 is ending anchor tag */
1794 + 'error_register' => sprintf( __( 'Error registering account. You can try again %1$shere%2$s', 'optimole-wp' ), '<a href="' . esc_url( tsdk_translate_link( 'https://dashboard.optimole.com/register', 'query' ) ) . '" target="_blank"> ', '</a>' ),
1795 + 'invalid_email' => __( 'Please use a valid email address.', 'optimole-wp' ),
1796 + 'connected' => __( 'CONNECTED', 'optimole-wp' ),
1797 + 'connecting' => __( 'CONNECTING', 'optimole-wp' ),
1798 + 'not_connected' => __( 'NOT CONNECTED', 'optimole-wp' ),
666 1799 'usage' => __( 'Monthly Usage', 'optimole-wp' ),
667 - 'quota' => __( 'Monthly Quota', 'optimole-wp' ),
668 - 'logged_in_as' => __( 'Logged in as', 'optimole-wp' ),
669 - 'private_cdn_url' => __( 'Images domain', 'optimole-wp' ),
670 - 'notification_message_register' => __( 'We have sent you an email with the API key. Please copy and paste the key in the field below.', 'optimole-wp' ),
1800 + 'quota' => __( 'Monthly visits:', 'optimole-wp' ),
1801 + 'tooltip_visits_title' => __( 'What are visits?', 'optimole-wp' ),
1802 + /* translators: 1 is the day when the visits reset, for example 1st of each month */
1803 + 'tooltip_visits_description' => __( 'Each visitor to your site is counted as a unique daily user, regardless of their actions or return visits on the same day. Your visit count resets on %s.', 'optimole-wp' ),
1804 + 'logged_in_as' => __( 'LOGGED IN AS', 'optimole-wp' ),
1805 + 'private_cdn_url' => __( 'IMAGES DOMAIN', 'optimole-wp' ),
1806 + 'existing_user' => __( 'Existing user?', 'optimole-wp' ),
1807 + 'notification_message_register' => __( 'We sent you the API Key in the email. Add it below to connect to Optimole.', 'optimole-wp' ),
1808 + 'premium_support' => __( 'Access our Premium Support', 'optimole-wp' ),
671 1809 'account_needed_title' => sprintf(
672 - __( 'In order to get access to free image optimization service you will need an API key from %s.', 'optimole-wp' ),
673 - ' <a href="https://dashboard.optimole.com/register" target="_blank">optimole.com</a>'
1810 + /* translators: 1 is the link to optimole.com */
1811 + __( 'Connect to Optimole\'s powerful image optimization service with a free API key from %s.', 'optimole-wp' ),
1812 + ' <a href="' . esc_url( tsdk_translate_link( 'https://dashboard.optimole.com/register', 'query' ) ) . '" target="_blank">optimole.com</a>'
674 1813 ),
675 1814 'account_needed_subtitle_1' => sprintf(
676 - __( 'You will get access to our image optimization service for %1$sFREE%2$s in the limit of %3$s5k%4$s %5$svisitors%6$s per month. ', 'optimole-wp' ),
1815 + /* translators: 1 is the starting bold tag, 2 is the ending bold tag, 3 is the starting bold tag, 4 is the limit number, 5 is ending bold tag, 6 is the starting anchor tag for the docs link on how we count visits, 7 is the ending anchor tag. */
1816 + __( '%1$sOptimize unlimited images%2$s for up to %3$s monthly %4$svisitors%5$s - completely FREE.', 'optimole-wp' ),
677 1817 '<strong>',
678 1818 '</strong>',
679 - '<strong>',
680 - '</strong>',
1819 + number_format_i18n( 2000 ),
681 1820 '<a href="https://docs.optimole.com/article/1134-how-optimole-counts-the-number-of-visitors" target="_blank">',
682 1821 '</a>'
683 1822 ),
684 1823 'account_needed_subtitle_3' => sprintf(
685 - __( 'Here’s %1$show%2$s to install Optimole on your WordPress site in 3 steps.', 'optimole-wp' ),
1824 + /* translators: 1 is the starting anchor tag for the docs link, 2 is the ending anchor tag. */
1825 + __( 'Need help? %1$sGetting Started with Optimole%2$s', 'optimole-wp' ),
686 1826 '<a target="_blank" href="https://docs.optimole.com/article/1173-how-to-get-started-with-optimole-in-just-3-steps">',
687 1827 '</a>'
688 1828 ),
689 1829 'account_needed_subtitle_2' => sprintf(
690 - __( 'Bonus, if you dont use a CDN, we got you covered, we will serve the images using CloudFront CD from 200 locations.', 'optimole-wp' )
1830 + /* translators: 1 is the starting bold tag, 2 is the ending bold tag */
1831 + __(
1832 + '%1$sInstant global delivery%2$s with CloudFront CDN - your images load 2-3x faster worldwide from 450+ locations.',
1833 + 'optimole-wp'
1834 + ),
1835 + '<strong>',
1836 + '</strong>'
691 1837 ),
1838 + 'account_needed_subtitle_4' => sprintf(
1839 + /* translators: 1 is the starting bold tag, 2 is the ending bold tag */
1840 + __(
1841 + '%1$sAdaptive optimization%2$s that perfectly sizes images for every visitor\'s device and connection speed.',
1842 + 'optimole-wp'
1843 + ),
1844 + '<strong>',
1845 + '</strong>'
1846 + ),
1847 + 'account_needed_footer' => sprintf(
1848 + /* translators: 1 is the number of users using Optimole */
1849 + __( 'Trusted by more than %1$s happy users', 'optimole-wp' ),
1850 + number_format_i18n( 200000, 0 )
1851 + ),
1852 + 'account_connecting_title' => __( 'Connecting to Optimole', 'optimole-wp' ),
1853 + 'account_connecting_subtitle' => __( 'Sit tight while we connect you to the Dashboard', 'optimole-wp' ),
692 1854 'notice_just_activated' => ! $this->settings->is_connected() ?
693 - sprintf( __( '%1$sImage optimisation is currently running.%2$s <br/> Your visitors will now view the best image for their device automatically, all served from the Optimole Cloud Service on the fly. You might see for the very first image request being redirected to the original URL while we do the optimization in the background.<br/> You can relax, we\'ll take it from here.', 'optimole-wp' ), '<strong>', '</strong>' )
1855 + sprintf(
1856 + /* translators: 1 starting bold tag, 2 is the ending bold tag */
1857 + __( '%1$sImage optimisation is currently running.%2$s <br/> Your visitors will now view the best image for their device automatically, all served from the Optimole Cloud Service on the fly. You might see for the very first image request being redirected to the original URL while we do the optimization in the background. You can relax, we\'ll take it from here.', 'optimole-wp' ),
1858 + '<strong>',
1859 + '</strong>'
1860 + )
694 1861 : '',
695 1862 'notice_api_not_working' => __(
696 1863 'It seems there is an issue with your WordPress configuration and the core REST API functionality is not available. This is crucial as Optimole relies on this functionality in order to work.<br/>
697 1864 The root cause might be either a security plugin which blocks this feature or some faulty server configuration which constrain this WordPress feature.You can try to disable any of the security plugins that you use in order to see if the issue persists or ask the hosting company to further investigate.',
@@ -696,11 +1863,28 @@
696 1863 'It seems there is an issue with your WordPress configuration and the core REST API functionality is not available. This is crucial as Optimole relies on this functionality in order to work.<br/>
697 1864 The root cause might be either a security plugin which blocks this feature or some faulty server configuration which constrain this WordPress feature.You can try to disable any of the security plugins that you use in order to see if the issue persists or ask the hosting company to further investigate.',
698 1865 'optimole-wp'
699 1866 ),
700 - 'notice_disabled_account' => sprintf( __( 'Your account has been disabled due to exceeding quota. All images are being redirected to the original unoptimized URL. Please %1$supgrade%2$s to re-activate the account.', 'optimole-wp' ), '<a href="https://optimole.com/pricing">', '</a>' ),
1867 + 'notice_disabled_account' => sprintf(
1868 + /* translators: 1 anchor tag to pricing, 2 is the ending endin anchor tag, 3 is the bold tag start, 4 ending bold tag, 5 new line tag */
1869 + __( '%3$sYour account has been disabled due to exceeding quota.%4$s All images are being redirected to the original unoptimized URL. %5$sPlease %1$supgrade%2$s to re-activate the account.', 'optimole-wp' ),
1870 + '<b><a href="' . esc_url( tsdk_translate_link( self::get_upgrade_base_link() ) ) . '">',
1871 + '</a></b>',
1872 + '<b>',
1873 + '</b>',
1874 + '<br>'
1875 + ),
1876 + 'signup_terms' => sprintf(
1877 + /* translators: 1 is starting anchor tag to terms, 2 is starting anchor tag to privacy link and 3 is ending anchor tag. */
1878 + __( 'By signing up, you agree to our %1$sTerms of Service %3$s and %2$sPrivacy Policy %3$s.', 'optimole-wp' ),
1879 + '<a href="' . esc_url( tsdk_translate_link( 'https://optimole.com/terms/' ) ) . '" target="_blank" >',
1880 + '<a href="' . esc_url( tsdk_translate_link( 'https://optimole.com/privacy-policy/' ) ) . '" target="_blank">',
1881 + '</a>'
1882 + ),
701 1883 'dashboard_menu_item' => __( 'Dashboard', 'optimole-wp' ),
702 1884 'settings_menu_item' => __( 'Settings', 'optimole-wp' ),
1885 + 'help_menu_item' => __( 'Help', 'optimole-wp' ),
1886 + 'settings_watermark_menu_item' => __( 'Watermark', 'optimole-wp' ),
703 1887 'settings_exclusions_menu_item' => __( 'Exclusions', 'optimole-wp' ),
704 1888 'settings_resize_menu_item' => __( 'Resize', 'optimole-wp' ),
705 1889 'settings_compression_menu_item' => __( 'Compression', 'optimole-wp' ),
706 1890 'advanced_settings_menu_item' => __( 'Advanced', 'optimole-wp' ),
@@ -706,105 +1890,448 @@
706 1890 'advanced_settings_menu_item' => __( 'Advanced', 'optimole-wp' ),
707 1891 'general_settings_menu_item' => __( 'General', 'optimole-wp' ),
708 1892 'lazyload_settings_menu_item' => __( 'Lazyload', 'optimole-wp' ),
709 1893 'watermarks_menu_item' => __( 'Watermark', 'optimole-wp' ),
710 - 'conflicts_menu_item' => __( 'Possible issues', 'optimole-wp' ),
711 - 'conflicts' => array(
1894 + 'conflicts_menu_item' => __( 'Possible Issues', 'optimole-wp' ),
1895 + 'conflicts' => [
712 1896 'title' => __( 'We might have some possible conflicts with the plugins that you use. In order to benefit from Optimole\'s full potential you will need to address this issues.', 'optimole-wp' ),
713 1897 'message' => __( 'Details', 'optimole-wp' ),
714 1898 'conflict_close' => __( 'I\'ve done this.', 'optimole-wp' ),
715 1899 'no_conflicts_found' => __( 'No conflicts found. We are all peachy now. 🍑', 'optimole-wp' ),
716 - ),
717 - 'upgrade' => array(
718 - 'title' => __( 'Upgrade to Pro', 'optimole-wp' ),
719 - 'reason_1' => __( 'Priority & Live Chat support', 'optimole-wp' ),
720 - 'reason_2' => __( 'Extend visits limit', 'optimole-wp' ),
721 - 'reason_3' => __( 'Custom domain', 'optimole-wp' ),
722 - 'reason_4' => __( 'Site audit', 'optimole-wp' ),
723 - 'cta' => __( 'View plans', 'optimole-wp' ),
724 - ),
725 - 'options_strings' => array(
726 - 'add_filter' => __( 'Add filter', 'optimole-wp' ),
727 - 'admin_bar_desc' => __( 'Show in the WordPress admin bar the available quota from Optimole service.', 'optimole-wp' ),
728 - 'auto_q_title' => __( 'Auto', 'optimole-wp' ),
729 - 'cache_desc' => __( 'Clear all cached resources(images,js,css) by Optimole from this site. Useful if you updated them and Optimole shows the old version.', 'optimole-wp' ),
730 - 'cache_title' => __( 'Clear cached resources', 'optimole-wp' ),
731 - 'clear_cache' => __( 'Clear cached resources', 'optimole-wp' ),
732 - 'connect_step_0' => __( 'Connecting your site to the Optimole service.', 'optimole-wp' ),
733 - 'connect_step_1' => __( 'Checking for possible conflicts.', 'optimole-wp' ),
734 - 'connect_step_2' => __( 'Inspecting the images from your site.', 'optimole-wp' ),
735 - 'connect_step_3' => __( 'All done, Optimole is currently optimizing your site.', 'optimole-wp' ),
736 - 'disabled' => __( 'Disabled', 'optimole-wp' ),
737 - 'enable_bg_lazyload_desc' => __( 'Lazyload images used as CSS backgrounds.', 'optimole-wp' ),
738 - 'enable_bg_lazyload_title' => __( 'Enable lazyload for background images', 'optimole-wp' ),
739 - 'enable_video_lazyload_desc' => __( 'Lazyload iframes/videos', 'optimole-wp' ),
740 - 'enable_video_lazyload_title' => __( 'Enable lazyload for embedded videos and iframes.', 'optimole-wp' ),
741 - 'enable_gif_replace_title' => __( 'Enable Gif to Video conversion', 'optimole-wp' ),
742 - 'enable_report_title' => __( 'Enable error diagnosis tool' ),
743 - 'enable_report_desc' => __( 'Provides a troubleshooting mechanism which should help you identify any possible issues with your site using Optimole.' ),
744 - 'enable_image_replace' => __( 'Enable image replacement', 'optimole-wp' ),
745 - 'enable_lazyload_placeholder_desc' => __( 'Enabling this might affect the user experience in some cases, however it will reduce the number of total requests and page weight. Try it out and see how works best for you!', 'optimole-wp' ),
746 - 'enable_lazyload_placeholder_title' => __( 'Enable generic lazyload placeholder', 'optimole-wp' ),
747 - 'enable_network_opt_desc' => __( 'Optimole provides an option to automatically downgrade the image quality when it detects a slower network.', 'optimole-wp' ),
748 - 'enable_network_opt_title' => __( 'Enable network based optimizations', 'optimole-wp' ),
749 - 'enable_resize_smart_desc' => __( 'Detects the most interesting section of the image and considers it as the center of the resulting image.', 'optimole-wp' ),
750 - 'enable_resize_smart_title' => __( 'Enable Smart Cropping', 'optimole-wp' ),
751 - 'enable_retina_desc' => __( 'Deliver retina ready images to your visitors', 'optimole-wp' ),
752 - 'enable_retina_title' => __( 'Enable Retina images', 'optimole-wp' ),
753 - 'enabled' => __( 'Enabled', 'optimole-wp' ),
754 - 'exclude_class_desc' => __( 'Image tag contains class', 'optimole-wp' ),
755 - 'exclude_ext_desc' => __( 'Image extension is', 'optimole-wp' ),
756 - 'exclude_filename_desc' => __( 'Image filename contains', 'optimole-wp' ),
757 - 'exclude_title_lazyload' => __( 'Don\'t lazyload images if', 'optimole-wp' ),
758 - 'exclude_title_optimize' => __( 'Don\'t optimize images if', 'optimole-wp' ),
759 - 'exclude_url_desc' => __( 'Page url contains', 'optimole-wp' ),
760 - 'filter_class' => __( 'Image class', 'optimole-wp' ),
761 - 'filter_ext' => __( 'Image extension', 'optimole-wp' ),
762 - 'filter_filename' => __( 'Image filename', 'optimole-wp' ),
763 - 'filter_operator_contains' => __( 'contains', 'optimole-wp' ),
764 - 'filter_operator_is' => __( 'is', 'optimole-wp' ),
765 - 'filter_url' => __( 'Page URL', 'optimole-wp' ),
766 - 'gif_replacer_desc' => __( 'Automatically convert GIF images to Video files(MP4 and WebM)', 'optimole-wp' ),
767 - 'height_field' => __( 'Height', 'optimole-wp' ),
768 - 'hide' => __( 'Hide', 'optimole-wp' ),
769 - 'high_q_title' => __( 'High', 'optimole-wp' ),
770 - 'image_1_label' => __( 'Original', 'optimole-wp' ),
771 - 'image_2_label' => __( 'Optimized', 'optimole-wp' ),
772 - 'lazyload_desc' => __( 'We will generate images size based on your visitor\'s screen using javascript and render them without blocking the page execution via lazyload .', 'optimole-wp' ),
773 - 'scale_desc' => __( 'When this option is off, we disable the automatic scaling of images on lazyload.', 'optimole-wp' ),
774 - 'low_q_title' => __( 'Low', 'optimole-wp' ),
775 - 'medium_q_title' => __( 'Medium', 'optimole-wp' ),
776 - 'no_images_found' => __( 'You dont have any images in your Media Library. Add one and check how the Optimole will perform.', 'optimole-wp' ),
777 - 'native_desc' => __( 'Use browser native lazyload if supported, fallback to classic lazyload otherwise. When using browser native lazyload the auto scale feature is disabled', 'optimole-wp' ),
778 - 'option_saved' => __( 'Option saved.', 'optimole-wp' ),
779 - 'quality_desc' => __( 'Lower image quality might not always be perceived by users and would result in a boost of your loading speed by lowering the page size. Try experimenting with the setting, then click the View sample image link to see what option works best for you.', 'optimole-wp' ),
780 - 'quality_selected_value' => __( 'Selected value', 'optimole-wp' ),
781 - 'quality_slider_desc' => __( 'See one sample image which will help you choose the right quality of the compression.', 'optimole-wp' ),
782 - 'quality_title' => __( 'Image quality', 'optimole-wp' ),
783 - 'replacer_desc' => __( 'Replace all the image urls from your website with the ones optimized by Optimole.', 'optimole-wp' ),
784 - 'sample_image_loading' => __( 'Loading a sample image. ', 'optimole-wp' ),
785 - 'save_changes' => __( 'Save changes', 'optimole-wp' ),
786 - 'show' => __( 'Show', 'optimole-wp' ),
787 - 'size_desc' => __( 'We resize all images with sizes greater than the values defined here. Changing this option is not recommended unless large images are not being processed correctly. This does NOT affect the scaling of images on the frontend.', 'optimole-wp' ),
788 - 'size_title' => __( 'Resize large images original source.', 'optimole-wp' ),
789 - 'toggle_ab_item' => __( 'Admin bar status', 'optimole-wp' ),
790 - 'toggle_lazyload' => __( 'Scale images & Lazy load', 'optimole-wp' ),
791 - 'toggle_scale' => __( 'Scale Images', 'optimole-wp' ),
792 - 'toggle_native' => __( 'Use native lazyload', 'optimole-wp' ),
793 - 'on_toggle' => __( 'On', 'optimole-wp' ),
794 - 'off_toggle' => __( 'Off', 'optimole-wp' ),
795 - 'view_sample_image' => __( 'View sample image', 'optimole-wp' ),
796 - 'watch_desc_lazyload' => __( 'You can add each CSS selector on a new line or separated by comma(,).', 'optimole-wp' ),
797 - 'watch_title_lazyload' => __( 'Lazyload background images for selectors:', 'optimole-wp' ),
798 - 'width_field' => __( 'Width', 'optimole-wp' ),
799 - 'toggle_cdn' => __( 'Serve CSS & JS through Optimole', 'optimole-wp' ),
800 - 'cdn_desc' => __( 'Useful when you have images into CSS/JS files. Optimole wil optimize the images from them and serve the CSS/JS through the CDN.', 'optimole-wp' ),
801 - 'enable_css_minify_title' => __( 'Minify CSS files', 'optimole-wp' ),
802 - 'css_minify_desc' => __( 'Once Optimole will serve your CSS files, it will also minify the files and serve them via CDN.', 'optimole-wp' ),
803 - 'enable_js_minify_title' => __( 'Minify JS files', 'optimole-wp' ),
804 - 'js_minify_desc' => __( 'Once Optimole will serve your JS files, it will also minify the files and serve them via CDN.', 'optimole-wp' ),
805 - ),
806 - 'watermarks' => array(
1900 + ],
1901 + 'upgrade' => [
1902 + 'title' => __( 'Upgrade', 'optimole-wp' ),
1903 + 'title_long' => __( 'Upgrade to Optimole Pro', 'optimole-wp' ),
1904 + 'reason_1' => __( 'Priority & Live Chat support', 'optimole-wp' ),
1905 + 'reason_2' => __( 'Extend visits limit', 'optimole-wp' ),
1906 + 'reason_3' => __( 'Custom domain', 'optimole-wp' ),
1907 + 'reason_4' => __( 'Site audit', 'optimole-wp' ),
1908 + 'cta' => __( 'View plans', 'optimole-wp' ),
1909 + ],
1910 + 'neve' => [
1911 + 'is_active' => defined( 'NEVE_VERSION' ) ? 'yes' : 'no',
1912 + 'byline' => __( 'Fast, perfomance built-in WordPress theme.', 'optimole-wp' ),
1913 + 'reason_1' => __( 'Lightweight, 25kB in page-weight.', 'optimole-wp' ),
1914 + 'reason_2' => __( '100+ Starter Sites available.', 'optimole-wp' ),
1915 + 'reason_3' => __( 'AMP/Mobile ready.', 'optimole-wp' ),
1916 + 'reason_4' => __( 'Lots of customizations options.', 'optimole-wp' ),
1917 + 'reason_5' => __( 'Fully compatible with Optimole.', 'optimole-wp' ),
1918 + ],
1919 + 'metrics' => [
1920 + 'metricsTitle1' => __( 'Images optimized', 'optimole-wp' ),
1921 + 'metricsSubtitle1' => __( 'Since plugin activation', 'optimole-wp' ),
1922 + 'metricsTitle2' => __( 'Saved File Size', 'optimole-wp' ),
1923 + 'metricsSubtitle2' => __( 'Latest 10 images', 'optimole-wp' ),
1924 + 'metricsTitle3' => __( 'Average Compression', 'optimole-wp' ),
1925 + 'metricsSubtitle3' => __( 'Average Reduction', 'optimole-wp' ),
1926 + 'metricsTitle4' => __( 'CDN Traffic', 'optimole-wp' ),
1927 + 'metricsSubtitle4' => __( 'This month', 'optimole-wp' ),
1928 + 'metricsTitle5' => __( 'Offloaded images', 'optimole-wp' ),
1929 + 'metricsSubtitle5' => __( 'Offloaded to cloud', 'optimole-wp' ),
1930 + // translators: is used as singular when the number of images is 1 for a unit label.
1931 + 'image' => __( 'image', 'optimole-wp' ),
1932 + // translators: is used as plural when the number of images is more than 1 for a unit label.
1933 + 'images' => __( 'images', 'optimole-wp' ),
1934 + 'view_analytics' => __( 'View Analytics', 'optimole-wp' ),
1935 + 'adjust_compression' => __( 'Adjust Compression', 'optimole-wp' ),
1936 + 'manage_offloading' => __( 'Manage image offloading', 'optimole-wp' ),
1937 + ],
1938 + 'quick_actions' => [
1939 + 'speed_test_title' => __( 'Test Your Site Speed', 'optimole-wp' ),
1940 + 'speed_test_desc' => __( 'Run speed test', 'optimole-wp' ),
1941 + 'speed_test_link' => add_query_arg( 'url', get_site_url(), 'https://pagespeed.web.dev/analysis' ),
1942 + 'clear_cache_images' => __( 'Clear Cached Images', 'optimole-wp' ),
1943 + 'clear_cache' => __( 'Clear cache', 'optimole-wp' ),
1944 + 'offload_images' => __( 'Enable Offload Images', 'optimole-wp' ),
1945 + 'offload_images_desc' => __( 'Free up space on your server', 'optimole-wp' ),
1946 + 'advance_settings' => __( 'Advanced Settings', 'optimole-wp' ),
1947 + 'configure_settings' => __( 'Configure settings', 'optimole-wp' ),
1948 + ],
1949 + 'options_strings' => [
1950 + 'watermark_media_desc' => __( 'Protect your brand by adding a watermark to your images. Configure placement, opacity, and rules directly in your Optimole Dashboard.', 'optimole-wp' ),
1951 + 'watermark_media_title' => __( 'Watermarks', 'optimole-wp' ),
1952 + 'learn_more' => __( 'Learn more', 'optimole-wp' ),
1953 + 'open_optimole_dashboard' => __( 'Open Optimole Dashboard', 'optimole-wp' ),
1954 + 'watermark_footer' => __( 'Watermark rules and exclusions are fully managed in your Optimole Dashboard.', 'optimole-wp' ),
1955 + 'watermark_info_1' => __( 'Choose from multiple watermark positions and set opacity.', 'optimole-wp' ),
1956 + 'watermark_info_2' => __( 'Set different watermarks for different image sizes.', 'optimole-wp' ),
1957 + 'watermark_info_3' => __( 'Create rule-based targeting by filename, page URL, class, and more.', 'optimole-wp' ),
1958 + 'compression_mode' => __( 'Compression Mode', 'optimole-wp' ),
1959 + 'compression_mode_speed_optimized' => __( 'Speed Optimized', 'optimole-wp' ),
1960 + 'compression_mode_quality_optimized' => __( 'Quality Optimized', 'optimole-wp' ),
1961 + 'compression_mode_custom' => __( 'Custom', 'optimole-wp' ),
1962 + 'compression_mode_speed_optimized_desc' => __( 'Prioritizes faster loading and smaller image sizes by applying a balanced level of lossy compression. This setting reduces file size significantly without severely degrading visual quality.', 'optimole-wp' ),
1963 + 'compression_mode_quality_optimized_desc' => __( 'Delivers high-quality images by applying lossless or near-lossless optimization. Ensures images retain their sharpness and details while still benefiting from moderate file size reduction.', 'optimole-wp' ),
1964 + 'compression_mode_custom_desc' => __( 'Customize each setting individually for complete control over image compression.', 'optimole-wp' ),
1965 +
1966 + 'best_format_title' => __( 'Automatic Best Image Format Selection', 'optimole-wp' ),
1967 + 'best_format_desc' => sprintf(
1968 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
1969 + __( 'When enabled, Optimole chooses the best format for your images, balancing quality with speed. It tries options like AVIF and WebP to keep visuals sharp and pages fast, though uncached images may take a bit longer to process. %1$sLearn more%2$s.', 'optimole-wp' ),
1970 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1942-best-format">',
1971 + '</a>'
1972 + ),
1973 + 'add_filter' => __( 'Add filter', 'optimole-wp' ),
1974 + 'add_site' => __( 'Add site', 'optimole-wp' ),
1975 + 'admin_bar_desc' => __( 'Show in the WordPress admin bar the available quota from Optimole service.', 'optimole-wp' ),
1976 + 'auto_q_title' => __( 'Auto', 'optimole-wp' ),
1977 + 'cache_desc' => sprintf(
1978 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
1979 + __( 'Clears all Optimole’s cached resources (images, JS, CSS). Useful if you made changes to your images and don\'t see those applying on your site. %1$sLearn more%2$s', 'optimole-wp' ),
1980 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1941-clear-cached-resources">',
1981 + '</a>'
1982 + ),
1983 + 'cache_title' => __( 'Clear Cached Resources', 'optimole-wp' ),
1984 + 'clear_cache_notice' => __( 'Clearing cached resources will re-optimize the images and might affect the site performance for a few minutes.', 'optimole-wp' ),
1985 + 'image_size_notice' => sprintf(
1986 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
1987 + __( 'Use this option if you notice images are not cropped correctly after using Optimole. Add the affected image sizes here to automatically adjust and correct their cropping for optimal display. %1$sLearn more%2$s', 'optimole-wp' ),
1988 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1947-add-new-image-crop-size">',
1989 + '</a>'
1990 + ),
1991 + 'clear_cache_images' => __( 'Clear cached images', 'optimole-wp' ),
1992 + 'clear_cache_assets' => __( 'Clear cached CSS & JS', 'optimole-wp' ),
1993 + 'connect_step_0' => __( 'Connecting your site to the Optimole service.', 'optimole-wp' ),
1994 + 'connect_step_1' => __( 'Checking for possible conflicts.', 'optimole-wp' ),
1995 + 'connect_step_2' => __( 'Inspecting the images from your site.', 'optimole-wp' ),
1996 + 'connect_step_3' => __( 'All done, Optimole is currently optimizing your site.', 'optimole-wp' ),
1997 + 'disabled' => __( 'Disabled', 'optimole-wp' ),
1998 + 'enable_avif_title' => __( 'AVIF Image Support', 'optimole-wp' ),
1999 + 'enable_avif_desc' => sprintf(
2000 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2001 + __( 'Enable this to automatically convert images to the AVIF format on browsers that support it. This format provides quality images with reduced file sizes, and faster webpage loading. %1$sLearn more%2$s', 'optimole-wp' ),
2002 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1943-avif-conversion">',
2003 + '</a>'
2004 + ),
2005 + 'enable_bg_lazyload_desc' => sprintf(
2006 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2007 + __( 'Apply lazy loading to CSS background images. The toggle below enables it globally. The selector list is optional and only needed to extend coverage if certain elements are missed. %1$sLearn more%2$s', 'optimole-wp' ),
2008 + '<a class="inline-block text-purple-gray underline" target="_blank" href="https://docs.optimole.com/article/1169-how-to-enable-the-background-lazyload-feature-for-certain-background-images">',
2009 + '</a>'
2010 + ),
2011 + 'enable_bg_lazyload_title' => __( 'CSS Background Lazy Load', 'optimole-wp' ),
2012 + 'enable_video_lazyload_desc' => __( 'Lazy load embedded videos and iframe content', 'optimole-wp' ),
2013 + 'enable_video_lazyload_title' => __( 'Video & iframes', 'optimole-wp' ),
2014 + 'enable_noscript_desc' => sprintf(
2015 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2016 + __( 'Enables fallback images for browsers that can\'t handle JavaScript-based lazy loading or related features. Disabling it may resolve conflicts with other plugins or configurations and decrease HTML page size. %1$sLearn more%2$s', 'optimole-wp' ),
2017 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1959-noscript-tag">',
2018 + '</a>'
2019 + ),
2020 + // translators: %s is the name of the html tag (e.g: <noscript> );
2021 + 'enable_noscript_title' => sprintf( __( 'Enable %s fallback', 'optimole-wp' ), '<custom_component/>' ),
2022 + 'enable_gif_replace_title' => __( 'GIF to Video Conversion', 'optimole-wp' ),
2023 + 'enable_offload_media_title' => __( 'Store Your Images in Optimole Cloud', 'optimole-wp' ),
2024 + 'enable_offload_media_desc' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'Free up space on your server by transferring your images to Optimole Cloud; you can transfer them back anytime. Once moved, the images will still be visible in the Media Library and can be used as before. %1$sLearn more%2$s', 'optimole-wp' ), '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1967-store-your-images-in-optimole-cloud">', '</a>' ),
2025 + 'enable_cloud_images_title' => __( 'Unified Image Access', 'optimole-wp' ),
2026 + 'enable_cloud_images_desc' => sprintf(
2027 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2028 + __( 'Enable this setting to access all your Optimole images, including those from other websites connected to your Optimole account, directly on this site. They will be available for browsing in the Cloud Library tab. %1$sLearn more%2$s', 'optimole-wp' ),
2029 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1323-cloud-library-browsing">',
2030 + '</a>'
2031 + ),
2032 + 'enable_image_replace' => __( 'Enable Optimole Image Handling', 'optimole-wp' ),
2033 + 'enable_lazyload_placeholder_desc' => sprintf(
2034 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2035 + __( 'Enable this to use a generic transparent placeholder instead of the blurry images during lazy loading. %1$sLearn more%2$s', 'optimole-wp' ),
2036 + '<a class="inline-block underline text-white" target=”_blank” href="https://docs.optimole.com/article/1192-lazy-load-generic-placeholder">',
2037 + '</a>'
2038 + ),
2039 +
2040 + 'lazyload_behaviour_title' => __( 'Loading Method', 'optimole-wp' ),
2041 + 'lazyload_behaviour_desc' => sprintf(
2042 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2043 + __( 'Choose how Optimole will handle lazy loading for images on your website. %1$sLearn more%2$s', 'optimole-wp' ),
2044 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1958-lazy-loading-behaviour">',
2045 + '</a>'
2046 + ),
2047 + 'lazyload_behaviour_all' => __( 'Lazy Load All Images', 'optimole-wp' ),
2048 + 'lazyload_behaviour_all_desc' => __( 'All images will use lazy loading regardless of position.', 'optimole-wp' ),
2049 + 'lazyload_behaviour_viewport' => __( 'Skip images above the fold', 'optimole-wp' ),
2050 + 'lazyload_behaviour_viewport_desc' => __( 'Automatically detects and immediately loads images visible in the initial viewport. Detection is done with a lightweight client-side script that identifies what\'s visible on each user\'s screen. All other images will lazy load.', 'optimole-wp' ),
2051 + 'lazyload_behaviour_fixed' => __( 'Skip lazy loading for first images', 'optimole-wp' ),
2052 + 'lazyload_behaviour_fixed_desc' => __( 'Indicate how many images at the top of each page should bypass lazy loading, ensuring they\'re instantly visible.', 'optimole-wp' ),
2053 + 'enable_lazyload_placeholder_title' => __( 'Enable generic placeholder color', 'optimole-wp' ),
2054 + 'enable_network_opt_desc' => sprintf(
2055 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2056 + __( 'When enabled, Optimole will automatically reduce the image quality when it detects a slower network, making your images load faster on low-speed internet connections. %1$sLearn more%2$s', 'optimole-wp' ),
2057 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1945-network-based-optimizations">',
2058 + '</a>'
2059 + ),
2060 + 'enable_network_opt_title' => __( 'Network-based Optimizations', 'optimole-wp' ),
2061 + 'enable_resize_smart_desc' => sprintf(
2062 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2063 + __( 'When enabled, Optimole automatically detects the most interesting or important part of your images. When pictures are resized or cropped, this feature ensures the focus stays on the most interesting part of the picture. %1$sLearn more%2$s', 'optimole-wp' ),
2064 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1871-how-to-use-the-smart-cropping-in-optimole">',
2065 + '</a>'
2066 + ),
2067 + 'enable_resize_smart_title' => __( 'Smart Cropping', 'optimole-wp' ),
2068 + 'enable_retina_desc' => sprintf(
2069 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2070 + __( 'Optimizes images for Retina (high-DPI) screens for sharper results. Always enabled for images under 150px wide. %1$sLearn more%2$s', 'optimole-wp' ),
2071 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1391-what-is-a-retina-image">',
2072 + '</a>'
2073 + ),
2074 + 'enable_retina_title' => __( 'Retina Quality', 'optimole-wp' ),
2075 + 'enable_limit_dimensions_desc' => sprintf(
2076 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2077 + __( 'Define the max width or height limits for images on your website. Larger images will be automatically adjusted to fit within these parameters while preserving their original aspect ratio. %1$sLearn more%2$s', 'optimole-wp' ),
2078 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1946-limit-image-sizes">',
2079 + '</a>'
2080 + ),
2081 + 'enable_limit_dimensions_title' => __( 'Limit Image Sizes', 'optimole-wp' ),
2082 + 'enable_limit_dimensions_notice' => __( 'When you enable this feature to define a max width or height for image resizing, please note that DPR (retina) images will be disabled. This is done to ensure consistency in image dimensions across your website. Although this may result in slightly lower image quality for high-resolution displays, it will help maintain uniform image sizes, improving your website\'s overall layout and potentially boosting performance.', 'optimole-wp' ),
2083 + 'enable_badge_title' => __( 'Enable Optimole Badge', 'optimole-wp' ),
2084 + 'enable_badge_settings' => __( 'Badge settings', 'optimole-wp' ),
2085 + 'enable_badge_show_icon' => __( 'Show only Optimole icon', 'optimole-wp' ),
2086 + 'enable_badge_position' => __( 'Badge Position', 'optimole-wp' ),
2087 + 'badge_position_text_1' => __( 'Left', 'optimole-wp' ),
2088 + 'badge_position_text_2' => __( 'Right', 'optimole-wp' ),
2089 + 'enable_badge_description' => sprintf(
2090 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2091 + __( 'Get 20.000 more visits for free by enabling the Optimole badge on your websites. %1$sLearn more%2$s', 'optimole-wp' ),
2092 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1940-optimole-badge">',
2093 + '</a>'
2094 + ),
2095 + 'image_sizes_title' => __( 'Your cropped image sizes', 'optimole-wp' ),
2096 + 'enabled' => __( 'Enabled', 'optimole-wp' ),
2097 + 'exclude_class_desc' => sprintf(
2098 + /* translators: 1 is the starting bold tag, 2 is the ending bold tag */
2099 + __( '%1$sImage tag%2$s contains class', 'optimole-wp' ),
2100 + '<strong>',
2101 + '</strong>'
2102 + ),
2103 + 'exclude_ext_desc' => sprintf(
2104 + /* translators: 1 is the starting bold tag, 2 is the ending bold tag */
2105 + __( '%1$sImage extension%2$s is', 'optimole-wp' ),
2106 + '<strong>',
2107 + '</strong>'
2108 + ),
2109 + 'exclude_filename_desc' => sprintf(
2110 + /* translators: 1 is the starting bold tag, 2 is the ending bold tag */ __( '%1$sImage filename%2$s contains', 'optimole-wp' ),
2111 + '<strong>',
2112 + '</strong>'
2113 + ),
2114 + 'exclude_desc_optimize' => sprintf(
2115 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2116 + __( 'Here you can define exceptions, in case you don\'t want some images to be optimised. %1$sLearn more%2$s', 'optimole-wp' ),
2117 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1191-exclude-from-optimizing-or-lazy-loading">',
2118 + '</a>'
2119 + ),
2120 + 'exclude_title_lazyload' => __( 'Don\'t lazy-load images if', 'optimole-wp' ),
2121 + 'exclude_desc_lazyload' => sprintf(
2122 + /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */
2123 + __( 'Define exceptions, in case you don\'t want the lazy-load to be active on certain images. %1$sLearn more%2$s', 'optimole-wp' ),
2124 + '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1191-exclude-from-optimizing-or-lazy-loading">',
2125 + '</a>'
2126 + ),
2127 + 'exclude_title_optimize' => __( 'Don\'t optimize and don\'t lazy-load images if', 'optimole-wp' ),
2128 + 'exclude_url_desc' => sprintf(
2129 + /* translators: 1 is the starting bold tag, 2 is the ending bold tag */ __( '%1$sPage url%2$s contains', 'optimole-wp' ),
2130 + '<strong>',
2131 + '</strong>'
2132 + ),
2133 + 'name' => sprintf(
2134 + /* translators: 1 is the starting bold tag, 2 is the ending bold tag */ __( '%1$sName: %2$s', 'optimole-wp' ),
2135 + '<strong>',
2136 + '</strong>'
2137 + ),
2138 + 'cropped' => __( 'cropped', 'optimole-wp' ),
2139 + 'exclude_url_match_desc' => sprintf(
2140 + /* translators: 1 is the starting bold tag, 2 is the ending bold tag */ __( '%1$sPage url%2$s matches', 'optimole-wp' ),
2141 + '<strong>',
2142 + '</strong>'
2143 + ),
2144 + 'exclude_first' => __( 'Exclude first', 'optimole-wp' ),
2145 + 'images' => __( 'images', 'optimole-wp' ),
2146 + 'exclude_first_images_title' => __( 'Bypass Lazy Load for First Images', 'optimole-wp' ),
2147 + 'exclude_first_images_desc' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'Indicate how many images at the top of each page should bypass lazy loading, ensuring they’re instantly visible. Enter 0 to not exclude any images from the lazy loading process. %1$sLearn more%2$s', 'optimole-wp' ), '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1948-bypass-lazy-load-for-first-images">', '</a>' ),
2148 + 'filter_class' => __( 'Image class', 'optimole-wp' ),
2149 + 'filter_ext' => __( 'Image extension', 'optimole-wp' ),
2150 + 'filter_filename' => __( 'Image filename', 'optimole-wp' ),
2151 + 'filter_operator_contains' => __( 'contains', 'optimole-wp' ),
2152 + 'filter_operator_matches' => __( 'matches', 'optimole-wp' ),
2153 + 'filter_operator_is' => __( 'is', 'optimole-wp' ),
2154 + 'filter_url' => __( 'Page URL', 'optimole-wp' ),
2155 + 'filter_helper' => __( 'For homepage use `home` keyword.', 'optimole-wp' ),
2156 + 'gif_replacer_desc' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'Enable this to automatically convert GIF images to Video files (MP4 and WebM). %1$sLearn more%2$s', 'optimole-wp' ), '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1171-gif-to-video-conversion">', '</a>' ),
2157 + 'height_field' => __( 'Height', 'optimole-wp' ),
2158 + 'add_image_size_button' => __( 'Add size', 'optimole-wp' ),
2159 + 'add_image_size_desc' => __( 'Add New Image Crop Size', 'optimole-wp' ),
2160 + 'here' => __( ' here.', 'optimole-wp' ),
2161 + 'hide' => __( 'Hide', 'optimole-wp' ),
2162 + 'high_q_title' => __( 'High', 'optimole-wp' ),
2163 + 'image_1_label' => __( 'Original', 'optimole-wp' ),
2164 + 'image_2_label' => __( 'Optimized', 'optimole-wp' ),
2165 + 'lazyload_desc' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'Images load only when they\'re about to enter the viewport. %1$sLearn more%2$s', 'optimole-wp' ), '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1948-bypass-lazy-load-for-first-images">', '</a>' ),
2166 + 'filter_length_error' => __( 'The filter should be at least 3 characters long.', 'optimole-wp' ),
2167 + 'scale_desc' => __( 'Automatically resize the images based on device and viewport', 'optimole-wp' ),
2168 + 'low_q_title' => __( 'Low', 'optimole-wp' ),
2169 + 'medium_q_title' => __( 'Medium', 'optimole-wp' ),
2170 + 'no_images_found' => __( 'You dont have any images in your Media Library. Add one and check how the Optimole will perform.', 'optimole-wp' ),
2171 + 'native_desc' => __( 'Uses the browser\'s built-in lazy loading feature. Enabling this will disable the auto scale.', 'optimole-wp' ),
2172 + 'option_saved' => __( 'Option saved.', 'optimole-wp' ),
2173 + 'ml_quality_desc' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'Optimole ML algorithms will predict the optimal image quality to get the smallest possible size with minimum perceived quality losses. When disabled, you can control the quality manually. %1$sLearn more%2$s', 'optimole-wp' ), '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1016-what-is-the-difference-between-the-auto-high-medium-low-compression-levels">', '</a>' ),
2174 + 'quality_desc' => __( 'Lower image quality might boost your loading speed by lowering the size. However, the low image quality may negatively impact the visual appearance of the images. Try experimenting with the setting, then click the View sample image link to see what option works best for you.', 'optimole-wp' ),
2175 + 'quality_selected_value' => __( 'Selected value', 'optimole-wp' ),
2176 + 'quality_slider_desc' => __( 'See one sample image which will help you choose the right quality of the compression.', 'optimole-wp' ),
2177 + 'quality_title' => __( 'Auto Quality Powered by ML(Machine Learning)', 'optimole-wp' ),
2178 + 'strip_meta_title' => __( 'Strip Image Metadata', 'optimole-wp' ),
2179 + 'strip_meta_desc' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'Removes extra information from images, including EXIF and IPTC data (like camera settings and copyright info). This makes the pictures lighter and helps your website load faster. %1$sLearn more%2$s', 'optimole-wp' ), '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1944-strip-image-metadata">', '</a>' ),
2180 + 'replacer_desc' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'When enabled, Optimole will manage, optimize, and serve all the images on your website. If disabled, optimization, lazy loading, and other features will no longer be available. %1$sLearn more%2$s', 'optimole-wp' ), '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1218-how-to-turn-off-image-replacement-in-optimoles-wordpress-plugin">', '</a>' ),
2181 + 'sample_image_loading' => __( 'Loading a sample image.', 'optimole-wp' ),
2182 + 'save_changes' => __( 'Save changes', 'optimole-wp' ),
2183 + 'show' => __( 'Show', 'optimole-wp' ),
2184 + 'selected_sites_title' => __( 'CURRENTLY SHOWING IMAGES FROM', 'optimole-wp' ),
2185 + 'selected_sites_desc' => __( 'Site:', 'optimole-wp' ),
2186 + 'selected_all_sites_desc' => __( 'Currently viewing images from all sites', 'optimole-wp' ),
2187 + 'select_all_sites_desc' => __( 'View images from all sites', 'optimole-wp' ),
2188 + 'select_site' => __( 'Select a website', 'optimole-wp' ),
2189 + 'cloud_site_title' => __( 'Show images only from these sites:', 'optimole-wp' ),
2190 + 'cloud_site_desc' => __( 'Browse images only from the specified websites. Otherwise, images from all websites will appear in the library.', 'optimole-wp' ),
2191 + 'toggle_ab_item' => __( 'Admin bar status', 'optimole-wp' ),
2192 + 'toggle_lazyload' => __( 'Enable Lazy Loading & Scaling', 'optimole-wp' ),
2193 + 'toggle_scale' => __( 'Smart Image Scaling', 'optimole-wp' ),
2194 + 'toggle_native' => __( 'Native Browser Loading', 'optimole-wp' ),
2195 + 'on_toggle' => __( 'On', 'optimole-wp' ),
2196 + 'off_toggle' => __( 'Off', 'optimole-wp' ),
2197 + 'view_sample_image' => __( 'View sample image', 'optimole-wp' ),
2198 + 'watch_placeholder_lazyload' => __( 'Optional selectors to extend coverage (one per line or comma-separated)', 'optimole-wp' ),
2199 + // translators: %s is the example selector list.
2200 + 'watch_placeholder_lazyload_example' => sprintf( __( 'Example: %s', 'optimole-wp' ), '.hero-bg, #banner-image, .lazy-bg, .footer-bg' ),
2201 + 'watch_desc_lazyload' => __( 'If everything is covered out of the box, leave this empty.', 'optimole-wp' ),
2202 + 'smart_loading_title' => __( 'Smart Loading', 'optimole-wp' ),
2203 + 'smart_loading_desc' => __( 'JavaScript-driven with advanced controls', 'optimole-wp' ),
2204 + 'browser_native_lazy' => __( 'Uses the browser\'s built-in lazy loading feature', 'optimole-wp' ),
2205 + // translators: viewport is the visible area of a web page on a display device.
2206 + 'viewport_detection' => __( 'Viewport detection', 'optimole-wp' ),
2207 + // translators: set the colors for the placeholder image.
2208 + 'placeholders_color' => __( 'Placeholders', 'optimole-wp' ),
2209 + // translators: it can handle many images without slowing down the site.
2210 + 'auto_scaling' => __( 'Auto Scaling', 'optimole-wp' ),
2211 + // translators: it uses the browser's built-in lazy loading feature without any additional scripts.
2212 + 'lightweight_native' => __( 'Lightweight', 'optimole-wp' ),
2213 +
2214 + 'watch_title_lazyload' => __( 'Extend CSS Background Images', 'optimole-wp' ),
2215 + 'width_field' => __( 'Width', 'optimole-wp' ),
2216 + 'crop' => __( 'crop', 'optimole-wp' ),
2217 + 'toggle_cdn' => __( 'Serve CSS & JS Through Optimole', 'optimole-wp' ),
2218 + 'cdn_desc' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'When enabled, Optimole will optimize your CSS and JS files and, if they contain images, the images as well, then deliver them via the CDN for faster webpage loading. %1$sLearn more%2$s', 'optimole-wp' ), '<a class="inline-block text-purple-gray underline" target=”_blank” href="https://docs.optimole.com/article/1966-serve-css-js-through-optimole">', '</a>' ),
2219 + 'enable_css_minify_title' => __( 'Minify CSS files', 'optimole-wp' ),
2220 + 'css_minify_desc' => __( 'Once Optimole will serve your CSS files, it will also minify the files and serve them via CDN.', 'optimole-wp' ),
2221 + 'enable_js_minify_title' => __( 'Minify JS files', 'optimole-wp' ),
2222 + 'js_minify_desc' => __( 'Once Optimole will serve your JS files, it will also minify the files and serve them via CDN.', 'optimole-wp' ),
2223 + 'sync_title' => __( 'Offload Existing Images', 'optimole-wp' ),
2224 + 'rollback_title' => __( 'Restore Offloaded Images', 'optimole-wp' ),
2225 + 'sync_desc' => __( 'Right now all the new images uploaded to your site are moved automatically to Optimole Cloud. In order to offload the existing ones, please click Sync images and wait for the process to finish. You can rollback anytime.', 'optimole-wp' ),
2226 + 'rollback_desc' => __( 'Pull all the offloaded images to Optimole Cloud back to your server.', 'optimole-wp' ),
2227 + 'sync_media' => __( 'Sync images', 'optimole-wp' ),
2228 + 'rollback_media' => __( 'Rollback images', 'optimole-wp' ),
2229 + 'sync_media_progress' => __( 'Moving your images to Optimole...', 'optimole-wp' ),
2230 + 'estimated_time' => __( 'Estimated time remaining', 'optimole-wp' ),
2231 + 'calculating_estimated_time' => __( 'We are currently calculating the estimated time for this job...', 'optimole-wp' ),
2232 + 'images_processing' => __( 'We are currently processing your images in the background. Leaving the page won\'t stop the process.', 'optimole-wp' ),
2233 + 'active_optimize_exclusions' => __( 'Active Optimizing Exclusions', 'optimole-wp' ),
2234 + 'active_lazyload_exclusions' => __( 'Active Lazy-loading Exclusions', 'optimole-wp' ),
2235 + 'minutes' => __( 'minutes', 'optimole-wp' ),
2236 + 'stop' => __( 'Stop', 'optimole-wp' ),
2237 + 'show_logs' => __( 'Show Logs', 'optimole-wp' ),
2238 + 'hide_logs' => __( 'Hide Logs', 'optimole-wp' ),
2239 + 'view_logs' => __( 'View Full Logs', 'optimole-wp' ),
2240 + 'rollback_media_progress' => __( 'Moving images into your media library...', 'optimole-wp' ),
2241 + 'rollback_media_error' => __( 'An unexpected error occured while pulling the offloaded back to your site', 'optimole-wp' ),
2242 + 'rollback_media_error_desc' => __( 'You can try again to pull back the rest of the images.', 'optimole-wp' ),
2243 + 'remove_notice' => __( 'Remove notice', 'optimole-wp' ),
2244 + 'sync_media_error' => __( 'An unexpected error occured while offloading all existing images from your site to Optimole', 'optimole-wp' ),
2245 + 'sync_media_link' => __( 'The selected images have been offloaded to our servers, you can check them', 'optimole-wp' ),
2246 + 'rollback_media_link' => __( 'The selected images have been restored to your server, you can check them', 'optimole-wp' ),
2247 + 'sync_media_error_desc' => __( 'You can try again to offload the rest of the images to Optimole.', 'optimole-wp' ),
2248 + 'offload_disable_warning_title' => __( 'Important! Please read carefully', 'optimole-wp' ),
2249 + 'offload_disable_warning_desc' => __( 'If you turn off this option, you will not be able to see the images in the Media Library without restoring the images first. Do you want to restore the images to your site upon turning off the option?', 'optimole-wp' ),
2250 + 'offload_enable_info_desc' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'You are not required to use the offload functionality for the plugin to work, use it if you want to save on hosting space. %1$s More details%2$s', 'optimole-wp' ), '<a style="white-space: nowrap;" target=”_blank” href="https://docs.optimole.com/article/1323-cloud-library-browsing">', '<span style="font-size:15px; margin-top:2px;" class="dashicons dashicons-external"></span></a>' ),
2251 + 'offload_conflicts_part_1' => __( 'We have detected the following plugins that conflict with the offload features:', 'optimole-wp' ),
2252 + 'offload_conflicts_part_2' => __( 'Please disable those plugins temporarily in order for Optimole to rollback the images to your site.', 'optimole-wp' ),
2253 + 'offloading_success' => sprintf( /* translators: 1 is the starting bold tag, 2 is the ending bold tag */ __( '%s Your images are now stored in Optimole Cloud.', 'optimole-wp' ), '<strong>' . __( 'Transfer Complete.', 'optimole-wp' ) . '</strong>' ),
2254 + 'rollback_success' => sprintf( /* translators: 1 is the starting bold tag, 2 is the ending bold tag */ __( '%s Your images have been restored to your website.', 'optimole-wp' ), '<strong>' . __( 'Transfer Complete.', 'optimole-wp' ) . '</strong>' ),
2255 + 'offloading_radio_legend' => __( 'Where your images are stored', 'optimole-wp' ),
2256 + 'offload_radio_option_rollback_title' => __( 'Optimole Cloud and your website', 'optimole-wp' ),
2257 + 'offload_radio_option_rollback_desc' => __( 'Images are stored in both the local WordPress media library and Optimole Cloud.', 'optimole-wp' ),
2258 + 'offload_radio_option_offload_title' => __( 'Optimole Cloud only', 'optimole-wp' ),
2259 + 'offload_radio_option_offload_desc' => __( 'Images are stored only in Optimole Cloud, allowing you to save space on your server. When enabled, any new images you upload in the Media Library will be automatically transferred to Optimole Cloud.', 'optimole-wp' ),
2260 + 'offload_limit_reached' => sprintf( /* translators: Current limit of offloaded images */ __( 'You have reached the maximum offloading limit of %s images. To increase the offload limit and for more information, contact our support.', 'optimole-wp' ), '<strong>#offload_limit#</strong>' ),
2261 + 'select' => __( 'Please select one ...', 'optimole-wp' ),
2262 + 'yes' => __( 'Restore images after disabling', 'optimole-wp' ),
2263 + 'no' => __( 'Do not restore images after disabling', 'optimole-wp' ),
2264 + 'lazyload_placeholder_color' => __( 'Placeholder Color', 'optimole-wp' ),
2265 + 'clear' => __( 'Clear', 'optimole-wp' ),
2266 + 'settings_saved' => __( 'Settings saved', 'optimole-wp' ),
2267 + 'settings_saved_error' => __( 'Error saving settings. Please reload the page and try again.', 'optimole-wp' ),
2268 + 'cache_cleared' => __( 'Cache cleared', 'optimole-wp' ),
2269 + 'cache_cleared_error' => __( 'Error clearing cache. Please reload the page and try again.', 'optimole-wp' ),
2270 + 'offloading_start_title' => __( 'Transfer your images to Optimole', 'optimole-wp' ),
2271 + 'offloading_start_description' => __( 'This process will transfer and store your images in Optimole Cloud and may take a while, depending on the number of images.', 'optimole-wp' ),
2272 + 'offloading_start_action' => __( 'Transfer to Optimole Cloud', 'optimole-wp' ),
2273 + 'offloading_stop_title' => __( 'Are you sure?', 'optimole-wp' ),
2274 + 'offloading_stop_description' => __( 'This will halt the ongoing process. To retrieve images transferred from the Optimole Cloud, use the Rollback option.', 'optimole-wp' ),
2275 + 'offloading_stop_action' => __( 'Cancel the transfer to Optimole', 'optimole-wp' ),
2276 + 'rollback_start_title' => __( 'Transfer back all images to your site', 'optimole-wp' ),
2277 + 'rollback_start_description' => __( 'This process will transfer back all images from Optimole to your website and may take a while, depending on the number of images.', 'optimole-wp' ),
2278 + 'rollback_start_action' => __( 'Transfer back from Optimole', 'optimole-wp' ),
2279 + 'rollback_stop_title' => __( 'Are you sure?', 'optimole-wp' ),
2280 + 'rollback_stop_description' => __( 'Canceling will halt the ongoing process, and any remaining images will stay in the Optimole Cloud. To transfer images to the Optimole Cloud, use the Offloading option.', 'optimole-wp' ),
2281 + 'rollback_stop_action' => __( 'Cancel the transfer from Optimole', 'optimole-wp' ),
2282 + 'cloud_library_btn_text' => __( 'Go to Cloud Library', 'optimole-wp' ),
2283 + 'cloud_library_btn_link' => add_query_arg( 'page', 'optimole-dam', admin_url( 'admin.php' ) ),
2284 + 'exceed_plan_quota_notice_title' => __( 'Your site has already reached over 50% of your monthly visits limit within just two weeks.', 'optimole-wp' ),
2285 + 'exceed_plan_quota_notice_description' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'Based on this trend, you are likely to exceed your free quota before the month ends. To avoid any disruption in service, we strongly recommend %1$supgrading%2$s your plan or waiting until your traffic stabilizes before offloading your images. Do you still wish to proceed?', 'optimole-wp' ), '<a style="white-space: nowrap;" target=”_blank” href="https://dashboard.optimole.com/settings/billing/">', '</a>' ),
2286 + 'exceed_plan_quota_notice_start_action' => __( 'Yes, Transfer to Optimole Cloud', 'optimole-wp' ),
2287 + 'exceed_plan_quota_notice_secondary_action' => __( 'No, keep images on my website', 'optimole-wp' ),
2288 + 'plan_update_notice_title' => __( 'Plan Update', 'optimole-wp' ),
2289 + 'plan_update_notice_desc' => __( 'We\'ve changed how plans work. Users on the Optimole Free plan cannot offload new images. Existing images that are already offloaded will remain offloaded.', 'optimole-wp' ),
2290 + 'upgrade_to_use_offloading_notice_desc' => __( 'Offloading images is a PRO feature. Please upgrade your plan to enable image transfer to Optimole Cloud.', 'optimole-wp' ),
2291 + 'visual_settings' => __( 'Visual Settings', 'optimole-wp' ),
2292 + 'extended_features' => __( 'Extended Features', 'optimole-wp' ),
2293 + // translators: mark that the options are aplied globally.
2294 + 'global_option' => __( 'Global', 'optimole-wp' ),
2295 + // translators: This option is discouraged from being used.
2296 + 'not_recommended' => __( 'Not recommended', 'optimole-wp' ),
2297 + // translators: %1$s is the starting bold tag, %2$s is the ending bold tag.
2298 + 'viewport_skip_images_notice' => sprintf( __( 'When %1$sskip above the fold%2$s and %1$sskip first images%2$s are both enabled: the skip rule applies on a user\'s first page view. On subsequent views, once viewport data exists, the skip rule is ignored in favor of the viewport data.', 'optimole-wp' ), '<strong>', '</strong>' ),
2299 + // translators: %1$s is the starting bold tag, %2$s is the ending bold tag.
2300 + 'native_lazy_load_warning' => sprintf( __( 'Native browser loading works with viewport detection, but it does not support smart image scaling. It can offer better cross-browser compatibility. Still, we do %1$snot recommend%2$s it for most sites due to fewer controls and features compared to Smart Loading.', 'optimole-wp' ), '<strong>', '</strong>' ),
2301 + 'performance_impact_alert_title' => __( 'Performance Impact Alert', 'optimole-wp' ),
2302 + 'performance_impact_alert_lazy_desc' => __( 'Disabling Lazy Load may significantly impact your site\'s loading speed and Core Web Vitals scores.', 'optimole-wp' ),
2303 + 'performance_impact_alert_scale_desc' => __( 'Disabling Image Scaling may significantly impact your site\'s loading speed and Core Web Vitals scores.', 'optimole-wp' ),
2304 + 'performance_impact_alert_action_label' => __( 'Continue with disabling', 'optimole-wp' ),
2305 + 'performance_impact_alert_secondary_action_label' => __( 'Keep enabled and get help', 'optimole-wp' ),
2306 + ],
2307 + 'help' => [
2308 + 'section_one_title' => __( 'Help and Support', 'optimole-wp' ),
2309 + 'section_two_title' => __( 'Documentation', 'optimole-wp' ),
2310 + 'section_two_sub' => __( 'Docs Page', 'optimole-wp' ),
2311 + 'get_support_title' => __( 'Get Support', 'optimole-wp' ),
2312 + 'get_support_desc' => __( 'Need help or got a question? Submit a ticket and we\'ll get back to you.', 'optimole-wp' ),
2313 + 'get_support_cta' => __( 'Contact Support', 'optimole-wp' ),
2314 + 'feat_request_title' => __( 'Have a feature request?', 'optimole-wp' ),
2315 + 'feat_request_desc' => __( 'Help us improve Optimole by sharing feedback and ideas for new features.', 'optimole-wp' ),
2316 + 'feat_request_cta' => __( 'Submit a Feature Request', 'optimole-wp' ),
2317 + 'feedback_title' => __( 'Changelog', 'optimole-wp' ),
2318 + 'feedback_desc' => __( 'Check our changelog to see latest fixes and features implemented.', 'optimole-wp' ),
2319 + 'feedback_cta' => __( 'View Changelog', 'optimole-wp' ),
2320 + 'account_title' => __( 'Account', 'optimole-wp' ),
2321 + 'account_item_one' => __( 'How Optimole counts the visitors?', 'optimole-wp' ),
2322 + 'account_item_two' => __( 'What happens if I exceed plan limits?', 'optimole-wp' ),
2323 + 'account_item_three' => __( 'Visits based plan', 'optimole-wp' ),
2324 + 'image_processing_title' => __( 'Image Processing', 'optimole-wp' ),
2325 + 'image_processing_item_one' => __( 'Getting Started With Optimole', 'optimole-wp' ),
2326 + 'image_processing_item_two' => __( 'How Optimole can serve WebP images', 'optimole-wp' ),
2327 + 'image_processing_item_three' => __( 'Adding Watermarks to your images', 'optimole-wp' ),
2328 + 'api_title' => __( 'API', 'optimole-wp' ),
2329 + 'api_item_one' => __( 'Cloud Library Browsing', 'optimole-wp' ),
2330 + 'api_item_two' => __( 'Exclude from Optimizing or Lazy Loading', 'optimole-wp' ),
2331 + 'api_item_three' => __( 'Custom Integration', 'optimole-wp' ),
2332 + ],
2333 + 'watermarks' => [
807 2334 'image' => __( 'Image', 'optimole-wp' ),
808 2335 'loading_remove_watermark' => __( 'Removing watermark resource ...', 'optimole-wp' ),
809 2336 'max_allowed' => __( 'You are allowed to save maximum 5 images.', 'optimole-wp' ),
810 2337 'list_header' => __( 'Possible watermarks', 'optimole-wp' ),
@@ -839,21 +2366,240 @@
839 2366 'scale_field' => __( 'Scale', 'optimole-wp' ),
840 2367 'scale_title' => __( 'Watermark scale', 'optimole-wp' ),
841 2368 'scale_desc' => __( 'A value between 0 and 300 for the scale of the watermark (100 is the original size and 300 is 3x the size) relative to the resulting image size. If set to 0 it will default to the original size.', 'optimole-wp' ),
842 2369 'save_changes' => __( 'Save changes', 'optimole-wp' ),
843 - ),
844 - 'latest_images' => array(
2370 + ],
2371 + 'latest_images' => [
845 2372 'image' => __( 'Image', 'optimole-wp' ),
846 - 'no_images_found' => sprintf( __( 'We are currently optimizing your images. Meanwhile you can visit your %1$shomepage%2$s and check how our plugin performs. ', 'optimole-wp' ), '<a href="' . esc_url( home_url() ) . '" target="_blank" >', '</a>' ),
2373 + 'no_images_found' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'We are currently optimizing your images. Meanwhile you can visit your %1$shomepage%2$s and check how our plugin performs.', 'optimole-wp' ), '<a href="' . esc_url( home_url() ) . '" target="_blank" >', '</a>' ),
847 2374 'compression' => __( 'Optimization', 'optimole-wp' ),
848 2375 'loading_latest_images' => __( 'Loading your optimized images...', 'optimole-wp' ),
849 - 'last' => __( 'Last', 'optimole-wp' ),
850 - 'optimized_images' => __( 'optimized images', 'optimole-wp' ),
851 - 'same_size' => __( '🙉 We couldn\'t do better, this image is already optimized at maximum. ', 'optimole-wp' ),
2376 + 'last_optimized_images' => __( 'Last optimized images', 'optimole-wp' ),
2377 + // translators: %s is the percentage (e.g. 10%).
2378 + 'percentage_saved' => sprintf( __( '%s Saved', 'optimole-wp' ), '{ratio}%' ),
2379 + // translators: %s is the percentage (e.g. 10%).
2380 + 'percentage_smaller' => sprintf( __( '%s smaller', 'optimole-wp' ), '{ratio}%' ),
2381 + 'same_size' => __( '🙉 We couldn\'t do better, this image is already optimized at maximum.', 'optimole-wp' ),
852 2382 'small_optimization' => __( '😬 Not that much, just <strong>{ratio}</strong> smaller.', 'optimole-wp' ),
853 2383 'medium_optimization' => __( '🤓 We are on the right track, <strong>{ratio}</strong> squeezed.', 'optimole-wp' ),
854 - 'big_optimization' => __( '❤️❤️❤️ Our moles just nailed it, this one is <strong>{ratio}</strong> smaller. ', 'optimole-wp' ),
2384 + 'big_optimization' => __( '❤️❤️❤️ Our moles just nailed it, this one is <strong>{ratio}</strong> smaller.', 'optimole-wp' ),
2385 + ],
2386 + 'csat' => [
2387 + 'close' => __( 'Close', 'optimole-wp' ),
2388 + ],
2389 + 'cron_error' => sprintf( /* translators: 1 is code to disable cron, 2 value of the constant */ __( 'It seems that you have the %1$s constant defined as %2$s. The offloading process uses cron events to offload the images in the background. Please remove the constant from your wp-config.php file in order for the offloading process to work.', 'optimole-wp' ), '<code>DISABLE_WP_CRON</code>', '<code>true</code>' ),
2390 + 'cancel' => __( 'Cancel', 'optimole-wp' ),
2391 + 'optimization_status' => [
2392 + 'title' => __( 'Optimization Status', 'optimole-wp' ),
2393 + 'statusTitle1' => __( 'Image Handling', 'optimole-wp' ),
2394 + 'statusSubTitle1' => __( 'All images are optimized automatically', 'optimole-wp' ),
2395 + 'statusTitle2' => __( 'Smart Lazy-Loading', 'optimole-wp' ),
2396 + 'statusSubTitle2' => __( 'Images load as visitors scroll', 'optimole-wp' ),
2397 + 'statusTitle3' => __( 'Image Scalling', 'optimole-wp' ),
2398 + 'statusSubTitle3' => __( 'All images are perfectly sized for devices', 'optimole-wp' ),
2399 + 'disable' => __( 'Disable', 'optimole-wp' ),
2400 + 'enable' => __( 'Enable', 'optimole-wp' ),
2401 + 'manage' => __( 'Manage', 'optimole-wp' ),
2402 + ],
2403 + 'optimization_tips' => sprintf(
2404 + /* translators: 1 is the opening anchor tag, 2 is the closing anchor tag */
2405 + __( '%1$sView all optimization tips%2$s', 'optimole-wp' ),
2406 + '<a class="flex justify-center items-center font-semibold" href="https://docs.optimole.com/article/2238-optimization-tips" target="_blank"> ',
2407 + '<span style="text-decoration:none; font-size:15px; margin-top:2px;" class="dashicons dashicons-external"></span></a>'
855 2408 ),
856 - );
2409 + 'contact_support' => [
2410 + // translators: %s is the email main subject.
2411 + 'title_prefix' => __( '[Lazy Load Issue] %s', 'optimole-wp' ),
2412 + 'disable_lazy_load_scaling' => __( 'Disable Lazy Load & Scaling', 'optimole-wp' ),
2413 + 'disable_image_scaling' => __( 'Disable Image Scaling', 'optimole-wp' ),
2414 + 'enable_native_lazy_load' => __( 'Enable Native Lazy Load', 'optimole-wp' ),
2415 + ],
2416 + // translators: %s is the date of the renewal.
2417 + 'renew_date' => __( 'Renews %s', 'optimole-wp' ),
2418 + ];
857 2419 }
858 2420
2421 + /**
2422 + * Allow SVG uploads
2423 + *
2424 + * @param array $mimes Supported mimes.
2425 + *
2426 + * @return array
2427 + * @access public
2428 + * @uses filter:upload_mimes
2429 + */
2430 + public function allow_svg( $mimes ) {
2431 + $mimes['svg'] = 'image/svg+xml';
2432 +
2433 + return $mimes;
2434 + }
2435 +
2436 + /**
2437 + * Get the Formbricks survey metadata.
2438 + *
2439 + * @param array $data The data in Formbricks format.
2440 + * @param string $page_slug The slug of the page.
2441 + *
2442 + * @return array - The data in Formbricks format.
2443 + */
2444 + public function get_survey_metadata( $data, $page_slug ) {
2445 +
2446 + if ( 'dashboard' !== $page_slug ) {
2447 + return $data;
2448 + }
2449 +
2450 + $dashboard_data = $this->localize_dashboard_app();
2451 + $user_data = $dashboard_data['user_data'];
2452 +
2453 + if ( ! isset( $user_data['plan'] ) ) {
2454 + return $data;
2455 + }
2456 +
2457 + $data = [
2458 + 'environmentId' => 'clo8wxwzj44orpm0gjchurujm',
2459 + 'attributes' => [
2460 + 'plan' => $user_data['plan'],
2461 + 'status' => $user_data['status'],
2462 + 'cname_assigned' => ! empty( $user_data['is_cname_assigned'] ) ? $user_data['is_cname_assigned'] : 'no',
2463 + 'connected_websites' => isset( $user_data['whitelist'] ) ? strval( count( $user_data['whitelist'] ) ) : '0',
2464 + 'install_days_number' => intval( $dashboard_data['days_since_install'] ),
2465 + 'traffic' => strval( isset( $user_data['traffic'] ) ? $this->survey_category( $user_data['traffic'], 500 ) : 0 ),
2466 + 'images_number' => strval( isset( $user_data['images_number'] ) ? $this->survey_category( $user_data['images_number'], 100 ) : 0 ),
2467 + ],
2468 + ];
2469 +
2470 + return $data;
2471 + }
2472 +
2473 + /**
2474 + * Categorize a number for survey based on its scale.
2475 + *
2476 + * @param int $value The value.
2477 + * @param int $scale The scale.
2478 + *
2479 + * @return int - The category.
2480 + */
2481 + public function survey_category( $value, $scale = 1 ) {
2482 + $value = intval( $value / $scale );
2483 +
2484 + if ( 1 < $value && 8 > $value ) {
2485 + return 7;
2486 + }
2487 +
2488 + if ( 8 <= $value && 31 > $value ) {
2489 + return 30;
2490 + }
2491 +
2492 + if ( 30 < $value && 90 > $value ) {
2493 + return 90;
2494 + }
2495 +
2496 + if ( 90 <= $value ) {
2497 + return 91;
2498 + }
2499 +
2500 + return 0;
2501 + }
2502 +
2503 + /**
2504 + * Determines whether the exceed quota warning should be displayed to users.
2505 + *
2506 + * This function checks if the user's quota usage has exceeded a predefined limit
2507 + * and returns a boolean value indicating whether the warning should be shown.
2508 + *
2509 + * @return bool True if the exceed quota warning should be displayed, false otherwise.
2510 + */
2511 + public function should_show_exceed_quota_warning() {
2512 + if ( ! $this->settings->is_connected() ) {
2513 + return false;
2514 + }
2515 + if ( get_option( 'optml_notice_hide_upg', 'no' ) === 'yes' ) {
2516 + return false;
2517 + }
2518 +
2519 + $service_data = $this->settings->get( 'service_data' );
2520 +
2521 + if ( ! isset( $service_data['plan'] ) ) {
2522 + return false;
2523 + }
2524 + if ( $service_data['plan'] !== 'free' ) {
2525 + return false;
2526 + }
2527 + if ( ! isset( $service_data['renews_on'] ) ) {
2528 + return false;
2529 + }
2530 + $renews_on = $service_data['renews_on'];
2531 + $timestamp_before_two_weeks = strtotime( '-2 weeks', $renews_on );
2532 + $today_timestamp = strtotime( 'today' );
2533 +
2534 + if ( $timestamp_before_two_weeks < $today_timestamp ) {
2535 + return false;
2536 + }
2537 +
2538 + $visitors_limit = isset( $service_data['visitors_limit'] ) ? (int) $service_data['visitors_limit'] : 0;
2539 + $visitors_left = isset( $service_data['visitors_left'] ) ? (int) $service_data['visitors_left'] : 0;
2540 +
2541 + if ( ! $visitors_limit || ! $visitors_left ) {
2542 + return false;
2543 + }
2544 +
2545 + $used_quota = $visitors_limit - $visitors_left;
2546 + $is_50_percent_used = ( $used_quota / $visitors_limit ) >= 0.5;
2547 +
2548 + if ( ! $is_50_percent_used ) {
2549 + return false;
2550 + }
2551 +
2552 + return true;
2553 + }
2554 +
2555 + /**
2556 + * Get the number of active notices (not dismissed).
2557 + *
2558 + * @return int - Number of active notices
2559 + */
2560 + private function get_active_notices_count() {
2561 + $conflicting_plugins = $this->conflicting_plugins->get_conflicting_plugins();
2562 + $conflicts_count = 0;
2563 +
2564 + foreach ( $conflicting_plugins as $key => $plugin ) {
2565 + $key = str_replace( 'wp-', '', $key );
2566 + $class_name = 'Optml_' . ucfirst( $key );
2567 +
2568 + if ( ! class_exists( $class_name ) ) {
2569 + continue;
2570 + }
2571 + $conflict_instance = new $class_name();
2572 +
2573 + if ( ! is_a( $conflict_instance, 'Optml_Abstract_Conflict' ) ) {
2574 + continue;
2575 + }
2576 +
2577 + if ( $conflict_instance->is_conflict_valid() ) {
2578 + ++$conflicts_count;
2579 + }
2580 + }
2581 +
2582 + $dismissed_notices = get_option( 'optml_dismissed_conflicts', [] );
2583 +
2584 + return $conflicts_count - count( $dismissed_notices );
2585 + }
2586 +
2587 + /**
2588 + * Mark if the user had offloading enabled on first run.
2589 + *
2590 + * If it is an old free user that had offloading enabled, we will use the mark to show a notice about the plan changes.
2591 + *
2592 + * @return void
2593 + */
2594 + public function mark_user_with_offload() {
2595 + if ( ! $this->settings->is_connected() ) {
2596 + return;
2597 + }
2598 +
2599 + if ( false !== get_option( 'optml_has_offloading_enabled_on_upgrade', false ) ) {
2600 + return;
2601 + }
2602 +
2603 + update_option( 'optml_has_offloading_enabled_on_upgrade', $this->settings->is_offload_enabled() ? 'yes' : 'no' );
2604 + }
859 2605 }