PluginProbe ʕ •ᴥ•ʔ
Permalink Manager Lite / 2.5.4
Permalink Manager Lite v2.5.4
2.5.4 2.5.3.4 2.2.18 2.2.19.2 2.2.19.3 2.2.19.3.1 2.2.2 2.2.20 2.2.20.1 2.2.20.3 2.2.4 2.2.5 2.2.6 2.2.7.2 2.2.7.3 2.2.7.5 2.2.7.6 2.2.8.4 2.2.8.5 2.2.8.6 2.2.8.7 2.2.8.9 2.2.9.1 2.2.9.2 2.2.9.2.1 2.2.9.3 2.2.9.4 2.2.9.6 2.2.9.7 2.2.9.9 2.3.0 2.3.1.1 2.4.0 2.4.1 2.4.1.2 2.4.1.3 2.4.1.4 2.4.1.5 2.4.1.6 2.4.2 2.4.2.1 2.4.3 2.4.3.1 2.4.3.2 2.4.3.3 2.4.3.4 2.4.4 2.4.4.1 2.4.4.2 2.4.4.3 2.5.0 2.5.1 2.5.1.1 2.5.1.2 2.5.1.3 2.5.1.4 2.5.2 2.5.2.1 2.5.2.2 2.5.2.3 2.5.2.4 2.5.3 2.5.3.1 2.5.3.2 2.5.3.3 trunk 0.2 0.3 0.3.1 0.3.2 0.3.3 0.3.4 0.4 0.4.1 0.4.2 0.4.3 0.4.4 0.4.6 0.4.7 0.4.8 0.4.9 0.5.3 0.5.4 1.0.0 1.0.1 1.0.4 1.1.0 1.1.1 1.1.2 1.11.6.3 2.0.0 2.0.3 2.0.4 2.0.4.3 2.0.5.1 2.0.5.2 2.0.5.3 2.0.5.3.1 2.0.5.4 2.0.5.4a 2.0.5.5 2.0.5.6 2.0.5.6.1 2.0.5.7 2.0.5.9a 2.0.6.2.1 2.0.6.2a 2.0.6.3 2.1.0 2.1.1 2.1.2.4 2.2.0 2.2.1.1 2.2.1.2 2.2.11 2.2.12 2.2.13.1 2.2.14 2.2.15.1 2.2.16 2.2.17
permalink-manager / includes / core / permalink-manager-actions.php
permalink-manager / includes / core Last commit date
permalink-manager-actions.php 3 days ago permalink-manager-admin-functions.php 3 days ago permalink-manager-core-functions.php 3 days ago permalink-manager-debug.php 3 days ago permalink-manager-gutenberg.php 3 days ago permalink-manager-helper-functions.php 3 days ago permalink-manager-permastructures-functions.php 3 days ago permalink-manager-uri-functions-post.php 3 days ago permalink-manager-uri-functions.php 3 days ago
permalink-manager-actions.php
786 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 /**
8 * Additional hooks for "Permalink Manager Pro"
9 */
10 class Permalink_Manager_Actions {
11
12 public function __construct() {
13 add_action( 'admin_init', array( $this, 'trigger_action' ), 9 );
14 add_action( 'admin_init', array( $this, 'extra_actions' ) );
15
16 // Ajax-based functions
17 if ( is_admin() ) {
18 add_action( 'wp_ajax_pm_bulk_tools', array( $this, 'ajax_bulk_tools' ) );
19 add_action( 'wp_ajax_pm_save_permalink', array( $this, 'ajax_save_permalink' ) );
20 add_action( 'wp_ajax_pm_detect_duplicates', array( $this, 'ajax_detect_duplicates' ) );
21 add_action( 'wp_ajax_pm_dismissed_notice_handler', array( $this, 'ajax_hide_global_notice' ) );
22 }
23 }
24
25 /**
26 * Route the requests to functions that save datasets with associated callbacks
27 */
28 public function trigger_action() {
29 global $permalink_manager_after_sections_html;
30
31 // 1. Check if the form was submitted
32 if ( empty( $_POST ) ) {
33 return;
34 }
35
36 // 2. Do nothing if search query is not empty
37 if ( isset( $_POST['uri_editor_nonce'] ) && ( isset( $_REQUEST['search-submit'] ) || isset( $_REQUEST['filter-button'] ) ) ) {
38 $this->trigger_filter_action();
39 }
40
41 $actions_map = array(
42 'uri_editor_nonce' => array( 'function' => 'update_all_permalinks', 'display_uri_table' => true ),
43 'permalink_manager_options' => array( 'function' => 'save_settings' ),
44 'permalink_manager_permastructs' => array( 'function' => 'save_permastructures' ),
45 'import' => array( 'function' => 'import_custom_permalinks_uris' ),
46 );
47
48 // 3. Find the action
49 foreach ( $actions_map as $action => $map ) {
50 $nonce = ( isset( $_POST[ $action ] ) ) ? sanitize_key( $_POST[ $action ] ) : '';
51
52 if ( ! empty( $nonce ) && wp_verify_nonce( $nonce, 'permalink-manager' ) ) {
53 // Execute the function
54 $output = call_user_func( array( $this, $map['function'] ) );
55
56 // Get list of updated URIs
57 if ( ! empty( $map['display_uri_table'] ) ) {
58 $updated_slugs_count = ( isset( $output['updated_count'] ) && $output['updated_count'] > 0 ) ? $output['updated_count'] : false;
59 $updated_slugs_array = ( $updated_slugs_count ) ? $output['updated'] : '';
60 }
61
62 // Trigger only one function
63 break;
64 }
65 }
66
67 // 4. Display the slugs table (and append the globals)
68 if ( isset( $updated_slugs_count ) && isset( $updated_slugs_array ) ) {
69 $permalink_manager_after_sections_html .= Permalink_Manager_UI_Elements::display_updated_slugs( $updated_slugs_array );
70 }
71 }
72
73 /**
74 * Adjust the Bulk URI Editor filter URL
75 *
76 * @return void
77 */
78 public function trigger_filter_action() {
79 if ( empty( $_POST['uri_editor_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['uri_editor_nonce'] ), 'permalink-manager' ) ) {
80 return;
81 }
82
83 $query_args = array(
84 'langcode' => ! empty( $_REQUEST['langcode'] ) ? sanitize_key( $_REQUEST['langcode'] ) : null,
85 'month' => ! empty( $_REQUEST['month'] ) ? sanitize_key( $_REQUEST['month'] ) : null,
86 's' => ! empty( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : null,
87 );
88
89 if ( ! empty( $query_args ) ) {
90 $sendback = remove_query_arg( array_keys( $query_args ), wp_get_referer() );
91 $sendback = add_query_arg( array_filter( $query_args ), $sendback );
92
93 wp_safe_redirect( $sendback );
94 exit;
95 }
96 }
97
98 /**
99 * Route the requests to the additional tools-related functions with the relevant callbacks
100 */
101 public static function extra_actions() {
102 global $permalink_manager_before_sections_html;
103
104 if ( current_user_can( 'manage_options' ) && ! empty( $_GET['permalink-manager-nonce'] ) ) {
105 // Check if the nonce field is correct
106 $nonce = sanitize_key( $_GET['permalink-manager-nonce'] );
107
108 if ( ! wp_verify_nonce( $nonce, 'permalink-manager' ) ) {
109 $permalink_manager_before_sections_html = Permalink_Manager_UI_Elements::get_alert_message( __( 'You are not allowed to remove Permalink Manager data!', 'permalink-manager' ), 'error updated_slugs' );
110
111 return;
112 }
113
114 if ( isset( $_GET['clear-permalink-manager-uris'] ) ) {
115 self::clear_all_uris();
116 } else if ( isset( $_GET['remove-permalink-manager-settings'] ) ) {
117 $option_name = sanitize_key( $_GET['remove-permalink-manager-settings'] );
118 self::remove_plugin_data( $option_name );
119 } else if ( ! empty( $_REQUEST['remove-uri'] ) ) {
120 $uri_key = sanitize_key( $_REQUEST['remove-uri'] );
121 self::force_clear_single_element_uris_and_redirects( $uri_key );
122 } else if ( ! empty( $_REQUEST['remove-redirect'] ) ) {
123 $redirect_key = sanitize_key( $_REQUEST['remove-redirect'] );
124 self::force_clear_single_redirect( $redirect_key );
125 }
126 } else if ( ! empty( $_POST['screen-options-apply'] ) ) {
127 self::save_screen_options();
128 }
129 }
130
131 /**
132 * Bulk remove obsolete custom permalinks and redirects
133 */
134 public static function clear_all_uris() {
135 global $permalink_manager_redirects, $permalink_manager_before_sections_html;
136
137 // Get all custom permalinks & redirects
138 $custom_permalinks = Permalink_Manager_URI_Functions::get_all_uris();
139 $custom_redirects = (array) $permalink_manager_redirects;
140
141 // Check if array with custom URIs exists
142 if ( empty( $custom_permalinks ) ) {
143 return;
144 }
145
146 // Count removed URIs & redirects
147 $removed_uris = 0;
148 $removed_redirects = 0;
149
150 // Get all element IDs
151 $element_ids = array_merge( array_keys( $custom_permalinks ), array_keys( $custom_redirects ) );
152
153 // 1. Remove unused custom URI & redirects for deleted post or term
154 foreach ( $element_ids as $element_id ) {
155 $count = self::clear_single_element_uris_and_redirects( $element_id, true );
156
157 $removed_uris = ( ! empty( $count[0] ) ) ? $count[0] + $removed_uris : $removed_uris;
158 $removed_redirects = ( ! empty( $count[1] ) ) ? $count[1] + $removed_redirects : $removed_redirects;
159 }
160
161 // 2. Keep only a single redirect
162 $removed_redirects += self::clear_redirects_array();
163
164 // 3. Save cleared URIs & Redirects
165 if ( $removed_uris > 0 || $removed_redirects > 0 ) {
166 Permalink_Manager_URI_Functions::save_all_uris();
167 update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
168
169 // Translators: 1: Number of custom URIs, 2: Number of custom redirects.
170 $permalink_manager_before_sections_html .= Permalink_Manager_UI_Elements::get_alert_message( sprintf( __( '%1$d Custom URIs and %2$d Custom Redirects were removed!', 'permalink-manager' ), $removed_uris, $removed_redirects ), 'updated updated_slugs' );
171 } else {
172 $permalink_manager_before_sections_html .= Permalink_Manager_UI_Elements::get_alert_message( __( 'No Custom URIs or Custom Redirects were removed!', 'permalink-manager' ), 'error updated_slugs' );
173 }
174 }
175
176 /**
177 * Remove obsolete custom permalink & redirects for specific post or term
178 *
179 * @param string|int $element_id
180 * @param bool $count_removed
181 *
182 * @return array
183 */
184 public static function clear_single_element_uris_and_redirects( $element_id, $count_removed = false ) {
185 global $wpdb, $permalink_manager_redirects, $permalink_manager_options;
186
187 // Count removed URIs & redirects
188 $removed_uris = 0;
189 $removed_redirects = 0;
190
191 // Only admin users can remove the broken URIs for removed post types & taxonomies
192 $check_if_admin = is_admin();
193
194 // Check if the advanced mode is turned on
195 $advanced_mode = Permalink_Manager_Helper_Functions::is_advanced_mode_on();
196
197 // If "Disable URI Editor to disallow Permalink changes" is set globally, the pages that follow the global settings should also be removed
198 if ( $advanced_mode && ! empty( $permalink_manager_options["general"]["auto_update_uris"] ) && $permalink_manager_options["general"]["auto_update_uris"] == 2 ) {
199 $strict_mode = true;
200 } else {
201 $strict_mode = false;
202 }
203
204 // 1. Check if element exists
205 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct SQL query is needed to join multiple tables
206 if ( strpos( $element_id, 'tax-' ) !== false ) {
207 $term_id = preg_replace( "/[^0-9]/", "", $element_id );
208 $term_info = $wpdb->get_row( $wpdb->prepare( "SELECT taxonomy, meta_value FROM {$wpdb->term_taxonomy} AS t LEFT JOIN {$wpdb->termmeta} AS tm ON tm.term_id = t.term_id AND tm.meta_key = 'auto_update_uri' WHERE t.term_id = %d", $term_id ) );
209
210 // Custom URIs for disabled taxonomies may only be deleted via the admin dashboard, although they will always be removed if the term no longer exists in the database
211 $remove = ( ! empty( $term_info->taxonomy ) ) ? Permalink_Manager_Helper_Functions::is_taxonomy_disabled( $term_info->taxonomy, $check_if_admin ) : true;
212
213 // Remove custom URIs for URIs disabled in URI Editor
214 if ( $strict_mode ) {
215 $remove = ( empty( $term_info->meta_value ) || $term_info->meta_value == 2 ) ? true : $remove;
216 } else {
217 $remove = ( ! empty( $term_info->meta_value ) && $term_info->meta_value == 2 ) ? true : $remove;
218 }
219 } else if ( is_numeric( $element_id ) ) {
220 $post_info = $wpdb->get_row( $wpdb->prepare( "SELECT post_type, meta_value FROM {$wpdb->posts} AS p LEFT JOIN {$wpdb->postmeta} AS pm ON pm.post_ID = p.ID AND pm.meta_key = 'auto_update_uri' WHERE ID = %d AND post_status NOT IN ('auto-draft', 'trash') AND post_type != 'nav_menu_item'", $element_id ) );
221
222 // Custom URIs for disabled post types may only be deleted via the admin dashboard, although they will always be removed if the post no longer exists in the database
223 $remove = ( ! empty( $post_info->post_type ) ) ? Permalink_Manager_Helper_Functions::is_post_type_disabled( $post_info->post_type, $check_if_admin ) : true;
224
225 // Remove custom URIs for URIs disabled in URI Editor
226 if ( $strict_mode ) {
227 $remove = ( empty( $post_info->meta_value ) || $post_info->meta_value == 2 ) ? true : $remove;
228 } else {
229 $remove = ( ! empty( $post_info->meta_value ) && $post_info->meta_value == 2 ) ? true : $remove;
230 }
231
232 // Remove custom URIs for attachments redirected with Yoast's SEO Premium
233 $yoast_permalink_options = ( class_exists( 'WPSEO_Premium' ) ) ? get_option( 'wpseo_permalinks' ) : array();
234
235 if ( ! empty( $yoast_permalink_options['redirectattachment'] ) && $post_info->post_type == 'attachment' ) {
236 $attachment_parent = $wpdb->get_var( $wpdb->prepare( "SELECT post_parent FROM {$wpdb->prefix}posts WHERE ID = %d AND post_type = %s", $element_id, 'attachment' ) );
237 if ( ! empty( $attachment_parent ) ) {
238 $remove = true;
239 }
240 }
241 }
242 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
243
244 // 2A. Remove ALL unused custom permalinks & redirects
245 if ( ! empty( $remove ) ) {
246 $current_uri = Permalink_Manager_URI_Functions::get_single_uri( $element_id, false, true, null );
247
248 // Remove URI
249 if ( ! empty( $current_uri ) ) {
250 $removed_uris = 1;
251 Permalink_Manager_URI_Functions::remove_single_uri( $element_id, null, false );
252 }
253
254 // Remove all custom redirects
255 if ( ! empty( $permalink_manager_redirects[ $element_id ] ) && is_array( $permalink_manager_redirects[ $element_id ] ) ) {
256 $removed_redirects = count( $permalink_manager_redirects[ $element_id ] );
257 unset( $permalink_manager_redirects[ $element_id ] );
258 }
259 } // 2B. Check if the post/term uses the same URI for both permalink & custom redirects
260 else {
261 $removed_redirect = self::clear_single_element_duplicated_redirect( $element_id, false );
262 $removed_redirects = ( ! empty( $removed_redirect ) ) ? 1 : 0;
263 }
264
265 // Check if function should only return the counts or update
266 if ( $count_removed ) {
267 return array( $removed_uris, $removed_redirects );
268 } else if ( ! empty( $removed_uris ) || ! empty( $removed_redirects ) ) {
269 Permalink_Manager_URI_Functions::save_all_uris();
270 update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
271 }
272
273 return array();
274 }
275
276 /**
277 * Remove the duplicated custom redirect if the post/term has the same URI for both custom permalink and custom redirect
278 *
279 * @param string|int $element_id
280 * @param bool $save_redirects
281 * @param string $uri
282 *
283 * @return int
284 */
285 public static function clear_single_element_duplicated_redirect( $element_id, $save_redirects = true, $uri = null ) {
286 global $permalink_manager_redirects;
287
288 // If the custom permalink is not changed ($uri) use the one that is currently used
289 if ( ! empty( $uri ) ) {
290 $current_uri = $uri;
291 } else {
292 $current_uri = Permalink_Manager_URI_Functions::get_single_uri( $element_id, false, true, null );
293 }
294
295 if ( ! empty( $current_uri ) && ! empty( $permalink_manager_redirects[ $element_id ] ) && in_array( $current_uri, $permalink_manager_redirects[ $element_id ] ) ) {
296 $duplicated_redirect_id = array_search( $current_uri, $permalink_manager_redirects[ $element_id ] );
297 unset( $permalink_manager_redirects[ $element_id ][ $duplicated_redirect_id ] );
298 }
299
300 // Update the redirects array in the database if the duplicated redirect was unset
301 if ( isset( $duplicated_redirect_id ) && $save_redirects ) {
302 update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
303 }
304
305 return ( isset( $duplicated_redirect_id ) ) ? 1 : 0;
306 }
307
308 /**
309 * Remove the duplicated if the same URI is used for multiple custom redirects and return the removed redirects count
310 *
311 * @param bool $save_redirects
312 *
313 * @return int
314 */
315 public static function clear_redirects_array( $save_redirects = false ) {
316 global $permalink_manager_redirects;
317
318 $removed_redirects = 0;
319
320 $all_redirect_duplicates = Permalink_Manager_Admin_Functions::get_all_duplicates();
321
322 foreach ( $all_redirect_duplicates as $single_redirect_duplicate ) {
323 $last_element = reset( $single_redirect_duplicate );
324
325 foreach ( $single_redirect_duplicate as $redirect_key ) {
326 // Keep a single redirect
327 if ( $last_element == $redirect_key ) {
328 continue;
329 }
330 preg_match( "/redirect-(\d+)_(tax-\d+|\d+)/", $redirect_key, $ids );
331
332 if ( ! empty( $ids[2] ) && ! empty( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] ) ) {
333 $removed_redirects ++;
334 unset( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] );
335 }
336 }
337 }
338
339 // Update the redirects array in the database if the duplicated redirect was unset
340 if ( isset( $duplicated_redirect_id ) && $save_redirects ) {
341 update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
342 }
343
344 return $removed_redirects;
345 }
346
347 /**
348 * Remove custom permalinks & custom redirects for requested post or term
349 *
350 * @param $uri_key
351 *
352 * @return bool
353 */
354 public static function force_clear_single_element_uris_and_redirects( $uri_key ) {
355 global $permalink_manager_redirects, $permalink_manager_before_sections_html;
356
357 $custom_uri = Permalink_Manager_URI_Functions::get_single_uri( $uri_key, false, true, null );
358
359 // Check if custom URI is set
360 if ( ! empty( $custom_uri ) ) {
361 Permalink_Manager_URI_Functions::remove_single_uri( $uri_key, null, true );
362 // translators: %s is the custom URI that was removed.
363 $updated = Permalink_Manager_UI_Elements::get_alert_message( sprintf( __( 'URI "%s" was removed successfully!', 'permalink-manager' ), $custom_uri ), 'updated' );
364 }
365
366 // Check if custom redirects are set
367 if ( isset( $permalink_manager_redirects[ $uri_key ] ) ) {
368 unset( $permalink_manager_redirects[ $uri_key ] );
369 update_option( 'permalink-manager-redirects', $permalink_manager_redirects );
370 }
371
372 if ( empty( $updated ) ) {
373 $permalink_manager_before_sections_html .= Permalink_Manager_UI_Elements::get_alert_message( __( 'URI and/or custom redirects does not exist or were already removed!', 'permalink-manager' ), 'error' );
374 } else {
375 // Display the alert in admin panel
376 if ( isset( $permalink_manager_before_sections_html ) && is_admin() ) {
377 $permalink_manager_before_sections_html .= $updated;
378 }
379 }
380
381 return true;
382 }
383
384 /**
385 * Remove only custom redirects for requested post or term
386 *
387 * @param string $redirect_key
388 */
389 public static function force_clear_single_redirect( $redirect_key ) {
390 global $permalink_manager_redirects, $permalink_manager_before_sections_html;
391
392 preg_match( "/redirect-(\d+)_(tax-\d+|\d+)/", $redirect_key, $ids );
393
394 if ( ! empty( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] ) ) {
395 unset( $permalink_manager_redirects[ $ids[2] ][ $ids[1] ] );
396
397 update_option( 'permalink-manager-redirects', array_filter( $permalink_manager_redirects ) );
398
399 $permalink_manager_before_sections_html = Permalink_Manager_UI_Elements::get_alert_message( __( 'The redirect was removed successfully!', 'permalink-manager' ), 'updated' );
400 }
401 }
402
403 /**
404 * Save "Screen Options"
405 */
406 public static function save_screen_options() {
407 check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' );
408
409 $screen_options = ( isset( $_POST['screen-options'] ) ) ? map_deep( wp_unslash( $_POST['screen-options'] ), 'sanitize_text_field' ) : array();
410
411 if ( ! empty( $screen_options ) ) {
412 self::save_settings( 'screen-options', $screen_options );
413 }
414 }
415
416 /**
417 * Save the plugin settings
418 *
419 * @param bool $field
420 * @param bool $value
421 * @param bool $display_alert
422 */
423 public static function save_settings( $field = false, $value = false, $display_alert = true ) {
424 global $permalink_manager_options, $permalink_manager_before_sections_html;
425
426 // Info: The settings array is used also by "Screen Options"
427 $new_options = $permalink_manager_options;
428
429 // Save only selected field/sections
430 if ( $field && $value ) {
431 $new_options[ $field ] = $value;
432 } else {
433 $post_fields = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The nonce is already validated in enclosing function
434
435 foreach ( $post_fields as $option_name => $option_value ) {
436 $new_options[ $option_name ] = $option_value;
437 }
438 }
439
440 // Allow only white-listed option groups
441 foreach ( $new_options as $group => $group_options ) {
442 if ( ! in_array( $group, array( 'licence', 'screen-options', 'general', 'permastructure-settings', 'stop-words' ) ) ) {
443 unset( $new_options[ $group ] );
444 }
445 }
446
447 // Sanitize & override the global with new settings
448 $new_options = Permalink_Manager_Helper_Functions::sanitize_array( $new_options );
449 $permalink_manager_options = $new_options = array_filter( $new_options );
450
451 // Save the settings in database
452 update_option( 'permalink-manager', $new_options );
453
454 // Display the message
455 $permalink_manager_before_sections_html .= ( $display_alert ) ? Permalink_Manager_UI_Elements::get_alert_message( __( 'The settings are saved!', 'permalink-manager' ), 'updated' ) : "";
456 }
457
458 /**
459 * Save the permastructures
460 */
461 public static function save_permastructures() {
462 global $permalink_manager_permastructs;
463
464 $permastructure_options = $permastructures = array();
465 $permastructure_types = array( 'post_types', 'taxonomies' );
466
467 // Split permastructures & sanitize them
468 // phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- The nonce is already validated in enclosing function
469 foreach ( $permastructure_types as $type ) {
470 if ( empty( $_POST[ $type ] ) || ! is_array( $_POST[ $type ] ) ) {
471 continue;
472 }
473
474 $permastructures[ $type ] = $_POST[ $type ];
475
476 foreach ( $permastructures[ $type ] as &$single_permastructure ) {
477 $single_permastructure = Permalink_Manager_Helper_Functions::sanitize_title( $single_permastructure, true, false, false );
478 $single_permastructure = trim( $single_permastructure, '\/ ' );
479 }
480 }
481
482 if ( ! empty( $_POST['permastructure-settings'] ) ) {
483 $permastructure_options = $_POST['permastructure-settings'];
484 }
485 // phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
486
487 // A. Permastructures
488 if ( ! empty( $permastructures['post_types'] ) || ! empty( $permastructures['taxonomies'] ) ) {
489 // Override the global with settings
490 $permalink_manager_permastructs = $permastructures;
491
492 // Save the settings in database
493 update_option( 'permalink-manager-permastructs', $permastructures );
494 }
495
496 // B. Permastructure settings
497 if ( ! empty( $permastructure_options ) ) {
498 self::save_settings( 'permastructure-settings', $permastructure_options );
499 }
500 }
501
502 /**
503 * Update all permalinks in "Bulk URI Editor"
504 */
505 function update_all_permalinks() {
506 // Check if posts or terms should be updated
507 if ( ! empty( $_POST['content_type'] ) && $_POST['content_type'] == 'taxonomies' ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The nonce is already validated in enclosing function
508 return Permalink_Manager_URI_Functions_Tax::update_all_permalinks();
509 } else {
510 return Permalink_Manager_URI_Functions_Post::update_all_permalinks();
511 }
512 }
513
514 /**
515 * Remove a specific section of the plugin data stored in the database
516 *
517 * @param $field_name
518 */
519 public static function remove_plugin_data( $field_name ) {
520 global $permalink_manager, $permalink_manager_before_sections_html;
521
522 // Make sure that the user is allowed to remove the plugin data
523 if ( ! current_user_can( 'manage_options' ) ) {
524 $permalink_manager_before_sections_html .= Permalink_Manager_UI_Elements::get_alert_message( __( 'You are not allowed to remove Permalink Manager data!', 'permalink-manager' ), 'error updated_slugs' );
525 }
526
527 switch ( $field_name ) {
528 case 'uris' :
529 $option_name = 'permalink-manager-uris';
530 $alert = __( 'Custom permalinks', 'permalink-manager' );
531 break;
532 case 'redirects' :
533 $option_name = 'permalink-manager-redirects';
534 $alert = __( 'Custom redirects', 'permalink-manager' );
535 break;
536 case 'external-redirects' :
537 $option_name = 'permalink-manager-external-redirects';
538 $alert = __( 'External redirects', 'permalink-manager' );
539 break;
540 case 'permastructs' :
541 $option_name = 'permalink-manager-permastructs';
542 $alert = __( 'Permastructure settings', 'permalink-manager' );
543 break;
544 case 'settings' :
545 $option_name = 'permalink-manager';
546 $alert = __( 'Permastructure settings', 'permalink-manager' );
547 break;
548 default :
549 $alert = '';
550 }
551
552 if ( ! empty( $option_name ) ) {
553 // Remove the option from DB
554 delete_option( $option_name );
555
556 // Reload globals
557 $permalink_manager->get_options_and_globals();
558
559 // Translators: %s is the name of the data field that was removed
560 $alert_message = sprintf( __( '%s were removed!', 'permalink-manager' ), $alert );
561 $permalink_manager_before_sections_html .= Permalink_Manager_UI_Elements::get_alert_message( $alert_message, 'updated updated_slugs' );
562 }
563 }
564
565 /**
566 * Trigger bulk tools ("Regenerate & reset", "Find & replace") via AJAX
567 */
568 function ajax_bulk_tools() {
569 global $sitepress, $wpdb;
570
571 // Define variables
572 $return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No items</strong> were processed!', 'permalink-manager' ), 'error updated_slugs' ) );
573
574 // Get the name of the function
575 if ( isset( $_POST['regenerate'] ) ) {
576 $nonce_name = sanitize_key( $_POST['regenerate'] );
577 $operation = 'regenerate';
578 } else if ( isset( $_POST['find_and_replace'] ) ) {
579 $nonce_name = sanitize_key( $_POST['find_and_replace'] );
580 $operation = ( ! empty( $_POST['old_string'] ) && ! empty( $_POST['new_string'] ) ) ? 'find_and_replace' : '';
581 }
582
583 // Validate the nonce
584 if ( empty( $nonce_name ) || ! wp_verify_nonce( $nonce_name, 'permalink-manager' ) ) {
585 $error = true;
586 $return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( 'Nonce is invalid!', 'permalink-manager' ), 'error updated_slugs' ) );
587 }
588
589 // Get the session ID
590 $uniq_id = ( ! empty( $_POST['pm_session_id'] ) ) ? sanitize_key( $_POST['pm_session_id'] ) : '';
591
592 // Get content type & post statuses
593 if ( ! empty( $_POST['content_type'] ) && $_POST['content_type'] == 'taxonomies' ) {
594 $content_type = 'taxonomies';
595
596 if ( empty( $_POST['taxonomies'] ) ) {
597 $error = true;
598 $return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No taxonomy</strong> selected!', 'permalink-manager' ), 'error updated_slugs' ) );
599 }
600 } else {
601 $content_type = 'post_types';
602
603 // Check if any post type was selected
604 if ( empty( $_POST['post_types'] ) ) {
605 $error = true;
606 $return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No post type</strong> selected!', 'permalink-manager' ), 'error updated_slugs' ) );
607 }
608
609 // Check post status
610 if ( empty( $_POST['post_statuses'] ) ) {
611 $error = true;
612 $return = array( 'alert' => Permalink_Manager_UI_Elements::get_alert_message( __( '<strong>No post status</strong> selected!', 'permalink-manager' ), 'error updated_slugs' ) );
613 }
614 }
615
616 if ( ! empty( $operation ) && empty( $error ) ) {
617 // Hotfix for WPML (start)
618 if ( $sitepress ) {
619 remove_filter( 'get_terms_args', array( $sitepress, 'get_terms_args_filter' ), 10 );
620 remove_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1 );
621 remove_filter( 'terms_clauses', array( $sitepress, 'terms_clauses' ), 10 );
622 remove_filter( 'get_pages', array( $sitepress, 'get_pages_adjust_ids' ), 1 );
623 }
624
625 // Get the mode
626 $mode = ( isset( $_POST['mode'] ) ) ? sanitize_key( $_POST['mode'] ) : 'custom_uris';
627 $preview_mode = ( ! empty( $_POST['preview_mode'] ) ) ? true : false;
628
629 // Get items (try to get them from transient)
630 $items = get_transient( "pm_{$uniq_id}" );
631
632 // Get the iteration count and chunk size
633 $iteration = isset( $_POST['iteration'] ) ? intval( $_POST['iteration'] ) : 1;
634 $chunk_size = apply_filters( 'permalink_manager_chunk_size', 50 );
635
636 if ( empty( $items ) && ! empty ( $chunk_size ) ) {
637 if ( $content_type == 'taxonomies' ) {
638 $items = Permalink_Manager_URI_Functions_Tax::get_items();
639 } else {
640 $items = Permalink_Manager_URI_Functions_Post::get_items();
641 }
642
643 if ( ! empty( $items ) ) {
644 // Count how many items need to be processed
645 $total = count( $items );
646
647 // Split items array into chunks and save them to transient
648 $items = array_chunk( $items, $chunk_size );
649
650 set_transient( "pm_{$uniq_id}", $items, 600 );
651
652 // Check for MySQL errors
653 if ( ! empty( $wpdb->last_error ) ) {
654 printf( '%s (%sMB)', esc_html( $wpdb->last_error ), esc_html( number_format( strlen( serialize( $items ) ) / 1000000, 2 ) ) );
655 http_response_code( 500 );
656 die();
657 }
658 }
659 }
660
661 // Get homepage URL and ensure that it ends with slash
662 $home_url = Permalink_Manager_Permastructure_Functions::get_permalink_base() . "/";
663
664 // Process the variables from $_POST object
665 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- The escaped slashes can be a part of REGEX formula
666 $old_string = ( ! empty( $_POST['old_string'] ) ) ? str_replace( $home_url, '', esc_sql( sanitize_text_field( $_POST['old_string'] ) ) ) : '';
667 $new_string = ( ! empty( $_POST['new_string'] ) ) ? str_replace( $home_url, '', esc_sql( sanitize_text_field( $_POST['new_string'] ) ) ) : '';
668 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
669
670 // Process only one subarray
671 if ( ! empty( $items[ $iteration - 1 ] ) ) {
672 $chunk = $items[ $iteration - 1 ];
673
674 // Check how many iterations are needed
675 $total_iterations = count( $items );
676
677 if ( $content_type == 'taxonomies' ) {
678 $output = Permalink_Manager_URI_Functions_Tax::bulk_process_items( $chunk, $mode, $operation, $old_string, $new_string, $preview_mode );
679 } else {
680 $output = Permalink_Manager_URI_Functions_Post::bulk_process_items( $chunk, $mode, $operation, $old_string, $new_string, $preview_mode );
681 }
682
683 if ( ! empty( $output['updated_count'] ) ) {
684 $return = array_merge( $return, (array) Permalink_Manager_UI_Elements::display_updated_slugs( $output['updated'], true, true, $preview_mode ) );
685 $return['updated_count'] = $output['updated_count'];
686 }
687
688 // Send total number of processed items with a first chunk
689 if ( ! empty( $total ) && ! empty( $total_iterations ) && $iteration == 1 ) {
690 $return['total'] = $total;
691 $return['items'] = $items;
692 }
693
694 $return['iteration'] = $iteration;
695 $return['total_iterations'] = $total_iterations;
696 $return['progress'] = $chunk_size * $iteration;
697 $return['chunk'] = $chunk;
698
699 // After all chunks are processed remove the transient
700 if ( $iteration == $total_iterations ) {
701 delete_transient( "pm_{$uniq_id}" );
702 }
703 }
704
705 // Hotfix for WPML (end)
706 if ( $sitepress ) {
707 add_filter( 'terms_clauses', array( $sitepress, 'terms_clauses' ), 10, 4 );
708 add_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1, 1 );
709 add_filter( 'get_terms_args', array( $sitepress, 'get_terms_args_filter' ), 10, 2 );
710 add_filter( 'get_pages', array( $sitepress, 'get_pages_adjust_ids' ), 1, 2 );
711 }
712 }
713
714 wp_send_json( $return );
715 die();
716 }
717
718 /**
719 * Save permalink via AJAX
720 */
721 public function ajax_save_permalink() {
722 $element_id = ( ! empty( $_POST['permalink-manager-edit-uri-element-id'] ) ) ? sanitize_key( $_POST['permalink-manager-edit-uri-element-id'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is validated inside update_post_hook
723
724 if ( ! empty( $element_id ) && is_numeric( $element_id ) && current_user_can( 'edit_post', $element_id ) ) {
725 Permalink_Manager_URI_Functions_Post::update_post_hook( $element_id );
726
727 // Reload URI Editor & clean post cache
728 clean_post_cache( $element_id );
729 die();
730 }
731 }
732
733 /**
734 * Check if URI was used before
735 */
736 function ajax_detect_duplicates() {
737 $duplicate_alert = __( "Permalink is already in use, please select another one!", "permalink-manager" );
738 $duplicates_data = array();
739
740 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- No data is saved here; the array is recursively sanitized via sanitize_array().
741 $custom_uris = ( ! empty( $_REQUEST['custom_uris'] ) ) ? Permalink_Manager_Helper_Functions::sanitize_array( wp_unslash( $_REQUEST['custom_uris'] ) ) : array();
742
743 if ( ! empty( $custom_uris ) ) {
744 // Check each URI
745 foreach ( $custom_uris as $raw_element_id => $element_uri ) {
746 $element_id = sanitize_key( $raw_element_id );
747 $duplicates_data[ $element_id ] = Permalink_Manager_URI_Functions::is_uri_duplicated( $element_uri, $element_id ) ? $duplicate_alert : 0;
748 }
749 } else {
750 // phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended -- No data is saved here
751 $single_uri = ( ! empty( $_REQUEST['custom_uri'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['custom_uri'] ) ) : '';
752 $element_id = ( ! empty( $_REQUEST['element_id'] ) ) ? sanitize_key( $_REQUEST['element_id'] ) : '';
753 // phpcs:enable
754
755 if ( ! empty( $single_uri ) && ! empty( $element_id ) ) {
756 $duplicates_data = Permalink_Manager_URI_Functions::is_uri_duplicated( $single_uri, $element_id );
757 }
758 }
759
760 wp_send_json( $duplicates_data );
761 }
762
763 /**
764 * Hide global notices (AJAX)
765 */
766 function ajax_hide_global_notice() {
767 global $permalink_manager_alerts;
768
769 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Low-risk cosmetic notice dismissal; authenticated admin-only AJAX action and no nonce is transmitted by the notice markup.
770 $alert_id = ( ! empty( $_REQUEST['alert_id'] ) ) ? sanitize_title( wp_unslash( $_REQUEST['alert_id'] ) ) : "";
771 if ( ! empty( $permalink_manager_alerts[ $alert_id ] ) ) {
772 $dismissed_transient_name = sprintf( 'permalink-manager-notice_%s', $alert_id );
773 $dismissed_time = ( ! empty( $permalink_manager_alerts[ $alert_id ]['dismissed_time'] ) ) ? (int) $permalink_manager_alerts[ $alert_id ]['dismissed_time'] : DAY_IN_SECONDS;
774
775 set_transient( $dismissed_transient_name, 1, $dismissed_time );
776 }
777 }
778
779 /**
780 * Import old URIs from "Custom Permalinks" (Pro)
781 */
782 function import_custom_permalinks_uris() {
783 Permalink_Manager_Third_Parties::import_custom_permalinks_uris();
784 }
785
786 }