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