PluginProbe ʕ •ᴥ•ʔ
Oxyplug Preload / 2.1.2
Oxyplug Preload v2.1.2
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
759 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.2
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.2';
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('wp_head', array($this, 'add_preload_tag'));
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 add_preload_tag()
115 {
116 $preload_featured_image = $this->oxyplug_preload_get_option('_oxyplug_preload_featured_image') == 'true';
117 if ($preload_featured_image) {
118 if (is_single() || is_page()) {
119 $thumbnail_id = (int)(get_post_thumbnail_id());
120 if ($thumbnail_id > 0) {
121 $this->imgurl = get_the_post_thumbnail_url();
122 } else if (function_exists('wc_get_product')) {
123 if ($product = wc_get_product(get_the_id())) {
124 $attachment_ids = $product->get_gallery_image_ids();
125 if (sizeof($attachment_ids) > 0) {
126 $thumbnail_id = reset($attachment_ids);
127 $this->imgurl = wp_get_attachment_url($thumbnail_id);
128 }
129 }
130 }
131
132 if ($thumbnail_id) {
133 $this->srcset = wp_get_attachment_image_srcset($thumbnail_id);
134 $this->sizes = wp_get_attachment_image_sizes($thumbnail_id, 'full');
135 ?>
136 <link rel="preload"
137 as="image"
138 href="<?php esc_attr_e($this->imgurl) ?>"
139 imagesrcset="<?php esc_attr_e($this->srcset) ?>"
140 imagesizes="<?php esc_attr_e($this->sizes) ?>"
141 fetchpriority="high">
142 <?php
143 }
144 }
145 }
146 }
147
148 /**
149 * @return void
150 */
151 public function admin_head()
152 {
153 // Load only in specific pages
154 $screen = get_current_screen();
155 if ($screen && $screen->base == 'tools_page_oxyplug-preload-settings') {
156 $component_path = plugins_url('assets/js/dist/', __FILE__);
157 $components = array(
158 'tools_page_oxyplug-preload-settings' => array(
159 'outlined-text-field',
160 'icon',
161 'icon-button',
162 'outlined-button',
163 'filled-button',
164 'divider',
165 'switch'
166 ),
167 );
168 $components = wp_json_encode($components[$screen->base]);
169 ?>
170 <link rel="preconnect" href="https://fonts.googleapis.com">
171 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
172 <link href="https://fonts.googleapis.com/css2?family=Oxygen:wght@300;400;700&display=swap" rel="stylesheet">
173 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
174
175 <script type="module">
176 // Load MD3 components
177 (async function () {
178 const OXYPLUG_PRELOAD_VERSION = '<?php echo self::OXYPLUG_PRELOAD_VERSION ?>'
179 const components = '<?php echo $components ?>'
180 const component_path = '<?php echo $component_path ?>'
181
182 for (const component of JSON.parse(components)) {
183 await import(`${component_path}${component}.js?ver=${OXYPLUG_PRELOAD_VERSION}`);
184 }
185 })();
186 </script>
187 <?php }
188 }
189
190 /**
191 * @return void
192 */
193 public function add_admin_assets()
194 {
195 wp_register_script('oxyplug-preload-admin-script', plugins_url('assets/js/admin-script.js', __FILE__), array('jquery'), self::OXYPLUG_PRELOAD_VERSION);
196 wp_enqueue_script('oxyplug-preload-admin-script');
197
198 wp_register_style(
199 'oxyplug-preload-admin-style',
200 plugins_url('assets/css/admin-style.css', __FILE__),
201 array(),
202 self::OXYPLUG_PRELOAD_VERSION
203 );
204 wp_enqueue_style('oxyplug-preload-admin-style');
205
206 wp_localize_script(
207 'oxyplug-preload-admin-script',
208 'oxyplug_preload_defines',
209 array(
210 'trans' => array(
211 'invalid_url' => __('Invalid URL', 'oxyplug-preload'),
212 )
213 )
214 );
215 }
216
217 /**
218 * @return void
219 */
220 public function add_menu()
221 {
222 add_submenu_page(
223 'tools.php',
224 'Oxyplug Preload',
225 'Oxyplug Preload',
226 'manage_options',
227 'oxyplug-preload-settings',
228 array($this, 'oxyplug_preload_settings')
229 );
230 }
231
232 /**
233 * @param $actions
234 * @param $plugin_file
235 * @param $plugin_data
236 *
237 * @return mixed
238 */
239 public function add_settings($actions, $plugin_file, $plugin_data)
240 {
241 if (isset($plugin_data['slug']) && $plugin_data['slug'] == 'oxyplug-preload') {
242 $href = admin_url('tools.php?page=oxyplug-preload-settings');
243
244 $actions['Settings'] = '<a href="' . $href . '">' . __('Settings', 'oxyplug-preload') . '</a>';
245 }
246
247 return $actions;
248 }
249
250 /**
251 * @return void
252 */
253 public function oxyplug_preload_settings()
254 {
255 $preloads = $this->oxyplug_preload_get_option('_oxyplug_preload_preloads', array()); ?>
256
257 <div class="oxyplug-preload-admin-page">
258 <section class="oxyplug-preload-admin-head">
259 <h1 class="oxyplug-preload-head-title">
260 <span class="oxyplug-preload-brand-highlight">
261 <?php esc_html_e('Oxyplug Preload', 'oxyplug-preload') ?>
262 </span>
263 <span>|</span>
264 <span>
265 <?php esc_html_e('Settings', 'oxyplug-preload') ?>
266 </span>
267 </h1>
268
269 <div class="oxyplug-preload-need-help">
270 <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
271 <path
272 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"
273 fill="#2D2D2D"></path>
274 </svg>
275 <div>
276 <span><?php esc_html_e('Need Help Or Have Questions?', 'oxyplug-preload') ?></span>
277 <a class="oxyplug-preload-a" href="https://www.oxyplug.com/docs/oxy-preload/" target="_blank">
278 <?php esc_html_e('Check our documentation.', 'oxyplug-preload') ?>
279 </a>
280 </div>
281 </div>
282 </section>
283
284 <div class="oxyplug-preload-in-row">
285 <div class="oxyplug-preload-card">
286 <h2>
287 <?php esc_html_e('Script Preload', 'oxyplug-preload') ?>
288 <i class="dashicons dashicons-editor-help oxyplug-preload-has-tooltip"
289 data-tooltip="<?php esc_attr_e('Preload scripts', 'oxyplug-preload') ?>"
290 data-href="https://www.oxyplug.com/docs/oxy-preload/settings/?utm_source=plugin-settings&utm_medium=wordpress&utm_campaign=oxyplug-preload#preload-settings"
291 data-href-text="<?php esc_attr_e('Learn More', 'oxyplug-preload'); ?>"></i>
292 </h2>
293
294 <?php if (empty($preloads['script'])): ?>
295 <div class="oxyplug-preload-input-wrap">
296 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
297 name="preloads[script][]"
298 label="<?php esc_attr_e('Script URL', 'oxyplug-preload'); ?>"
299 placeholder="https://www.example.com/wp-content/my-script.js"
300 type="url">
301 <md-icon-button toggle slot="trailing-icon" type="button">
302 <md-icon>cancel</md-icon>
303 </md-icon-button>
304 </md-outlined-text-field>
305 <md-icon-button class="oxyplug-preload-remove-url"
306 toggle
307 slot="trailing-icon"
308 type="button"
309 style="display:none">
310 <md-icon>delete</md-icon>
311 </md-icon-button>
312 </div>
313 <?php else: ?>
314 <?php foreach ($preloads['script'] as $index => $link): ?>
315 <div class="oxyplug-preload-input-wrap">
316 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
317 name="preloads[script][]"
318 value="<?php echo esc_url($link) ?>"
319 label="Script URL"
320 placeholder="https://www.example.com/wp-content/my-script.js"
321 type="url">
322 <md-icon-button toggle slot="trailing-icon" type="button">
323 <md-icon>cancel</md-icon>
324 </md-icon-button>
325 </md-outlined-text-field>
326 <md-icon-button class="oxyplug-preload-remove-url"
327 toggle
328 slot="trailing-icon"
329 type="button"
330 <?php if ($index == 0): ?> style="display:none" <?php endif; ?>>
331 <md-icon>delete</md-icon>
332 </md-icon-button>
333 </div>
334 <?php endforeach; ?>
335 <?php endif; ?>
336 <md-outlined-button class="oxyplug-preload-add-more">
337 <?php esc_html_e('Add More Script URL', 'oxyplug-preload') ?>
338 </md-outlined-button>
339
340 </div>
341 <div class="oxyplug-preload-card">
342 <h2>
343 <?php esc_html_e('Style Preload', 'oxyplug-preload') ?>
344 <i class="dashicons dashicons-editor-help oxyplug-preload-has-tooltip"
345 data-tooltip="<?php esc_attr_e('Preload scripts', 'oxyplug-preload') ?>"
346 data-href="https://www.oxyplug.com/docs/oxy-preload/settings/?utm_source=plugin-settings&utm_medium=wordpress&utm_campaign=oxyplug-preload#preload-settings"
347 data-href-text="<?php esc_attr_e('Learn More', 'oxyplug-preload'); ?>"></i>
348 </h2>
349
350 <?php if (empty($preloads['style'])): ?>
351 <div class="oxyplug-preload-input-wrap">
352 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
353 name="preloads[style][]"
354 label="<?php esc_attr_e('Style URL', 'oxyplug-preload'); ?>"
355 placeholder="https://www.example.com/wp-content/my-style.css"
356 type="url">
357 <md-icon-button toggle slot="trailing-icon" type="button">
358 <md-icon>cancel</md-icon>
359 </md-icon-button>
360 </md-outlined-text-field>
361 <md-icon-button class="oxyplug-preload-remove-url"
362 toggle
363 slot="trailing-icon"
364 type="button"
365 style="display:none">
366 <md-icon>delete</md-icon>
367 </md-icon-button>
368 </div>
369 <?php else: ?>
370 <?php foreach ($preloads['style'] as $index => $link): ?>
371 <div class="oxyplug-preload-input-wrap">
372 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
373 name="preloads[style][]"
374 value="<?php echo esc_url($link) ?>"
375 label="<?php esc_attr_e('Style URL', 'oxyplug-preload'); ?>"
376 placeholder="https://www.example.com/wp-content/my-style.css"
377 type="url">
378 <md-icon-button toggle slot="trailing-icon" type="button">
379 <md-icon>cancel</md-icon>
380 </md-icon-button>
381 </md-outlined-text-field>
382 <md-icon-button class="oxyplug-preload-remove-url"
383 toggle
384 slot="trailing-icon"
385 type="button"
386 <?php if ($index == 0): ?> style="display:none" <?php endif; ?>>
387 <md-icon>delete</md-icon>
388 </md-icon-button>
389 </div>
390 <?php endforeach; ?>
391 <?php endif; ?>
392 <md-outlined-button class="oxyplug-preload-add-more">
393 <?php esc_html_e('Add More Style URL', 'oxyplug-preload') ?>
394 </md-outlined-button>
395
396 </div>
397 </div>
398
399 <div class="oxyplug-preload-in-row">
400 <div class="oxyplug-preload-card">
401 <h2>
402 <?php esc_html_e('Font Preload', 'oxyplug-preload') ?>
403 <i class="dashicons dashicons-editor-help oxyplug-preload-has-tooltip"
404 data-tooltip="<?php esc_attr_e('Preload fonts', 'oxyplug-preload') ?>"
405 data-href="https://www.oxyplug.com/docs/oxy-preload/settings/?utm_source=plugin-settings&utm_medium=wordpress&utm_campaign=oxyplug-preload#preload-settings"
406 data-href-text="<?php esc_attr_e('Learn More', 'oxyplug-preload'); ?>"></i>
407 </h2>
408
409 <?php if (empty($preloads['font'])): ?>
410 <div class="oxyplug-preload-input-wrap">
411 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
412 name="preloads[font][]"
413 label="<?php esc_attr_e('Font URL', 'oxyplug-preload'); ?>"
414 placeholder="https://www.example.com/wp-content/my-font.woff2"
415 type="url">
416 <md-icon-button toggle slot="trailing-icon" type="button">
417 <md-icon>cancel</md-icon>
418 </md-icon-button>
419 </md-outlined-text-field>
420 <md-icon-button class="oxyplug-preload-remove-url"
421 toggle
422 slot="trailing-icon"
423 type="button"
424 style="display:none">
425 <md-icon>delete</md-icon>
426 </md-icon-button>
427 </div>
428 <?php else: ?>
429 <?php foreach ($preloads['font'] as $index => $link): ?>
430 <div class="oxyplug-preload-input-wrap">
431 <md-outlined-text-field class="oxyplug-preload-text-field has-clear-button"
432 name="preloads[font][]"
433 value="<?php echo esc_url($link) ?>"
434 label="<?php esc_attr_e('Font URL', 'oxyplug-preload'); ?>"
435 placeholder="https://www.example.com/wp-content/my-font.woff2"
436 type="url">
437 <md-icon-button toggle slot="trailing-icon" type="button">
438 <md-icon>cancel</md-icon>
439 </md-icon-button>
440 </md-outlined-text-field>
441 <md-icon-button class="oxyplug-preload-remove-url"
442 toggle
443 slot="trailing-icon"
444 type="button"
445 <?php if ($index == 0): ?> style="display:none" <?php endif; ?>>
446 <md-icon>delete</md-icon>
447 </md-icon-button>
448 </div>
449 <?php endforeach; ?>
450 <?php endif; ?>
451 <md-outlined-button class="oxyplug-preload-add-more">
452 <?php esc_html_e('Add More Font URL', 'oxyplug-preload') ?>
453 </md-outlined-button>
454 </div>
455 <div class="oxyplug-preload-card oxyplug-preload-self-height">
456 <h2>
457 <?php esc_html_e('Featured Image Preload', 'oxyplug-preload') ?>
458 <i class="dashicons dashicons-editor-help oxyplug-preload-has-tooltip"
459 data-tooltip="<?php esc_attr_e('Preload featured image', 'oxyplug-preload') ?>"
460 data-href="https://www.oxyplug.com/docs/oxy-preload/settings/?utm_source=plugin-settings&utm_medium=wordpress&utm_campaign=oxyplug-preload#preload-settings"
461 data-href-text="<?php esc_attr_e('Learn More', 'oxyplug-preload'); ?>"></i>
462 </h2>
463
464 <div class="oxyplug-preload-switch-wrap">
465 <md-switch icons
466 name="featured_image_preload" <?php if ($this->oxyplug_preload_get_option('_oxyplug_preload_featured_image', 'true') == 'true'): ?> selected <?php endif ?>></md-switch>
467 <span><?php esc_html_e('Preload featured image automatically? (No need to add URLs)', 'oxyplug-preload'); ?></span>
468 </div>
469 </div>
470 </div>
471
472 <md-divider class="oxyplug-preload-horizontal-divider"></md-divider>
473
474 <md-filled-button id="oxyplug-preload-save-preloads"
475 class="oxyplug-preload-has-loading"
476 data-nonce="<?php echo esc_attr(wp_create_nonce('oxyplug_preload_save_preloads')) ?>">
477 <?php esc_html_e('Save', 'oxyplug-preload'); ?>
478 </md-filled-button>
479 <div class="oxyplug-preload-spinner-wrap">
480 <div class="oxyplug-preload-spinner"></div>
481 <p><?php esc_html_e('Saving...Please wait.', 'oxyplug-preload'); ?></p>
482 </div>
483
484 </div>
485 <?php }
486
487 /**
488 * @return void
489 */
490 public function oxyplug_preload_save_preloads()
491 {
492 if (!empty($_POST['oxyplug_preload_save_preloads_nonce'])) {
493 $sanitized_nonce = sanitize_text_field(wp_unslash($_POST['oxyplug_preload_save_preloads_nonce']));
494 if (wp_verify_nonce($sanitized_nonce, 'oxyplug_preload_save_preloads')) {
495
496 // Featured image preload
497 $preload_featured_image = empty($_POST['featured_image_preload']) ? 'false' : 'true';
498 $this->oxyplug_preload_update_option('_oxyplug_preload_featured_image', $preload_featured_image);
499
500 // CSS/JS/Font preload
501 if (!empty($_POST['preloads']) && is_array($_POST['preloads'])) {
502 $valid_links = array();
503 $htaccess_preloads = array();
504 $link_regex = '/^(https?:\/\/([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(\/[-a-zA-Z0-9@:%._+~#=]*)*(\?[a-zA-Z0-9=&._-]*)?)?$/';
505
506 foreach ($_POST['preloads'] as $key => $links) {
507 if (in_array((string)($key), array('script', 'style', 'font'))) {
508 foreach ($links as $link) {
509 $is_valid = preg_match($link_regex, $link);
510 if (!empty(trim($link)) && $is_valid) {
511 $md5link = md5($link);
512 if (!isset($valid_links[$key][$md5link])) {
513 $escaped_url = esc_url($link);
514 $valid_links[$key][$md5link] = $escaped_url;
515
516 // Store for htaccess generation
517 if (!isset($htaccess_preloads[$key])) {
518 $htaccess_preloads[$key] = array();
519 }
520 $htaccess_preloads[$key][] = $escaped_url;
521 }
522 }
523 }
524
525 if (isset($valid_links[$key])) {
526 $valid_links[$key] = array_values($valid_links[$key]);
527 }
528 }
529 }
530
531 // Generate htaccess content
532 $htaccess_content = $this->generate_htaccess_content($htaccess_preloads);
533
534 // Add preload URLs to .htaccess file
535 $write_to_htaccess_result = $this->update_htaccess($htaccess_content);
536 if ($write_to_htaccess_result !== true) {
537 wp_send_json(array('messages' => array($write_to_htaccess_result)), 500);
538 }
539
540 // Insert preload URLs into the database
541 $this->oxyplug_preload_update_option('_oxyplug_preload_preloads', $valid_links);
542 }
543
544 wp_send_json_success(array('messages' => array(esc_html__('Successfully saved.', 'oxyplug-preload'))), 200);
545 }
546
547 wp_send_json(array('messages' => array(esc_html__('Wrong wpnonce. Refresh the page.', 'oxyplug-preload'))), 403);
548 }
549 }
550
551 /**
552 * Generate .htaccess content for preloading
553 *
554 * @param array $preloads Array of preload URLs grouped by type
555 * @return string Generated .htaccess content
556 */
557 private function generate_htaccess_content($preloads)
558 {
559 $htaccess_content = '';
560
561 // Add preload directives
562 foreach ($preloads as $type => $urls) {
563 foreach ($urls as $url) {
564 $htaccess_content .= " Header append Link \"<$url>; rel=preload; as=$type";
565 if ($type == 'font') {
566 $htaccess_content .= '; crossorigin';
567 }
568 $htaccess_content .= "\"\n";
569 }
570 }
571
572 if (!empty($htaccess_content)) {
573 $htaccess_content =
574 ' <FilesMatch "index\.(html|htm|php)$">' . "\n" .
575 ' <IfModule mod_headers.c>' . "\n" .
576 $htaccess_content .
577 ' </IfModule>' . "\n" .
578 ' </FilesMatch>' . "\n";
579 }
580
581 return $htaccess_content;
582 }
583
584 /**
585 * @param $htaccess_content
586 *
587 * @return string|true
588 */
589 private function update_htaccess($htaccess_content)
590 {
591 $htaccess_path = ABSPATH . '.htaccess';
592 global $wp_filesystem;
593
594 if (!$wp_filesystem) {
595 require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
596 require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
597 $wp_filesystem = new \WP_Filesystem_Direct(null);
598 }
599
600 if (!$wp_filesystem->exists($htaccess_path) && $this->is_server(array('iis', 'nginx'))) {
601 wp_send_json(array('messages' => array(esc_html__('Your server does not support .htaccess file.', 'oxyplug-preload'))), 422);
602 }
603
604 if ($wp_filesystem->is_writable($htaccess_path) && $wp_filesystem->is_readable($htaccess_path)) {
605 $htaccess_backup_path = $htaccess_path . '.oxybackup';
606
607 try {
608 // Read current content
609 $current_content = $wp_filesystem->get_contents($htaccess_path);
610 if ($current_content === false) {
611 throw new Exception(esc_html__('Could not read .htaccess file.', 'oxyplug-preload'));
612 }
613
614 // Remove existing Oxyplug Preload section
615 $pattern = '/\n*# BEGIN Oxyplug Preload\n.*?# END Oxyplug Preload\n*/s';
616 $current_content = preg_replace($pattern, '', $current_content);
617 $current_content = rtrim($current_content);
618
619 if (!empty($htaccess_content)) {
620 // Create new section with headers
621 $section = "\n\n# BEGIN Oxyplug Preload\n";
622 $section .= $htaccess_content;
623 $section .= "# END Oxyplug Preload\n";
624
625 // Append the new section to the content
626 $htaccess_content = $current_content . $section;
627 } else {
628 $htaccess_content = $current_content;
629 }
630
631 // Create backup before making changes
632 if (!$wp_filesystem->copy($htaccess_path, $htaccess_backup_path, true)) {
633 throw new Exception(esc_html__('Failed to create .htaccess backup file.', 'oxyplug-preload'));
634 }
635
636 // Write new content
637 if (!$wp_filesystem->put_contents($htaccess_path, $htaccess_content)) {
638 throw new Exception(esc_html__('Failed to write new .htaccess content.', 'oxyplug-preload'));
639 }
640
641 // Quick site check for errors or 500 status
642 if (!$this->check_site_availability()) {
643 throw new Exception(esc_html__('Site check failed after .htaccess update.', 'oxyplug-preload'));
644 }
645
646 // Success - clean up backup
647 $wp_filesystem->delete($htaccess_backup_path);
648 return true;
649
650 } catch (Exception $e) {
651 // Restore backup if available
652 if ($wp_filesystem->exists($htaccess_backup_path)) {
653 $wp_filesystem->copy($htaccess_backup_path, $htaccess_path, true);
654 $wp_filesystem->delete($htaccess_backup_path);
655 }
656
657 return esc_html__('Oxyplug Preload .htaccess update failed: ' . $e->getMessage(), 'oxyplug-preload');
658 }
659 }
660
661 return esc_html__('Oxyplug Preload .htaccess update failed.', 'oxyplug-preload');
662 }
663
664 /**
665 * @return bool
666 */
667 private function check_site_availability(): bool
668 {
669 // Try REST url first (fastest)
670 $rest_url = get_rest_url();
671 $response = wp_remote_head($rest_url, [
672 'timeout' => 5,
673 'redirection' => 0,
674 'sslverify' => false
675 ]);
676
677 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) < 500) {
678 return true;
679 }
680
681 // Fallback to admin-ajax.php
682 $response = wp_remote_head(admin_url('admin-ajax.php'), [
683 'timeout' => 5,
684 'redirection' => 0,
685 'sslverify' => false
686 ]);
687
688 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) < 500) {
689 return true;
690 }
691
692 // Last resort - check homepage
693 $response = wp_remote_head(home_url(), [
694 'timeout' => 5,
695 'redirection' => 0,
696 'sslverify' => false
697 ]);
698
699 return !is_wp_error($response) && wp_remote_retrieve_response_code($response) < 500;
700 }
701
702 /**
703 * @param array $servers
704 *
705 * @return bool
706 */
707 private function is_server(array $servers): bool
708 {
709 $server = '';
710 $server_software = strtolower(sanitize_text_field($_SERVER['SERVER_SOFTWARE']));
711 if (strpos($server_software, 'apache') !== false) {
712 $server = 'apache';
713 } elseif (strpos($server_software, 'litespeed') !== false) {
714 $server = 'litespeed';
715 } elseif (strpos($server_software, 'nginx') !== false) {
716 $server = 'nginx';
717 } elseif (strpos($server_software, 'microsoft-iis') !== false || strpos($server_software, 'expressiondevserver') !== false) {
718 $server = 'iis';
719 }
720
721 return in_array($server, $servers);
722 }
723
724 /**
725 * @param $option_name
726 * @param $default
727 *
728 * @return false|mixed|void
729 */
730 protected function oxyplug_preload_get_option($option_name, $default = false)
731 {
732 if (is_multisite()) {
733 $network_id = get_current_blog_id();
734
735 return get_network_option($network_id, $option_name, $default);
736 }
737
738 return get_option($option_name, $default);
739 }
740
741 /**
742 * @param $option_name
743 * @param $option_value
744 *
745 * @return void
746 */
747 protected function oxyplug_preload_update_option($option_name, $option_value): void
748 {
749 if (is_multisite()) {
750 update_network_option(get_current_blog_id(), $option_name, $option_value);
751 } else {
752 update_option($option_name, $option_value);
753 }
754 }
755 }
756
757 new OxyPreload();
758
759