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

600 lines 16.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 include_once dirname( __FILE__ ) . '/url.php';
4 include_once dirname( __FILE__ ) . '/regex.php';
5 include_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 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 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 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 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 static public function reduce_sorted_items( $item ) {
169 return $item['item'];
170 }
171
172 static public 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 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 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', 'Unable to get newly added redirect' );
265 }
266
267 return new WP_Error( 'redirect', __( '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', __( '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 $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}redirection_items SET last_count=last_count+1, last_access=NOW() WHERE id=%d", $this->id ) );
355
356 if ( isset( $options['expire_redirect'] ) && $options['expire_redirect'] !== -1 && $target ) {
357 $details = array(
358 'redirect_id' => $this->id,
359 'group_id' => $this->group_id,
360 );
361
362 if ( $target === true ) {
363 $target = $this->action_type === 'pass' ? $this->match->get_data()['url'] : '';
364 }
365
366 RE_Log::create( $url, $target, Redirection_Request::get_user_agent(), Redirection_Request::get_ip(), Redirection_Request::get_referrer(), $details );
367 }
368 }
369
370 public function is_enabled() {
371 return $this->status === 'enabled';
372 }
373
374 public function reset() {
375 global $wpdb;
376
377 $this->last_count = 0;
378 $this->last_access = '0000-00-00 00:00:00';
379
380 $update = array(
381 'last_count' => 0,
382 'last_access' => $this->last_access,
383 );
384 $where = array(
385 'id' => $this->id,
386 );
387
388 $wpdb->update( $wpdb->prefix . 'redirection_items', $update, $where );
389 }
390
391 public function enable() {
392 global $wpdb;
393
394 $this->status = 'enabled';
395 $wpdb->update( $wpdb->prefix . 'redirection_items', array( 'status' => $this->status ), array( 'id' => $this->id ) );
396 }
397
398 public function disable() {
399 global $wpdb;
400
401 $this->status = 'disabled';
402 $wpdb->update( $wpdb->prefix . 'redirection_items', array( 'status' => $this->status ), array( 'id' => $this->id ) );
403 }
404
405 public function get_id() {
406 return intval( $this->id, 10 );
407 }
408
409 public function get_position() {
410 return intval( $this->position, 10 );
411 }
412
413 public function get_group_id() {
414 return intval( $this->group_id, 10 );
415 }
416
417 public function get_url() {
418 return $this->url;
419 }
420
421 public function get_match_url() {
422 return $this->match_url;
423 }
424
425 public function get_match_data() {
426 $source = $this->source_flags->get_json_with_defaults();
427
428 if ( ! empty( $source ) ) {
429 return [ 'source' => $source ];
430 }
431
432 return null;
433 }
434
435 public function get_title() {
436 return $this->title ? $this->title : '';
437 }
438
439 public function get_hits() {
440 return intval( $this->last_count, 10 );
441 }
442
443 public function get_last_hit() {
444 return intval( $this->last_access, 10 );
445 }
446
447 public function is_regex() {
448 return $this->regex ? true : false;
449 }
450
451 public function get_match_type() {
452 return $this->match_type;
453 }
454
455 public function get_action_type() {
456 return $this->action_type;
457 }
458
459 public function get_action_code() {
460 return intval( $this->action_code, 10 );
461 }
462
463 public function get_action_data() {
464 return $this->action_data ? $this->action_data : '';
465 }
466
467 public static function get_filtered( array $params ) {
468 global $wpdb;
469
470 $orderby = 'id';
471 $direction = 'DESC';
472 $limit = RED_DEFAULT_PER_PAGE;
473 $offset = 0;
474 $where = '';
475
476 if ( isset( $params['orderby'] ) && in_array( $params['orderby'], array( 'url', 'last_count', 'last_access', 'position' ), true ) ) {
477 $orderby = $params['orderby'];
478 }
479
480 if ( isset( $params['direction'] ) && in_array( $params['direction'], array( 'asc', 'desc' ), true ) ) {
481 $direction = strtoupper( $params['direction'] );
482 }
483
484 if ( isset( $params['filterBy'] ) && is_array( $params['filterBy'] ) ) {
485 $filters = new Red_Item_Filters( $params['filterBy'] );
486 $where = $filters->get_as_sql();
487 }
488
489 if ( isset( $params['per_page'] ) ) {
490 $limit = intval( $params['per_page'], 10 );
491 $limit = min( RED_MAX_PER_PAGE, $limit );
492 $limit = max( 5, $limit );
493 }
494
495 if ( isset( $params['page'] ) ) {
496 $offset = intval( $params['page'], 10 );
497 $offset = max( 0, $offset );
498 $offset *= $limit;
499 }
500
501 // $orderby and $direction is whitelisted
502 $rows = $wpdb->get_results(
503 "SELECT * FROM {$wpdb->prefix}redirection_items $where ORDER BY $orderby $direction " . $wpdb->prepare( 'LIMIT %d,%d', $offset, $limit )
504 );
505 $total_items = intval( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}redirection_items " . $where ) );
506 $items = array();
507
508 foreach ( $rows as $row ) {
509 $group = new Red_Item( $row );
510 $items[] = $group->to_json();
511 }
512
513 return array(
514 'items' => $items,
515 'total' => intval( $total_items, 10 ),
516 );
517 }
518
519 public function to_json() {
520 return array(
521 'id' => $this->get_id(),
522 'url' => $this->get_url(),
523 'match_url' => $this->get_match_url(),
524 'match_data' => $this->get_match_data(),
525 'action_code' => $this->get_action_code(),
526 'action_type' => $this->get_action_type(),
527 'action_data' => $this->match ? $this->match->get_data() : null,
528 'match_type' => $this->get_match_type(),
529 'title' => $this->get_title(),
530 'hits' => $this->get_hits(),
531 'regex' => $this->is_regex(),
532 'group_id' => $this->get_group_id(),
533 'position' => $this->get_position(),
534 'last_access' => $this->get_last_hit() > 0 ? date_i18n( get_option( 'date_format' ), $this->get_last_hit() ) : '-',
535 'enabled' => $this->is_enabled(),
536 );
537 }
538 }
539
540 class Red_Item_Filters {
541 private $filters = [];
542
543 public function __construct( $filter_params ) {
544 global $wpdb;
545
546 foreach ( $filter_params as $filter_by => $filter ) {
547 $filter = trim( $filter );
548
549 if ( $filter_by === 'status' ) {
550 if ( $filter === 'enabled' ) {
551 $this->filters[] = "status='enabled'";
552 } else {
553 $this->filters[] = "status='disabled'";
554 }
555 } elseif ( $filter_by === 'url-match' ) {
556 if ( $filter === 'regular' ) {
557 $this->filters[] = 'regex=1';
558 } else {
559 $this->filters[] = 'regex=0';
560 }
561 } elseif ( $filter_by === 'match' && in_array( $filter, array_keys( Red_Match::available() ), true ) ) {
562 $this->filters[] = $wpdb->prepare( 'match_type=%s', $filter );
563 } elseif ( $filter_by === 'action' && in_array( $filter, array_keys( Red_Action::available() ), true ) ) {
564 $this->filters[] = $wpdb->prepare( 'action_type=%s', $filter );
565 } elseif ( $filter_by === 'http' ) {
566 $sanitizer = new Red_Item_Sanitize();
567 $filter = intval( $filter, 10 );
568
569 if ( $sanitizer->is_valid_error_code( $filter ) || $sanitizer->is_valid_redirect_code( $filter ) ) {
570 $this->filters[] = $wpdb->prepare( 'action_code=%d', $filter );
571 }
572 } elseif ( $filter_by === 'access' ) {
573 if ( $filter === 'year' ) {
574 $this->filters[] = 'last_access < DATE_SUB(NOW(),INTERVAL 1 YEAR)';
575 } elseif ( $filter === 'month' ) {
576 $this->filters[] = 'last_access < DATE_SUB(NOW(),INTERVAL 1 MONTH)';
577 } else {
578 $this->filters[] = "last_access = '0000-00-00 00:00:00'";
579 }
580 } elseif ( $filter_by === 'url' ) {
581 $this->filters[] = $wpdb->prepare( 'url LIKE %s', '%' . $wpdb->esc_like( $filter ) . '%' );
582 } elseif ( $filter_by === 'target' ) {
583 $this->filters[] = $wpdb->prepare( 'action_data LIKE %s', '%' . $wpdb->esc_like( $filter ) . '%' );
584 } elseif ( $filter_by === 'title' ) {
585 $this->filters[] = $wpdb->prepare( 'title LIKE %s', '%' . $wpdb->esc_like( $filter ) . '%' );
586 } elseif ( $filter_by === 'group' ) {
587 $this->filters[] = $wpdb->prepare( 'group_id=%d', intval( $filter, 10 ) );
588 }
589 }
590 }
591
592 public function get_as_sql() {
593 if ( count( $this->filters ) > 0 ) {
594 return 'WHERE ' . implode( ' AND ', $this->filters );
595 }
596
597 return '';
598 }
599 }
600