PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.7.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / includes / class-wp-optimize-htaccess.php

class-wp-optimize-htaccess.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 4.7.0, at includes/class-wp-optimize-htaccess.php

376 lines 9.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) die('No direct access allowed');
4
5 class WP_Optimize_Htaccess {
6
7 /**
8 * Full path to .htaccess file.
9 *
10 * @var string
11 */
12 private $_htaccess_file;
13
14 /**
15 * Structured content of .htaccess file.
16 *
17 * @var array
18 */
19 private $_file_tree = array();
20
21 /**
22 * WP_Optimize_Htaccess constructor.
23 *
24 * @param string $htaccess_file Full path to .htaccess file.
25 */
26 public function __construct($htaccess_file = '') {
27 $this->_htaccess_file = ('' != $htaccess_file) ? $htaccess_file : $this->get_home_path() . '.htaccess';
28 // read .htaccess content into $_file_tree.
29 $this->read_file();
30 }
31
32 /**
33 * Returns .htaccess filename.
34 *
35 * @return string
36 */
37 public function get_filename() {
38 return $this->_htaccess_file;
39 }
40
41 /**
42 * Checks if .htaccess file exists.
43 *
44 * @return bool
45 */
46 public function is_exists() {
47 return is_file($this->_htaccess_file);
48 }
49
50 /**
51 * Checks if .htaccess file is readable.
52 *
53 * @return bool
54 */
55 public function is_readable() {
56 return is_readable($this->_htaccess_file);
57 }
58
59 /**
60 * Checks if .htaccess file is writable.
61 *
62 * @return bool
63 */
64 public function is_writable() {
65 return wp_is_writable($this->_htaccess_file);
66 }
67
68 /**
69 * Read content of .htaccess file and store it as a tree in $_file_tree variable.
70 * For ex.
71 * [0] => '# BEGIN WordPress',
72 * [1] => [
73 * [0] => '<IfModule mod_rewrite.c>',
74 * [1] => 'RewriteEngine On',
75 * [2] => 'RewriteBase /',
76 * [3] => 'RewriteRule ^index\.php$ - [L]',
77 * [4] => 'RewriteCond %{REQUEST_FILENAME} !-f',
78 * [5] => 'RewriteCond %{REQUEST_FILENAME} !-d',
79 * [6] => 'RewriteRule . /index.php [L]',
80 * [7] => '</IfModule>',
81 * ]
82 * [2] => '# END WordPress'
83 *
84 * @return void
85 */
86 public function read_file() {
87 if (!$this->is_exists() || !$this->is_readable()) return;
88
89 $content = file_get_contents($this->_htaccess_file);
90
91 $content = explode("\n", $content);
92
93 $content_tree = array();
94
95 $section = array();
96 $sections = array();
97
98 foreach ($content as $line) {
99 $line = trim($line);
100
101 if (preg_match("/^\<\/(.+)(\s.*)?\>/", $line, $matches)) {
102 $section[] = $line;
103
104 // close section
105 if (!empty($sections)) {
106 $_section = $section;
107 $section = array_pop($sections);
108 $section[] = $_section;
109 } else {
110 $content_tree[] = $section;
111 $section = array();
112 }
113 } elseif (preg_match('/^\<(.+)>/', $line, $matches)) {
114 // open section
115 if (!empty($section)) {
116 $sections[] = $section;
117 }
118
119 $section = array();
120 $section[] = $line;
121 } elseif (!empty($section)) {
122 $section[] = $line;
123 } else {
124 $content_tree[] = $line;
125 }
126 }
127
128 $this->_file_tree = $content_tree;
129 }
130
131 /**
132 * Write current $_file_tree content into .htaccess file.
133 */
134 public function write_file() {
135 if ($this->is_writable()) {
136 @file_put_contents($this->_htaccess_file, $this->get_content()); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Hosts like WP Engine restricts use of `.htaccess` file
137 }
138 }
139
140 /**
141 * Get .htaccess content as a string.
142 *
143 * @return string
144 */
145 public function get_content() {
146 return implode(PHP_EOL, $this->get_flat_array($this->_file_tree));
147 }
148
149 /**
150 * Returns section content as a string
151 *
152 * @param string $section
153 * @return string
154 */
155 public function get_section_content($section = 'WP-Optimize Browser Cache') {
156 $section_index = $this->search_commented_section($section);
157
158 if (false === $section_index) return '';
159
160 $section_length = (false === $section_index['end']) ? null : ($section_index['end'] - $section_index['begin'] + 1);
161 $section_array = array_slice($this->_file_tree, $section_index['begin'], $section_length);
162
163 return implode(PHP_EOL, $this->get_flat_array($section_array));
164 }
165
166 /**
167 * Recursive function that builds a flat array from a tree-structured .htaccess file array.
168 * Get .htaccess hash
169 *
170 * @return string
171 */
172 public function get_hash() {
173 return hash('sha256', serialize($this->_file_tree));
174 }
175
176 /**
177 * Recursive function used to prepare data for output - build flat array from $_file_tree.
178 *
179 * @param array $array
180 * @param string $prefix
181 *
182 * @return array
183 */
184 public function get_flat_array($array, $prefix = '') {
185 $flat_array = array();
186
187 if (!empty($array)) {
188 foreach ($array as $item) {
189 if (is_array($item)) {
190 $item = $this->get_flat_array($item, "\t");
191 $flat_array = array_merge($flat_array, $item);
192 } else {
193 $flat_array[] = $item;
194 }
195 }
196 }
197
198 reset($flat_array);
199 $first = key($flat_array);
200 end($flat_array);
201 $last = key($flat_array);
202
203 foreach ($flat_array as $key => $value) {
204 if ('' !== $value && '#' === $value[0]) {
205 // never add prefix for comment lines.
206 $flat_array[$key] = $value;
207 } else {
208 $flat_array[$key] = ($key === $first || $key === $last) ? $value : $prefix . $value;
209 }
210 }
211
212 return $flat_array;
213 }
214
215 /**
216 * Update commented section in array $_file_tree, i.e. section wrapped with comments
217 * # BEGIN WP-Optimize Browser Cache
218 * ...
219 * # END WP-Optimize Browser Cache
220 *
221 * @param array $content
222 * @param string $section
223 * @param bool $prepend
224 */
225 public function update_commented_section($content, $section = 'WP-Optimize Browser Cache', $prepend = false) {
226 $section_begin = $this->get_section_begin_comment($section);
227 $section_end = $this->get_section_end_comment($section);
228
229 // add begin-end section comments.
230 array_unshift($content, $section_begin);
231 array_push($content, $section_end);
232
233 $section_index = $this->search_commented_section($section);
234
235 // check if section with cache settings already in the file.
236 if (false === $section_index) {
237 // no section in file then add it to the end of file.
238 $this->_file_tree = $prepend ? array_merge($content, $this->_file_tree) : array_merge($this->_file_tree, $content);
239 } else {
240 $remove_length = (false === $section_index['end']) ? null : ($section_index['end'] - $section_index['begin'] + 1);
241 array_splice($this->_file_tree, $section_index['begin'], $remove_length, $content);
242 }
243 }
244
245 /**
246 * Removes commented section in $_file_tree, i.e. section wrapped with comments
247 * # BEGIN WP-Optimize Browser Cache
248 * ...
249 * # END WP-Optimize Browser Cache
250 *
251 * @param string $comment
252 *
253 * @return bool
254 */
255 public function remove_commented_section($comment = 'WP-Optimize Browser Cache') {
256 $section_index = $this->search_commented_section($comment);
257 if (false === $section_index) return false;
258
259 $remove_length = (false === $section_index['end']) ? null : ($section_index['end'] - $section_index['begin'] + 1);
260 array_splice($this->_file_tree, $section_index['begin'], $remove_length);
261
262 $this->_file_tree = array_values($this->_file_tree);
263
264 return true;
265 }
266
267 /**
268 * Check if section exists wrapped by comments like
269 *
270 * # BEGIN WP-Optimize Browser Cache
271 * ...
272 * # END WP-Optimize Browser Cache
273 *
274 * @param string $section
275 * @return bool
276 */
277 public function is_commented_section_exists($section = 'WP-Optimize Browser Cache') {
278 $search = $this->search_commented_section($section);
279
280 return false !== $search;
281 }
282
283 /**
284 * Search section in $_file_tree array wrapped by begin and end comments.
285 *
286 * @param string $section
287 * @return array|bool
288 */
289 private function search_commented_section($section) {
290 $section_begin = $this->get_section_begin_comment($section);
291 $section_end = $this->get_section_end_comment($section);
292
293 $section_begin_index = $section_end_index = false;
294
295 $section_begin_normalized = $this->normalize_string($section_begin);
296 $section_end_normalized = $this->normalize_string($section_end);
297
298 foreach ($this->_file_tree as $i => $value) {
299 // if it is subsection then we don't go in deep.
300 if (is_array($value)) continue;
301
302 $value = $this->normalize_string($value);
303
304 if ($value === $section_begin_normalized) $section_begin_index = $i;
305 if ($value === $section_end_normalized) $section_end_index = $i;
306 }
307
308 if (false === $section_begin_index) {
309 return false;
310 } else {
311 return array(
312 'begin' => $section_begin_index,
313 'end' => $section_end_index,
314 );
315 }
316 }
317
318 /**
319 * Generate begin cache section comment.
320 *
321 * @param string $section
322 * @return string
323 */
324 public function get_section_begin_comment($section = 'WP-Optimize Browser Cache') {
325 return '# BEGIN ' . $section;
326 }
327
328 /**
329 * Generate end cache section comment.
330 *
331 * @param string $section
332 * @return string
333 */
334 public function get_section_end_comment($section = 'WP-Optimize Browser Cache') {
335 return '# END ' . $section;
336 }
337
338 /**
339 * Normalize string - make all letters lowercase and remove spaces.
340 *
341 * @param string $string
342 * @return string
343 */
344 private function normalize_string($string) {
345 return strtolower(str_replace(array("\n", "\r", ' '), '', $string));
346 }
347
348 /**
349 * Get the absolute filesystem path to the root of the WordPress installation.
350 * WP_Core function from wp-admin/includes/file.php.
351 *
352 * @since 1.5.0
353 *
354 * @return string Full filesystem path to the root of the WordPress installation
355 */
356 private function get_home_path() {
357 if (function_exists('get_home_path')) {
358 return get_home_path();
359 }
360
361 $home = set_url_scheme(get_option('home'), 'http');
362 $siteurl = set_url_scheme(get_option('siteurl'), 'http');
363 if (!empty($home) && 0 !== strcasecmp($home, $siteurl)) {
364 $script_filename = isset($_SERVER['SCRIPT_FILENAME']) ? sanitize_text_field(wp_unslash($_SERVER['SCRIPT_FILENAME'])) : '';
365 $wp_path_rel_to_home = str_ireplace($home, '', $siteurl); /* $siteurl - $home */
366 $pos = strripos(str_replace('\\', '/', $script_filename), trailingslashit($wp_path_rel_to_home));
367 $home_path = substr($script_filename, 0, $pos);
368 $home_path = trailingslashit($home_path);
369 } else {
370 $home_path = ABSPATH;
371 }
372
373 return str_replace('\\', '/', $home_path);
374 }
375 }
376