PluginProbe
Redirection / 5.0.1
Redirection v5.0.1
5.10.0 5.9.0 5.8.1 5.8.0 3.7.2 3.7.3 4.0 4.0.1 4.1 4.1.1 4.2 4.2.1 4.2.2 4.2.3 4.3 4.3.1 4.3.2 4.3.3 4.4 4.4.1 4.4.2 4.5 4.5.1 4.6.2 4.7.1 All 130 releases
redirection / models / redirect / redirect.php

redirect.php in Redirection 5.0.1, at models/redirect/redirect.php

1,051 lines 23.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 require_once dirname( __FILE__ ) . '/redirect-sanitizer.php';
4 require_once dirname( __FILE__ ) . '/redirect-filter.php';
5 require_once dirname( __FILE__ ) . '/redirect-options.php';
6 require_once dirname( __FILE__ ) . '/redirect-cache.php';
7
8 /**
9 * Redirect class
10 */
11 class Red_Item {
12 /**
13 * Maximum number of redirects to get from the database in one request.
14 *
15 * @var integer
16 */
17 const MAX_REDIRECTS = 10000;
18
19 /**
20 * Redirect ID
21 *
22 * @var integer
23 */
24 private $id = 0;
25
26 /**
27 * Source URL (full)
28 *
29 * @var String
30 */
31 private $url = '';
32
33 /**
34 * Source URL (match)
35 *
36 * @var String
37 */
38 private $match_url = '';
39
40 /**
41 * Undocumented variable
42 *
43 * @var String
44 */
45 private $match_data = '';
46
47 /**
48 * Is regular expression?
49 *
50 * @var boolean
51 */
52 private $regex = false;
53
54 /**
55 * Action data
56 *
57 * @var String
58 */
59 private $action_data = '';
60
61 /**
62 * HTTP code
63 *
64 * @var integer
65 */
66 private $action_code = 0;
67
68 /**
69 * Action type
70 *
71 * @var String
72 */
73 private $action_type = '';
74
75 /**
76 * Match type
77 *
78 * @var String
79 */
80 private $match_type = '';
81
82 /**
83 * Redirect title
84 *
85 * @var String
86 */
87 private $title = '';
88
89 /**
90 * Last time the redirect was accessed
91 *
92 * @var integer
93 */
94 private $last_access = 0;
95
96 /**
97 * Number of hits
98 *
99 * @var integer
100 */
101 private $last_count = 0;
102
103 /**
104 * Status of the redirect
105 *
106 * @var string
107 */
108 private $status = 'enabled';
109
110 /**
111 * Position value
112 *
113 * @var integer
114 */
115 private $position = 0;
116
117 /**
118 * Group ID
119 *
120 * @var integer
121 */
122 private $group_id = 0;
123
124 /**
125 * Source flags
126 *
127 * @var Red_Source_Flags|null
128 */
129 public $source_flags = null;
130
131 /**
132 * Source options
133 *
134 * @var Red_Source_Options|null
135 */
136 public $source_options = null;
137
138 /**
139 * Match object
140 *
141 * @var Red_Match|null
142 */
143 public $match = null;
144
145 /**
146 * Action object
147 *
148 * @var Red_Action|null
149 */
150 public $action = null;
151
152 /**
153 * Constructor
154 *
155 * @param stdClass|array|null $values Values.
156 */
157 public function __construct( $values = null ) {
158 if ( is_object( $values ) ) {
159 $this->load_from_data( (array) $values );
160 } elseif ( is_array( $values ) ) {
161 $this->load_from_data( $values );
162 }
163 }
164
165 /**
166 * Load values into the object
167 *
168 * @param array $values Values.
169 * @return void
170 */
171 private function load_from_data( array $values ) {
172 foreach ( $values as $key => $value ) {
173 if ( property_exists( $this, $key ) ) {
174 $this->$key = $value;
175 }
176 }
177
178 $this->regex = (bool) $this->regex;
179 $this->last_access = ( $this->last_access === '1970-01-01 00:00:00' || $this->last_access === '0000-00-00 00:00:00' ) ? 0 : mysql2date( 'U', $this->last_access );
180
181 $this->load_matcher();
182 $this->load_action();
183 $this->load_source();
184 }
185
186 /**
187 * Load the Red_Source_Flags and Red_Source_Options.
188 *
189 * Requires a v4 database
190 *
191 * @return void
192 */
193 private function load_source() {
194 // Default regex flag to regex column. This will be removed once the regex column has been migrated
195 // todo: deprecate
196 $this->source_flags = new Red_Source_Flags( array_merge( red_get_options(), [ 'flag_regex' => $this->regex ] ) );
197 $this->source_options = new Red_Source_Options();
198
199 if ( isset( $this->match_data ) ) {
200 $json = json_decode( $this->match_data, true );
201
202 if ( $json && isset( $json['source'] ) ) {
203 // Merge redirect flags with default flags
204 $this->source_flags->set_flags( array_merge( red_get_options(), $json['source'] ) );
205 }
206
207 if ( $json && isset( $json['options'] ) ) {
208 $this->source_options->set_options( $json['options'] );
209 }
210 }
211 }
212
213 /**
214 * Load the Red_Match object
215 *
216 * @return void
217 */
218 private function load_matcher() {
219 if ( empty( $this->match_type ) ) {
220 $this->match_type = 'url';
221 }
222
223 $this->match = Red_Match::create( $this->match_type, $this->action_data );
224 }
225
226 /**
227 * Load the Red_Action object
228 *
229 * @return void
230 */
231 private function load_action() {
232 if ( empty( $this->action_type ) ) {
233 $this->action_type = 'nothing';
234 }
235
236 $this->action = Red_Action::create( $this->action_type, $this->action_code );
237 }
238
239 /**
240 * Get all redirects for a module. No paging.
241 *
242 * @param integer $module Module ID.
243 * @return Red_Item[]
244 */
245 public static function get_all_for_module( $module ) {
246 global $wpdb;
247
248 $rows = $wpdb->get_results(
249 $wpdb->prepare(
250 "SELECT {$wpdb->prefix}redirection_items.* FROM {$wpdb->prefix}redirection_items
251 INNER JOIN {$wpdb->prefix}redirection_groups ON {$wpdb->prefix}redirection_groups.id={$wpdb->prefix}redirection_items.group_id
252 AND {$wpdb->prefix}redirection_groups.module_id=%d
253 AND {$wpdb->prefix}redirection_groups.status='enabled'
254 WHERE {$wpdb->prefix}redirection_items.status='enabled'
255 ORDER BY {$wpdb->prefix}redirection_items.position",
256 $module
257 )
258 );
259 $items = array();
260
261 foreach ( (array) $rows as $row ) {
262 $items[] = new Red_Item( $row );
263 }
264
265 return $items;
266 }
267
268 /**
269 * Get a list of redirects that match a URL. This is a helper function that calls the appropriate method for the current database version.
270 *
271 * @param string $url URL to match.
272 * @return array
273 */
274 public static function get_for_url( $url ) {
275 $status = new Red_Database_Status();
276
277 // deprecate
278 if ( $status->does_support( '4.0' ) ) {
279 return self::get_for_matched_url( $url );
280 }
281
282 return self::get_old_url( $url );
283 }
284
285 /**
286 * Get a redirect that matches a URL
287 *
288 * @param string $url URL to match.
289 * @return Red_Item[]
290 */
291 public static function get_for_matched_url( $url ) {
292 global $wpdb;
293
294 // Anything in the cache?
295 $cache = Redirect_Cache::init();
296 $rows = $cache->get( $url );
297
298 $url = new Red_Url_Match( $url );
299 $url_without = $url->get_url();
300 $url_with = $url->get_url_with_params();
301
302 if ( $rows === false ) {
303 // Nothing in cache, get from DB
304 $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}redirection_items WHERE match_url IN (%s, %s, 'regex') AND status='enabled' LIMIT %d", $url_without, $url_with, self::MAX_REDIRECTS ) );
305 }
306
307 $items = [];
308
309 if ( is_array( $rows ) ) {
310 foreach ( $rows as $row ) {
311 $items[] = new Red_Item( $row );
312 }
313
314 usort( $items, [ 'Red_Item', 'sort_urls' ] );
315
316 if ( count( $items ) >= self::MAX_REDIRECTS ) {
317 // Something has gone pretty wrong at this point
318 error_log( 'Redirection: maximum redirect limit exceeded' );
319 }
320 }
321
322 return $items;
323 }
324
325 /**
326 * Get a redirect that matches a URL
327 *
328 * @deprecated 3.7.3
329 * @param string $url URL to match.
330 * @return array
331 */
332 public static function get_old_url( $url ) {
333 global $wpdb;
334
335 // Yeah I know
336 $rows = $wpdb->get_results(
337 $wpdb->prepare(
338 "SELECT {$wpdb->prefix}redirection_items.*,{$wpdb->prefix}redirection_groups.position AS group_pos
339 FROM {$wpdb->prefix}redirection_items INNER JOIN {$wpdb->prefix}redirection_groups ON
340 {$wpdb->prefix}redirection_groups.id={$wpdb->prefix}redirection_items.group_id AND {$wpdb->prefix}redirection_groups.status='enabled'
341 AND {$wpdb->prefix}redirection_groups.module_id=%d WHERE ({$wpdb->prefix}redirection_items.regex=1
342 OR {$wpdb->prefix}redirection_items.url=%s)",
343 WordPress_Module::MODULE_ID,
344 $url
345 )
346 );
347
348 $items = array();
349 if ( count( $rows ) > 0 ) {
350 foreach ( $rows as $row ) {
351 $items[] = array(
352 'position' => ( $row->group_pos * 1000 ) + $row->position,
353 'item' => new Red_Item( $row ),
354 );
355 }
356 }
357
358 usort( $items, array( 'Red_Item', 'sort_urls_old' ) );
359 $items = array_map( array( 'Red_Item', 'reduce_sorted_items' ), $items );
360
361 // Sort it in PHP
362 ksort( $items );
363 $items = array_values( $items );
364 return $items;
365 }
366
367 /**
368 * Return only the 'item' element
369 *
370 * @param array $item Item.
371 * @return String
372 */
373 public static function reduce_sorted_items( $item ) {
374 return $item['item'];
375 }
376
377 /**
378 * Get all redirects. No paging, and it could be too large for memory
379 *
380 * @return Red_Item[]
381 */
382 public static function get_all() {
383 global $wpdb;
384
385 $rows = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}redirection_items" );
386 $items = array();
387
388 foreach ( (array) $rows as $row ) {
389 $items[] = new Red_Item( $row );
390 }
391
392 return $items;
393 }
394
395 /**
396 * Sort URLs
397 *
398 * @param object $first First URL.
399 * @param object $second Second URL.
400 * @return integer
401 */
402 public static function sort_urls( $first, $second ) {
403 if ( $first->position === $second->position ) {
404 // Fall back to which redirect was created first
405 return ( $first->id < $second->id ) ? -1 : 1;
406 }
407
408 return ( $first->position < $second->position ) ? -1 : 1;
409 }
410
411 /**
412 * Sort URLs (deprecated)
413 *
414 * @param array $first First URL.
415 * @param array $second Second URL.
416 * @return integer
417 */
418 public static function sort_urls_old( $first, $second ) {
419 if ( $first['position'] === $second['position'] ) {
420 return 0;
421 }
422
423 return ( $first['position'] < $second['position'] ) ? -1 : 1;
424 }
425
426 /**
427 * Get a redirect by ID
428 *
429 * @param integer $id Redirect ID.
430 * @return Red_Item|false
431 */
432 public static function get_by_id( $id ) {
433 global $wpdb;
434
435 $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}redirection_items WHERE id=%d", $id ) );
436 if ( $row ) {
437 return new Red_Item( $row );
438 }
439
440 return false;
441 }
442
443 /**
444 * Disable all redirects that match the URL
445 *
446 * @param String $url URL to match.
447 * @return void
448 */
449 public static function disable_where_matches( $url ) {
450 global $wpdb;
451
452 $wpdb->update( $wpdb->prefix . 'redirection_items', array( 'status' => 'disabled' ), array( 'url' => $url ) );
453 }
454
455 /**
456 * Delete this redirect
457 *
458 * @return void
459 */
460 public function delete() {
461 global $wpdb;
462
463 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}redirection_items WHERE id=%d", $this->id ) );
464 do_action( 'redirection_redirect_deleted', $this );
465
466 Red_Module::flush( $this->group_id );
467 }
468
469 /**
470 * Create a redirect with new details
471 *
472 * @param array $details Redirect details.
473 * @return WP_Error|Red_Item
474 */
475 public static function create( array $details ) {
476 global $wpdb;
477
478 $sanitizer = new Red_Item_Sanitize();
479 $data = $sanitizer->get( $details );
480 if ( is_wp_error( $data ) ) {
481 return $data;
482 }
483
484 $data['status'] = 'enabled';
485
486 // todo: fix this mess
487 if ( ( isset( $details['enabled'] ) && ( $details['enabled'] === 'disabled' || $details['enabled'] === false ) ) || ( isset( $details['status'] ) && $details['status'] === 'disabled' ) ) {
488 $data['status'] = 'disabled';
489 }
490
491 if ( ! isset( $details['position'] ) || $details['position'] === 0 ) {
492 $data['position'] = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}redirection_items WHERE group_id=%d", $data['group_id'] ) );
493 }
494
495 $data = apply_filters( 'redirection_create_redirect', $data );
496
497 if ( ! empty( $data['match_data'] ) ) {
498 $data['match_data'] = wp_json_encode( $data['match_data'], JSON_UNESCAPED_SLASHES );
499 }
500
501 // Create
502 if ( $wpdb->insert( $wpdb->prefix . 'redirection_items', $data ) !== false ) {
503 Red_Module::flush( $data['group_id'] );
504
505 $redirect = self::get_by_id( $wpdb->insert_id );
506 if ( $redirect ) {
507 do_action( 'redirection_redirect_updated', $wpdb->insert_id, $redirect );
508
509 return $redirect;
510 }
511
512 return new WP_Error( 'redirect_create_failed', 'Unable to get newly added redirect' );
513 }
514
515 return new WP_Error( 'redirect_create_failed', 'Unable to add new redirect' );
516 }
517
518 /**
519 * Update the redirect with new details
520 *
521 * @param array $details Redirect details.
522 * @return WP_Error|true
523 */
524 public function update( $details ) {
525 global $wpdb;
526
527 $sanitizer = new Red_Item_Sanitize();
528 $data = $sanitizer->get( $details );
529
530 if ( is_wp_error( $data ) ) {
531 return $data;
532 }
533
534 $old_group = false;
535 if ( $data['group_id'] !== $this->group_id ) {
536 $old_group = $this->group_id;
537 }
538
539 // Save this
540 $data = apply_filters( 'redirection_update_redirect', $data );
541 if ( ! empty( $data['match_data'] ) ) {
542 $data['match_data'] = wp_json_encode( $data['match_data'], JSON_UNESCAPED_SLASHES );
543 }
544
545 $result = $wpdb->update( $wpdb->prefix . 'redirection_items', $data, array( 'id' => $this->id ) );
546 if ( $result !== false ) {
547 do_action( 'redirection_redirect_updated', $this, self::get_by_id( $this->id ) );
548 $this->load_from_data( $data );
549
550 Red_Module::flush( $this->group_id );
551
552 if ( $old_group !== $this->group_id && $old_group !== false ) {
553 Red_Module::flush( $old_group );
554 }
555
556 return true;
557 }
558
559 return new WP_Error( 'redirect_create_failed', 'Unable to update redirect' );
560 }
561
562 /**
563 * Determine if a requested URL matches this URL
564 *
565 * @param string $requested_url The URL being requested (decoded).
566 * @param string|false $original_url The URL being requested (not decoded).
567 * @return Red_Action|false true if matched, false otherwise
568 */
569 public function get_match( $requested_url, $original_url = false ) {
570 if ( ! $this->is_enabled() || ! $this->match || ! $this->source_flags || ! $this->action ) {
571 // Don't do anything if Redirection is disabled or we don't have any of the objects
572 return false;
573 }
574
575 if ( $original_url === false ) {
576 $original_url = $requested_url;
577 }
578
579 $url = new Red_Url( $this->url );
580
581 // Does the URL match? This may not be the case for regular expressions
582 if ( ! $url->is_match( $requested_url, $this->source_flags ) ) {
583 return false;
584 }
585
586 // Does the additional Red_Match logic also match? This provides dynamic checking of things like IP, cookies, etc
587 $matched = $this->match->is_match( $requested_url );
588 $target_url = false;
589
590 // Does the action need a target (URL)?
591 if ( $this->action->needs_target() ) {
592 // Get the target from the action and the match status - some matches have a matched/unmatched target
593 $target_url = $this->match->get_target_url( $original_url, $url->get_url(), $this->source_flags, $matched );
594 if ( $target_url ) {
595 $target_url = Red_Url_Query::add_to_target( $target_url, $original_url, $this->source_flags );
596 }
597
598 // Allow plugins a look
599 $target_url = apply_filters( 'redirection_url_target', $target_url, $url->get_url() );
600
601 // Do we have still have a target?
602 if ( ! $target_url ) {
603 // No, return early and move on to the next redirect. This could be a matched/unmatched target that has no value
604 return false;
605 }
606
607 // Set this in the action
608 $this->action->set_target( $target_url );
609
610 // Fire an action to let people know
611 do_action( 'redirection_visit', $this, $original_url, $target_url );
612 }
613
614 // Return the action for processing if we have either matched or we have a target URL (possibly from an 'not matched' condition)
615 if ( $matched || $target_url ) {
616 return $this->action;
617 }
618
619 return false;
620 }
621
622 /**
623 * Register a visit against this redirect
624 *
625 * @param String $url Full URL that is visited, including query parameters.
626 * @param String|true $target Target URL, if appropriate.
627 * @return void
628 */
629 public function visit( $url, $target ) {
630 global $wpdb;
631
632 $options = red_get_options();
633
634 // Update the counters
635 $this->last_count++;
636
637 if ( apply_filters( 'redirection_redirect_counter', $options['track_hits'], $url ) ) {
638 $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}redirection_items SET last_count=last_count+1, last_access=NOW() WHERE id=%d", $this->id ) );
639 }
640
641 if ( $target && $this->source_options && $this->source_options->can_log() ) {
642 if ( $target === true && $this->match ) {
643 $target = $this->action_type === 'pass' ? $this->match->get_data()['url'] : '';
644 }
645
646 $details = [
647 'target' => $target,
648 'agent' => Redirection_Request::get_user_agent(),
649 'referrer' => Redirection_Request::get_referrer(),
650 'request_method' => Redirection_Request::get_request_method(),
651 'http_code' => $this->get_action_code(),
652 'redirect_id' => $this->id,
653 'redirect_by' => 'redirection',
654 ];
655
656 if ( $options['log_header'] ) {
657 $details['request_data'] = [
658 'headers' => Redirection_Request::get_request_headers(),
659 ];
660 }
661
662 Red_Redirect_Log::create( Redirection_Request::get_server(), $url, Redirection_Request::get_ip(), $details );
663 }
664 }
665
666 /**
667 * Is this redirect enabled?
668 *
669 * @return boolean
670 */
671 public function is_enabled() {
672 return $this->status === 'enabled';
673 }
674
675 /**
676 * Reset this redirect
677 *
678 * @return void
679 */
680 public function reset() {
681 global $wpdb;
682
683 $this->last_count = 0;
684 $this->last_access = 0;
685
686 $update = [
687 'last_count' => 0,
688 'last_access' => '1970-01-01 00:00:00',
689 ];
690 $where = [
691 'id' => $this->id,
692 ];
693
694 $wpdb->update( $wpdb->prefix . 'redirection_items', $update, $where );
695 }
696
697 /**
698 * Enable this redirect
699 *
700 * @return void
701 */
702 public function enable() {
703 global $wpdb;
704
705 $this->status = 'enabled';
706 $wpdb->update( $wpdb->prefix . 'redirection_items', [ 'status' => $this->status ], [ 'id' => $this->id ] );
707 }
708
709 /**
710 * Disable this redirect
711 *
712 * @return void
713 */
714 public function disable() {
715 global $wpdb;
716
717 $this->status = 'disabled';
718 $wpdb->update( $wpdb->prefix . 'redirection_items', [ 'status' => $this->status ], [ 'id' => $this->id ] );
719 }
720
721 /**
722 * Get the redirect ID
723 *
724 * @return integer
725 */
726 public function get_id() {
727 return intval( $this->id, 10 );
728 }
729
730 /**
731 * Get the redirect position
732 *
733 * @return integer
734 */
735 public function get_position() {
736 return intval( $this->position, 10 );
737 }
738
739 /**
740 * Get the redirect group ID
741 *
742 * @return integer
743 */
744 public function get_group_id() {
745 return intval( $this->group_id, 10 );
746 }
747
748 /**
749 * Get the redirect URL
750 *
751 * @return string
752 */
753 public function get_url() {
754 return $this->url;
755 }
756
757 /**
758 * Get the match URL
759 *
760 * @return string
761 */
762 public function get_match_url() {
763 return $this->match_url;
764 }
765
766 /**
767 * Get match data
768 *
769 * @return array|null
770 */
771 public function get_match_data() {
772 if ( ! $this->source_flags || ! $this->source_options ) {
773 return null;
774 }
775
776 $source = $this->source_flags->get_json_with_defaults();
777 $options = $this->source_options->get_json();
778
779 $data = [];
780
781 if ( ! empty( $source ) ) {
782 $data['source'] = $source;
783 }
784
785 if ( ! empty( $options ) ) {
786 $data['options'] = $options;
787 }
788
789 if ( count( $data ) > 0 ) {
790 return $data;
791 }
792
793 return null;
794 }
795
796 /**
797 * Get title
798 *
799 * @return string
800 */
801 public function get_title() {
802 return $this->title ? $this->title : '';
803 }
804
805 /**
806 * Get number of hits
807 *
808 * @return integer
809 */
810 public function get_hits() {
811 return intval( $this->last_count, 10 );
812 }
813
814 /**
815 * Get time of last hit
816 *
817 * @return integer
818 */
819 public function get_last_hit() {
820 return intval( $this->last_access, 10 );
821 }
822
823 /**
824 * Is this a regular expression?
825 *
826 * @return boolean
827 */
828 public function is_regex() {
829 return $this->regex ? true : false;
830 }
831
832 /**
833 * Does this redirect depend on dynamic match data? For example, it is checking a cookie or IP
834 *
835 * @return boolean
836 */
837 public function is_dynamic() {
838 if ( $this->match ) {
839 return $this->match->get_type() !== 'url';
840 }
841
842 return false;
843 }
844
845 /**
846 * Get match type
847 *
848 * @return string
849 */
850 public function get_match_type() {
851 return $this->match_type;
852 }
853
854 /**
855 * Get action type
856 *
857 * @return string
858 */
859 public function get_action_type() {
860 return $this->action_type;
861 }
862
863 /**
864 * Get action code
865 *
866 * @return integer
867 */
868 public function get_action_code() {
869 return intval( $this->action_code, 10 );
870 }
871
872 /**
873 * Get action data
874 *
875 * @return String
876 */
877 public function get_action_data() {
878 return $this->action_data ? $this->action_data : '';
879 }
880
881 /**
882 * Delete all redirects that match a filter
883 *
884 * @param array $params Filter parameters.
885 * @return boolean
886 */
887 public static function delete_all( array $params ) {
888 global $wpdb;
889
890 $filters = new Red_Item_Filters( isset( $params['filterBy'] ) ? $params['filterBy'] : [] );
891 $where = $filters->get_as_sql();
892
893 // where is known
894 // phpcs:ignore
895 return $wpdb->query( "DELETE FROM {$wpdb->prefix}redirection_items $where" );
896 }
897
898 /**
899 * Reset all redirects that match a filter
900 *
901 * @param array $params Filter parameters.
902 * @return boolean
903 */
904 public static function reset_all( array $params ) {
905 global $wpdb;
906
907 $filters = new Red_Item_Filters( isset( $params['filterBy'] ) ? $params['filterBy'] : [] );
908 $where = $filters->get_as_sql();
909
910 // where is known
911 // phpcs:ignore
912 return $wpdb->query( "UPDATE {$wpdb->prefix}redirection_items SET last_count=0, last_access='1970-01-01 00:00:00' $where" );
913 }
914
915 /**
916 * Set the status of all redirects that match the filter
917 *
918 * @param 'enable'|'disable' $status Status to set.
919 * @param array $params Filter parameters.
920 * @return boolean
921 */
922 public static function set_status_all( $status, array $params ) {
923 global $wpdb;
924
925 $filters = new Red_Item_Filters( isset( $params['filterBy'] ) ? $params['filterBy'] : [] );
926 $where = $filters->get_as_sql();
927
928 // where is known
929 // phpcs:ignore
930 return $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}redirection_items SET status=%s $where", $status === 'enable' ? 'enabled' : 'disabled' ) );
931 }
932
933 /**
934 * Get a filtered list of redirects
935 *
936 * @param array $params Filter parameters.
937 * @return array<total: integer, items: Red_Item[]>
938 */
939 public static function get_filtered( array $params ) {
940 global $wpdb;
941
942 $orderby = 'id';
943 $direction = 'DESC';
944 $limit = RED_DEFAULT_PER_PAGE;
945 $offset = 0;
946 $where = '';
947
948 if ( isset( $params['orderby'] ) && in_array( $params['orderby'], [ 'source', 'last_count', 'last_access', 'position' ], true ) ) {
949 $orderby = $params['orderby'];
950
951 if ( $orderby === 'source' ) {
952 $orderby = 'url';
953 }
954 }
955
956 if ( isset( $params['direction'] ) && in_array( $params['direction'], [ 'asc', 'desc' ], true ) ) {
957 $direction = strtoupper( $params['direction'] );
958 }
959
960 if ( isset( $params['filterBy'] ) && is_array( $params['filterBy'] ) ) {
961 $filters = new Red_Item_Filters( $params['filterBy'] );
962 $where = $filters->get_as_sql();
963 }
964
965 if ( isset( $params['per_page'] ) ) {
966 $limit = intval( $params['per_page'], 10 );
967 $limit = min( RED_MAX_PER_PAGE, $limit );
968 $limit = max( 5, $limit );
969 }
970
971 if ( isset( $params['page'] ) ) {
972 $offset = intval( $params['page'], 10 );
973 $offset = max( 0, $offset );
974 $offset *= $limit;
975 }
976
977 // $orderby and $direction is whitelisted
978 // phpcs:ignore
979 $rows = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}redirection_items $where ORDER BY $orderby $direction " . $wpdb->prepare( 'LIMIT %d,%d', $offset, $limit ) );
980
981 // phpcs:ignore
982 $total_items = intval( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}redirection_items " . $where ) );
983 $items = [];
984
985 foreach ( $rows as $row ) {
986 $group = new Red_Item( $row );
987 $items[] = $group->to_json();
988 }
989
990 return [
991 'items' => $items,
992 'total' => intval( $total_items, 10 ),
993 ];
994 }
995
996 /**
997 * Convert the redirect to JSON
998 *
999 * @return array
1000 */
1001 public function to_json() {
1002 return [
1003 'id' => $this->get_id(),
1004 'url' => $this->get_url(),
1005 'match_url' => $this->get_match_url(),
1006 'match_data' => $this->get_match_data(),
1007 'action_code' => $this->get_action_code(),
1008 'action_type' => $this->get_action_type(),
1009 'action_data' => $this->match ? $this->match->get_data() : null,
1010 'match_type' => $this->get_match_type(),
1011 'title' => $this->get_title(),
1012 'hits' => $this->get_hits(),
1013 'regex' => $this->is_regex(),
1014 'group_id' => $this->get_group_id(),
1015 'position' => $this->get_position(),
1016 'last_access' => $this->get_last_hit() > 0 ? date_i18n( get_option( 'date_format' ), $this->get_last_hit() ) : '-',
1017 'enabled' => $this->is_enabled(),
1018 ];
1019 }
1020
1021 /**
1022 * Convert the redirect to SQL
1023 *
1024 * @return array
1025 */
1026 public function to_sql() {
1027 $json = $this->to_json();
1028 $action_data = null;
1029
1030 if ( $this->match ) {
1031 $data = $this->match->get_data();
1032
1033 if ( $data ) {
1034 $action_data = $this->match->save( $data, false );
1035
1036 if ( is_array( $action_data ) ) {
1037 // phpcs:ignore
1038 $action_data = serialize( $action_data );
1039 }
1040 }
1041 }
1042
1043 return array_merge( $json, [
1044 'match_data' => wp_json_encode( $this->get_match_data(), JSON_UNESCAPED_SLASHES ),
1045 'action_data' => $action_data,
1046 'last_access' => date( 'Y-m-d H:i:s', $this->last_access ),
1047 'status' => $this->is_enabled() ? 'enabled' : 'disabled',
1048 ] );
1049 }
1050 }
1051