PluginProbe
Redirection / 4.8
Redirection v4.8
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.8, at models/redirect.php

607 lines 16.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__ ) . '/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 The URL being requested.
312 * @return bool true if matched, false otherwise
313 */
314 public function is_match( $requested_url, $original_url = false ) {
315 if ( ! $this->is_enabled() ) {
316 return false;
317 }
318
319 if ( $original_url === false ) {
320 $original_url = $requested_url;
321 }
322
323 $url = new Red_Url( $this->url );
324 if ( $url->is_match( $requested_url, $this->source_flags ) ) {
325 // URL is matched, now match the redirect type (i.e. login status, IP address)
326 $target = $this->match->is_match( $requested_url );
327
328 // Check if our action wants a URL
329 if ( $this->action->needs_target() ) {
330 // Our action requires a target URL - get this, using our type match result
331 $target = $this->match->get_target_url( $original_url, $url->get_url(), $this->source_flags, $target );
332 $target = Red_Url_Query::add_to_target( $target, $original_url, $this->source_flags );
333 $target = apply_filters( 'redirection_url_target', $target, $this->url );
334 }
335
336 // Fire any early actions
337 if ( $target ) {
338 $target = $this->action->process_before( $this->action_code, $target );
339 }
340
341 if ( $target ) {
342 // We still have a target, so log it and carry on with the action
343 do_action( 'redirection_visit', $this, $requested_url, $target );
344 return $this->action->process_after( $this->action_code, $target );
345 }
346 }
347
348 return false;
349 }
350
351 public function visit( $url, $target ) {
352 global $wpdb;
353
354 $options = red_get_options();
355
356 // Update the counters
357 $this->last_count++;
358
359 if ( apply_filters( 'redirection_redirect_counter', true ) ) {
360 $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}redirection_items SET last_count=last_count+1, last_access=NOW() WHERE id=%d", $this->id ) );
361 }
362
363 if ( isset( $options['expire_redirect'] ) && $options['expire_redirect'] !== -1 && $target ) {
364 $details = array(
365 'redirect_id' => $this->id,
366 'group_id' => $this->group_id,
367 );
368
369 if ( $target === true ) {
370 $target = $this->action_type === 'pass' ? $this->match->get_data()['url'] : '';
371 }
372
373 RE_Log::create( $url, $target, Redirection_Request::get_user_agent(), Redirection_Request::get_ip(), Redirection_Request::get_referrer(), $details );
374 }
375 }
376
377 public function is_enabled() {
378 return $this->status === 'enabled';
379 }
380
381 public function reset() {
382 global $wpdb;
383
384 $this->last_count = 0;
385 $this->last_access = '0000-00-00 00:00:00';
386
387 $update = array(
388 'last_count' => 0,
389 'last_access' => $this->last_access,
390 );
391 $where = array(
392 'id' => $this->id,
393 );
394
395 $wpdb->update( $wpdb->prefix . 'redirection_items', $update, $where );
396 }
397
398 public function enable() {
399 global $wpdb;
400
401 $this->status = 'enabled';
402 $wpdb->update( $wpdb->prefix . 'redirection_items', array( 'status' => $this->status ), array( 'id' => $this->id ) );
403 }
404
405 public function disable() {
406 global $wpdb;
407
408 $this->status = 'disabled';
409 $wpdb->update( $wpdb->prefix . 'redirection_items', array( 'status' => $this->status ), array( 'id' => $this->id ) );
410 }
411
412 public function get_id() {
413 return intval( $this->id, 10 );
414 }
415
416 public function get_position() {
417 return intval( $this->position, 10 );
418 }
419
420 public function get_group_id() {
421 return intval( $this->group_id, 10 );
422 }
423
424 public function get_url() {
425 return $this->url;
426 }
427
428 public function get_match_url() {
429 return $this->match_url;
430 }
431
432 public function get_match_data() {
433 $source = $this->source_flags->get_json_with_defaults();
434
435 if ( ! empty( $source ) ) {
436 return [ 'source' => $source ];
437 }
438
439 return null;
440 }
441
442 public function get_title() {
443 return $this->title ? $this->title : '';
444 }
445
446 public function get_hits() {
447 return intval( $this->last_count, 10 );
448 }
449
450 public function get_last_hit() {
451 return intval( $this->last_access, 10 );
452 }
453
454 public function is_regex() {
455 return $this->regex ? true : false;
456 }
457
458 public function get_match_type() {
459 return $this->match_type;
460 }
461
462 public function get_action_type() {
463 return $this->action_type;
464 }
465
466 public function get_action_code() {
467 return intval( $this->action_code, 10 );
468 }
469
470 public function get_action_data() {
471 return $this->action_data ? $this->action_data : '';
472 }
473
474 public static function get_filtered( array $params ) {
475 global $wpdb;
476
477 $orderby = 'id';
478 $direction = 'DESC';
479 $limit = RED_DEFAULT_PER_PAGE;
480 $offset = 0;
481 $where = '';
482
483 if ( isset( $params['orderby'] ) && in_array( $params['orderby'], array( 'url', 'last_count', 'last_access', 'position' ), true ) ) {
484 $orderby = $params['orderby'];
485 }
486
487 if ( isset( $params['direction'] ) && in_array( $params['direction'], array( 'asc', 'desc' ), true ) ) {
488 $direction = strtoupper( $params['direction'] );
489 }
490
491 if ( isset( $params['filterBy'] ) && is_array( $params['filterBy'] ) ) {
492 $filters = new Red_Item_Filters( $params['filterBy'] );
493 $where = $filters->get_as_sql();
494 }
495
496 if ( isset( $params['per_page'] ) ) {
497 $limit = intval( $params['per_page'], 10 );
498 $limit = min( RED_MAX_PER_PAGE, $limit );
499 $limit = max( 5, $limit );
500 }
501
502 if ( isset( $params['page'] ) ) {
503 $offset = intval( $params['page'], 10 );
504 $offset = max( 0, $offset );
505 $offset *= $limit;
506 }
507
508 // $orderby and $direction is whitelisted
509 $rows = $wpdb->get_results(
510 "SELECT * FROM {$wpdb->prefix}redirection_items $where ORDER BY $orderby $direction " . $wpdb->prepare( 'LIMIT %d,%d', $offset, $limit )
511 );
512 $total_items = intval( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}redirection_items " . $where ) );
513 $items = array();
514
515 foreach ( $rows as $row ) {
516 $group = new Red_Item( $row );
517 $items[] = $group->to_json();
518 }
519
520 return array(
521 'items' => $items,
522 'total' => intval( $total_items, 10 ),
523 );
524 }
525
526 public function to_json() {
527 return array(
528 'id' => $this->get_id(),
529 'url' => $this->get_url(),
530 'match_url' => $this->get_match_url(),
531 'match_data' => $this->get_match_data(),
532 'action_code' => $this->get_action_code(),
533 'action_type' => $this->get_action_type(),
534 'action_data' => $this->match ? $this->match->get_data() : null,
535 'match_type' => $this->get_match_type(),
536 'title' => $this->get_title(),
537 'hits' => $this->get_hits(),
538 'regex' => $this->is_regex(),
539 'group_id' => $this->get_group_id(),
540 'position' => $this->get_position(),
541 'last_access' => $this->get_last_hit() > 0 ? date_i18n( get_option( 'date_format' ), $this->get_last_hit() ) : '-',
542 'enabled' => $this->is_enabled(),
543 );
544 }
545 }
546
547 class Red_Item_Filters {
548 private $filters = [];
549
550 public function __construct( $filter_params ) {
551 global $wpdb;
552
553 foreach ( $filter_params as $filter_by => $filter ) {
554 $filter = trim( $filter );
555
556 if ( $filter_by === 'status' ) {
557 if ( $filter === 'enabled' ) {
558 $this->filters[] = "status='enabled'";
559 } else {
560 $this->filters[] = "status='disabled'";
561 }
562 } elseif ( $filter_by === 'url-match' ) {
563 if ( $filter === 'regular' ) {
564 $this->filters[] = 'regex=1';
565 } else {
566 $this->filters[] = 'regex=0';
567 }
568 } elseif ( $filter_by === 'match' && in_array( $filter, array_keys( Red_Match::available() ), true ) ) {
569 $this->filters[] = $wpdb->prepare( 'match_type=%s', $filter );
570 } elseif ( $filter_by === 'action' && in_array( $filter, array_keys( Red_Action::available() ), true ) ) {
571 $this->filters[] = $wpdb->prepare( 'action_type=%s', $filter );
572 } elseif ( $filter_by === 'http' ) {
573 $sanitizer = new Red_Item_Sanitize();
574 $filter = intval( $filter, 10 );
575
576 if ( $sanitizer->is_valid_error_code( $filter ) || $sanitizer->is_valid_redirect_code( $filter ) ) {
577 $this->filters[] = $wpdb->prepare( 'action_code=%d', $filter );
578 }
579 } elseif ( $filter_by === 'access' ) {
580 if ( $filter === 'year' ) {
581 $this->filters[] = 'last_access < DATE_SUB(NOW(),INTERVAL 1 YEAR)';
582 } elseif ( $filter === 'month' ) {
583 $this->filters[] = 'last_access < DATE_SUB(NOW(),INTERVAL 1 MONTH)';
584 } else {
585 $this->filters[] = "last_access = '0000-00-00 00:00:00'";
586 }
587 } elseif ( $filter_by === 'url' ) {
588 $this->filters[] = $wpdb->prepare( 'url LIKE %s', '%' . $wpdb->esc_like( $filter ) . '%' );
589 } elseif ( $filter_by === 'target' ) {
590 $this->filters[] = $wpdb->prepare( 'action_data LIKE %s', '%' . $wpdb->esc_like( $filter ) . '%' );
591 } elseif ( $filter_by === 'title' ) {
592 $this->filters[] = $wpdb->prepare( 'title LIKE %s', '%' . $wpdb->esc_like( $filter ) . '%' );
593 } elseif ( $filter_by === 'group' ) {
594 $this->filters[] = $wpdb->prepare( 'group_id=%d', intval( $filter, 10 ) );
595 }
596 }
597 }
598
599 public function get_as_sql() {
600 if ( count( $this->filters ) > 0 ) {
601 return 'WHERE ' . implode( ' AND ', $this->filters );
602 }
603
604 return '';
605 }
606 }
607