WP Htaccess Editor - Version 1.71

Version Description

Download this release

Release Info

Developer WebFactory
Plugin Icon 128x128 WP Htaccess Editor
Version 1.71
Comparing to
See all releases

Code changes from version 1.70 to 1.71

css/wp-htaccess-editor.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * WP Htaccess Editor
3
- * (c) WebFactory Ltd, 2018-2021
4
  */
5
 
6
  body,
1
  /**
2
  * WP Htaccess Editor
3
+ * (c) WebFactory Ltd, 2018-2022
4
  */
5
 
6
  body,
js/wp-htaccess-editor-pointers.js CHANGED
@@ -1,7 +1,7 @@
1
  /*
2
  * WP Htaccess Editor
3
  * Backend GUI pointers
4
- * (c) WebFactory Ltd, 2017 - 2021
5
  */
6
 
7
  jQuery(document).ready(function ($) {
1
  /*
2
  * WP Htaccess Editor
3
  * Backend GUI pointers
4
+ * (c) WebFactory Ltd, 2017 - 2022
5
  */
6
 
7
  jQuery(document).ready(function ($) {
js/wp-htaccess-editor.js CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * WP Htaccess Editor
3
- * (c) WebFactory Ltd, 2018 - 2021
4
  */
5
 
6
  jQuery(document).ready(function ($) {
1
  /**
2
  * WP Htaccess Editor
3
+ * (c) WebFactory Ltd, 2018 - 2022
4
  */
5
 
6
  jQuery(document).ready(function ($) {
readme.txt CHANGED
@@ -4,7 +4,7 @@ Contributors: WebFactory
4
  Requires at least: 4.0
5
  Requires PHP: 5.2
6
  Tested up to: 6.1
7
- Stable tag: 1.70
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -32,6 +32,10 @@ WP Htaccess Editor is fully compatible and tested with WP Network (WPMU). It sho
32
 
33
  The plugin was originally developed by <a href="https://profiles.wordpress.org/lukenzi">Lukenzi</a> in March of 2011.
34
 
 
 
 
 
35
  == Installation ==
36
 
37
  Follow the usual routine;
@@ -56,6 +60,9 @@ Or if needed, upload manually;
56
 
57
 
58
  == Changelog ==
 
 
 
59
 
60
  = v1.70 =
61
  * 2021/03/04
4
  Requires at least: 4.0
5
  Requires PHP: 5.2
6
  Tested up to: 6.1
7
+ Stable tag: 1.71
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
32
 
33
  The plugin was originally developed by <a href="https://profiles.wordpress.org/lukenzi">Lukenzi</a> in March of 2011.
34
 
35
+ #### GDPR compatibility
36
+ We are not lawyers. Please do not take any of the following as legal advice.
37
+ WP Htaccess Editor does not track, collect or process any user data. Nothing is logged or pushed to any 3rd parties nor do we use any 3rd party services or CDNs. Based on that, we feel it's GDPR compatible, but again, please, don't take this as legal advice.
38
+
39
  == Installation ==
40
 
41
  Follow the usual routine;
60
 
61
 
62
  == Changelog ==
63
+ = v1.71 =
64
+ * 2022/11/21
65
+ * Minor security fixes
66
 
67
  = v1.70 =
68
  * 2021/03/04
wf-flyout/config.php CHANGED
@@ -12,6 +12,7 @@ $config['menu_accent_color'] = '#dd3036';
12
  $config['custom_css'] = '#wf-flyout .wff-menu-item .dashicons.dashicons-universal-access { font-size: 30px; padding: 0px 10px 0px 0; } #wf-flyout .ucp-icon .wff-icon img { max-width: 70%; } #wf-flyout .ucp-icon .wff-icon { line-height: 57px; } #wf-flyout .wp301-icon .wff-icon img { max-width: 66%; } #wf-flyout .wp301-icon .wff-icon { line-height: 57px; }';
13
 
14
  $config['menu_items'] = array(
 
15
  array('href' => 'https://wp301redirects.com/?ref=wff-htaccess&coupon=50off', 'label' => 'Fix most common SEO issues on WordPress that everbody ignores', 'icon' => '301-logo.png', 'class' => 'wp301-icon'),
16
  array('href' => 'https://wpreset.com/?ref=wff-wp-htaccess', 'target' => '_blank', 'label' => 'Get WP Reset PRO with 50% off', 'icon' => 'wp-reset.png'),
17
  array('href' => 'https://underconstructionpage.com/?ref=wff-wp-htaccess&coupon=welcome', 'target' => '_blank', 'label' => 'Create the perfect Under Construction Page', 'icon' => 'ucp.png', 'class' => 'ucp-icon'),
12
  $config['custom_css'] = '#wf-flyout .wff-menu-item .dashicons.dashicons-universal-access { font-size: 30px; padding: 0px 10px 0px 0; } #wf-flyout .ucp-icon .wff-icon img { max-width: 70%; } #wf-flyout .ucp-icon .wff-icon { line-height: 57px; } #wf-flyout .wp301-icon .wff-icon img { max-width: 66%; } #wf-flyout .wp301-icon .wff-icon { line-height: 57px; }';
13
 
14
  $config['menu_items'] = array(
15
+ array('href' => 'https://wpforcessl.com/?ref=wff-htaccess', 'label' => 'Fix all SSL problems &amp; monitor site in real-time', 'icon' => 'wp-ssl.png', 'class' => 'wpfssl-icon'),
16
  array('href' => 'https://wp301redirects.com/?ref=wff-htaccess&coupon=50off', 'label' => 'Fix most common SEO issues on WordPress that everbody ignores', 'icon' => '301-logo.png', 'class' => 'wp301-icon'),
17
  array('href' => 'https://wpreset.com/?ref=wff-wp-htaccess', 'target' => '_blank', 'label' => 'Get WP Reset PRO with 50% off', 'icon' => 'wp-reset.png'),
18
  array('href' => 'https://underconstructionpage.com/?ref=wff-wp-htaccess&coupon=welcome', 'target' => '_blank', 'label' => 'Create the perfect Under Construction Page', 'icon' => 'ucp.png', 'class' => 'ucp-icon'),
wf-flyout/icons/wp-ssl.png ADDED
Binary file
wf-flyout/wf-flyout.css CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * Universal fly-out menu for WebFactory plugins
3
- * (c) WebFactory Ltd, 2021
4
  */
5
 
6
  #wf-flyout {
1
  /**
2
  * Universal fly-out menu for WebFactory plugins
3
+ * (c) WebFactory Ltd, 2022
4
  */
5
 
6
  #wf-flyout {
wf-flyout/wf-flyout.js CHANGED
@@ -1,6 +1,6 @@
1
  /**
2
  * Universal fly-out menu for WebFactory plugins
3
- * (c) WebFactory Ltd, 2021
4
  */
5
 
6
  jQuery(document).ready(function ($) {
1
  /**
2
  * Universal fly-out menu for WebFactory plugins
3
+ * (c) WebFactory Ltd, 2022
4
  */
5
 
6
  jQuery(document).ready(function ($) {
wf-flyout/wf-flyout.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  /**
4
  * Universal fly-out menu for WebFactory plugins
5
- * (c) WebFactory Ltd, 2021
6
  */
7
 
8
 
@@ -89,6 +89,8 @@ if (false == class_exists('wf_flyout')) {
89
 
90
  function admin_head()
91
  {
 
 
92
  if (false === $this->is_plugin_screen()) {
93
  return;
94
  }
@@ -112,12 +114,13 @@ if (false == class_exists('wf_flyout')) {
112
  $out .= $this->config['custom_css'];
113
  $out .= '</style>';
114
 
115
- echo $out;
116
  } // admin_head
117
 
118
 
119
  function admin_footer()
120
  {
 
121
  if (false === $this->is_plugin_screen()) {
122
  return;
123
  }
@@ -161,7 +164,7 @@ if (false == class_exists('wf_flyout')) {
161
 
162
  $out .= '</div>'; // #wf-flyout
163
 
164
- echo $out;
165
  } // admin_footer
166
  } // wf_flyout
167
  } // if class exists
2
 
3
  /**
4
  * Universal fly-out menu for WebFactory plugins
5
+ * (c) WebFactory Ltd, 2022
6
  */
7
 
8
 
89
 
90
  function admin_head()
91
  {
92
+ global $wp_htaccess_editor;
93
+
94
  if (false === $this->is_plugin_screen()) {
95
  return;
96
  }
114
  $out .= $this->config['custom_css'];
115
  $out .= '</style>';
116
 
117
+ $wp_htaccess_editor->wp_kses_wf($out);
118
  } // admin_head
119
 
120
 
121
  function admin_footer()
122
  {
123
+ global $wp_htaccess_editor;
124
  if (false === $this->is_plugin_screen()) {
125
  return;
126
  }
164
 
165
  $out .= '</div>'; // #wf-flyout
166
 
167
+ $wp_htaccess_editor->wp_kses_wf($out);
168
  } // admin_footer
169
  } // wf_flyout
170
  } // if class exists
wp-htaccess-editor.php CHANGED
@@ -3,10 +3,10 @@
3
  Plugin Name: WP Htaccess Editor
4
  Plugin URI: https://wphtaccess.com/
5
  Description: Safe and easy way to edit the .htaccess file directly from WP admin without using FTP.
6
- Version: 1.70
7
  Requires at least: 4.0
8
  Requires PHP: 5.2
9
- Tested up to: 6.0
10
  Author: WebFactory Ltd
11
  Author URI: https://www.webfactoryltd.com/
12
  Text Domain: wp-htaccess-editor
@@ -32,930 +32,1281 @@
32
 
33
  // include only file
34
  if (!defined('ABSPATH')) {
35
- wp_die(__('Do not open this file directly.', 'wp-htaccess-editor'));
36
  }
37
 
38
 
39
  class WP_Htaccess_Editor
40
  {
41
- protected static $instance = null;
42
- public $version = 0;
43
- public $plugin_url = '';
44
- public $plugin_basename = '';
45
- protected $options = array();
46
- protected $wp_filesystem = null;
47
- public $backup_folder = 'htaccess-editor-backups';
48
-
49
-
50
- /**
51
- * Creates a new WP_Htaccess_Editor object and implements singleton
52
- *
53
- * @return WP_Htaccess_Editor
54
- */
55
- static function get_instance()
56
- {
57
- if (false == is_a(self::$instance, 'WP_Htaccess_Editor')) {
58
- self::$instance = new WP_Htaccess_Editor();
59
- }
60
 
61
- return self::$instance;
62
- } // get_instance
63
-
64
-
65
- /**
66
- * Initialize properties, hook to filters and actions
67
- *
68
- * @return null
69
- */
70
- private function __construct()
71
- {
72
- $this->version = $this->get_plugin_version();
73
- $this->plugin_url = plugin_dir_url(__FILE__);
74
- $this->plugin_basename = plugin_basename(__FILE__);
75
-
76
- $this->load_options();
77
- $this->setup_wp_filesystem();
78
-
79
- add_action('admin_menu', array($this, 'admin_menu'));
80
- add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
81
- add_action('wp_ajax_wp_htaccess_editor_dismiss_notice', array($this, 'ajax_dismiss_notice'));
82
- add_action('wp_ajax_wp_htaccess_editor_do_action', array($this, 'ajax_do_action'));
83
-
84
- add_filter('plugin_action_links_' . $this->plugin_basename, array($this, 'plugin_action_links'));
85
- add_filter('plugin_row_meta', array($this, 'plugin_meta_links'), 10, 2);
86
- add_filter('admin_footer_text', array($this, 'admin_footer_text'));
87
- } // __construct
88
-
89
-
90
- /**
91
- * Get plugin version from file header.
92
- *
93
- * @return string
94
- */
95
- function get_plugin_version()
96
- {
97
- $plugin_data = get_file_data(__FILE__, array('version' => 'Version'), 'plugin');
98
-
99
- return $plugin_data['version'];
100
- } // get_plugin_version
101
-
102
-
103
- /**
104
- * Add "Edit .htaccess file" action link to plugins table, left side
105
- *
106
- * @param array $links Initial list of links.
107
- *
108
- * @return array
109
- */
110
- function plugin_action_links($links)
111
- {
112
- // whole plugin is for admins only
113
- if (false === current_user_can('administrator')) {
114
- return $links;
115
- }
116
 
117
- $settings_link = '<a href="' . admin_url('options-general.php?page=wp-htaccess-editor') . '" title="' . __('Edit .htaccess file', 'wp-htaccess-editor') . '">' . __('Edit .htaccess file', 'wp-htaccess-editor') . '</a>';
118
 
119
- array_unshift($links, $settings_link);
120
 
121
- return $links;
122
- } // plugin_action_links
123
 
124
 
125
- /**
126
- * Add links to plugin's description in plugins table
127
- *
128
- * @param array $links Initial list of links.
129
- * @param string $file Basename of current plugin.
130
- *
131
- * @return array
132
- */
133
- function plugin_meta_links($links, $file)
134
- {
135
- if ($file !== $this->plugin_basename) {
136
- return $links;
137
- }
138
 
139
- $home_link = '<a target="_blank" href="' . $this->generate_web_link('plugins-table-right') . '" title="' . __('Plugin Homepage', 'wp-htaccess-editor') . '">' . __('Plugin Homepage', 'wp-htaccess-editor') . '</a>';
140
- $support_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-htaccess-editor" title="' . __('Get help', 'wp-htaccess-editor') . '">' . __('Support', 'wp-htaccess-editor') . '</a>';
141
- $rate_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-htaccess-editor/reviews/#new-post" title="' . __('Rate the plugin', 'wp-htaccess-editor') . '">' . __('Rate the plugin ★★★★★', 'wp-htaccess-editor') . '</a>';
142
 
143
- $links[] = $home_link;
144
- $links[] = $support_link;
145
- $links[] = $rate_link;
146
 
147
- return $links;
148
- } // plugin_meta_links
149
 
150
 
151
- /**
152
- * Test if we're on plugin's admin page
153
- *
154
- * @return bool
155
- */
156
- function is_plugin_page()
157
- {
158
- $current_screen = get_current_screen();
159
 
160
- if ($current_screen->id === 'settings_page_wp-htaccess-editor') {
161
- return true;
162
- } else {
163
- return false;
164
- }
165
- } // is_plugin_page
166
-
167
-
168
- /**
169
- * Add powered by text in admin footer
170
- *
171
- * @param string $text_org Default footer text.
172
- *
173
- * @return string
174
- */
175
- function admin_footer_text($text_org)
176
- {
177
- if (false === $this->is_plugin_page()) {
178
- return $text_org;
179
- }
180
 
181
- $text = '<i><a target="_blank" href="' . $this->generate_web_link('admin_footer') . '">WP Htaccess Editor</a> v' . $this->version . ' by <a href="https://www.webfactoryltd.com/" title="' . __('Visit our site to get more great plugins', 'wp-htaccess-editor') . '" target="_blank">WebFactory Ltd</a>.';
182
- $text .= ' Please <a target="_blank" href="https://wordpress.org/support/plugin/wp-htaccess-editor/reviews/#new-post" title="' . __('Rate the plugin', 'wp-htaccess-editor') . '">' . __('Rate the plugin ★★★★★', 'wp-htaccess-editor') . '</a>.</i> ';
183
 
184
- return $text;
185
- } // admin_footer_text
186
 
187
 
188
- /**
189
- * Loads plugin's translated strings
190
- *
191
- * @return null
192
- */
193
- function load_textdomain()
194
- {
195
- load_plugin_textdomain('wp-htaccess-editor');
196
- } // load_textdomain
197
 
198
 
199
- /**
200
- * Initialize the WP file system.
201
- *
202
- * @return object
203
- */
204
- private function setup_wp_filesystem()
205
- {
206
- global $wp_filesystem;
207
 
208
- if (empty($wp_filesystem)) {
209
- require_once ABSPATH . '/wp-admin/includes/file.php';
210
- WP_Filesystem();
211
- }
212
 
213
- $this->wp_filesystem = $wp_filesystem;
214
- return $this->wp_filesystem;
215
- } // setup_wp_filesystem
216
-
217
-
218
- /**
219
- * Get full backup folder path
220
- *
221
- * @return string
222
- */
223
- function get_backup_folder()
224
- {
225
- $folder = trailingslashit(WP_CONTENT_DIR) . trailingslashit($this->backup_folder);
226
-
227
- return $folder;
228
- } // get_backup_folder
229
-
230
-
231
- /**
232
- * Get .htaccess file path.
233
- *
234
- * @param bool $folder_only Optional. Return folder name only without filename.
235
- *
236
- * @return string
237
- */
238
- function get_htaccess_path($folder_only = false)
239
- {
240
- if ($folder_only) {
241
- return get_home_path();
242
- } else {
243
- return get_home_path() . '.htaccess';
244
- }
245
- } // get_htaccess_path
246
-
247
-
248
- /**
249
- * Get .htaccess file content.
250
- *
251
- * @return string
252
- */
253
- function get_htaccess_content()
254
- {
255
- $content = $this->wp_filesystem->get_contents($this->get_htaccess_path());
256
-
257
- return $content;
258
- } // get_htaccess_path
259
-
260
-
261
- /**
262
- * Check if .htaccess is writable.
263
- *
264
- * @return bool
265
- */
266
- function is_htaccess_writable()
267
- {
268
- $htaccess_path = $this->get_htaccess_path();
269
-
270
- return $this->wp_filesystem->is_writable($htaccess_path);
271
- } // is_htaccess_writable
272
-
273
-
274
- /**
275
- * Check if .htaccess exists and is redable.
276
- *
277
- * @return bool
278
- */
279
- function is_htaccess_readable()
280
- {
281
- $htaccess_path = $this->get_htaccess_path();
282
-
283
- return $this->wp_filesystem->is_readable($htaccess_path);
284
- } // is_htaccess_writable
285
-
286
-
287
- /**
288
- * Load and prepare the options array. If needed create a new DB options entry.
289
- *
290
- * @return array
291
- */
292
- private function load_options()
293
- {
294
- $options = get_option('wp-htaccess-editor', array());
295
- $change = false;
296
-
297
- if (empty($options['meta'])) {
298
- $options['meta'] = array('first_version' => $this->version, 'first_install' => current_time('timestamp', true), 'edits_count' => 0);
299
- $change = true;
300
- }
301
- if (empty($options['dismissed_notices'])) {
302
- $options['dismissed_notices'] = array();
303
- $change = true;
304
- }
305
- if (empty($options['options'])) {
306
- $options['options'] = array('last_backup' => false);
307
- $change = true;
308
- }
309
- if ($change) {
310
- update_option('wp-htaccess-editor', $options, true);
311
- }
312
 
313
- $this->options = $options;
314
- return $options;
315
- } // load_options
316
-
317
-
318
- /**
319
- * Get meta part of plugin options.
320
- *
321
- * @return array
322
- */
323
- function get_meta()
324
- {
325
- return $this->options['meta'];
326
- } // get_meta
327
-
328
-
329
- /**
330
- * Get all dismissed notices, or check for one specific notice.
331
- *
332
- * @param string $notice_name Optional. Check if specified notice is dismissed.
333
- *
334
- * @return bool|array
335
- */
336
- function get_dismissed_notices($notice_name = '')
337
- {
338
- $notices = $this->options['dismissed_notices'];
339
-
340
- if (empty($notice_name)) {
341
- return $notices;
342
- } else {
343
- if (empty($notices[$notice_name])) {
344
- return false;
345
- } else {
346
- return true;
347
- }
348
- }
349
- } // get_dismissed_notices
350
-
351
-
352
- /**
353
- * Get all options or a single option key.
354
- *
355
- * @param string $option_key Optional.
356
- *
357
- * @return array
358
- */
359
- function get_options($option_key = '')
360
- {
361
- if (empty($option_key)) {
362
- return $this->options['options'];
363
- } else {
364
- if (isset($this->options['options'][$option_key])) {
365
- return $this->options['options'][$option_key];
366
- } else {
367
- return null;
368
- }
369
- }
370
- } // get_options
371
-
372
-
373
- /**
374
- * Update plugin options array
375
- *
376
- * @param string $key Option key.
377
- * @param string $data Data to save.
378
- *
379
- * @return bool
380
- */
381
- private function update_options($key, $data)
382
- {
383
- if (false === in_array($key, array('options', 'meta', 'dismissed_notices'))) {
384
- trigger_error('Unknown option key used in update_options($key, $data) function.', E_USER_ERROR);
385
- return false;
386
- }
387
 
388
- $this->options[$key] = $data;
389
- $tmp = update_option('wp-htaccess-editor', $this->options);
390
-
391
- return $tmp;
392
- } // set_options
393
-
394
-
395
- /**
396
- * Add plugin menu entry under Settings menu
397
- *
398
- * @return null
399
- */
400
- function admin_menu()
401
- {
402
- add_options_page(__('WP Htaccess Editor', 'wp-htaccess-editor'), __('WP Htaccess Editor', 'wp-htaccess-editor'), 'administrator', 'wp-htaccess-editor', array($this, 'plugin_page'));
403
- } // admin_menu
404
-
405
-
406
- /**
407
- * Helper function for generating UTM tagged links
408
- *
409
- * @param string $placement Optional. UTM content param.
410
- * @param string $page Optional. Page to link to.
411
- * @param array $params Optional. Extra URL params.
412
- * @param string $anchor Optional. URL anchor part.
413
- *
414
- * @return string
415
- */
416
- function generate_web_link($placement = '', $page = '/', $params = array(), $anchor = '')
417
- {
418
- $base_url = 'https://wphtaccess.com';
419
-
420
- if ('/' != $page) {
421
- $page = '/' . trim($page, '/') . '/';
422
- }
423
- if ($page == '//') {
424
- $page = '/';
425
- }
426
 
427
- $parts = array_merge(array('utm_source' => 'wp-htaccess-free', 'utm_medium' => 'plugin', 'utm_content' => $placement, 'utm_campaign' => 'wp-htaccess-free-v' . $this->version), $params);
428
 
429
- if (!empty($anchor)) {
430
- $anchor = '#' . trim($anchor, '#');
431
- }
432
 
433
- $out = $base_url . $page . '?' . http_build_query($parts, '', '&amp;') . $anchor;
434
 
435
- return $out;
436
- } // generate_web_link
437
 
438
 
439
- /**
440
- * Dismiss notice via AJAX call
441
- *
442
- * @return null
443
- */
444
- function ajax_dismiss_notice()
445
- {
446
- check_ajax_referer('wp-htaccess-editor_dismiss_notice');
447
 
448
- // complete plugin is for admins only
449
- if (false === current_user_can('administrator')) {
450
- wp_send_json_error(__('You are not allowed to perform this action.', 'wp-htaccess-editor'));
451
- }
452
 
453
- if (empty($_GET['notice_name'])) {
454
- wp_send_json_error(__('Notice name is undefined.', 'wp-htaccess-editor'));
455
- } else {
456
- $notice_name = substr(sanitize_key($_GET['notice_name']), 0, 64);
457
- }
458
 
459
- if (!$this->dismiss_notice($notice_name)) {
460
- wp_send_json_error(__('Notice is already dismissed.', 'wp-htaccess-editor'));
461
- } else {
462
- wp_send_json_success();
463
- }
464
- } // ajax_dismiss_notice
465
-
466
-
467
- /**
468
- * Dismiss notice by adding it to dismissed_notices options array
469
- *
470
- * @param string $notice_name Notice to dismiss.
471
- *
472
- * @return bool
473
- */
474
- function dismiss_notice($notice_name)
475
- {
476
- if ($this->get_dismissed_notices($notice_name)) {
477
- return false;
478
- } else {
479
- $notices = $this->get_dismissed_notices();
480
- $notices[$notice_name] = true;
481
- $this->update_options('dismissed_notices', $notices);
482
- return true;
483
- }
484
- } // dismiss_notice
485
 
486
 
487
- /**
488
- * Returns all WP pointers
489
- *
490
- * @return array
491
- */
492
- function get_pointers()
493
- {
494
- $pointers = array();
495
- $meta = $this->get_meta();
496
 
497
- // TODO: reformat & prepare for translation
498
- $pointers['welcome'] = array('target' => '#menu-settings', 'edge' => 'left', 'align' => 'right', 'content' => 'Thank you for using the <b style="font-weight: 700;">WP Htaccess Editor</b> plugin.<br>Open <a href="' . admin_url('options-general.php?page=wp-htaccess-editor') . '">Settings - WP Htaccess Editor</a> to access the editor and start editing the <i>.htaccess</i> file.');
499
 
500
- if (true === version_compare($meta['first_version'], '1.60', '<')) {
501
- // TODO: reformat & prepare for translation
502
- $pointers['menu-relocation'] = array('target' => '#menu-settings', 'edge' => 'left', 'align' => 'right', 'content' => 'We\'ve moved the <b style="font-weight: 700;">WP Htaccess Editor</b> plugin from Tools to the Settings menu. Sorry for the inconvenience. <br>To edit htaccess open <a href="' . admin_url('options-general.php?page=wp-htaccess-editor') . '">Settings - WP Htaccess Editor</a>.');
503
- }
504
 
505
- return $pointers;
506
- } // get_pointers
507
-
508
-
509
- /**
510
- * Enqueue CSS and JS files
511
- *
512
- * @param string $hook Page hook name.
513
- *
514
- * @return null
515
- */
516
- function admin_enqueue_scripts($hook)
517
- {
518
- // welcome pointer is shown on all pages except WPHE, only to admins, until dismissed
519
- $pointers = $this->get_pointers();
520
- $dismissed_notices = $this->get_dismissed_notices();
521
-
522
- foreach ($dismissed_notices as $notice_name => $tmp) {
523
- if ($tmp) {
524
- unset($pointers[$notice_name]);
525
- }
526
- } // foreach
527
-
528
- if (current_user_can('administrator') && !empty($pointers) && 'settings_page_wp-htaccess-editor' != $hook) {
529
- $pointers['_nonce_dismiss_pointer'] = wp_create_nonce('wp-htaccess-editor_dismiss_notice');
530
-
531
- wp_enqueue_style('wp-pointer');
532
-
533
- wp_enqueue_script('wp-htaccess-editor-pointers', $this->plugin_url . 'js/wp-htaccess-editor-pointers.js', array('jquery'), $this->version, true);
534
- wp_enqueue_script('wp-pointer');
535
- wp_localize_script('wp-pointer', 'wp_htaccess_editor_pointers', $pointers);
536
- }
537
 
538
- // exit early if not on WPHE page
539
- if ('settings_page_wp-htaccess-editor' != $hook) {
540
- return;
541
- }
542
 
543
- $editor_settings['codeEditor'] = wp_enqueue_code_editor(array($this->get_htaccess_path()));
544
- $editor_settings['codeEditor']['codemirror']['mode'] = 'nginx';
545
-
546
- $js_localize = array(
547
- 'undocumented_error' => __('An undocumented error has occurred. Please refresh the page and try again.', 'wp-htaccess-editor'),
548
- 'documented_error' => __('An error has occurred.', 'wp-htaccess-editor'),
549
- 'plugin_name' => __('WP Htaccess Editor', 'wp-htaccess-editor'),
550
- 'home_url' => get_home_url(),
551
- 'settings_url' => admin_url('options-general.php?page=wp-htaccess-editor'),
552
- 'loading_icon_url' => $this->plugin_url . 'img/loading-icon.png',
553
- 'cancel_button' => __('Cancel', 'wp-htaccess-editor'),
554
- 'ok_button' => __('OK', 'wp-htaccess-editor'),
555
- 'saving' => __('Saving in progress. Please wait.', 'wp-htaccess-editor'),
556
- 'restoring' => __('Restoring in progress. Please wait.', 'wp-htaccess-editor'),
557
- 'save_success' => __('Changes have been saved.', 'wp-htaccess-editor'),
558
- 'restore_message' => __('This will restore the last saved backup of .htaccess. There is NO UNDO.', 'wp-htaccess-editor'),
559
- 'restore_title' => __('Restore last backup?', 'wp-htaccess-editor'),
560
- 'restore_button' => __('Restore Last Saved Backup of .htaccess', 'wp-htaccess-editor'),
561
- 'restore_success' => __('Backup has been successfully restored. Click OK to reload the page.', 'wp-htaccess-editor'),
562
- 'test_success' => __('Test Completed Successfully', 'wp-htaccess-editor'),
563
- 'test_failed' => __('Test Failed', 'wp-htaccess-editor'),
564
- 'testing' => __('Testing .htaccess syntax. Please wait.', 'wp-htaccess-editor'),
565
- 'site_error' => __('There is an error in the .htaccess file and your site is probably no longer accessible.<br><br>DO NOT panic or reload this page. Close this message. First, try the "Restore Last Backup" button. If it doesn\'t work read instruction on this very page on how to restore the site.', 'wp-htaccess-editor'),
566
- 'nonce_dismiss_notice' => wp_create_nonce('wp-htaccess-editor_dismiss_notice'),
567
- 'nonce_do_action' => wp_create_nonce('wp-htaccess-editor_do_action'),
568
- 'cm_settings' => $editor_settings
569
- );
570
-
571
- wp_enqueue_style('wp-codemirror');
572
- wp_enqueue_style('wp-htaccess-editor', $this->plugin_url . 'css/wp-htaccess-editor.css', array(), $this->version);
573
- wp_enqueue_style('wp-htaccess-editor-sweetalert2', $this->plugin_url . 'css/sweetalert2.min.css', array(), $this->version);
574
-
575
- wp_enqueue_script('jquery-ui-tabs');
576
- wp_enqueue_script('wp-htaccess-editor-sweetalert2', $this->plugin_url . 'js/sweetalert2.min.js', array('jquery'), $this->version, true);
577
- wp_enqueue_script('wp-theme-plugin-editor');
578
- wp_enqueue_script('wp-htaccess-editor', $this->plugin_url . 'js/wp-htaccess-editor.js', array('jquery'), $this->version, true);
579
- wp_enqueue_script('wp-htaccess-cm-resize', $this->plugin_url . 'js/cm-resize.min.js', array('jquery'), $this->version, true);
580
- wp_localize_script('wp-htaccess-editor', 'wp_htaccess_editor', $js_localize);
581
-
582
- // fix for aggressive plugins that include their CSS files on all pages
583
- wp_dequeue_style('uiStyleSheet');
584
- wp_dequeue_style('wpcufpnAdmin');
585
- wp_dequeue_style('unifStyleSheet');
586
- wp_dequeue_style('wpcufpn_codemirror');
587
- wp_dequeue_style('wpcufpn_codemirrorTheme');
588
- wp_dequeue_style('collapse-admin-css');
589
- wp_dequeue_style('jquery-ui-css');
590
- wp_dequeue_style('tribe-common-admin');
591
- wp_dequeue_style('file-manager__jquery-ui-css');
592
- wp_dequeue_style('file-manager__jquery-ui-css-theme');
593
- wp_dequeue_style('wpmegmaps-jqueryui');
594
- wp_dequeue_style('wp-botwatch-css');
595
- } // admin_enqueue_scripts
596
-
597
-
598
- /**
599
- * Run an action via AJAX call.
600
- *
601
- * @return null
602
- */
603
- function ajax_do_action()
604
- {
605
- check_ajax_referer('wp-htaccess-editor_do_action');
606
-
607
- if (false === current_user_can('administrator')) {
608
- wp_send_json_error(__('You are not allowed to perform this action.', 'wp-htaccess-editor'));
609
- }
610
 
611
- if (empty($_POST['subaction'])) {
612
- wp_send_json_error(__('Subaction name is undefined.', 'wp-htaccess-editor'));
613
- } else {
614
- $subaction = substr(sanitize_key($_POST['subaction']), 0, 64);
615
- }
616
 
617
- if ($subaction == 'save_htaccess') {
618
- if (false == $this->create_htaccess_backup(true, true)) {
619
- wp_send_json_error(__('Unable to create .htaccess backup in /wp-content/. Please check file permissions.', 'wp-htaccess-editor'));
620
- }
621
-
622
- // since we're working with code it's hard/impossible to sanitize input
623
- // WP core doesn't sanitize either for plugin/theme editor so it should be OK
624
- // nonce is in place and user permissions are double checked
625
- $new_content = wp_unslash(trim($_POST['new_content']));
626
- if (false == $this->is_htaccess_writable() || false == $this->write_htaccess($new_content)) {
627
- wp_send_json_error(__('Could not write .htaccess file. Please check file permissions.', 'wp-htaccess-editor'));
628
- }
629
-
630
- wp_send_json_success();
631
- } elseif ($subaction == 'test_htaccess') {
632
- $new_content = wp_unslash(trim($_POST['new_content']));
633
- $uploads_directory = wp_upload_dir();
634
- $test_id = rand(1000, 9999);
635
- $htaccess_test_folder = $uploads_directory['basedir'] . '/htaccess-test-' . $test_id . '/';
636
- $htaccess_test_url = $uploads_directory['baseurl'] . '/htaccess-test-' . $test_id . '/';
637
-
638
- // Create test directory and files
639
- if (!$this->wp_filesystem->is_dir($htaccess_test_folder)) {
640
- if (true !== $this->wp_filesystem->mkdir($htaccess_test_folder, 0777)) {
641
- wp_send_json_error(__('Failed to create test directory. Please check that your uploads folder is writable.', 'wp-htaccess-editor'));
642
- }
643
- }
644
-
645
- if (true !== $this->wp_filesystem->put_contents($htaccess_test_folder . 'index.html', 'htaccess-test-' . $test_id)) {
646
- wp_send_json_error(__('Failed to create test files. Please check that your uploads folder is writable.', 'wp-htaccess-editor'));
647
- }
648
-
649
- if (true !== $this->wp_filesystem->put_contents($htaccess_test_folder . '.htaccess', $new_content)) {
650
- wp_send_json_error(__('Failed to create test directory and files. Please check that your uploads folder is writeable.', 'wp-htaccess-editor'));
651
- }
652
-
653
- // Retrieve test file over http
654
- $response = wp_remote_get($htaccess_test_url . 'index.html', array('sslverify' => false, 'redirection' => 0));
655
- $response_code = wp_remote_retrieve_response_code($response);
656
-
657
- // Remove Test Directory
658
- $this->wp_filesystem->delete($htaccess_test_folder . '.htaccess');
659
- $this->wp_filesystem->delete($htaccess_test_folder . 'index.html');
660
- $this->wp_filesystem->rmdir($htaccess_test_folder);
661
-
662
- // Check if test file content is what we expect
663
- if ((in_array($response_code, range(200, 299)) && !is_wp_error($response) && wp_remote_retrieve_body($response) == 'htaccess-test-' . $test_id) || (in_array($response_code, range(300, 399)) && !is_wp_error($response))) {
664
- wp_send_json_success(__('This test only makes sure there are no syntax errors that could result in 500 errors for your entire site. It does not check the logic of the <i>.htaccess</i> file, ie if redirects work as intended.', 'wp-htaccess-editor'));
665
- } else {
666
- wp_send_json_error(__('There are syntax errors in your unsaved <i>.htaccess</i> content. Saving it will cause your entire site, including the admin, to become inaccessible. Fix the errors before saving.', 'wp-htaccess-editor'));
667
- }
668
- } elseif ($subaction == 'restore_htaccess_from_db') {
669
- $res = $this->restore_db_backup();
670
- if (!is_wp_error($res)) {
671
- wp_send_json_success();
672
- } else {
673
- wp_send_json_error($res->get_error_message());
674
- }
675
- } else {
676
- wp_send_json_error(__('Unknown subaction.', 'wp-htaccess-editor'));
677
- }
678
- } // ajax_do_action
679
-
680
-
681
- /**
682
- * Write new content to .htaccess
683
- *
684
- * @param string $new_content New content for .htaccess file.
685
- *
686
- * @return bool
687
- */
688
- function write_htaccess($new_content)
689
- {
690
- $htaccess_path = $this->get_htaccess_path();
691
- $result = $this->wp_filesystem->put_contents($htaccess_path, $new_content);
692
- @clearstatcache();
693
-
694
- if (true === $result) {
695
- $meta = $this->get_meta();
696
- $meta['edits_count']++;
697
- $this->update_options('meta', $meta);
698
- return true;
699
- } else {
700
- return false;
701
- }
702
- } // write_htaccess
703
-
704
-
705
- /**
706
- * Backup .htaccess file.
707
- *
708
- * @param bool $db_save Create backup in DB.
709
- * @param bool $file_save Create backup in FS.
710
- *
711
- * @return bool
712
- */
713
- function create_htaccess_backup($db_save = true, $file_save = true)
714
- {
715
- $success = false;
716
- $orig_path = $this->get_htaccess_path();
717
-
718
- if ($this->is_htaccess_readable()) {
719
- $htaccess_content_orig = $this->wp_filesystem->get_contents($orig_path);
720
- } else {
721
- // .htaccess doesn't exists, nothing to backup
722
- return true;
723
- }
724
 
725
- if ($db_save) {
726
- $tmp = $this->get_options();
727
- $tmp['last_backup'] = $htaccess_content_orig;
728
- $this->update_options('options', $tmp);
729
- $success = true;
730
- }
731
 
732
- if ($file_save) {
733
- if (!$this->create_secure_backup_folder()) {
734
- return false;
735
- }
736
 
737
- $backup_path = trailingslashit(WP_CONTENT_DIR) . $this->backup_folder . '/htaccess-' . date('Y-m-d-H-i-s', current_time('timestamp')) . '.backup';
738
- $success = $this->wp_filesystem->put_contents($backup_path, $htaccess_content_orig);
739
- }
740
 
741
- if ($success) {
742
- return true;
743
- } else {
744
- return false;
745
- }
746
- } // create_htaccess_backup
747
-
748
-
749
- /**
750
- * Create .htaccess backup folder and secure it.
751
- *
752
- * @return bool
753
- */
754
- function create_secure_backup_folder()
755
- {
756
- @clearstatcache();
757
- $secure_path = $this->get_backup_folder() . '.htaccess';
758
- $secure_text = trim('
759
  # WP Htaccess Editor - secure backups
760
  <files *.*>
761
  order allow,deny
762
  deny from all
763
  </files>');
764
 
765
- if (false == $this->wp_filesystem->is_dir($this->get_backup_folder())) {
766
- $new_folder = $this->wp_filesystem->mkdir($this->get_backup_folder());
767
- if (!$new_folder) {
768
- return false;
769
- }
770
- }
771
 
772
- if ($this->wp_filesystem->exists($secure_path)) {
773
- return true;
774
- } else {
775
- $write = $this->wp_filesystem->put_contents($secure_path, $secure_text);
776
- if ($write) {
777
- return true;
778
- } else {
779
- return false;
780
- }
781
- }
782
- } // create_secure_backup_folder
783
 
784
 
785
- /**
786
- * Restore last .htaccess backup from DB.
787
- *
788
- * @return bool|WP_Error
789
- */
790
- function restore_db_backup()
791
- {
792
- $htaccess_path = $this->get_htaccess_path();
793
 
794
- $backup_contents = $this->get_options('last_backup');
795
 
796
- if (false === $backup_contents) {
797
- return new WP_Error(1, __('There is no available backup to restore.', 'wp-htaccess-editor'));
798
- }
799
 
800
- if (!empty($backup_contents)) {
801
- @clearstatcache();
802
- $write = $this->wp_filesystem->put_contents($htaccess_path, $backup_contents);
803
- @clearstatcache();
804
- if ($write) {
805
- return true;
806
- } else {
807
- return new WP_Error(1, __('Unable to write .htaccess file.', 'wp-htaccess-editor'));
808
- }
809
- } else {
810
- return new WP_Error(1, __('Backup data is empty.', 'wp-htaccess-editor'));
811
- }
812
- } // restore_db_backup
813
-
814
-
815
- /**
816
- * Outputs complete plugin's admin page
817
- *
818
- * @return null
819
- */
820
- function plugin_page()
821
- {
822
- $notice_shown = false;
823
- $meta = $this->get_meta();
824
- $htaccess_content = $this->get_htaccess_content();
825
-
826
- // double check for admin priv
827
- if (!current_user_can('administrator')) {
828
- wp_die(__('Sorry, you are not allowed to access this page.', 'wp-htaccess-editor'));
829
- }
830
 
831
- settings_errors();
832
- echo '<div class="wrap">';
833
- echo '<h1><img src="' . $this->plugin_url . 'img/wp-htaccess-editor-logo.png" alt="' . __('WP Htaccess Editor', 'wp-htaccess-editor') . '" title="' . __('WP Htaccess Editor', 'wp-htaccess-editor') . '"></h1>';
834
- echo '<form id="wp-htaccess-editor-form" action="' . admin_url('options-general.php?page=wp-htaccess-editor') . '" method="post" autocomplete="off">';
835
-
836
- // TODO: properly mark for translation
837
- if (false === $this->is_htaccess_readable()) {
838
- echo '<div class="card notice-wrapper notice-error">';
839
- echo '<h2>' . __('.htaccess file does not exist!', 'wp-htaccess-editor') . '</h2>';
840
- echo '<p>We couldn\'t locate your .htaccess file on the default location <code>' . $this->get_htaccess_path() . '</code>. We\'ll attempt to create the file when you make the first save with the editor.<br>We recommend opening <a href="' . admin_url('options-permalink.php') . '">Settings - Permalinks</a> and setting a URL structure other than plain which will create the default WP .htaccess file.</p>';
841
- echo '</div>';
842
- $notice_shown = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
843
  }
844
 
845
- // TODO: properly mark for translation
846
- if (false === $this->is_htaccess_writable() && false === $notice_shown) {
847
- echo '<div class="card notice-wrapper notice-error">';
848
- echo '<h2>' . __('.htaccess file is not writable!', 'wp-htaccess-editor') . '</h2>';
849
- echo '<p>Your .htaccess file located in <code>' . $this->get_htaccess_path() . '</code> can\'t be edited by WordPress or this plugin. You can only edit it via FTP.<br>If you want to edit it via WordPress check the file permissions and set them to 644.</p>';
850
- echo '</div>';
851
- $notice_shown = true;
 
 
 
 
 
 
 
 
 
 
 
 
852
  }
853
 
854
- // TODO: properly mark for translation
855
- echo '<div class="card" id="card-description">';
856
- echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-htaccess-editor') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
857
- echo '<h2>' . __('Please read carefully before proceeding', 'wp-htaccess-editor') . '</h2>';
858
- echo '<p>There is nothing wrong with editing the .htaccess file. However, <b>in case you make a mistake while editing it, there is a possibility you\'ll need FTP access to restore your site to a working state</b>. That\'s why this plugin makes automatic backups, and we have described in detail how to recover from such incidents in the paragraphs below.<br>';
859
-
860
- echo 'For more details about .htaccess syntax and examples, please visit the <a href="http://httpd.apache.org/docs/current/howto/htaccess.html" target="_blank">official Apache Tutorial</a>.</p>';
861
-
862
- echo '<b>How to restore the site in case of error 500 or white screen caused by .htaccess</b>';
863
- echo '<p>Do not panic. No data is lost, and your site will be up again in minutes. FTP to your site or open the server\'s control panel such as cPanel to locate the .htaccess file in <code>' . $this->get_htaccess_path() . '</code>. Once you find the file there are several options to restore the site;<ol><li>Edit the file and fix the error(s) you made, or</li><li>Delete the file. Obviously, any custom rules in it will be gone, and in order for permalinks to work again you have to visit <a href="' . admin_url('options-permalink.php') . '">WP Admin - Options - Permalinks</a> and click "Save Changes". This will rebuild the default .htaccess file, or</li><li>Third (and preferred) way of fixing is to restore the file from the backup which you\'ll find in the <code>' . $this->get_backup_folder() . '</code> folder. The folder will probably contain multiple backup files. Locate the latest one by looking at the timestamp in the filename. Once located copy the file to <code>' . $this->get_htaccess_path(true) . '</code> and rename it to .htaccess.</li></ol>';
864
-
865
- echo '<b>How to restore .htaccess in case of a non-white-screen error</b>';
866
- echo '<p>Click the "Restore Last Saved Backup" button below the editor and .htaccess will be restored to the version before the last save. Please note that this method only works if the error in the file is logical, not syntactical. For instance, if you banned the wrong IP you can undo. But if you misspelled "RewriteCond" you have to use the method above as the only way to recover is via FTP or cPanel.</p>';
867
-
868
- echo '<b>Support</b>';
869
- echo '<p>For additional support and questions, please visit the <a href="https://wordpress.org/support/plugin/wp-htaccess-editor" target="_blank">official support forum</a>.</p>';
870
- echo '</div>';
871
-
872
-
873
- // ask for rating after first save
874
- if (true == $this->get_dismissed_notices('rate')) {
875
- // notice dismissed, never show again
876
- } else {
877
- if ($meta['edits_count'] > 0 && false === $notice_shown) {
878
- $notice_shown = true;
879
- $show_rate_notice = '';
880
- } else {
881
- $show_rate_notice = 'display: none;';
882
- }
883
-
884
- echo '<div id="wphe-rating-notice" class="card notice-wrapper" style="' . $show_rate_notice . '">';
885
- echo '<h2>' . __('Please help us keep the plugin free &amp; up-to-date', 'wp-htaccess-editor') . '</h2>';
886
- echo '<p>' . __('If you use &amp; enjoy WP Htaccess Editor, <b>please rate it on WordPress.org</b>. It only takes a second and helps us keep the plugin free and maintained. Thank you!', 'wp-htaccess-editor') . '</p>';
887
- echo '<p><a class="button-primary button" title="' . __('Rate WP Htaccess Editor', 'wp-htaccess-editor') . '" target="_blank" href="https://wordpress.org/support/plugin/wp-htaccess-editor/reviews/#new-post">' . __('Help keep the plugin free - rate it!', 'wp-htaccess-editor') . '</a> <a href="#" class="wphe-dismiss-notice dismiss-notice-rate" data-notice="rate">' . __('I\'ve already rated it', 'wp-htaccess-editor') . '</a></p>';
888
- echo '</div>';
889
  }
890
 
891
- echo '<div id="htaccess-editor-wrap">';
892
- if (false == $this->get_dismissed_notices('editor-warning')) {
893
- echo '<div id="enable-editor-notice" class="notice-wrapper"><h3><strong>Please be careful when editing the .htaccess file!</strong><br>This plugin makes automatic backups every time you make a change. Detailed instructions on how to restore backups are available in the box above.</h3><br><a href="#" data-notice="editor-warning" class="wphe-dismiss-notice button button-secondary">I understand. Enable the editor.</a></div>';
 
 
 
 
 
894
  }
895
- echo '<textarea cols="70" rows="20" name="newcontent" id="newcontent" aria-describedby="editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4">' . esc_textarea($htaccess_content) . '</textarea>';
896
- echo '</div>';
897
-
898
- echo '<p id="wphe-buttons" style="' . ($this->get_dismissed_notices('editor-warning') ? '' : 'display: none;') . '">';
899
- echo '<a id="wphe_save_htaccess" href="#" class="button button-primary"> Save Changes</a>';
900
- echo '<a id="wphe_test_htaccess" href="#" class="button button-secondary">Test Before Saving</a>';
901
- echo '<a id="wphe_restore_htaccess" href="#" class="button button-secondary">Restore Last Backup</a>';
902
- echo '</p>';
903
-
904
- echo '</form>';
905
- echo '</div>'; // wrap
906
- } // plugin_page
907
-
908
-
909
- /**
910
- * Clean up on uninstall; no action on deactive at the moment.
911
- *
912
- * @return null
913
- */
914
- static function uninstall()
915
- {
916
- delete_option('wp-htaccess-editor');
917
- } // uninstall
918
-
919
-
920
- /**
921
- * Disabled; we use singleton pattern so magic functions need to be disabled.
922
- *
923
- * @return null
924
- */
925
- public function __clone()
926
- {
927
- }
928
-
929
-
930
- /**
931
- * Disabled; we use singleton pattern so magic functions need to be disabled.
932
- *
933
- * @return null
934
- */
935
- public function __sleep()
936
- {
937
- }
938
-
939
-
940
- /**
941
- * Disabled; we use singleton pattern so magic functions need to be disabled.
942
- *
943
- * @return null
944
- */
945
- public function __wakeup()
946
- {
947
- }
948
  } // WP_Htaccess_Editor class
949
 
950
 
951
  // Create plugin instance and hook things up
952
  // Only in admin; there's no frontend functionality
953
  if (is_admin()) {
954
- global $wp_htaccess_editor;
955
- $wp_htaccess_editor = WP_Htaccess_Editor::get_instance();
956
- add_action('plugins_loaded', array($wp_htaccess_editor, 'load_textdomain'));
957
- register_uninstall_hook(__FILE__, array('WP_Htaccess_Editor', 'uninstall'));
958
 
959
- require_once dirname(__FILE__) . '/wf-flyout/wf-flyout.php';
960
- new wf_flyout(__FILE__);
961
  }
3
  Plugin Name: WP Htaccess Editor
4
  Plugin URI: https://wphtaccess.com/
5
  Description: Safe and easy way to edit the .htaccess file directly from WP admin without using FTP.
6
+ Version: 1.71
7
  Requires at least: 4.0
8
  Requires PHP: 5.2
9
+ Tested up to: 6.1
10
  Author: WebFactory Ltd
11
  Author URI: https://www.webfactoryltd.com/
12
  Text Domain: wp-htaccess-editor
32
 
33
  // include only file
34
  if (!defined('ABSPATH')) {
35
+ wp_die(__('Do not open this file directly.', 'wp-htaccess-editor'));
36
  }
37
 
38
 
39
  class WP_Htaccess_Editor
40
  {
41
+ protected static $instance = null;
42
+ public $version = 0;
43
+ public $plugin_url = '';
44
+ public $plugin_basename = '';
45
+ protected $options = array();
46
+ protected $wp_filesystem = null;
47
+ public $backup_folder = 'htaccess-editor-backups';
48
+
49
+
50
+ /**
51
+ * Creates a new WP_Htaccess_Editor object and implements singleton
52
+ *
53
+ * @return WP_Htaccess_Editor
54
+ */
55
+ static function get_instance()
56
+ {
57
+ if (false == is_a(self::$instance, 'WP_Htaccess_Editor')) {
58
+ self::$instance = new WP_Htaccess_Editor();
59
+ }
60
 
61
+ return self::$instance;
62
+ } // get_instance
63
+
64
+
65
+ /**
66
+ * Initialize properties, hook to filters and actions
67
+ *
68
+ * @return null
69
+ */
70
+ private function __construct()
71
+ {
72
+ $this->version = $this->get_plugin_version();
73
+ $this->plugin_url = plugin_dir_url(__FILE__);
74
+ $this->plugin_basename = plugin_basename(__FILE__);
75
+
76
+ $this->load_options();
77
+ $this->setup_wp_filesystem();
78
+
79
+ add_action('admin_menu', array($this, 'admin_menu'));
80
+ add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
81
+ add_action('wp_ajax_wp_htaccess_editor_dismiss_notice', array($this, 'ajax_dismiss_notice'));
82
+ add_action('wp_ajax_wp_htaccess_editor_do_action', array($this, 'ajax_do_action'));
83
+
84
+ add_filter('plugin_action_links_' . $this->plugin_basename, array($this, 'plugin_action_links'));
85
+ add_filter('plugin_row_meta', array($this, 'plugin_meta_links'), 10, 2);
86
+ add_filter('admin_footer_text', array($this, 'admin_footer_text'));
87
+ } // __construct
88
+
89
+
90
+ /**
91
+ * Get plugin version from file header.
92
+ *
93
+ * @return string
94
+ */
95
+ function get_plugin_version()
96
+ {
97
+ $plugin_data = get_file_data(__FILE__, array('version' => 'Version'), 'plugin');
98
+
99
+ return $plugin_data['version'];
100
+ } // get_plugin_version
101
+
102
+
103
+ /**
104
+ * Add "Edit .htaccess file" action link to plugins table, left side
105
+ *
106
+ * @param array $links Initial list of links.
107
+ *
108
+ * @return array
109
+ */
110
+ function plugin_action_links($links)
111
+ {
112
+ // whole plugin is for admins only
113
+ if (false === current_user_can('administrator')) {
114
+ return $links;
115
+ }
116
 
117
+ $settings_link = '<a href="' . admin_url('options-general.php?page=wp-htaccess-editor') . '" title="' . __('Edit .htaccess file', 'wp-htaccess-editor') . '">' . __('Edit .htaccess file', 'wp-htaccess-editor') . '</a>';
118
 
119
+ array_unshift($links, $settings_link);
120
 
121
+ return $links;
122
+ } // plugin_action_links
123
 
124
 
125
+ /**
126
+ * Add links to plugin's description in plugins table
127
+ *
128
+ * @param array $links Initial list of links.
129
+ * @param string $file Basename of current plugin.
130
+ *
131
+ * @return array
132
+ */
133
+ function plugin_meta_links($links, $file)
134
+ {
135
+ if ($file !== $this->plugin_basename) {
136
+ return $links;
137
+ }
138
 
139
+ $home_link = '<a target="_blank" href="' . $this->generate_web_link('plugins-table-right') . '" title="' . __('Plugin Homepage', 'wp-htaccess-editor') . '">' . __('Plugin Homepage', 'wp-htaccess-editor') . '</a>';
140
+ $support_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-htaccess-editor" title="' . __('Get help', 'wp-htaccess-editor') . '">' . __('Support', 'wp-htaccess-editor') . '</a>';
141
+ $rate_link = '<a target="_blank" href="https://wordpress.org/support/plugin/wp-htaccess-editor/reviews/#new-post" title="' . __('Rate the plugin', 'wp-htaccess-editor') . '">' . __('Rate the plugin ★★★★★', 'wp-htaccess-editor') . '</a>';
142
 
143
+ $links[] = $home_link;
144
+ $links[] = $support_link;
145
+ $links[] = $rate_link;
146
 
147
+ return $links;
148
+ } // plugin_meta_links
149
 
150
 
151
+ /**
152
+ * Test if we're on plugin's admin page
153
+ *
154
+ * @return bool
155
+ */
156
+ function is_plugin_page()
157
+ {
158
+ $current_screen = get_current_screen();
159
 
160
+ if ($current_screen->id === 'settings_page_wp-htaccess-editor') {
161
+ return true;
162
+ } else {
163
+ return false;
164
+ }
165
+ } // is_plugin_page
166
+
167
+
168
+ /**
169
+ * Add powered by text in admin footer
170
+ *
171
+ * @param string $text_org Default footer text.
172
+ *
173
+ * @return string
174
+ */
175
+ function admin_footer_text($text_org)
176
+ {
177
+ if (false === $this->is_plugin_page()) {
178
+ return $text_org;
179
+ }
180
 
181
+ $text = '<i><a target="_blank" href="' . $this->generate_web_link('admin_footer') . '">WP Htaccess Editor</a> v' . $this->version . ' by <a href="https://www.webfactoryltd.com/" title="' . __('Visit our site to get more great plugins', 'wp-htaccess-editor') . '" target="_blank">WebFactory Ltd</a>.';
182
+ $text .= ' Please <a target="_blank" href="https://wordpress.org/support/plugin/wp-htaccess-editor/reviews/#new-post" title="' . __('Rate the plugin', 'wp-htaccess-editor') . '">' . __('Rate the plugin ★★★★★', 'wp-htaccess-editor') . '</a>.</i> ';
183
 
184
+ return $text;
185
+ } // admin_footer_text
186
 
187
 
188
+ /**
189
+ * Loads plugin's translated strings
190
+ *
191
+ * @return null
192
+ */
193
+ function load_textdomain()
194
+ {
195
+ load_plugin_textdomain('wp-htaccess-editor');
196
+ } // load_textdomain
197
 
198
 
199
+ /**
200
+ * Initialize the WP file system.
201
+ *
202
+ * @return object
203
+ */
204
+ private function setup_wp_filesystem()
205
+ {
206
+ global $wp_filesystem;
207
 
208
+ if (empty($wp_filesystem)) {
209
+ require_once ABSPATH . '/wp-admin/includes/file.php';
210
+ WP_Filesystem();
211
+ }
212
 
213
+ $this->wp_filesystem = $wp_filesystem;
214
+ return $this->wp_filesystem;
215
+ } // setup_wp_filesystem
216
+
217
+
218
+ /**
219
+ * Get full backup folder path
220
+ *
221
+ * @return string
222
+ */
223
+ function get_backup_folder()
224
+ {
225
+ $folder = trailingslashit(WP_CONTENT_DIR) . trailingslashit($this->backup_folder);
226
+
227
+ return $folder;
228
+ } // get_backup_folder
229
+
230
+
231
+ /**
232
+ * Get .htaccess file path.
233
+ *
234
+ * @param bool $folder_only Optional. Return folder name only without filename.
235
+ *
236
+ * @return string
237
+ */
238
+ function get_htaccess_path($folder_only = false)
239
+ {
240
+ if ($folder_only) {
241
+ return get_home_path();
242
+ } else {
243
+ return get_home_path() . '.htaccess';
244
+ }
245
+ } // get_htaccess_path
246
+
247
+
248
+ /**
249
+ * Get .htaccess file content.
250
+ *
251
+ * @return string
252
+ */
253
+ function get_htaccess_content()
254
+ {
255
+ $content = $this->wp_filesystem->get_contents($this->get_htaccess_path());
256
+
257
+ return $content;
258
+ } // get_htaccess_path
259
+
260
+
261
+ /**
262
+ * Check if .htaccess is writable.
263
+ *
264
+ * @return bool
265
+ */
266
+ function is_htaccess_writable()
267
+ {
268
+ $htaccess_path = $this->get_htaccess_path();
269
+
270
+ return $this->wp_filesystem->is_writable($htaccess_path);
271
+ } // is_htaccess_writable
272
+
273
+
274
+ /**
275
+ * Check if .htaccess exists and is redable.
276
+ *
277
+ * @return bool
278
+ */
279
+ function is_htaccess_readable()
280
+ {
281
+ $htaccess_path = $this->get_htaccess_path();
282
+
283
+ return $this->wp_filesystem->is_readable($htaccess_path);
284
+ } // is_htaccess_writable
285
+
286
+
287
+ /**
288
+ * Load and prepare the options array. If needed create a new DB options entry.
289
+ *
290
+ * @return array
291
+ */
292
+ private function load_options()
293
+ {
294
+ $options = get_option('wp-htaccess-editor', array());
295
+ $change = false;
296
+
297
+ if (empty($options['meta'])) {
298
+ $options['meta'] = array('first_version' => $this->version, 'first_install' => current_time('timestamp', true), 'edits_count' => 0);
299
+ $change = true;
300
+ }
301
+ if (empty($options['dismissed_notices'])) {
302
+ $options['dismissed_notices'] = array();
303
+ $change = true;
304
+ }
305
+ if (empty($options['options'])) {
306
+ $options['options'] = array('last_backup' => false);
307
+ $change = true;
308
+ }
309
+ if ($change) {
310
+ update_option('wp-htaccess-editor', $options, true);
311
+ }
312
 
313
+ $this->options = $options;
314
+ return $options;
315
+ } // load_options
316
+
317
+
318
+ /**
319
+ * Get meta part of plugin options.
320
+ *
321
+ * @return array
322
+ */
323
+ function get_meta()
324
+ {
325
+ return $this->options['meta'];
326
+ } // get_meta
327
+
328
+
329
+ /**
330
+ * Get all dismissed notices, or check for one specific notice.
331
+ *
332
+ * @param string $notice_name Optional. Check if specified notice is dismissed.
333
+ *
334
+ * @return bool|array
335
+ */
336
+ function get_dismissed_notices($notice_name = '')
337
+ {
338
+ $notices = $this->options['dismissed_notices'];
339
+
340
+ if (empty($notice_name)) {
341
+ return $notices;
342
+ } else {
343
+ if (empty($notices[$notice_name])) {
344
+ return false;
345
+ } else {
346
+ return true;
347
+ }
348
+ }
349
+ } // get_dismissed_notices
350
+
351
+
352
+ /**
353
+ * Get all options or a single option key.
354
+ *
355
+ * @param string $option_key Optional.
356
+ *
357
+ * @return array
358
+ */
359
+ function get_options($option_key = '')
360
+ {
361
+ if (empty($option_key)) {
362
+ return $this->options['options'];
363
+ } else {
364
+ if (isset($this->options['options'][$option_key])) {
365
+ return $this->options['options'][$option_key];
366
+ } else {
367
+ return null;
368
+ }
369
+ }
370
+ } // get_options
371
+
372
+
373
+ /**
374
+ * Update plugin options array
375
+ *
376
+ * @param string $key Option key.
377
+ * @param string $data Data to save.
378
+ *
379
+ * @return bool
380
+ */
381
+ private function update_options($key, $data)
382
+ {
383
+ if (false === in_array($key, array('options', 'meta', 'dismissed_notices'))) {
384
+ trigger_error('Unknown option key used in update_options($key, $data) function.', E_USER_ERROR);
385
+ return false;
386
+ }
387
 
388
+ $this->options[$key] = $data;
389
+ $tmp = update_option('wp-htaccess-editor', $this->options);
390
+
391
+ return $tmp;
392
+ } // set_options
393
+
394
+
395
+ /**
396
+ * Add plugin menu entry under Settings menu
397
+ *
398
+ * @return null
399
+ */
400
+ function admin_menu()
401
+ {
402
+ add_options_page(__('WP Htaccess Editor', 'wp-htaccess-editor'), __('WP Htaccess Editor', 'wp-htaccess-editor'), 'administrator', 'wp-htaccess-editor', array($this, 'plugin_page'));
403
+ } // admin_menu
404
+
405
+
406
+ /**
407
+ * Helper function for generating UTM tagged links
408
+ *
409
+ * @param string $placement Optional. UTM content param.
410
+ * @param string $page Optional. Page to link to.
411
+ * @param array $params Optional. Extra URL params.
412
+ * @param string $anchor Optional. URL anchor part.
413
+ *
414
+ * @return string
415
+ */
416
+ function generate_web_link($placement = '', $page = '/', $params = array(), $anchor = '')
417
+ {
418
+ $base_url = 'https://wphtaccess.com';
419
+
420
+ if ('/' != $page) {
421
+ $page = '/' . trim($page, '/') . '/';
422
+ }
423
+ if ($page == '//') {
424
+ $page = '/';
425
+ }
426
 
427
+ $parts = array_merge(array('utm_source' => 'wp-htaccess-free', 'utm_medium' => 'plugin', 'utm_content' => $placement, 'utm_campaign' => 'wp-htaccess-free-v' . $this->version), $params);
428
 
429
+ if (!empty($anchor)) {
430
+ $anchor = '#' . trim($anchor, '#');
431
+ }
432
 
433
+ $out = $base_url . $page . '?' . http_build_query($parts, '', '&amp;') . $anchor;
434
 
435
+ return $out;
436
+ } // generate_web_link
437
 
438
 
439
+ /**
440
+ * Dismiss notice via AJAX call
441
+ *
442
+ * @return null
443
+ */
444
+ function ajax_dismiss_notice()
445
+ {
446
+ check_ajax_referer('wp-htaccess-editor_dismiss_notice');
447
 
448
+ // complete plugin is for admins only
449
+ if (false === current_user_can('administrator')) {
450
+ wp_send_json_error(__('You are not allowed to perform this action.', 'wp-htaccess-editor'));
451
+ }
452
 
453
+ if (empty($_GET['notice_name'])) {
454
+ wp_send_json_error(__('Notice name is undefined.', 'wp-htaccess-editor'));
455
+ } else {
456
+ $notice_name = substr(sanitize_key($_GET['notice_name']), 0, 64);
457
+ }
458
 
459
+ if (!$this->dismiss_notice($notice_name)) {
460
+ wp_send_json_error(__('Notice is already dismissed.', 'wp-htaccess-editor'));
461
+ } else {
462
+ wp_send_json_success();
463
+ }
464
+ } // ajax_dismiss_notice
465
+
466
+
467
+ /**
468
+ * Dismiss notice by adding it to dismissed_notices options array
469
+ *
470
+ * @param string $notice_name Notice to dismiss.
471
+ *
472
+ * @return bool
473
+ */
474
+ function dismiss_notice($notice_name)
475
+ {
476
+ if ($this->get_dismissed_notices($notice_name)) {
477
+ return false;
478
+ } else {
479
+ $notices = $this->get_dismissed_notices();
480
+ $notices[$notice_name] = true;
481
+ $this->update_options('dismissed_notices', $notices);
482
+ return true;
483
+ }
484
+ } // dismiss_notice
485
 
486
 
487
+ /**
488
+ * Returns all WP pointers
489
+ *
490
+ * @return array
491
+ */
492
+ function get_pointers()
493
+ {
494
+ $pointers = array();
495
+ $meta = $this->get_meta();
496
 
497
+ // TODO: reformat & prepare for translation
498
+ $pointers['welcome'] = array('target' => '#menu-settings', 'edge' => 'left', 'align' => 'right', 'content' => 'Thank you for using the <b style="font-weight: 700;">WP Htaccess Editor</b> plugin.<br>Open <a href="' . admin_url('options-general.php?page=wp-htaccess-editor') . '">Settings - WP Htaccess Editor</a> to access the editor and start editing the <i>.htaccess</i> file.');
499
 
500
+ if (true === version_compare($meta['first_version'], '1.60', '<')) {
501
+ // TODO: reformat & prepare for translation
502
+ $pointers['menu-relocation'] = array('target' => '#menu-settings', 'edge' => 'left', 'align' => 'right', 'content' => 'We\'ve moved the <b style="font-weight: 700;">WP Htaccess Editor</b> plugin from Tools to the Settings menu. Sorry for the inconvenience. <br>To edit htaccess open <a href="' . admin_url('options-general.php?page=wp-htaccess-editor') . '">Settings - WP Htaccess Editor</a>.');
503
+ }
504
 
505
+ return $pointers;
506
+ } // get_pointers
507
+
508
+
509
+ /**
510
+ * Enqueue CSS and JS files
511
+ *
512
+ * @param string $hook Page hook name.
513
+ *
514
+ * @return null
515
+ */
516
+ function admin_enqueue_scripts($hook)
517
+ {
518
+ // welcome pointer is shown on all pages except WPHE, only to admins, until dismissed
519
+ $pointers = $this->get_pointers();
520
+ $dismissed_notices = $this->get_dismissed_notices();
521
+
522
+ foreach ($dismissed_notices as $notice_name => $tmp) {
523
+ if ($tmp) {
524
+ unset($pointers[$notice_name]);
525
+ }
526
+ } // foreach
527
+
528
+ if (current_user_can('administrator') && !empty($pointers) && 'settings_page_wp-htaccess-editor' != $hook) {
529
+ $pointers['_nonce_dismiss_pointer'] = wp_create_nonce('wp-htaccess-editor_dismiss_notice');
530
+
531
+ wp_enqueue_style('wp-pointer');
532
+
533
+ wp_enqueue_script('wp-htaccess-editor-pointers', $this->plugin_url . 'js/wp-htaccess-editor-pointers.js', array('jquery'), $this->version, true);
534
+ wp_enqueue_script('wp-pointer');
535
+ wp_localize_script('wp-pointer', 'wp_htaccess_editor_pointers', $pointers);
536
+ }
537
 
538
+ // exit early if not on WPHE page
539
+ if ('settings_page_wp-htaccess-editor' != $hook) {
540
+ return;
541
+ }
542
 
543
+ $editor_settings['codeEditor'] = wp_enqueue_code_editor(array($this->get_htaccess_path()));
544
+ $editor_settings['codeEditor']['codemirror']['mode'] = 'nginx';
545
+
546
+ $js_localize = array(
547
+ 'undocumented_error' => __('An undocumented error has occurred. Please refresh the page and try again.', 'wp-htaccess-editor'),
548
+ 'documented_error' => __('An error has occurred.', 'wp-htaccess-editor'),
549
+ 'plugin_name' => __('WP Htaccess Editor', 'wp-htaccess-editor'),
550
+ 'home_url' => get_home_url(),
551
+ 'settings_url' => admin_url('options-general.php?page=wp-htaccess-editor'),
552
+ 'loading_icon_url' => $this->plugin_url . 'img/loading-icon.png',
553
+ 'cancel_button' => __('Cancel', 'wp-htaccess-editor'),
554
+ 'ok_button' => __('OK', 'wp-htaccess-editor'),
555
+ 'saving' => __('Saving in progress. Please wait.', 'wp-htaccess-editor'),
556
+ 'restoring' => __('Restoring in progress. Please wait.', 'wp-htaccess-editor'),
557
+ 'save_success' => __('Changes have been saved.', 'wp-htaccess-editor'),
558
+ 'restore_message' => __('This will restore the last saved backup of .htaccess. There is NO UNDO.', 'wp-htaccess-editor'),
559
+ 'restore_title' => __('Restore last backup?', 'wp-htaccess-editor'),
560
+ 'restore_button' => __('Restore Last Saved Backup of .htaccess', 'wp-htaccess-editor'),
561
+ 'restore_success' => __('Backup has been successfully restored. Click OK to reload the page.', 'wp-htaccess-editor'),
562
+ 'test_success' => __('Test Completed Successfully', 'wp-htaccess-editor'),
563
+ 'test_failed' => __('Test Failed', 'wp-htaccess-editor'),
564
+ 'testing' => __('Testing .htaccess syntax. Please wait.', 'wp-htaccess-editor'),
565
+ 'site_error' => __('There is an error in the .htaccess file and your site is probably no longer accessible.<br><br>DO NOT panic or reload this page. Close this message. First, try the "Restore Last Backup" button. If it doesn\'t work read instruction on this very page on how to restore the site.', 'wp-htaccess-editor'),
566
+ 'nonce_dismiss_notice' => wp_create_nonce('wp-htaccess-editor_dismiss_notice'),
567
+ 'nonce_do_action' => wp_create_nonce('wp-htaccess-editor_do_action'),
568
+ 'cm_settings' => $editor_settings
569
+ );
570
+
571
+ wp_enqueue_style('wp-codemirror');
572
+ wp_enqueue_style('wp-htaccess-editor', $this->plugin_url . 'css/wp-htaccess-editor.css', array(), $this->version);
573
+ wp_enqueue_style('wp-htaccess-editor-sweetalert2', $this->plugin_url . 'css/sweetalert2.min.css', array(), $this->version);
574
+
575
+ wp_enqueue_script('jquery-ui-tabs');
576
+ wp_enqueue_script('wp-htaccess-editor-sweetalert2', $this->plugin_url . 'js/sweetalert2.min.js', array('jquery'), $this->version, true);
577
+ wp_enqueue_script('wp-theme-plugin-editor');
578
+ wp_enqueue_script('wp-htaccess-editor', $this->plugin_url . 'js/wp-htaccess-editor.js', array('jquery'), $this->version, true);
579
+ wp_enqueue_script('wp-htaccess-cm-resize', $this->plugin_url . 'js/cm-resize.min.js', array('jquery'), $this->version, true);
580
+ wp_localize_script('wp-htaccess-editor', 'wp_htaccess_editor', $js_localize);
581
+
582
+ // fix for aggressive plugins that include their CSS files on all pages
583
+ wp_dequeue_style('uiStyleSheet');
584
+ wp_dequeue_style('wpcufpnAdmin');
585
+ wp_dequeue_style('unifStyleSheet');
586
+ wp_dequeue_style('wpcufpn_codemirror');
587
+ wp_dequeue_style('wpcufpn_codemirrorTheme');
588
+ wp_dequeue_style('collapse-admin-css');
589
+ wp_dequeue_style('jquery-ui-css');
590
+ wp_dequeue_style('tribe-common-admin');
591
+ wp_dequeue_style('file-manager__jquery-ui-css');
592
+ wp_dequeue_style('file-manager__jquery-ui-css-theme');
593
+ wp_dequeue_style('wpmegmaps-jqueryui');
594
+ wp_dequeue_style('wp-botwatch-css');
595
+ } // admin_enqueue_scripts
596
+
597
+
598
+ /**
599
+ * Run an action via AJAX call.
600
+ *
601
+ * @return null
602
+ */
603
+ function ajax_do_action()
604
+ {
605
+ check_ajax_referer('wp-htaccess-editor_do_action');
606
+
607
+ if (false === current_user_can('administrator')) {
608
+ wp_send_json_error(__('You are not allowed to perform this action.', 'wp-htaccess-editor'));
609
+ }
610
 
611
+ if (empty($_POST['subaction'])) {
612
+ wp_send_json_error(__('Subaction name is undefined.', 'wp-htaccess-editor'));
613
+ } else {
614
+ $subaction = substr(sanitize_key($_POST['subaction']), 0, 64);
615
+ }
616
 
617
+ if ($subaction == 'save_htaccess') {
618
+ if (false == $this->create_htaccess_backup(true, true)) {
619
+ wp_send_json_error(__('Unable to create .htaccess backup in /wp-content/. Please check file permissions.', 'wp-htaccess-editor'));
620
+ }
621
+
622
+ // since we're working with code it's hard/impossible to sanitize input
623
+ // WP core doesn't sanitize either for plugin/theme editor so it should be OK
624
+ // nonce is in place and user permissions are double checked
625
+ $new_content = wp_unslash(trim($_POST['new_content']));
626
+ if (false == $this->is_htaccess_writable() || false == $this->write_htaccess($new_content)) {
627
+ wp_send_json_error(__('Could not write .htaccess file. Please check file permissions.', 'wp-htaccess-editor'));
628
+ }
629
+
630
+ wp_send_json_success();
631
+ } elseif ($subaction == 'test_htaccess') {
632
+ $new_content = wp_unslash(trim($_POST['new_content']));
633
+ $uploads_directory = wp_upload_dir();
634
+ $test_id = rand(1000, 9999);
635
+ $htaccess_test_folder = $uploads_directory['basedir'] . '/htaccess-test-' . $test_id . '/';
636
+ $htaccess_test_url = $uploads_directory['baseurl'] . '/htaccess-test-' . $test_id . '/';
637
+
638
+ // Create test directory and files
639
+ if (!$this->wp_filesystem->is_dir($htaccess_test_folder)) {
640
+ if (true !== $this->wp_filesystem->mkdir($htaccess_test_folder, 0777)) {
641
+ wp_send_json_error(__('Failed to create test directory. Please check that your uploads folder is writable.', 'wp-htaccess-editor'));
642
+ }
643
+ }
644
+
645
+ if (true !== $this->wp_filesystem->put_contents($htaccess_test_folder . 'index.html', 'htaccess-test-' . $test_id)) {
646
+ wp_send_json_error(__('Failed to create test files. Please check that your uploads folder is writable.', 'wp-htaccess-editor'));
647
+ }
648
+
649
+ if (true !== $this->wp_filesystem->put_contents($htaccess_test_folder . '.htaccess', $new_content)) {
650
+ wp_send_json_error(__('Failed to create test directory and files. Please check that your uploads folder is writeable.', 'wp-htaccess-editor'));
651
+ }
652
+
653
+ // Retrieve test file over http
654
+ $response = wp_remote_get($htaccess_test_url . 'index.html', array('sslverify' => false, 'redirection' => 0));
655
+ $response_code = wp_remote_retrieve_response_code($response);
656
+
657
+ // Remove Test Directory
658
+ $this->wp_filesystem->delete($htaccess_test_folder . '.htaccess');
659
+ $this->wp_filesystem->delete($htaccess_test_folder . 'index.html');
660
+ $this->wp_filesystem->rmdir($htaccess_test_folder);
661
+
662
+ // Check if test file content is what we expect
663
+ if ((in_array($response_code, range(200, 299)) && !is_wp_error($response) && wp_remote_retrieve_body($response) == 'htaccess-test-' . $test_id) || (in_array($response_code, range(300, 399)) && !is_wp_error($response))) {
664
+ wp_send_json_success(__('This test only makes sure there are no syntax errors that could result in 500 errors for your entire site. It does not check the logic of the <i>.htaccess</i> file, ie if redirects work as intended.', 'wp-htaccess-editor'));
665
+ } else {
666
+ wp_send_json_error(__('There are syntax errors in your unsaved <i>.htaccess</i> content. Saving it will cause your entire site, including the admin, to become inaccessible. Fix the errors before saving.', 'wp-htaccess-editor'));
667
+ }
668
+ } elseif ($subaction == 'restore_htaccess_from_db') {
669
+ $res = $this->restore_db_backup();
670
+ if (!is_wp_error($res)) {
671
+ wp_send_json_success();
672
+ } else {
673
+ wp_send_json_error($res->get_error_message());
674
+ }
675
+ } else {
676
+ wp_send_json_error(__('Unknown subaction.', 'wp-htaccess-editor'));
677
+ }
678
+ } // ajax_do_action
679
+
680
+
681
+ /**
682
+ * Write new content to .htaccess
683
+ *
684
+ * @param string $new_content New content for .htaccess file.
685
+ *
686
+ * @return bool
687
+ */
688
+ function write_htaccess($new_content)
689
+ {
690
+ $htaccess_path = $this->get_htaccess_path();
691
+ $result = $this->wp_filesystem->put_contents($htaccess_path, $new_content);
692
+ @clearstatcache();
693
+
694
+ if (true === $result) {
695
+ $meta = $this->get_meta();
696
+ $meta['edits_count']++;
697
+ $this->update_options('meta', $meta);
698
+ return true;
699
+ } else {
700
+ return false;
701
+ }
702
+ } // write_htaccess
703
+
704
+
705
+ /**
706
+ * Backup .htaccess file.
707
+ *
708
+ * @param bool $db_save Create backup in DB.
709
+ * @param bool $file_save Create backup in FS.
710
+ *
711
+ * @return bool
712
+ */
713
+ function create_htaccess_backup($db_save = true, $file_save = true)
714
+ {
715
+ $success = false;
716
+ $orig_path = $this->get_htaccess_path();
717
+
718
+ if ($this->is_htaccess_readable()) {
719
+ $htaccess_content_orig = $this->wp_filesystem->get_contents($orig_path);
720
+ } else {
721
+ // .htaccess doesn't exists, nothing to backup
722
+ return true;
723
+ }
724
 
725
+ if ($db_save) {
726
+ $tmp = $this->get_options();
727
+ $tmp['last_backup'] = $htaccess_content_orig;
728
+ $this->update_options('options', $tmp);
729
+ $success = true;
730
+ }
731
 
732
+ if ($file_save) {
733
+ if (!$this->create_secure_backup_folder()) {
734
+ return false;
735
+ }
736
 
737
+ $backup_path = trailingslashit(WP_CONTENT_DIR) . $this->backup_folder . '/htaccess-' . date('Y-m-d-H-i-s', current_time('timestamp')) . '.backup';
738
+ $success = $this->wp_filesystem->put_contents($backup_path, $htaccess_content_orig);
739
+ }
740
 
741
+ if ($success) {
742
+ return true;
743
+ } else {
744
+ return false;
745
+ }
746
+ } // create_htaccess_backup
747
+
748
+
749
+ /**
750
+ * Create .htaccess backup folder and secure it.
751
+ *
752
+ * @return bool
753
+ */
754
+ function create_secure_backup_folder()
755
+ {
756
+ @clearstatcache();
757
+ $secure_path = $this->get_backup_folder() . '.htaccess';
758
+ $secure_text = trim('
759
  # WP Htaccess Editor - secure backups
760
  <files *.*>
761
  order allow,deny
762
  deny from all
763
  </files>');
764
 
765
+ if (false == $this->wp_filesystem->is_dir($this->get_backup_folder())) {
766
+ $new_folder = $this->wp_filesystem->mkdir($this->get_backup_folder());
767
+ if (!$new_folder) {
768
+ return false;
769
+ }
770
+ }
771
 
772
+ if ($this->wp_filesystem->exists($secure_path)) {
773
+ return true;
774
+ } else {
775
+ $write = $this->wp_filesystem->put_contents($secure_path, $secure_text);
776
+ if ($write) {
777
+ return true;
778
+ } else {
779
+ return false;
780
+ }
781
+ }
782
+ } // create_secure_backup_folder
783
 
784
 
785
+ /**
786
+ * Restore last .htaccess backup from DB.
787
+ *
788
+ * @return bool|WP_Error
789
+ */
790
+ function restore_db_backup()
791
+ {
792
+ $htaccess_path = $this->get_htaccess_path();
793
 
794
+ $backup_contents = $this->get_options('last_backup');
795
 
796
+ if (false === $backup_contents) {
797
+ return new WP_Error(1, __('There is no available backup to restore.', 'wp-htaccess-editor'));
798
+ }
799
 
800
+ if (!empty($backup_contents)) {
801
+ @clearstatcache();
802
+ $write = $this->wp_filesystem->put_contents($htaccess_path, $backup_contents);
803
+ @clearstatcache();
804
+ if ($write) {
805
+ return true;
806
+ } else {
807
+ return new WP_Error(1, __('Unable to write .htaccess file.', 'wp-htaccess-editor'));
808
+ }
809
+ } else {
810
+ return new WP_Error(1, __('Backup data is empty.', 'wp-htaccess-editor'));
811
+ }
812
+ } // restore_db_backup
813
+
814
+
815
+ /**
816
+ * Outputs complete plugin's admin page
817
+ *
818
+ * @return null
819
+ */
820
+ function plugin_page()
821
+ {
822
+ $notice_shown = false;
823
+ $meta = $this->get_meta();
824
+ $htaccess_content = $this->get_htaccess_content();
825
+
826
+ // double check for admin priv
827
+ if (!current_user_can('administrator')) {
828
+ wp_die(__('Sorry, you are not allowed to access this page.', 'wp-htaccess-editor'));
829
+ }
830
 
831
+ settings_errors();
832
+ echo '<div class="wrap">';
833
+ echo '<h1><img src="' . esc_url($this->plugin_url) . 'img/wp-htaccess-editor-logo.png" alt="' . esc_html__('WP Htaccess Editor', 'wp-htaccess-editor') . '" title="' . __('WP Htaccess Editor', 'wp-htaccess-editor') . '"></h1>';
834
+ echo '<form id="wp-htaccess-editor-form" action="' . admin_url('options-general.php?page=wp-htaccess-editor') . '" method="post" autocomplete="off">';
835
+
836
+ // TODO: properly mark for translation
837
+ if (false === $this->is_htaccess_readable()) {
838
+ echo '<div class="card notice-wrapper notice-error">';
839
+ echo '<h2>' . __('.htaccess file does not exist!', 'wp-htaccess-editor') . '</h2>';
840
+ echo '<p>We couldn\'t locate your .htaccess file on the default location <code>' . esc_attr($this->get_htaccess_path()) . '</code>. We\'ll attempt to create the file when you make the first save with the editor.<br>We recommend opening <a href="' . admin_url('options-permalink.php') . '">Settings - Permalinks</a> and setting a URL structure other than plain which will create the default WP .htaccess file.</p>';
841
+ echo '</div>';
842
+ $notice_shown = true;
843
+ }
844
+
845
+ // TODO: properly mark for translation
846
+ if (false === $this->is_htaccess_writable() && false === $notice_shown) {
847
+ echo '<div class="card notice-wrapper notice-error">';
848
+ echo '<h2>' . __('.htaccess file is not writable!', 'wp-htaccess-editor') . '</h2>';
849
+ echo '<p>Your .htaccess file located in <code>' . esc_attr($this->get_htaccess_path()) . '</code> can\'t be edited by WordPress or this plugin. You can only edit it via FTP.<br>If you want to edit it via WordPress check the file permissions and set them to 644.</p>';
850
+ echo '</div>';
851
+ $notice_shown = true;
852
+ }
853
+
854
+ // TODO: properly mark for translation
855
+ echo '<div class="card" id="card-description">';
856
+ echo '<a class="toggle-card" href="#" title="' . __('Collapse / expand box', 'wp-htaccess-editor') . '"><span class="dashicons dashicons-arrow-up-alt2"></span></a>';
857
+ echo '<h2>' . __('Please read carefully before proceeding', 'wp-htaccess-editor') . '</h2>';
858
+ echo '<p>There is nothing wrong with editing the .htaccess file. However, <b>in case you make a mistake while editing it, there is a possibility you\'ll need FTP access to restore your site to a working state</b>. That\'s why this plugin makes automatic backups, and we have described in detail how to recover from such incidents in the paragraphs below.<br>';
859
+
860
+ echo 'For more details about .htaccess syntax and examples, please visit the <a href="http://httpd.apache.org/docs/current/howto/htaccess.html" target="_blank">official Apache Tutorial</a>.</p>';
861
+
862
+ echo '<b>How to restore the site in case of error 500 or white screen caused by .htaccess</b>';
863
+ echo '<p>Do not panic. No data is lost, and your site will be up again in minutes. FTP to your site or open the server\'s control panel such as cPanel to locate the .htaccess file in <code>' . esc_attr($this->get_htaccess_path()) . '</code>. Once you find the file there are several options to restore the site;<ol><li>Edit the file and fix the error(s) you made, or</li><li>Delete the file. Obviously, any custom rules in it will be gone, and in order for permalinks to work again you have to visit <a href="' . admin_url('options-permalink.php') . '">WP Admin - Options - Permalinks</a> and click "Save Changes". This will rebuild the default .htaccess file, or</li><li>Third (and preferred) way of fixing is to restore the file from the backup which you\'ll find in the <code>' . esc_attr($this->get_backup_folder()) . '</code> folder. The folder will probably contain multiple backup files. Locate the latest one by looking at the timestamp in the filename. Once located copy the file to <code>' . esc_attr($this->get_htaccess_path(true)) . '</code> and rename it to .htaccess.</li></ol>';
864
+
865
+ echo '<b>How to restore .htaccess in case of a non-white-screen error</b>';
866
+ echo '<p>Click the "Restore Last Saved Backup" button below the editor and .htaccess will be restored to the version before the last save. Please note that this method only works if the error in the file is logical, not syntactical. For instance, if you banned the wrong IP you can undo. But if you misspelled "RewriteCond" you have to use the method above as the only way to recover is via FTP or cPanel.</p>';
867
+
868
+ echo '<b>Support</b>';
869
+ echo '<p>For additional support and questions, please visit the <a href="https://wordpress.org/support/plugin/wp-htaccess-editor" target="_blank">official support forum</a>.</p>';
870
+ echo '</div>';
871
+
872
+
873
+ // ask for rating after first save
874
+ if (true == $this->get_dismissed_notices('rate')) {
875
+ // notice dismissed, never show again
876
+ } else {
877
+ if ($meta['edits_count'] > 0 && false === $notice_shown) {
878
+ $notice_shown = true;
879
+ $show_rate_notice = '';
880
+ } else {
881
+ $show_rate_notice = 'display: none;';
882
+ }
883
+
884
+ echo '<div id="wphe-rating-notice" class="card notice-wrapper" style="' . esc_attr($show_rate_notice) . '">';
885
+ echo '<h2>' . __('Please help us keep the plugin free &amp; up-to-date', 'wp-htaccess-editor') . '</h2>';
886
+ echo '<p>' . __('If you use &amp; enjoy WP Htaccess Editor, <b>please rate it on WordPress.org</b>. It only takes a second and helps us keep the plugin free and maintained. Thank you!', 'wp-htaccess-editor') . '</p>';
887
+ echo '<p><a class="button-primary button" title="' . __('Rate WP Htaccess Editor', 'wp-htaccess-editor') . '" target="_blank" href="https://wordpress.org/support/plugin/wp-htaccess-editor/reviews/#new-post">' . __('Help keep the plugin free - rate it!', 'wp-htaccess-editor') . '</a> <a href="#" class="wphe-dismiss-notice dismiss-notice-rate" data-notice="rate">' . __('I\'ve already rated it', 'wp-htaccess-editor') . '</a></p>';
888
+ echo '</div>';
889
+ }
890
+
891
+ echo '<div id="htaccess-editor-wrap">';
892
+ if (false == $this->get_dismissed_notices('editor-warning')) {
893
+ echo '<div id="enable-editor-notice" class="notice-wrapper"><h3><strong>Please be careful when editing the .htaccess file!</strong><br>This plugin makes automatic backups every time you make a change. Detailed instructions on how to restore backups are available in the box above.</h3><br><a href="#" data-notice="editor-warning" class="wphe-dismiss-notice button button-secondary">I understand. Enable the editor.</a></div>';
894
+ }
895
+ echo '<textarea cols="70" rows="20" name="newcontent" id="newcontent" aria-describedby="editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4">' . esc_textarea($htaccess_content) . '</textarea>';
896
+ echo '</div>';
897
+
898
+ echo '<p id="wphe-buttons" style="' . ($this->get_dismissed_notices('editor-warning') ? '' : 'display: none;') . '">';
899
+ echo '<a id="wphe_save_htaccess" href="#" class="button button-primary"> Save Changes</a>';
900
+ echo '<a id="wphe_test_htaccess" href="#" class="button button-secondary">Test Before Saving</a>';
901
+ echo '<a id="wphe_restore_htaccess" href="#" class="button button-secondary">Restore Last Backup</a>';
902
+ echo '</p>';
903
+
904
+ echo '</form>';
905
+ echo '</div>'; // wrap
906
+ } // plugin_page
907
+
908
+ function wp_kses_wf($html)
909
+ {
910
+ add_filter('safe_style_css', function ($styles) {
911
+ $styles_wf = array(
912
+ 'text-align',
913
+ 'margin',
914
+ 'color',
915
+ 'float',
916
+ 'border',
917
+ 'background',
918
+ 'background-color',
919
+ 'border-bottom',
920
+ 'border-bottom-color',
921
+ 'border-bottom-style',
922
+ 'border-bottom-width',
923
+ 'border-collapse',
924
+ 'border-color',
925
+ 'border-left',
926
+ 'border-left-color',
927
+ 'border-left-style',
928
+ 'border-left-width',
929
+ 'border-right',
930
+ 'border-right-color',
931
+ 'border-right-style',
932
+ 'border-right-width',
933
+ 'border-spacing',
934
+ 'border-style',
935
+ 'border-top',
936
+ 'border-top-color',
937
+ 'border-top-style',
938
+ 'border-top-width',
939
+ 'border-width',
940
+ 'caption-side',
941
+ 'clear',
942
+ 'cursor',
943
+ 'direction',
944
+ 'font',
945
+ 'font-family',
946
+ 'font-size',
947
+ 'font-style',
948
+ 'font-variant',
949
+ 'font-weight',
950
+ 'height',
951
+ 'letter-spacing',
952
+ 'line-height',
953
+ 'margin-bottom',
954
+ 'margin-left',
955
+ 'margin-right',
956
+ 'margin-top',
957
+ 'overflow',
958
+ 'padding',
959
+ 'padding-bottom',
960
+ 'padding-left',
961
+ 'padding-right',
962
+ 'padding-top',
963
+ 'text-decoration',
964
+ 'text-indent',
965
+ 'vertical-align',
966
+ 'width',
967
+ 'display',
968
+ );
969
+
970
+ foreach ($styles_wf as $style_wf) {
971
+ $styles[] = $style_wf;
972
+ }
973
+ return $styles;
974
+ });
975
+
976
+ $allowed_tags = wp_kses_allowed_html('post');
977
+ $allowed_tags['input'] = array(
978
+ 'type' => true,
979
+ 'style' => true,
980
+ 'class' => true,
981
+ 'id' => true,
982
+ 'checked' => true,
983
+ 'disabled' => true,
984
+ 'name' => true,
985
+ 'size' => true,
986
+ 'placeholder' => true,
987
+ 'value' => true,
988
+ 'data-*' => true,
989
+ 'size' => true,
990
+ 'disabled' => true
991
+ );
992
+
993
+ $allowed_tags['textarea'] = array(
994
+ 'type' => true,
995
+ 'style' => true,
996
+ 'class' => true,
997
+ 'id' => true,
998
+ 'checked' => true,
999
+ 'disabled' => true,
1000
+ 'name' => true,
1001
+ 'size' => true,
1002
+ 'placeholder' => true,
1003
+ 'value' => true,
1004
+ 'data-*' => true,
1005
+ 'cols' => true,
1006
+ 'rows' => true,
1007
+ 'disabled' => true,
1008
+ 'autocomplete' => true
1009
+ );
1010
+
1011
+ $allowed_tags['select'] = array(
1012
+ 'type' => true,
1013
+ 'style' => true,
1014
+ 'class' => true,
1015
+ 'id' => true,
1016
+ 'checked' => true,
1017
+ 'disabled' => true,
1018
+ 'name' => true,
1019
+ 'size' => true,
1020
+ 'placeholder' => true,
1021
+ 'value' => true,
1022
+ 'data-*' => true,
1023
+ 'multiple' => true,
1024
+ 'disabled' => true
1025
+ );
1026
+
1027
+ $allowed_tags['option'] = array(
1028
+ 'type' => true,
1029
+ 'style' => true,
1030
+ 'class' => true,
1031
+ 'id' => true,
1032
+ 'checked' => true,
1033
+ 'disabled' => true,
1034
+ 'name' => true,
1035
+ 'size' => true,
1036
+ 'placeholder' => true,
1037
+ 'value' => true,
1038
+ 'selected' => true,
1039
+ 'data-*' => true
1040
+ );
1041
+ $allowed_tags['optgroup'] = array(
1042
+ 'type' => true,
1043
+ 'style' => true,
1044
+ 'class' => true,
1045
+ 'id' => true,
1046
+ 'checked' => true,
1047
+ 'disabled' => true,
1048
+ 'name' => true,
1049
+ 'size' => true,
1050
+ 'placeholder' => true,
1051
+ 'value' => true,
1052
+ 'selected' => true,
1053
+ 'data-*' => true,
1054
+ 'label' => true
1055
+ );
1056
+
1057
+ $allowed_tags['a'] = array(
1058
+ 'href' => true,
1059
+ 'data-*' => true,
1060
+ 'class' => true,
1061
+ 'style' => true,
1062
+ 'id' => true,
1063
+ 'target' => true,
1064
+ 'data-*' => true,
1065
+ 'role' => true,
1066
+ 'aria-controls' => true,
1067
+ 'aria-selected' => true,
1068
+ 'disabled' => true
1069
+ );
1070
+
1071
+ $allowed_tags['div'] = array(
1072
+ 'style' => true,
1073
+ 'class' => true,
1074
+ 'id' => true,
1075
+ 'data-*' => true,
1076
+ 'role' => true,
1077
+ 'aria-labelledby' => true,
1078
+ 'value' => true,
1079
+ 'aria-modal' => true,
1080
+ 'tabindex' => true
1081
+ );
1082
+
1083
+ $allowed_tags['li'] = array(
1084
+ 'style' => true,
1085
+ 'class' => true,
1086
+ 'id' => true,
1087
+ 'data-*' => true,
1088
+ 'role' => true,
1089
+ 'aria-labelledby' => true,
1090
+ 'value' => true,
1091
+ 'aria-modal' => true,
1092
+ 'tabindex' => true
1093
+ );
1094
+
1095
+ $allowed_tags['span'] = array(
1096
+ 'style' => true,
1097
+ 'class' => true,
1098
+ 'id' => true,
1099
+ 'data-*' => true,
1100
+ 'aria-hidden' => true
1101
+ );
1102
+
1103
+ $allowed_tags['style'] = array(
1104
+ 'class' => true,
1105
+ 'id' => true,
1106
+ 'type' => true
1107
+ );
1108
+
1109
+ $allowed_tags['fieldset'] = array(
1110
+ 'class' => true,
1111
+ 'id' => true,
1112
+ 'type' => true
1113
+ );
1114
+
1115
+ $allowed_tags['link'] = array(
1116
+ 'class' => true,
1117
+ 'id' => true,
1118
+ 'type' => true,
1119
+ 'rel' => true,
1120
+ 'href' => true,
1121
+ 'media' => true
1122
+ );
1123
+
1124
+ $allowed_tags['form'] = array(
1125
+ 'style' => true,
1126
+ 'class' => true,
1127
+ 'id' => true,
1128
+ 'method' => true,
1129
+ 'action' => true,
1130
+ 'data-*' => true
1131
+ );
1132
+
1133
+ $allowed_tags['script'] = array(
1134
+ 'class' => true,
1135
+ 'id' => true,
1136
+ 'type' => true,
1137
+ 'src' => true
1138
+ );
1139
+
1140
+ $allowed_tags['path'] = array(
1141
+ 'class' => true,
1142
+ 'id' => true,
1143
+ 'fill' => true,
1144
+ 'd' => true
1145
+ );
1146
+
1147
+ $allowed_tags['svg'] = array(
1148
+ 'class' => true,
1149
+ 'id' => true,
1150
+ 'aria-hidden' => true,
1151
+ 'role' => true,
1152
+ 'xmlns' => true,
1153
+ 'viewBox' => true,
1154
+ 'viewbox' => true,
1155
+ 'width' => true,
1156
+ 'height' => true,
1157
+ 'focusable' => true,
1158
+ 'style' => true,
1159
+ 'xml:space' => true
1160
+ );
1161
+
1162
+ $allowed_tags['defs'] = array(
1163
+ 'class' => true,
1164
+ 'id' => true,
1165
+ );
1166
+
1167
+ $allowed_tags['filter'] = array(
1168
+ 'class' => true,
1169
+ 'id' => true,
1170
+ );
1171
+
1172
+ $allowed_tags['rect'] = array(
1173
+ 'class' => true,
1174
+ 'x' => true,
1175
+ 'y' => true,
1176
+ 'width' => true,
1177
+ 'height' => true,
1178
+ 'fill' => true
1179
+ );
1180
+
1181
+ $allowed_tags['polygon'] = array(
1182
+ 'class' => true,
1183
+ 'points' => true,
1184
+ 'width' => true,
1185
+ 'height' => true,
1186
+ 'fill' => true
1187
+ );
1188
+
1189
+ echo wp_kses($html, $allowed_tags);
1190
+
1191
+ add_filter('safe_style_css', function ($styles) {
1192
+ $styles_wf = array(
1193
+ 'text-align',
1194
+ 'margin',
1195
+ 'color',
1196
+ 'float',
1197
+ 'border',
1198
+ 'background',
1199
+ 'background-color',
1200
+ 'border-bottom',
1201
+ 'border-bottom-color',
1202
+ 'border-bottom-style',
1203
+ 'border-bottom-width',
1204
+ 'border-collapse',
1205
+ 'border-color',
1206
+ 'border-left',
1207
+ 'border-left-color',
1208
+ 'border-left-style',
1209
+ 'border-left-width',
1210
+ 'border-right',
1211
+ 'border-right-color',
1212
+ 'border-right-style',
1213
+ 'border-right-width',
1214
+ 'border-spacing',
1215
+ 'border-style',
1216
+ 'border-top',
1217
+ 'border-top-color',
1218
+ 'border-top-style',
1219
+ 'border-top-width',
1220
+ 'border-width',
1221
+ 'caption-side',
1222
+ 'clear',
1223
+ 'cursor',
1224
+ 'direction',
1225
+ 'font',
1226
+ 'font-family',
1227
+ 'font-size',
1228
+ 'font-style',
1229
+ 'font-variant',
1230
+ 'font-weight',
1231
+ 'height',
1232
+ 'letter-spacing',
1233
+ 'line-height',
1234
+ 'margin-bottom',
1235
+ 'margin-left',
1236
+ 'margin-right',
1237
+ 'margin-top',
1238
+ 'overflow',
1239
+ 'padding',
1240
+ 'padding-bottom',
1241
+ 'padding-left',
1242
+ 'padding-right',
1243
+ 'padding-top',
1244
+ 'text-decoration',
1245
+ 'text-indent',
1246
+ 'vertical-align',
1247
+ 'width'
1248
+ );
1249
+
1250
+ foreach ($styles_wf as $style_wf) {
1251
+ if (($key = array_search($style_wf, $styles)) !== false) {
1252
+ unset($styles[$key]);
1253
+ }
1254
+ }
1255
+ return $styles;
1256
+ });
1257
  }
1258
 
1259
+
1260
+ /**
1261
+ * Clean up on uninstall; no action on deactive at the moment.
1262
+ *
1263
+ * @return null
1264
+ */
1265
+ static function uninstall()
1266
+ {
1267
+ delete_option('wp-htaccess-editor');
1268
+ } // uninstall
1269
+
1270
+
1271
+ /**
1272
+ * Disabled; we use singleton pattern so magic functions need to be disabled.
1273
+ *
1274
+ * @return null
1275
+ */
1276
+ public function __clone()
1277
+ {
1278
  }
1279
 
1280
+
1281
+ /**
1282
+ * Disabled; we use singleton pattern so magic functions need to be disabled.
1283
+ *
1284
+ * @return null
1285
+ */
1286
+ public function __sleep()
1287
+ {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1288
  }
1289
 
1290
+
1291
+ /**
1292
+ * Disabled; we use singleton pattern so magic functions need to be disabled.
1293
+ *
1294
+ * @return null
1295
+ */
1296
+ public function __wakeup()
1297
+ {
1298
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1299
  } // WP_Htaccess_Editor class
1300
 
1301
 
1302
  // Create plugin instance and hook things up
1303
  // Only in admin; there's no frontend functionality
1304
  if (is_admin()) {
1305
+ global $wp_htaccess_editor;
1306
+ $wp_htaccess_editor = WP_Htaccess_Editor::get_instance();
1307
+ add_action('plugins_loaded', array($wp_htaccess_editor, 'load_textdomain'));
1308
+ register_uninstall_hook(__FILE__, array('WP_Htaccess_Editor', 'uninstall'));
1309
 
1310
+ require_once dirname(__FILE__) . '/wf-flyout/wf-flyout.php';
1311
+ new wf_flyout(__FILE__);
1312
  }