PluginProbe ʕ •ᴥ•ʔ
Oxyplug Preload / 2.1.3
Oxyplug Preload v2.1.3
2.2.1 2.2.0 trunk 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.5
oxyplug-preload / oxy-preload.php
oxyplug-preload Last commit date
assets 1 year ago oxy-preload.php 1 year ago package-lock.json 1 year ago package.json 1 year ago readme.txt 1 year ago vite.config.js 1 year ago
oxy-preload.php
769 lines
1 <?php
2 /**
3 * Plugin Name: Oxyplug Preload
4 * Plugin URI: https://www.oxyplug.com/products/oxy-preload
5 * Description: Preload post/page featured images and product images to enhance the Largest Contentful Paint (LCP) and achieve a better Core Web Vitals (CWV) score in Google's Lighthouse. Additionally, the tool supports preloading fonts, CSS, and JavaScript files when specified manually, allowing for even greater optimization of page load performance.
6 * Version: 2.1.3
7 * Author: Oxyplug
8 * Author URI: https://www.oxyplug.com
9 * Requires PHP: 7.4
10 * Requires at least: 4.9
11 * Tested up to: 6.8
12 * Text Domain: oxyplug-preload
13 * Domain Path: /lang/
14 * License: GPL v2 or later
15 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
16 *
17 * Copyright 2025 Oxyplug
18 */
19
20 if (!defined('ABSPATH')) {
21 exit;
22 }
23
24 /**
25 * Class OxyPreload
26 */
27 class OxyPreload
28 {
29 protected string $imgurl;
30 protected string $srcset;
31 protected string $sizes;
32 const OXYPLUG_PRELOAD_VERSION = '2.1.3';
33
34 public function __construct()
35 {
36 if (!defined('FS_CHMOD_FILE')) {
37 define('FS_CHMOD_FILE', 0644);
38 }
39
40 // Init on activate
41 register_activation_hook(__FILE__, array($this, 'activate_it'));
42 add_action('admin_init', array($this, 'init'));
43
44 // Add preload tag
45 add_action('plugins_loaded', array($this, 'check_required_plugin'));
46
47 // Save preloads
48 add_action('wp_ajax_oxyplug_preload_save_preloads', array($this, 'oxyplug_preload_save_preloads'));
49
50 // Add menu
51 add_action('admin_menu', array($this, 'add_menu'));
52
53 // Add settings in plugins page
54 add_filter('plugin_action_links', array($this, 'add_settings'), 10, 3);
55
56 // Add necessities in admin head
57 add_action('admin_head', array($this, 'admin_head'));
58
59 // Add admin assets
60 add_action('admin_enqueue_scripts', array($this, 'add_admin_assets'));
61 }
62
63 /**
64 * @return void
65 */
66 public function activate_it()
67 {
68 // Enable preloading `featured image` by default
69 $preload_featured_image = $this->oxyplug_preload_get_option('_oxyplug_preload_featured_image');
70 if (empty($preload_featured_image)) {
71 $this->oxyplug_preload_update_option('_oxyplug_preload_featured_image', 'true');
72 }
73
74 // Set a transient to indicate an update has occurred
75 set_transient('oxyplug_preload_updated', true, 30);
76 }
77
78 public function init()
79 {
80 // Check if we need to perform update tasks
81 if (get_transient('oxyplug_preload_updated')) {
82 // Delete the transient
83 delete_transient('oxyplug_preload_updated');
84
85 // Update .htaccess with improved rules if needed
86 $preloads = $this->oxyplug_preload_get_option('_oxyplug_preload_preloads', array());
87
88 if (!empty($preloads)) {
89 // Prepare data for htaccess generation
90 $htaccess_preloads = array();
91
92 foreach ($preloads as $type => $urls) {
93 if (!isset($htaccess_preloads[$type])) {
94 $htaccess_preloads[$type] = array();
95 }
96
97 foreach ($urls as $url) {
98 $htaccess_preloads[$type][] = $url;
99 }
100 }
101
102 // Generate htaccess content
103 $htaccess_content = $this->generate_htaccess_content($htaccess_preloads);
104
105 // Update .htaccess file
106 $this->update_htaccess($htaccess_content);
107 }
108 }
109 }
110
111 /**
112 * @return void
113 */
114 public function check_required_plugin()
115 {
116 if (!is_plugin_active('oxyplug-image/index.php')) {
117 add_action('wp_head', array($this, 'add_preload_tag'));
118 }
119 }
120
121 /**
122 * @return void
123 */
124 public function add_preload_tag()
125 {
126 $preload_featured_image = $this->oxyplug_preload_get_option('_oxyplug_preload_featured_image') == 'true';
127 if ($preload_featured_image) {
128 if (is_single() || is_page()) {
129 $thumbnail_id = (int)(get_post_thumbnail_id());
130 if ($thumbnail_id > 0) {
131 $this->imgurl = get_the_post_thumbnail_url();
132 } else if (function_exists('wc_get_product')) {
133 if ($product = wc_get_product(get_the_id())) {
134 $attachment_ids = $product->get_gallery_image_ids();
135 if (sizeof($attachment_ids) > 0) {
136 $thumbnail_id = reset($attachment_ids);
137 $this->imgurl = wp_get_attachment_url($thumbnail_id);
138 }
139 }
140 }
141
142 if ($thumbnail_id) {
143 $this->srcset = wp_get_attachment_image_srcset($thumbnail_id);
144 $this->sizes = wp_get_attachment_image_sizes($thumbnail_id, 'full');
145 ?>
146 <link rel="preload"
147 as="image"
148 href="<?php esc_attr_e($this->imgurl) ?>"
149 imagesrcset="<?php esc_attr_e($this->srcset) ?>"
150 imagesizes="<?php esc_attr_e($this->sizes) ?>"
151 fetchpriority="high">
152 <?php
153 }
154 }
155 }
156 }
157
158 /**
159 * @return void
160 */
161 public function admin_head()
162 {
163 // Load only in specific pages
164 $screen = get_current_screen();
165 if ($screen && $screen->base == 'tools_page_oxyplug-preload-settings') {
166 $component_path = plugins_url('assets/js/dist/', __FILE__);
167 $components = array(
168 'tools_page_oxyplug-preload-settings' => array(
169 'outlined-text-field',
170 'icon',
171 'icon-button',
172 'outlined-button',
173 'filled-button',
174 'divider',
175 'switch'
176 ),
177 );
178 $components = wp_json_encode($components[$screen->base]);
179 ?>
180 <link rel="preconnect" href="https://fonts.googleapis.com">
181 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
182 <link href="https://fonts.googleapis.com/css2?family=Oxygen:wght@300;400;700&display=swap" rel="stylesheet">
183 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
184
185 <script type="module">
186 // Load MD3 components
187 (async function () {
188 const OXYPLUG_PRELOAD_VERSION = '<?php echo self::OXYPLUG_PRELOAD_VERSION ?>'
189 const components = '<?php echo $components ?>'
190 const component_path = '<?php echo $component_path ?>'
191
192 for (const component of JSON.parse(components)) {
193 await import(`${component_path}${component}.js?ver=${OXYPLUG_PRELOAD_VERSION}`);
194 }
195 })();
196 </script>
197 <?php }
198 }
199
200 /**
201 * @return void
202 */
203 public function add_admin_assets()
204 {
205 wp_register_script('oxyplug-preload-admin-script', plugins_url('assets/js/admin-script.js', __FILE__), array('jquery'), self::OXYPLUG_PRELOAD_VERSION);
206 wp_enqueue_script('oxyplug-preload-admin-script');
207
208 wp_register_style(
209 'oxyplug-preload-admin-style',
210 plugins_url('assets/css/admin-style.css', __FILE__),
211 array(),
212 self::OXYPLUG_PRELOAD_VERSION
213 );
214 wp_enqueue_style('oxyplug-preload-admin-style');
215
216 wp_localize_script(
217 'oxyplug-preload-admin-script',
218 'oxyplug_preload_defines',
219 array(
220 'trans' => array(
221 'invalid_url' => __('Invalid URL', 'oxyplug-preload'),
222 )
223 )
224 );
225 }
226
227 /**
228 * @return void
229 */
230 public function add_menu()
231 {
232 add_submenu_page(
233 'tools.php',
234 'Oxyplug Preload',
235 'Oxyplug Preload',
236 'manage_options',
237 'oxyplug-preload-settings',
238 array($this, 'oxyplug_preload_settings')
239 );
240 }
241
242 /**
243 * @param $actions
244 * @param $plugin_file
245 * @param $plugin_data
246 *
247 * @return mixed
248 */
249 public function add_settings($actions, $plugin_file, $plugin_data)
250 {
251 if (isset($plugin_data['slug']) && $plugin_data['slug'] == 'oxyplug-preload') {
252 $href = admin_url('tools.php?page=oxyplug-preload-settings');
253
254 $actions['Settings'] = '<a href="' . $href . '">' . __('Settings', 'oxyplug-preload') . '</a>';
255 }
256
257 return $actions;
258 }
259
260 /**
261 * @return void
262 */
263 public function oxyplug_preload_settings()
264 {
265 $preloads = $this->oxyplug_preload_get_option('_oxyplug_preload_preloads', array()); ?>
266
267 <div class="oxyplug-preload-admin-page">
268 <section class="oxyplug-preload-admin-head">
269 <h1 class="oxyplug-preload-head-title">
270 <span class="oxyplug-preload-brand-highlight">
271 <?php esc_html_e('Oxyplug Preload', 'oxyplug-preload') ?>
272 </span>
273 <span>|</span>
274 <span>
275 <?php esc_html_e('Settings', 'oxyplug-preload') ?>
276 </span>
277 </h1>
278
279 <div class="oxyplug-preload-need-help">
280 <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
281 <path
282 d="M7.59161 31.5676L8.1794 30.7586L7.59161 31.5676ZM5.93237 29.9084L6.74139 29.3206L5.93237 29.9084ZM30.0676 29.9084L29.2586 29.3206L30.0676 29.9084ZM28.4084 31.5676L27.8206 30.7586L28.4084 31.5676ZM28.4084 4.43237L28.9962 3.62336L28.4084 4.43237ZM30.0676 6.09161L29.2586 6.6794L30.0676 6.09161ZM7.59161 4.43237L8.1794 5.24139L7.59161 4.43237ZM5.93237 6.09161L6.74139 6.6794L5.93237 6.09161ZM19.7192 9.89296L19.8756 10.8807L19.7192 9.89296ZM17.3727 9.89296L17.2163 10.8807L17.3727 9.89296ZM12 23C11.4477 23 11 23.4477 11 24C11 24.5523 11.4477 25 12 25V23ZM24 25C24.5523 25 25 24.5523 25 24C25 23.4477 24.5523 23 24 23V25ZM12 17C11.4477 17 11 17.4477 11 18C11 18.5523 11.4477 19 12 19V17ZM16.5 19C17.0523 19 17.5 18.5523 17.5 18C17.5 17.4477 17.0523 17 16.5 17V19ZM30.5 16.5V19.5H32.5V16.5H30.5ZM5.5 19.5V16.5H3.5V19.5H5.5ZM18 32C15.1654 32 13.1198 31.9986 11.5336 31.8268C9.9661 31.6569 8.96626 31.3303 8.1794 30.7586L7.00383 32.3766C8.18845 33.2373 9.58051 33.6269 11.3182 33.8151C13.0371 34.0014 15.21 34 18 34V32ZM3.5 19.5C3.5 22.29 3.49863 24.4629 3.68486 26.1818C3.87313 27.9195 4.26267 29.3116 5.12336 30.4962L6.74139 29.3206C6.1697 28.5337 5.84306 27.5339 5.67323 25.9664C5.50137 24.3802 5.5 22.3346 5.5 19.5H3.5ZM8.1794 30.7586C7.62758 30.3577 7.14231 29.8724 6.74139 29.3206L5.12336 30.4962C5.64763 31.2178 6.28222 31.8524 7.00383 32.3766L8.1794 30.7586ZM30.5 19.5C30.5 22.3346 30.4986 24.3802 30.3268 25.9664C30.1569 27.5339 29.8303 28.5337 29.2586 29.3206L30.8766 30.4962C31.7373 29.3116 32.1269 27.9195 32.3151 26.1818C32.5014 24.4629 32.5 22.29 32.5 19.5H30.5ZM18 34C20.79 34 22.9629 34.0014 24.6818 33.8151C26.4195 33.6269 27.8115 33.2373 28.9962 32.3766L27.8206 30.7586C27.0337 31.3303 26.0339 31.6569 24.4664 31.8268C22.8802 31.9986 20.8346 32 18 32V34ZM29.2586 29.3206C28.8577 29.8724 28.3724 30.3577 27.8206 30.7586L28.9962 32.3766C29.7178 31.8524 30.3524 31.2178 30.8766 30.4962L29.2586 29.3206ZM32.5 16.5C32.5 13.71 32.5014 11.5371 32.3151 9.81818C32.1269 8.08051 31.7373 6.68845 30.8766 5.50383L29.2586 6.6794C29.8303 7.46626 30.1569 8.4661 30.3268 10.0336C30.4986 11.6198 30.5 13.6654 30.5 16.5H32.5ZM27.8206 5.24139C28.3724 5.64231 28.8577 6.12758 29.2586 6.6794L30.8766 5.50383C30.3524 4.78222 29.7178 4.14763 28.9962 3.62336L27.8206 5.24139ZM5.5 16.5C5.5 13.6654 5.50137 11.6198 5.67323 10.0336C5.84306 8.4661 6.1697 7.46626 6.74139 6.6794L5.12336 5.50383C4.26267 6.68845 3.87313 8.08051 3.68486 9.81818C3.49863 11.5371 3.5 13.71 3.5 16.5H5.5ZM7.00383 3.62336C6.28222 4.14763 5.64763 4.78222 5.12336 5.50383L6.74139 6.6794C7.14231 6.12758 7.62758 5.64231 8.1794 5.24139L7.00383 3.62336ZM19.5628 8.90528C18.8891 9.01198 18.2028 9.01198 17.5291 8.90528L17.2163 10.8807C18.0972 11.0202 18.9947 11.0202 19.8756 10.8807L19.5628 8.90528ZM12 25H24V23H12V25ZM12 19H16.5V17H12V19ZM26.9539 3.26967C25.0951 5.12711 23.7338 6.46779 22.5558 7.39555C21.3926 8.31159 20.4881 8.75872 19.5628 8.90528L19.8756 10.8807C21.2687 10.66 22.4884 9.99435 23.7932 8.9668C25.0831 7.95097 26.5334 6.51727 28.3676 4.68439L26.9539 3.26967ZM18 4C20.4907 4 22.3761 4.00071 23.8821 4.11906C25.3848 4.23715 26.4116 4.46712 27.2105 4.86993L28.111 3.08413C26.9722 2.50991 25.6443 2.25137 24.0388 2.1252C22.4366 1.99929 20.4605 2 18 2V4ZM27.2105 4.86993C27.4269 4.97906 27.6289 5.1021 27.8206 5.24139L28.9962 3.62336C28.7158 3.41966 28.4216 3.24078 28.111 3.08413L27.2105 4.86993ZM8.40124 4.3614C10.3389 6.29902 11.8529 7.81034 13.1857 8.87708C14.5333 9.95562 15.7832 10.6537 17.2163 10.8807L17.5291 8.90528C16.5773 8.75451 15.6476 8.28574 14.4355 7.31562C13.2086 6.33371 11.7829 4.91456 9.81542 2.94716L8.40124 4.3614ZM18 2C15.8443 2 14.0633 1.99964 12.5843 2.08338C11.1063 2.16707 9.85915 2.33736 8.78216 2.70897L9.4345 4.59959C10.2537 4.31692 11.2865 4.16007 12.6974 4.08019C14.1073 4.00036 15.824 4 18 4V2ZM8.78216 2.70897C8.1318 2.93337 7.54439 3.23061 7.00383 3.62336L8.1794 5.24139C8.54517 4.97564 8.95294 4.76575 9.4345 4.59959L8.78216 2.70897Z"
283 fill="#2D2D2D"></path>
284 </svg>
285 <div>
286 <span><?php esc_html_e('Need Help Or Have Questions?', 'oxyplug-preload') ?></span>
287 <a class="oxyplug-preload-a" href="https://www.oxyplug.com/docs/oxy-preload/" target="_blank">
288 <?php esc_html_e('Check our documentation.', 'oxyplug-preload') ?>
289 </a>
290 </div>
291 </div>
292 </section>
293
294 <div class="oxyplug-preload-in-row">
295 <div class="oxyplug-preload-card">
296 <h2>
297 <?php esc_html_e('Script Preload', 'oxyplug-preload') ?>
298 <i class="dashicons dashicons-editor-help oxyplug-preload-has-tooltip"
299 data-tooltip="<?php esc_attr_e('Preload scripts', 'oxyplug-preload') ?>"
300 data-href="https://www.oxyplug.com/docs/oxy-preload/settings/?utm_source=plugin-settings&utm_medium=wordpress&utm_campaign=oxyplug-preload#preload-settings"
301 data-href-text="<?php esc_attr_e('Learn More', 'oxyplug-preload'); ?>"></i>
302 </h2>
303
304 <?php if (empty($preloads['script'])): ?>
305 <div class="oxyplug-preload-input-wrap">
306 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
307 name="preloads[script][]"
308 label="<?php esc_attr_e('Script URL', 'oxyplug-preload'); ?>"
309 placeholder="https://www.example.com/wp-content/my-script.js"
310 type="url">
311 <md-icon-button toggle slot="trailing-icon" type="button">
312 <md-icon>cancel</md-icon>
313 </md-icon-button>
314 </md-outlined-text-field>
315 <md-icon-button class="oxyplug-preload-remove-url"
316 toggle
317 slot="trailing-icon"
318 type="button"
319 style="display:none">
320 <md-icon>delete</md-icon>
321 </md-icon-button>
322 </div>
323 <?php else: ?>
324 <?php foreach ($preloads['script'] as $index => $link): ?>
325 <div class="oxyplug-preload-input-wrap">
326 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
327 name="preloads[script][]"
328 value="<?php echo esc_url($link) ?>"
329 label="Script URL"
330 placeholder="https://www.example.com/wp-content/my-script.js"
331 type="url">
332 <md-icon-button toggle slot="trailing-icon" type="button">
333 <md-icon>cancel</md-icon>
334 </md-icon-button>
335 </md-outlined-text-field>
336 <md-icon-button class="oxyplug-preload-remove-url"
337 toggle
338 slot="trailing-icon"
339 type="button"
340 <?php if ($index == 0): ?> style="display:none" <?php endif; ?>>
341 <md-icon>delete</md-icon>
342 </md-icon-button>
343 </div>
344 <?php endforeach; ?>
345 <?php endif; ?>
346 <md-outlined-button class="oxyplug-preload-add-more">
347 <?php esc_html_e('Add More Script URL', 'oxyplug-preload') ?>
348 </md-outlined-button>
349
350 </div>
351 <div class="oxyplug-preload-card">
352 <h2>
353 <?php esc_html_e('Style Preload', 'oxyplug-preload') ?>
354 <i class="dashicons dashicons-editor-help oxyplug-preload-has-tooltip"
355 data-tooltip="<?php esc_attr_e('Preload scripts', 'oxyplug-preload') ?>"
356 data-href="https://www.oxyplug.com/docs/oxy-preload/settings/?utm_source=plugin-settings&utm_medium=wordpress&utm_campaign=oxyplug-preload#preload-settings"
357 data-href-text="<?php esc_attr_e('Learn More', 'oxyplug-preload'); ?>"></i>
358 </h2>
359
360 <?php if (empty($preloads['style'])): ?>
361 <div class="oxyplug-preload-input-wrap">
362 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
363 name="preloads[style][]"
364 label="<?php esc_attr_e('Style URL', 'oxyplug-preload'); ?>"
365 placeholder="https://www.example.com/wp-content/my-style.css"
366 type="url">
367 <md-icon-button toggle slot="trailing-icon" type="button">
368 <md-icon>cancel</md-icon>
369 </md-icon-button>
370 </md-outlined-text-field>
371 <md-icon-button class="oxyplug-preload-remove-url"
372 toggle
373 slot="trailing-icon"
374 type="button"
375 style="display:none">
376 <md-icon>delete</md-icon>
377 </md-icon-button>
378 </div>
379 <?php else: ?>
380 <?php foreach ($preloads['style'] as $index => $link): ?>
381 <div class="oxyplug-preload-input-wrap">
382 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
383 name="preloads[style][]"
384 value="<?php echo esc_url($link) ?>"
385 label="<?php esc_attr_e('Style URL', 'oxyplug-preload'); ?>"
386 placeholder="https://www.example.com/wp-content/my-style.css"
387 type="url">
388 <md-icon-button toggle slot="trailing-icon" type="button">
389 <md-icon>cancel</md-icon>
390 </md-icon-button>
391 </md-outlined-text-field>
392 <md-icon-button class="oxyplug-preload-remove-url"
393 toggle
394 slot="trailing-icon"
395 type="button"
396 <?php if ($index == 0): ?> style="display:none" <?php endif; ?>>
397 <md-icon>delete</md-icon>
398 </md-icon-button>
399 </div>
400 <?php endforeach; ?>
401 <?php endif; ?>
402 <md-outlined-button class="oxyplug-preload-add-more">
403 <?php esc_html_e('Add More Style URL', 'oxyplug-preload') ?>
404 </md-outlined-button>
405
406 </div>
407 </div>
408
409 <div class="oxyplug-preload-in-row">
410 <div class="oxyplug-preload-card">
411 <h2>
412 <?php esc_html_e('Font Preload', 'oxyplug-preload') ?>
413 <i class="dashicons dashicons-editor-help oxyplug-preload-has-tooltip"
414 data-tooltip="<?php esc_attr_e('Preload fonts', 'oxyplug-preload') ?>"
415 data-href="https://www.oxyplug.com/docs/oxy-preload/settings/?utm_source=plugin-settings&utm_medium=wordpress&utm_campaign=oxyplug-preload#preload-settings"
416 data-href-text="<?php esc_attr_e('Learn More', 'oxyplug-preload'); ?>"></i>
417 </h2>
418
419 <?php if (empty($preloads['font'])): ?>
420 <div class="oxyplug-preload-input-wrap">
421 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
422 name="preloads[font][]"
423 label="<?php esc_attr_e('Font URL', 'oxyplug-preload'); ?>"
424 placeholder="https://www.example.com/wp-content/my-font.woff2"
425 type="url">
426 <md-icon-button toggle slot="trailing-icon" type="button">
427 <md-icon>cancel</md-icon>
428 </md-icon-button>
429 </md-outlined-text-field>
430 <md-icon-button class="oxyplug-preload-remove-url"
431 toggle
432 slot="trailing-icon"
433 type="button"
434 style="display:none">
435 <md-icon>delete</md-icon>
436 </md-icon-button>
437 </div>
438 <?php else: ?>
439 <?php foreach ($preloads['font'] as $index => $link): ?>
440 <div class="oxyplug-preload-input-wrap">
441 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
442 name="preloads[font][]"
443 value="<?php echo esc_url($link) ?>"
444 label="<?php esc_attr_e('Font URL', 'oxyplug-preload'); ?>"
445 placeholder="https://www.example.com/wp-content/my-font.woff2"
446 type="url">
447 <md-icon-button toggle slot="trailing-icon" type="button">
448 <md-icon>cancel</md-icon>
449 </md-icon-button>
450 </md-outlined-text-field>
451 <md-icon-button class="oxyplug-preload-remove-url"
452 toggle
453 slot="trailing-icon"
454 type="button"
455 <?php if ($index == 0): ?> style="display:none" <?php endif; ?>>
456 <md-icon>delete</md-icon>
457 </md-icon-button>
458 </div>
459 <?php endforeach; ?>
460 <?php endif; ?>
461 <md-outlined-button class="oxyplug-preload-add-more">
462 <?php esc_html_e('Add More Font URL', 'oxyplug-preload') ?>
463 </md-outlined-button>
464 </div>
465 <div class="oxyplug-preload-card oxyplug-preload-self-height">
466 <h2>
467 <?php esc_html_e('Featured Image Preload', 'oxyplug-preload') ?>
468 <i class="dashicons dashicons-editor-help oxyplug-preload-has-tooltip"
469 data-tooltip="<?php esc_attr_e('Preload featured image', 'oxyplug-preload') ?>"
470 data-href="https://www.oxyplug.com/docs/oxy-preload/settings/?utm_source=plugin-settings&utm_medium=wordpress&utm_campaign=oxyplug-preload#preload-settings"
471 data-href-text="<?php esc_attr_e('Learn More', 'oxyplug-preload'); ?>"></i>
472 </h2>
473
474 <div class="oxyplug-preload-switch-wrap">
475 <md-switch icons
476 name="featured_image_preload" <?php if ($this->oxyplug_preload_get_option('_oxyplug_preload_featured_image', 'true') == 'true'): ?> selected <?php endif ?>></md-switch>
477 <span><?php esc_html_e('Preload featured image automatically? (No need to add URLs)', 'oxyplug-preload'); ?></span>
478 </div>
479 </div>
480 </div>
481
482 <md-divider class="oxyplug-preload-horizontal-divider"></md-divider>
483
484 <md-filled-button id="oxyplug-preload-save-preloads"
485 class="oxyplug-preload-has-loading"
486 data-nonce="<?php echo esc_attr(wp_create_nonce('oxyplug_preload_save_preloads')) ?>">
487 <?php esc_html_e('Save', 'oxyplug-preload'); ?>
488 </md-filled-button>
489 <div class="oxyplug-preload-spinner-wrap">
490 <div class="oxyplug-preload-spinner"></div>
491 <p><?php esc_html_e('Saving...Please wait.', 'oxyplug-preload'); ?></p>
492 </div>
493
494 </div>
495 <?php }
496
497 /**
498 * @return void
499 */
500 public function oxyplug_preload_save_preloads()
501 {
502 if (!empty($_POST['oxyplug_preload_save_preloads_nonce'])) {
503 $sanitized_nonce = sanitize_text_field(wp_unslash($_POST['oxyplug_preload_save_preloads_nonce']));
504 if (wp_verify_nonce($sanitized_nonce, 'oxyplug_preload_save_preloads')) {
505
506 // Featured image preload
507 $preload_featured_image = empty($_POST['featured_image_preload']) ? 'false' : 'true';
508 $this->oxyplug_preload_update_option('_oxyplug_preload_featured_image', $preload_featured_image);
509
510 // CSS/JS/Font preload
511 if (!empty($_POST['preloads']) && is_array($_POST['preloads'])) {
512 $valid_links = array();
513 $htaccess_preloads = array();
514 $link_regex = '/^(https?:\/\/([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(\/[-a-zA-Z0-9@:%._+~#=]*)*(\?[a-zA-Z0-9=&._-]*)?)?$/';
515
516 foreach ($_POST['preloads'] as $key => $links) {
517 if (in_array((string)($key), array('script', 'style', 'font'))) {
518 foreach ($links as $link) {
519 $is_valid = preg_match($link_regex, $link);
520 if (!empty(trim($link)) && $is_valid) {
521 $md5link = md5($link);
522 if (!isset($valid_links[$key][$md5link])) {
523 $escaped_url = esc_url($link);
524 $valid_links[$key][$md5link] = $escaped_url;
525
526 // Store for htaccess generation
527 if (!isset($htaccess_preloads[$key])) {
528 $htaccess_preloads[$key] = array();
529 }
530 $htaccess_preloads[$key][] = $escaped_url;
531 }
532 }
533 }
534
535 if (isset($valid_links[$key])) {
536 $valid_links[$key] = array_values($valid_links[$key]);
537 }
538 }
539 }
540
541 // Generate htaccess content
542 $htaccess_content = $this->generate_htaccess_content($htaccess_preloads);
543
544 // Add preload URLs to .htaccess file
545 $write_to_htaccess_result = $this->update_htaccess($htaccess_content);
546 if ($write_to_htaccess_result !== true) {
547 wp_send_json(array('messages' => array($write_to_htaccess_result)), 500);
548 }
549
550 // Insert preload URLs into the database
551 $this->oxyplug_preload_update_option('_oxyplug_preload_preloads', $valid_links);
552 }
553
554 wp_send_json_success(array('messages' => array(esc_html__('Successfully saved.', 'oxyplug-preload'))), 200);
555 }
556
557 wp_send_json(array('messages' => array(esc_html__('Wrong wpnonce. Refresh the page.', 'oxyplug-preload'))), 403);
558 }
559 }
560
561 /**
562 * Generate .htaccess content for preloading
563 *
564 * @param array $preloads Array of preload URLs grouped by type
565 * @return string Generated .htaccess content
566 */
567 private function generate_htaccess_content($preloads)
568 {
569 $htaccess_content = '';
570
571 // Add preload directives
572 foreach ($preloads as $type => $urls) {
573 foreach ($urls as $url) {
574 $htaccess_content .= " Header append Link \"<$url>; rel=preload; as=$type";
575 if ($type == 'font') {
576 $htaccess_content .= '; crossorigin';
577 }
578 $htaccess_content .= "\"\n";
579 }
580 }
581
582 if (!empty($htaccess_content)) {
583 $htaccess_content =
584 ' <FilesMatch "index\.(html|htm|php)$">' . "\n" .
585 ' <IfModule mod_headers.c>' . "\n" .
586 $htaccess_content .
587 ' </IfModule>' . "\n" .
588 ' </FilesMatch>' . "\n";
589 }
590
591 return $htaccess_content;
592 }
593
594 /**
595 * @param $htaccess_content
596 *
597 * @return string|true
598 */
599 private function update_htaccess($htaccess_content)
600 {
601 $htaccess_path = ABSPATH . '.htaccess';
602 global $wp_filesystem;
603
604 if (!$wp_filesystem) {
605 require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
606 require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
607 $wp_filesystem = new \WP_Filesystem_Direct(null);
608 }
609
610 if (!$wp_filesystem->exists($htaccess_path) && $this->is_server(array('iis', 'nginx'))) {
611 wp_send_json(array('messages' => array(esc_html__('Your server does not support .htaccess file.', 'oxyplug-preload'))), 422);
612 }
613
614 if ($wp_filesystem->is_writable($htaccess_path) && $wp_filesystem->is_readable($htaccess_path)) {
615 $htaccess_backup_path = $htaccess_path . '.oxybackup';
616
617 try {
618 // Read current content
619 $current_content = $wp_filesystem->get_contents($htaccess_path);
620 if ($current_content === false) {
621 throw new Exception(esc_html__('Could not read .htaccess file.', 'oxyplug-preload'));
622 }
623
624 // Remove existing Oxyplug Preload section
625 $pattern = '/\n*# BEGIN Oxyplug Preload\n.*?# END Oxyplug Preload\n*/s';
626 $current_content = preg_replace($pattern, '', $current_content);
627 $current_content = rtrim($current_content);
628
629 if (!empty($htaccess_content)) {
630 // Create new section with headers
631 $section = "\n\n# BEGIN Oxyplug Preload\n";
632 $section .= $htaccess_content;
633 $section .= "# END Oxyplug Preload\n";
634
635 // Append the new section to the content
636 $htaccess_content = $current_content . $section;
637 } else {
638 $htaccess_content = $current_content;
639 }
640
641 // Create backup before making changes
642 if (!$wp_filesystem->copy($htaccess_path, $htaccess_backup_path, true)) {
643 throw new Exception(esc_html__('Failed to create .htaccess backup file.', 'oxyplug-preload'));
644 }
645
646 // Write new content
647 if (!$wp_filesystem->put_contents($htaccess_path, $htaccess_content)) {
648 throw new Exception(esc_html__('Failed to write new .htaccess content.', 'oxyplug-preload'));
649 }
650
651 // Quick site check for errors or 500 status
652 if (!$this->check_site_availability()) {
653 throw new Exception(esc_html__('Site check failed after .htaccess update.', 'oxyplug-preload'));
654 }
655
656 // Success - clean up backup
657 $wp_filesystem->delete($htaccess_backup_path);
658 return true;
659
660 } catch (Exception $e) {
661 // Restore backup if available
662 if ($wp_filesystem->exists($htaccess_backup_path)) {
663 $wp_filesystem->copy($htaccess_backup_path, $htaccess_path, true);
664 $wp_filesystem->delete($htaccess_backup_path);
665 }
666
667 return esc_html__('Oxyplug Preload .htaccess update failed: ' . $e->getMessage(), 'oxyplug-preload');
668 }
669 }
670
671 return esc_html__('Oxyplug Preload .htaccess update failed.', 'oxyplug-preload');
672 }
673
674 /**
675 * @return bool
676 */
677 private function check_site_availability(): bool
678 {
679 // Try REST url first (fastest)
680 $rest_url = get_rest_url();
681 $response = wp_remote_head($rest_url, [
682 'timeout' => 5,
683 'redirection' => 0,
684 'sslverify' => false
685 ]);
686
687 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) < 500) {
688 return true;
689 }
690
691 // Fallback to admin-ajax.php
692 $response = wp_remote_head(admin_url('admin-ajax.php'), [
693 'timeout' => 5,
694 'redirection' => 0,
695 'sslverify' => false
696 ]);
697
698 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) < 500) {
699 return true;
700 }
701
702 // Last resort - check homepage
703 $response = wp_remote_head(home_url(), [
704 'timeout' => 5,
705 'redirection' => 0,
706 'sslverify' => false
707 ]);
708
709 return !is_wp_error($response) && wp_remote_retrieve_response_code($response) < 500;
710 }
711
712 /**
713 * @param array $servers
714 *
715 * @return bool
716 */
717 private function is_server(array $servers): bool
718 {
719 $server = '';
720 $server_software = strtolower(sanitize_text_field($_SERVER['SERVER_SOFTWARE']));
721 if (strpos($server_software, 'apache') !== false) {
722 $server = 'apache';
723 } elseif (strpos($server_software, 'litespeed') !== false) {
724 $server = 'litespeed';
725 } elseif (strpos($server_software, 'nginx') !== false) {
726 $server = 'nginx';
727 } elseif (strpos($server_software, 'microsoft-iis') !== false || strpos($server_software, 'expressiondevserver') !== false) {
728 $server = 'iis';
729 }
730
731 return in_array($server, $servers);
732 }
733
734 /**
735 * @param $option_name
736 * @param $default
737 *
738 * @return false|mixed|void
739 */
740 protected function oxyplug_preload_get_option($option_name, $default = false)
741 {
742 if (is_multisite()) {
743 $network_id = get_current_blog_id();
744
745 return get_network_option($network_id, $option_name, $default);
746 }
747
748 return get_option($option_name, $default);
749 }
750
751 /**
752 * @param $option_name
753 * @param $option_value
754 *
755 * @return void
756 */
757 protected function oxyplug_preload_update_option($option_name, $option_value): void
758 {
759 if (is_multisite()) {
760 update_network_option(get_current_blog_id(), $option_name, $option_value);
761 } else {
762 update_option($option_name, $option_value);
763 }
764 }
765 }
766
767 new OxyPreload();
768
769