PluginProbe
Redirection / 5.5.0
Redirection v5.5.0
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.5.0, at models/redirect/redirect.php

1,053 lines 24.0 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 = 20000;
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 do_action( 'redirection_redirect_enabled', $this->id );
708 }
709
710 /**
711 * Disable this redirect
712 *
713 * @return void
714 */
715 public function disable() {
716 global $wpdb;
717
718 $this->status = 'disabled';
719 $wpdb->update( $wpdb->prefix . 'redirection_items', [ 'status' => $this->status ], [ 'id' => $this->id ] );
720 do_action( 'redirection_redirect_disabled', $this->id );
721 }
722
723 /**
724 * Get the redirect ID
725 *
726 * @return integer
727 */
728 public function get_id() {
729 return intval( $this->id, 10 );
730 }
731
732 /**
733 * Get the redirect position
734 *
735 * @return integer
736 */
737 public function get_position() {
738 return intval( $this->position, 10 );
739 }
740
741 /**
742 * Get the redirect group ID
743 *
744 * @return integer
745 */
746 public function get_group_id() {
747 return intval( $this->group_id, 10 );
748 }
749
750 /**
751 * Get the redirect URL
752 *
753 * @return string
754 */
755 public function get_url() {
756 return $this->url;
757 }
758
759 /**
760 * Get the match URL
761 *
762 * @return string
763 */
764 public function get_match_url() {
765 return $this->match_url;
766 }
767
768 /**
769 * Get match data
770 *
771 * @return array|null
772 */
773 public function get_match_data() {
774 if ( ! $this->source_flags || ! $this->source_options ) {
775 return null;
776 }
777
778 $source = $this->source_flags->get_json_with_defaults();
779 $options = $this->source_options->get_json();
780
781 $data = [];
782
783 if ( ! empty( $source ) ) {
784 $data['source'] = $source;
785 }
786
787 if ( ! empty( $options ) ) {
788 $data['options'] = $options;
789 }
790
791 if ( count( $data ) > 0 ) {
792 return $data;
793 }
794
795 return null;
796 }
797
798 /**
799 * Get title
800 *
801 * @return string
802 */
803 public function get_title() {
804 return $this->title ? $this->title : '';
805 }
806
807 /**
808 * Get number of hits
809 *
810 * @return integer
811 */
812 public function get_hits() {
813 return intval( $this->last_count, 10 );
814 }
815
816 /**
817 * Get time of last hit
818 *
819 * @return integer
820 */
821 public function get_last_hit() {
822 return intval( $this->last_access, 10 );
823 }
824
825 /**
826 * Is this a regular expression?
827 *
828 * @return boolean
829 */
830 public function is_regex() {
831 return $this->regex ? true : false;
832 }
833
834 /**
835 * Does this redirect depend on dynamic match data? For example, it is checking a cookie or IP
836 *
837 * @return boolean
838 */
839 public function is_dynamic() {
840 if ( $this->match ) {
841 return $this->match->get_type() !== 'url';
842 }
843
844 return false;
845 }
846
847 /**
848 * Get match type
849 *
850 * @return string
851 */
852 public function get_match_type() {
853 return $this->match_type;
854 }
855
856 /**
857 * Get action type
858 *
859 * @return string
860 */
861 public function get_action_type() {
862 return $this->action_type;
863 }
864
865 /**
866 * Get action code
867 *
868 * @return integer
869 */
870 public function get_action_code() {
871 return intval( $this->action_code, 10 );
872 }
873
874 /**
875 * Get action data
876 *
877 * @return string
878 */
879 public function get_action_data() {
880 return $this->action_data ? $this->action_data : '';
881 }
882
883 /**
884 * Delete all redirects that match a filter
885 *
886 * @param array $params Filter parameters.
887 * @return boolean
888 */
889 public static function delete_all( array $params ) {
890 global $wpdb;
891
892 $filters = new Red_Item_Filters( isset( $params['filterBy'] ) ? $params['filterBy'] : [] );
893 $where = $filters->get_as_sql();
894
895 // where is known
896 // phpcs:ignore
897 return $wpdb->query( "DELETE FROM {$wpdb->prefix}redirection_items $where" );
898 }
899
900 /**
901 * Reset all redirects that match a filter
902 *
903 * @param array $params Filter parameters.
904 * @return boolean
905 */
906 public static function reset_all( array $params ) {
907 global $wpdb;
908
909 $filters = new Red_Item_Filters( isset( $params['filterBy'] ) ? $params['filterBy'] : [] );
910 $where = $filters->get_as_sql();
911
912 // where is known
913 // phpcs:ignore
914 return $wpdb->query( "UPDATE {$wpdb->prefix}redirection_items SET last_count=0, last_access='1970-01-01 00:00:00' $where" );
915 }
916
917 /**
918 * Set the status of all redirects that match the filter
919 *
920 * @param 'enable'|'disable' $status Status to set.
921 * @param array $params Filter parameters.
922 * @return boolean
923 */
924 public static function set_status_all( $status, array $params ) {
925 global $wpdb;
926
927 $filters = new Red_Item_Filters( isset( $params['filterBy'] ) ? $params['filterBy'] : [] );
928 $where = $filters->get_as_sql();
929
930 // where is known
931 // phpcs:ignore
932 return $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}redirection_items SET status=%s $where", $status === 'enable' ? 'enabled' : 'disabled' ) );
933 }
934
935 /**
936 * Get a filtered list of redirects
937 *
938 * @param array $params Filter parameters.
939 * @return array{total:integer,items:Red_Item[]}
940 */
941 public static function get_filtered( array $params ) {
942 global $wpdb;
943
944 $orderby = 'id';
945 $direction = 'DESC';
946 $limit = RED_DEFAULT_PER_PAGE;
947 $offset = 0;
948 $where = '';
949
950 if ( isset( $params['orderby'] ) && in_array( $params['orderby'], [ 'source', 'last_count', 'last_access', 'position' ], true ) ) {
951 $orderby = $params['orderby'];
952
953 if ( $orderby === 'source' ) {
954 $orderby = 'url';
955 }
956 }
957
958 if ( isset( $params['direction'] ) && in_array( $params['direction'], [ 'asc', 'desc' ], true ) ) {
959 $direction = strtoupper( $params['direction'] );
960 }
961
962 if ( isset( $params['filterBy'] ) && is_array( $params['filterBy'] ) ) {
963 $filters = new Red_Item_Filters( $params['filterBy'] );
964 $where = $filters->get_as_sql();
965 }
966
967 if ( isset( $params['per_page'] ) ) {
968 $limit = intval( $params['per_page'], 10 );
969 $limit = min( RED_MAX_PER_PAGE, $limit );
970 $limit = max( 5, $limit );
971 }
972
973 if ( isset( $params['page'] ) ) {
974 $offset = intval( $params['page'], 10 );
975 $offset = max( 0, $offset );
976 $offset *= $limit;
977 }
978
979 // $orderby and $direction is whitelisted
980 // phpcs:ignore
981 $rows = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}redirection_items $where ORDER BY $orderby $direction " . $wpdb->prepare( 'LIMIT %d,%d', $offset, $limit ) );
982
983 // phpcs:ignore
984 $total_items = intval( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}redirection_items " . $where ) );
985 $items = [];
986
987 foreach ( $rows as $row ) {
988 $group = new Red_Item( $row );
989 $items[] = $group->to_json();
990 }
991
992 return [
993 'items' => $items,
994 'total' => intval( $total_items, 10 ),
995 ];
996 }
997
998 /**
999 * Convert the redirect to JSON
1000 *
1001 * @return array
1002 */
1003 public function to_json() {
1004 return [
1005 'id' => $this->get_id(),
1006 'url' => $this->get_url(),
1007 'match_url' => $this->get_match_url(),
1008 'match_data' => $this->get_match_data(),
1009 'action_code' => $this->get_action_code(),
1010 'action_type' => $this->get_action_type(),
1011 'action_data' => $this->match ? $this->match->get_data() : null,
1012 'match_type' => $this->get_match_type(),
1013 'title' => $this->get_title(),
1014 'hits' => $this->get_hits(),
1015 'regex' => $this->is_regex(),
1016 'group_id' => $this->get_group_id(),
1017 'position' => $this->get_position(),
1018 'last_access' => $this->get_last_hit() > 0 ? date_i18n( get_option( 'date_format' ), $this->get_last_hit() ) : '-',
1019 'enabled' => $this->is_enabled(),
1020 ];
1021 }
1022
1023 /**
1024 * Convert the redirect to SQL
1025 *
1026 * @return array
1027 */
1028 public function to_sql() {
1029 $json = $this->to_json();
1030 $action_data = null;
1031
1032 if ( $this->match ) {
1033 $data = $this->match->get_data();
1034
1035 if ( $data ) {
1036 $action_data = $this->match->save( $data, false );
1037
1038 if ( is_array( $action_data ) ) {
1039 // phpcs:ignore
1040 $action_data = serialize( $action_data );
1041 }
1042 }
1043 }
1044
1045 return array_merge( $json, [
1046 'match_data' => wp_json_encode( $this->get_match_data(), JSON_UNESCAPED_SLASHES ),
1047 'action_data' => $action_data,
1048 'last_access' => date( 'Y-m-d H:i:s', $this->last_access ),
1049 'status' => $this->is_enabled() ? 'enabled' : 'disabled',
1050 ] );
1051 }
1052 }
1053