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