PluginProbe
Redirection / 4.5.1
Redirection v4.5.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.php

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

603 lines 16.8 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__ ) . '/url.php';
4 require_once dirname( __FILE__ ) . '/regex.php';
5 require_once dirname( __FILE__ ) . '/redirect-sanitizer.php';
6
7 class Red_Item {
8 private $id = null;
9 private $url = null;
10 private $match_url = null;
11 private $match_data = null;
12 private $regex = false;
13 private $action_data = null;
14 private $action_code = 0;
15 private $action_type;
16 private $match_type;
17 private $title;
18 private $last_access = null;
19 private $last_count = 0;
20 private $status = 'enabled';
21 private $position;
22 private $group_id;
23
24 public $source_flags = false;
25
26 public function __construct( $values = null ) {
27 if ( is_object( $values ) ) {
28 $this->load_from_data( $values );
29 }
30 }
31
32 private function load_from_data( stdClass $values ) {
33 foreach ( $values as $key => $value ) {
34 if ( property_exists( $this, $key ) ) {
35 $this->$key = $value;
36 }
37 }
38
39 $this->regex = (bool) $this->regex;
40 $this->last_access = $this->last_access === '0000-00-00 00:00:00' ? 0 : mysql2date( 'U', $this->last_access );
41
42 $this->load_matcher();
43 $this->load_action();
44 $this->load_source_flags();
45 }
46
47 // v4 JSON
48 private function load_source_flags() {
49 // Default regex flag to regex column. This will be removed once the regex column has been migrated
50 // todo: deprecate
51 $this->source_flags = new Red_Source_Flags( array_merge( red_get_options(), [ 'flag_regex' => $this->regex ] ) );
52
53 if ( isset( $this->match_data ) ) {
54 $json = json_decode( $this->match_data, true );
55
56 if ( $json && isset( $json['source'] ) ) {
57 // Merge redirect flags with default flags
58 $this->source_flags->set_flags( array_merge( red_get_options(), $json['source'] ) );
59 }
60 }
61 }
62
63 private function load_matcher() {
64 if ( empty( $this->match_type ) ) {
65 $this->match_type = 'url';
66 }
67
68 $this->match = Red_Match::create( $this->match_type, $this->action_data );
69 }
70
71 private function load_action() {
72 if ( empty( $this->action_type ) ) {
73 $this->action_type = 'nothing';
74 }
75
76 $this->action = Red_Action::create( $this->action_type, $this->action_code );
77 if ( $this->match ) {
78 $this->match->action = $this->action;
79 }
80 }
81
82 public static function get_all_for_module( $module ) {
83 global $wpdb;
84
85 $rows = $wpdb->get_results(
86 $wpdb->prepare(
87 "SELECT {$wpdb->prefix}redirection_items.* FROM {$wpdb->prefix}redirection_items
88 INNER JOIN {$wpdb->prefix}redirection_groups ON {$wpdb->prefix}redirection_groups.id={$wpdb->prefix}redirection_items.group_id
89 AND {$wpdb->prefix}redirection_groups.status='enabled' AND {$wpdb->prefix}redirection_groups.module_id=%d
90 WHERE {$wpdb->prefix}redirection_items.status='enabled'
91 ORDER BY {$wpdb->prefix}redirection_groups.position,{$wpdb->prefix}redirection_items.position",
92 $module
93 )
94 );
95 $items = array();
96
97 foreach ( (array) $rows as $row ) {
98 $items[] = new Red_Item( $row );
99 }
100
101 return $items;
102 }
103
104 public static function get_for_url( $url ) {
105 $status = new Red_Database_Status();
106
107 // deprecate
108 if ( $status->does_support( '4.0' ) ) {
109 return self::get_for_matched_url( $url );
110 }
111
112 return self::get_old_url( $url );
113 }
114
115 public static function get_for_matched_url( $url ) {
116 global $wpdb;
117
118 $url = new Red_Url_Match( $url );
119 $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}redirection_items WHERE match_url=%s OR match_url='regex'", $url->get_url() ) );
120
121 $items = array();
122 if ( count( $rows ) > 0 ) {
123 foreach ( $rows as $row ) {
124 $items[] = new Red_Item( $row );
125 }
126 }
127
128 usort( $items, array( 'Red_Item', 'sort_urls' ) );
129
130 return $items;
131 }
132
133 // deprecate
134 public static function get_old_url( $url ) {
135 global $wpdb;
136
137 $rows = $wpdb->get_results(
138 $wpdb->prepare(
139 "SELECT {$wpdb->prefix}redirection_items.*,{$wpdb->prefix}redirection_groups.position AS group_pos
140 FROM {$wpdb->prefix}redirection_items INNER JOIN {$wpdb->prefix}redirection_groups ON
141 {$wpdb->prefix}redirection_groups.id={$wpdb->prefix}redirection_items.group_id AND {$wpdb->prefix}redirection_groups.status='enabled'
142 AND {$wpdb->prefix}redirection_groups.module_id=%d WHERE ({$wpdb->prefix}redirection_items.regex=1
143 OR {$wpdb->prefix}redirection_items.url=%s)",
144 WordPress_Module::MODULE_ID,
145 $url
146 )
147 );
148
149 $items = array();
150 if ( count( $rows ) > 0 ) {
151 foreach ( $rows as $row ) {
152 $items[] = array(
153 'position' => ( $row->group_pos * 1000 ) + $row->position,
154 'item' => new Red_Item( $row ),
155 );
156 }
157 }
158
159 usort( $items, array( 'Red_Item', 'sort_urls_old' ) );
160 $items = array_map( array( 'Red_Item', 'reduce_sorted_items' ), $items );
161
162 // Sort it in PHP
163 ksort( $items );
164 $items = array_values( $items );
165 return $items;
166 }
167
168 public static function reduce_sorted_items( $item ) {
169 return $item['item'];
170 }
171
172 public static function get_all() {
173 global $wpdb;
174
175 $rows = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}redirection_items" );
176 $items = array();
177
178 foreach ( (array) $rows as $row ) {
179 $items[] = new Red_Item( $row );
180 }
181
182 return $items;
183 }
184
185 public static function sort_urls( $first, $second ) {
186 if ( $first->position === $second->position ) {
187 return 0;
188 }
189
190 return ( $first->position < $second->position ) ? -1 : 1;
191 }
192
193 public static function sort_urls_old( $first, $second ) {
194 if ( $first['position'] === $second['position'] ) {
195 return 0;
196 }
197
198 return ( $first['position'] < $second['position'] ) ? -1 : 1;
199 }
200
201 public static function get_by_id( $id ) {
202 global $wpdb;
203
204 $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}redirection_items WHERE id=%d", $id ) );
205 if ( $row ) {
206 return new Red_Item( $row );
207 }
208
209 return false;
210 }
211
212 public static function disable_where_matches( $url ) {
213 global $wpdb;
214
215 $wpdb->update( $wpdb->prefix . 'redirection_items', array( 'status' => 'disabled' ), array( 'url' => $url ) );
216 }
217
218 public function delete() {
219 global $wpdb;
220
221 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}redirection_items WHERE id=%d", $this->id ) );
222 do_action( 'redirection_redirect_deleted', $this );
223
224 Red_Module::flush( $this->group_id );
225 }
226
227 public static function create( array $details ) {
228 global $wpdb;
229
230 $sanitizer = new Red_Item_Sanitize();
231 $data = $sanitizer->get( $details );
232 if ( is_wp_error( $data ) ) {
233 return $data;
234 }
235
236 $data['status'] = 'enabled';
237
238 // todo: fix this mess
239 if ( ( isset( $details['enabled'] ) && ( $details['enabled'] === 'disabled' || $details['enabled'] === false ) ) || ( isset( $details['status'] ) && $details['status'] === 'disabled' ) ) {
240 $data['status'] = 'disabled';
241 }
242
243 if ( ! isset( $details['position'] ) || $details['position'] === 0 ) {
244 $data['position'] = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}redirection_items WHERE group_id=%d", $data['group_id'] ) );
245 }
246
247 $data = apply_filters( 'redirection_create_redirect', $data );
248
249 if ( ! empty( $data['match_data'] ) ) {
250 $data['match_data'] = json_encode( $data['match_data'] );
251 }
252
253 // Create
254 if ( $wpdb->insert( $wpdb->prefix . 'redirection_items', $data ) !== false ) {
255 Red_Module::flush( $data['group_id'] );
256
257 $redirect = self::get_by_id( $wpdb->insert_id );
258 if ( $redirect ) {
259 do_action( 'redirection_redirect_updated', $wpdb->insert_id, $redirect );
260
261 return $redirect;
262 }
263
264 return new WP_Error( 'redirect_create_failed', 'Unable to get newly added redirect' );
265 }
266
267 return new WP_Error( 'redirect_create_failed', __( 'Unable to add new redirect' ) );
268 }
269
270 public function update( $details ) {
271 global $wpdb;
272
273 $sanitizer = new Red_Item_Sanitize();
274 $data = $sanitizer->get( $details );
275
276 if ( is_wp_error( $data ) ) {
277 return $data;
278 }
279
280 $old_group = false;
281 if ( $data['group_id'] !== $this->group_id ) {
282 $old_group = $this->group_id;
283 }
284
285 // Save this
286 $data = apply_filters( 'redirection_update_redirect', $data );
287 if ( ! empty( $data['match_data'] ) ) {
288 $data['match_data'] = json_encode( $data['match_data'] );
289 }
290
291 $result = $wpdb->update( $wpdb->prefix . 'redirection_items', $data, array( 'id' => $this->id ) );
292 if ( $result !== false ) {
293 do_action( 'redirection_redirect_updated', $this, self::get_by_id( $this->id ) );
294 $this->load_from_data( (object) $data );
295
296 Red_Module::flush( $this->group_id );
297
298 if ( $old_group !== $this->group_id ) {
299 Red_Module::flush( $old_group );
300 }
301
302 return true;
303 }
304
305 return new WP_Error( 'redirect_create_failed', __( 'Unable to update redirect' ) );
306 }
307
308 /**
309 * Determine if a requested URL matches this URL
310 *
311 * @param string $requested_url
312 * @return bool true if matched, false otherwise
313 */
314 public function is_match( $requested_url ) {
315 if ( ! $this->is_enabled() ) {
316 return false;
317 }
318
319 $url = new Red_Url( $this->url );
320 if ( $url->is_match( $requested_url, $this->source_flags ) ) {
321 // URL is matched, now match the redirect type (i.e. login status, IP address)
322 $target = $this->match->is_match( $requested_url );
323
324 // Check if our action wants a URL
325 if ( $this->action->needs_target() ) {
326 // Our action requires a target URL - get this, using our type match result
327 $target = $this->match->get_target_url( $requested_url, $url->get_url(), $this->source_flags, $target );
328 $target = Red_Url_Query::add_to_target( $target, $requested_url, $this->source_flags );
329 $target = apply_filters( 'redirection_url_target', $target, $this->url );
330 }
331
332 // Fire any early actions
333 if ( $target ) {
334 $target = $this->action->process_before( $this->action_code, $target );
335 }
336
337 if ( $target ) {
338 // We still have a target, so log it and carry on with the action
339 do_action( 'redirection_visit', $this, $requested_url, $target );
340 return $this->action->process_after( $this->action_code, $target );
341 }
342 }
343
344 return false;
345 }
346
347 public function visit( $url, $target ) {
348 global $wpdb;
349
350 $options = red_get_options();
351
352 // Update the counters
353 $this->last_count++;
354
355 if ( apply_filters( 'redirection_redirect_counter', true ) ) {
356 $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}redirection_items SET last_count=last_count+1, last_access=NOW() WHERE id=%d", $this->id ) );
357 }
358
359 if ( isset( $options['expire_redirect'] ) && $options['expire_redirect'] !== -1 && $target ) {
360 $details = array(
361 'redirect_id' => $this->id,
362 'group_id' => $this->group_id,
363 );
364
365 if ( $target === true ) {
366 $target = $this->action_type === 'pass' ? $this->match->get_data()['url'] : '';
367 }
368
369 RE_Log::create( $url, $target, Redirection_Request::get_user_agent(), Redirection_Request::get_ip(), Redirection_Request::get_referrer(), $details );
370 }
371 }
372
373 public function is_enabled() {
374 return $this->status === 'enabled';
375 }
376
377 public function reset() {
378 global $wpdb;
379
380 $this->last_count = 0;
381 $this->last_access = '0000-00-00 00:00:00';
382
383 $update = array(
384 'last_count' => 0,
385 'last_access' => $this->last_access,
386 );
387 $where = array(
388 'id' => $this->id,
389 );
390
391 $wpdb->update( $wpdb->prefix . 'redirection_items', $update, $where );
392 }
393
394 public function enable() {
395 global $wpdb;
396
397 $this->status = 'enabled';
398 $wpdb->update( $wpdb->prefix . 'redirection_items', array( 'status' => $this->status ), array( 'id' => $this->id ) );
399 }
400
401 public function disable() {
402 global $wpdb;
403
404 $this->status = 'disabled';
405 $wpdb->update( $wpdb->prefix . 'redirection_items', array( 'status' => $this->status ), array( 'id' => $this->id ) );
406 }
407
408 public function get_id() {
409 return intval( $this->id, 10 );
410 }
411
412 public function get_position() {
413 return intval( $this->position, 10 );
414 }
415
416 public function get_group_id() {
417 return intval( $this->group_id, 10 );
418 }
419
420 public function get_url() {
421 return $this->url;
422 }
423
424 public function get_match_url() {
425 return $this->match_url;
426 }
427
428 public function get_match_data() {
429 $source = $this->source_flags->get_json_with_defaults();
430
431 if ( ! empty( $source ) ) {
432 return [ 'source' => $source ];
433 }
434
435 return null;
436 }
437
438 public function get_title() {
439 return $this->title ? $this->title : '';
440 }
441
442 public function get_hits() {
443 return intval( $this->last_count, 10 );
444 }
445
446 public function get_last_hit() {
447 return intval( $this->last_access, 10 );
448 }
449
450 public function is_regex() {
451 return $this->regex ? true : false;
452 }
453
454 public function get_match_type() {
455 return $this->match_type;
456 }
457
458 public function get_action_type() {
459 return $this->action_type;
460 }
461
462 public function get_action_code() {
463 return intval( $this->action_code, 10 );
464 }
465
466 public function get_action_data() {
467 return $this->action_data ? $this->action_data : '';
468 }
469
470 public static function get_filtered( array $params ) {
471 global $wpdb;
472
473 $orderby = 'id';
474 $direction = 'DESC';
475 $limit = RED_DEFAULT_PER_PAGE;
476 $offset = 0;
477 $where = '';
478
479 if ( isset( $params['orderby'] ) && in_array( $params['orderby'], array( 'url', 'last_count', 'last_access', 'position' ), true ) ) {
480 $orderby = $params['orderby'];
481 }
482
483 if ( isset( $params['direction'] ) && in_array( $params['direction'], array( 'asc', 'desc' ), true ) ) {
484 $direction = strtoupper( $params['direction'] );
485 }
486
487 if ( isset( $params['filterBy'] ) && is_array( $params['filterBy'] ) ) {
488 $filters = new Red_Item_Filters( $params['filterBy'] );
489 $where = $filters->get_as_sql();
490 }
491
492 if ( isset( $params['per_page'] ) ) {
493 $limit = intval( $params['per_page'], 10 );
494 $limit = min( RED_MAX_PER_PAGE, $limit );
495 $limit = max( 5, $limit );
496 }
497
498 if ( isset( $params['page'] ) ) {
499 $offset = intval( $params['page'], 10 );
500 $offset = max( 0, $offset );
501 $offset *= $limit;
502 }
503
504 // $orderby and $direction is whitelisted
505 $rows = $wpdb->get_results(
506 "SELECT * FROM {$wpdb->prefix}redirection_items $where ORDER BY $orderby $direction " . $wpdb->prepare( 'LIMIT %d,%d', $offset, $limit )
507 );
508 $total_items = intval( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}redirection_items " . $where ) );
509 $items = array();
510
511 foreach ( $rows as $row ) {
512 $group = new Red_Item( $row );
513 $items[] = $group->to_json();
514 }
515
516 return array(
517 'items' => $items,
518 'total' => intval( $total_items, 10 ),
519 );
520 }
521
522 public function to_json() {
523 return array(
524 'id' => $this->get_id(),
525 'url' => $this->get_url(),
526 'match_url' => $this->get_match_url(),
527 'match_data' => $this->get_match_data(),
528 'action_code' => $this->get_action_code(),
529 'action_type' => $this->get_action_type(),
530 'action_data' => $this->match ? $this->match->get_data() : null,
531 'match_type' => $this->get_match_type(),
532 'title' => $this->get_title(),
533 'hits' => $this->get_hits(),
534 'regex' => $this->is_regex(),
535 'group_id' => $this->get_group_id(),
536 'position' => $this->get_position(),
537 'last_access' => $this->get_last_hit() > 0 ? date_i18n( get_option( 'date_format' ), $this->get_last_hit() ) : '-',
538 'enabled' => $this->is_enabled(),
539 );
540 }
541 }
542
543 class Red_Item_Filters {
544 private $filters = [];
545
546 public function __construct( $filter_params ) {
547 global $wpdb;
548
549 foreach ( $filter_params as $filter_by => $filter ) {
550 $filter = trim( $filter );
551
552 if ( $filter_by === 'status' ) {
553 if ( $filter === 'enabled' ) {
554 $this->filters[] = "status='enabled'";
555 } else {
556 $this->filters[] = "status='disabled'";
557 }
558 } elseif ( $filter_by === 'url-match' ) {
559 if ( $filter === 'regular' ) {
560 $this->filters[] = 'regex=1';
561 } else {
562 $this->filters[] = 'regex=0';
563 }
564 } elseif ( $filter_by === 'match' && in_array( $filter, array_keys( Red_Match::available() ), true ) ) {
565 $this->filters[] = $wpdb->prepare( 'match_type=%s', $filter );
566 } elseif ( $filter_by === 'action' && in_array( $filter, array_keys( Red_Action::available() ), true ) ) {
567 $this->filters[] = $wpdb->prepare( 'action_type=%s', $filter );
568 } elseif ( $filter_by === 'http' ) {
569 $sanitizer = new Red_Item_Sanitize();
570 $filter = intval( $filter, 10 );
571
572 if ( $sanitizer->is_valid_error_code( $filter ) || $sanitizer->is_valid_redirect_code( $filter ) ) {
573 $this->filters[] = $wpdb->prepare( 'action_code=%d', $filter );
574 }
575 } elseif ( $filter_by === 'access' ) {
576 if ( $filter === 'year' ) {
577 $this->filters[] = 'last_access < DATE_SUB(NOW(),INTERVAL 1 YEAR)';
578 } elseif ( $filter === 'month' ) {
579 $this->filters[] = 'last_access < DATE_SUB(NOW(),INTERVAL 1 MONTH)';
580 } else {
581 $this->filters[] = "last_access = '0000-00-00 00:00:00'";
582 }
583 } elseif ( $filter_by === 'url' ) {
584 $this->filters[] = $wpdb->prepare( 'url LIKE %s', '%' . $wpdb->esc_like( $filter ) . '%' );
585 } elseif ( $filter_by === 'target' ) {
586 $this->filters[] = $wpdb->prepare( 'action_data LIKE %s', '%' . $wpdb->esc_like( $filter ) . '%' );
587 } elseif ( $filter_by === 'title' ) {
588 $this->filters[] = $wpdb->prepare( 'title LIKE %s', '%' . $wpdb->esc_like( $filter ) . '%' );
589 } elseif ( $filter_by === 'group' ) {
590 $this->filters[] = $wpdb->prepare( 'group_id=%d', intval( $filter, 10 ) );
591 }
592 }
593 }
594
595 public function get_as_sql() {
596 if ( count( $this->filters ) > 0 ) {
597 return 'WHERE ' . implode( ' AND ', $this->filters );
598 }
599
600 return '';
601 }
602 }
603