PluginProbe
Redirection / 5.8.1
Redirection v5.8.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 / monitor.php

monitor.php in Redirection 5.8.1, at models/monitor.php

244 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class Red_Monitor {
4 /**
5 * @var int
6 */
7 private $monitor_group_id = 0;
8
9 /**
10 * @var array<int, string>
11 */
12 private $updated_posts = array();
13
14 /**
15 * @var list<string>
16 */
17 private $monitor_types = array();
18
19 /**
20 * @var string
21 */
22 private $associated = '';
23
24 /**
25 * @param array<string, mixed> $options
26 */
27 public function __construct( array $options ) {
28 $this->monitor_types = apply_filters( 'redirection_monitor_types', isset( $options['monitor_types'] ) ? $options['monitor_types'] : array() );
29
30 if ( count( $this->monitor_types ) > 0 && $options['monitor_post'] > 0 ) {
31 $this->monitor_group_id = intval( $options['monitor_post'], 10 );
32 $this->associated = isset( $options['associated_redirect'] ) ? $options['associated_redirect'] : '';
33
34 // Only monitor if permalinks enabled
35 if ( get_option( 'permalink_structure' ) !== false ) {
36 add_action( 'pre_post_update', array( $this, 'pre_post_update' ), 10, 2 );
37 add_action( 'post_updated', array( $this, 'post_updated' ), 11, 3 );
38 add_action( 'redirection_remove_existing', array( $this, 'remove_existing_redirect' ) );
39 add_filter( 'redirection_permalink_changed', array( $this, 'has_permalink_changed' ), 10, 3 );
40
41 if ( in_array( 'trash', $this->monitor_types, true ) ) {
42 add_action( 'wp_trash_post', array( $this, 'post_trashed' ) );
43 }
44 }
45 }
46 }
47
48 /**
49 * @param string $url
50 * @return void
51 */
52 public function remove_existing_redirect( string $url ): void {
53 Red_Item::disable_where_matches( $url );
54 }
55
56 /**
57 * @param WP_Post|null $post
58 * @param WP_Post|null $post_before
59 * @return bool
60 */
61 public function can_monitor_post( ?WP_Post $post, ?WP_Post $post_before ): bool {
62 // Defensive check: ensure we have valid post objects
63 if ( $post === null || $post_before === null ) {
64 return false;
65 }
66
67 // Check this is for the expected post
68 // @phpstan-ignore isset.property
69 if ( ! isset( $post->ID ) || ! isset( $this->updated_posts[ $post->ID ] ) ) {
70 return false;
71 }
72
73 // Don't do anything if we're not published
74 if ( $post->post_status !== 'publish' || $post_before->post_status !== 'publish' ) {
75 return false;
76 }
77
78 $type = get_post_type( $post->ID );
79 if ( ! in_array( $type, $this->monitor_types, true ) ) {
80 return false;
81 }
82
83 return true;
84 }
85
86 /**
87 * Called when a post has been updated - check if the slug has changed
88 *
89 * @param int $post_id
90 * @param WP_Post|null $post
91 * @param WP_Post|null $post_before
92 * @return void
93 */
94 public function post_updated( int $post_id, ?WP_Post $post, ?WP_Post $post_before ): void {
95 // WordPress may pass null during trash/delete operations - handle gracefully
96 if ( $post === null || $post_before === null ) {
97 return;
98 }
99
100 if ( isset( $this->updated_posts[ $post_id ] ) && $this->can_monitor_post( $post, $post_before ) ) {
101 $this->check_for_modified_slug( $post_id, $this->updated_posts[ $post_id ] );
102 }
103 }
104
105 /**
106 * Remember the previous post permalink
107 *
108 * @param int $post_id
109 * @param array<string, mixed> $data
110 * @return void
111 */
112 public function pre_post_update( int $post_id, array $data ): void {
113 $permalink = get_permalink( $post_id );
114 if ( $permalink !== false ) {
115 $this->updated_posts[ $post_id ] = $permalink;
116 }
117 }
118
119 /**
120 * @param int $post_id
121 * @return void
122 */
123 public function post_trashed( int $post_id ): void {
124 // Only create redirects for post types that are being monitored
125 $post_type = get_post_type( $post_id );
126 if ( $post_type === false || ! in_array( $post_type, $this->monitor_types, true ) ) {
127 return;
128 }
129
130 $permalink = get_permalink( $post_id );
131 if ( $permalink === false ) {
132 return;
133 }
134
135 $data = array(
136 'url' => wp_parse_url( $permalink, PHP_URL_PATH ),
137 'action_data' => array( 'url' => '/' ),
138 'match_type' => 'url',
139 'action_type' => 'url',
140 'action_code' => 301,
141 'group_id' => $this->monitor_group_id,
142 'status' => 'disabled',
143 );
144
145 /**
146 * Filter the redirect data before creating a redirect for a trashed post.
147 *
148 * @param array $data The redirect data to be created.
149 * @param int $post_id The ID of the trashed post.
150 */
151 $data = apply_filters( 'redirection_monitor_trashed_data', $data, $post_id );
152
153 // Create a new redirect for this post, but only if not draft
154 if ( $data['url'] !== null && $data['url'] !== false && $data['url'] !== '/' ) {
155 $new_item = Red_Item::create( $data );
156
157 if ( ! is_wp_error( $new_item ) ) {
158 do_action( 'redirection_monitor_created', $new_item, $data['url'], $post_id );
159 }
160 }
161 }
162
163 /**
164 * Changed if permalinks are different and the before wasn't the site url (we don't want to redirect the site URL)
165 *
166 * @param bool $result
167 * @param string|false $before
168 * @param string|false $after
169 * @return bool
170 */
171 public function has_permalink_changed( $result, $before, $after ) {
172 // Check it's not redirecting from the root
173 if ( $this->get_site_path() === $before || $before === '/' ) {
174 return false;
175 }
176
177 // Are the URLs the same?
178 if ( $before === $after ) {
179 return false;
180 }
181
182 return true;
183 }
184
185 /**
186 * @return string
187 */
188 private function get_site_path(): string {
189 $path = wp_parse_url( get_site_url(), PHP_URL_PATH );
190
191 if ( is_string( $path ) ) {
192 return rtrim( $path, '/' ) . '/';
193 }
194
195 return '/';
196 }
197
198 /**
199 * @param int $post_id
200 * @param string $before
201 * @return bool
202 */
203 public function check_for_modified_slug( int $post_id, string $before ): bool {
204 $permalink = get_permalink( $post_id );
205 if ( $permalink === false ) {
206 return false;
207 }
208
209 $after = wp_parse_url( $permalink, PHP_URL_PATH );
210 $before = wp_parse_url( esc_url( $before ), PHP_URL_PATH );
211
212 if ( is_string( $before ) && is_string( $after ) && apply_filters( 'redirection_permalink_changed', false, $before, $after ) ) {
213 do_action( 'redirection_remove_existing', $after, $post_id );
214
215 $data = array(
216 'url' => $before,
217 'action_data' => array( 'url' => $after ),
218 'match_type' => 'url',
219 'action_type' => 'url',
220 'action_code' => 301,
221 'group_id' => $this->monitor_group_id,
222 );
223
224 // Create a new redirect for this post
225 $new_item = Red_Item::create( $data );
226
227 if ( ! is_wp_error( $new_item ) ) {
228 do_action( 'redirection_monitor_created', $new_item, $before, $post_id );
229
230 if ( ! empty( $this->associated ) ) {
231 // Create an associated redirect for this post
232 $data['url'] = trailingslashit( $data['url'] ) . ltrim( $this->associated, '/' );
233 $data['action_data'] = array( 'url' => trailingslashit( $data['action_data']['url'] ) . ltrim( $this->associated, '/' ) );
234 Red_Item::create( $data );
235 }
236 }
237
238 return true;
239 }
240
241 return false;
242 }
243 }
244