PluginProbe
Redirection / 4.2
Redirection v4.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 / htaccess.php

htaccess.php in Redirection 4.2, at models/htaccess.php

298 lines 8.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Convert redirects to .htaccess format
5 *
6 * Ignores:
7 * - Trailing slash flag
8 * - Query flags
9 */
10 class Red_Htaccess {
11 private $items = array();
12 const INSERT_REGEX = '@\n?# Created by Redirection(?:.*?)# End of Redirection\n?@sm';
13
14 private function encode_from( $url ) {
15 $url = $this->encode( $url );
16
17 // Apache 2 does not need a leading slashing
18 $url = ltrim( $url, '/' );
19
20 // Exactly match the URL
21 return '^' . $url . '$';
22 }
23
24 // URL encode some things, but other things can be passed through
25 private function encode2nd( $url ) {
26 $allowed = [
27 '%2F' => '/',
28 '%3F' => '?',
29 '%3A' => ':',
30 '%3D' => '=',
31 '%26' => '&',
32 '%25' => '%',
33 '+' => '%20',
34 '%24' => '$',
35 '%23' => '#',
36 ];
37
38 $url = urlencode( $url );
39 return $this->replace_encoding( $url, $allowed );
40 }
41
42 private function replace_encoding( $str, $allowed ) {
43 foreach ( $allowed as $before => $after ) {
44 $str = str_replace( $before, $after, $str );
45 }
46
47 return $str;
48 }
49
50 private function encode( $url ) {
51 $allowed = [
52 '%2F' => '/',
53 '%3F' => '?',
54 '+' => '%20',
55 '.' => '\\.',
56 ];
57
58 return $this->replace_encoding( urlencode( $url ), $allowed );
59 }
60
61 private function encode_regex( $url ) {
62 // Remove any newlines
63 $url = preg_replace( "/[\r\n\t].*?$/s", '', $url );
64
65 // Remove invalid characters
66 $url = preg_replace( '/[^\PC\s]/u', '', $url );
67
68 // Make sure spaces are quoted
69 $url = str_replace( ' ', '%20', $url );
70 $url = str_replace( '%24', '$', $url );
71
72 // No leading slash
73 $url = ltrim( $url, '/' );
74
75 // If pattern has a ^ at the start then ensure we don't have a slash immediatley after
76 $url = preg_replace( '@^\^/@', '^', $url );
77
78 return $url;
79 }
80
81 private function add_referrer( $item, $match ) {
82 $from = $this->encode_from( ltrim( $item->get_url(), '/' ) );
83 if ( $item->is_regex() ) {
84 $from = $this->encode_regex( ltrim( $item->get_url(), '/' ) );
85 }
86
87 if ( ( $match->url_from || $match->url_notfrom ) && $match->referrer ) {
88 $this->items[] = sprintf( 'RewriteCond %%{HTTP_REFERER} %s [NC]', ( $match->regex ? $this->encode_regex( $match->referrer ) : $this->encode_from( $match->referrer ) ) );
89
90 if ( $match->url_from ) {
91 $to = $this->target( $item->get_action_type(), $match->url_from, $item->get_action_code(), $item->get_match_data() );
92 $this->items[] = sprintf( 'RewriteRule %s %s', $from, $to );
93 }
94
95 if ( $match->url_notfrom ) {
96 $to = $this->target( $item->get_action_type(), $match->url_notfrom, $item->get_action_code(), $item->get_match_data() );
97 $this->items[] = sprintf( 'RewriteRule %s %s', $from, $to );
98 }
99 }
100 }
101
102 private function add_agent( $item, $match ) {
103 $from = $this->encode( ltrim( $item->get_url(), '/' ) );
104 if ( $item->is_regex() ) {
105 $from = $this->encode_regex( ltrim( $item->get_url(), '/' ) );
106 }
107
108 if ( ( $match->url_from || $match->url_notfrom ) && $match->user_agent ) {
109 $this->items[] = sprintf( 'RewriteCond %%{HTTP_USER_AGENT} %s [NC]', ( $match->regex ? $this->encode_regex( $match->user_agent ) : $this->encode2nd( $match->user_agent ) ) );
110
111 if ( $match->url_from ) {
112 $to = $this->target( $item->get_action_type(), $match->url_from, $item->get_action_code(), $item->get_match_data() );
113 $this->items[] = sprintf( 'RewriteRule %s %s', $from, $to );
114 }
115
116 if ( $match->url_notfrom ) {
117 $to = $this->target( $item->get_action_type(), $match->url_notfrom, $item->get_action_code(), $item->get_match_data() );
118 $this->items[] = sprintf( 'RewriteRule %s %s', $from, $to );
119 }
120 }
121 }
122
123 private function add_server( $item, $match ) {
124 $match->url = $match->url_from;
125 $this->items[] = sprintf( 'RewriteCond %%{HTTP_HOST} ^%s$ [NC]', preg_quote( $match->server ) );
126 $this->add_url( $item, $match );
127 }
128
129 private function add_url( $item, $match ) {
130 $url = $item->get_url();
131
132 if ( $item->is_regex() === false && strpos( $url, '?' ) !== false || strpos( $url, '&' ) !== false ) {
133 $url_parts = wp_parse_url( $url );
134 $url = $url_parts['path'];
135 $query = isset( $url_parts['query'] ) ? $url_parts['query'] : '';
136 $this->items[] = sprintf( 'RewriteCond %%{QUERY_STRING} ^%s$', $query );
137 }
138
139 $to = $this->target( $item->get_action_type(), $match->url, $item->get_action_code(), $item->get_match_data() );
140 $from = $this->encode_from( $url );
141
142 if ( $item->is_regex() ) {
143 $from = $this->encode_regex( $item->get_url() );
144 }
145
146 if ( $to ) {
147 $this->items[] = sprintf( 'RewriteRule %s %s', $from, $to );
148 }
149 }
150
151 private function add_flags( $current, array $flags ) {
152 return $current . ' [' . implode( ',', $flags ) . ']';
153 }
154
155 private function get_source_flags( array $existing, array $source ) {
156 $flags = [];
157
158 if ( isset( $source['flag_case'] ) && $source['flag_case'] ) {
159 $flags[] = 'NC';
160 }
161
162 return array_merge( $existing, $flags );
163 }
164
165 private function action_random( $data, $code, $match_data ) {
166 // Pick a WP post at random
167 global $wpdb;
168
169 $post = $wpdb->get_var( "SELECT ID FROM {$wpdb->posts} ORDER BY RAND() LIMIT 0,1" );
170 $url = wp_parse_url( get_permalink( $post ) );
171
172 $flags = [ sprintf( 'R=%d', $code ) ];
173 $flags[] = 'L';
174 $flags = $this->get_source_flags( $flags, $match_data['source'] );
175
176 return $this->add_flags( $this->encode( $url['path'] ), $flags );
177 }
178
179 private function action_pass( $data, $code, $match_data ) {
180 $flags = $this->get_source_flags( [ 'L' ], $match_data['source'] );
181
182 return $this->add_flags( $this->encode2nd( $data ), $flags );
183 }
184
185 private function action_error( $data, $code, $match_data ) {
186 $flags = $this->get_source_flags( [ 'F' ], $match_data['source'] );
187
188 if ( $code === 410 ) {
189 $flags = $this->get_source_flags( [ 'G' ], $match_data['source'] );
190 }
191
192 return $this->add_flags( '/', $flags );
193 }
194
195 private function action_url( $data, $code, $match_data ) {
196 $flags = [ sprintf( 'R=%d', $code ) ];
197 $flags[] = 'L';
198 $flags = $this->get_source_flags( $flags, $match_data['source'] );
199
200 return $this->add_flags( $this->encode2nd( $data ), $flags );
201 }
202
203 private function target( $action, $data, $code, $match_data ) {
204 $target = 'action_' . $action;
205
206 if ( method_exists( $this, $target ) ) {
207 return $this->$target( $data, $code, $match_data );
208 }
209
210 return '';
211 }
212
213 private function generate() {
214 $version = red_get_plugin_data( dirname( dirname( __FILE__ ) ) . '/redirection.php' );
215
216 if ( count( $this->items ) === 0 ) {
217 return '';
218 }
219
220 $text[] = '# Created by Redirection';
221 $text[] = '# ' . date( 'r' );
222 $text[] = '# Redirection ' . trim( $version['Version'] ) . ' - https://redirection.me';
223 $text[] = '';
224
225 // mod_rewrite section
226 $text[] = '<IfModule mod_rewrite.c>';
227
228 // Add http => https option
229 $options = red_get_options();
230 if ( $options['https'] ) {
231 $text[] = 'RewriteCond %{HTTPS} off';
232 $text[] = 'RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}';
233 }
234
235 // Add redirects
236 $text = array_merge( $text, array_filter( array_map( [ $this, 'sanitize_redirect' ], $this->items ) ) );
237
238 // End of mod_rewrite
239 $text[] = '</IfModule>';
240 $text[] = '';
241
242 // End of redirection section
243 $text[] = '# End of Redirection';
244
245 $text = implode( "\n", $text );
246 return "\n" . $text . "\n";
247 }
248
249 public function add( $item ) {
250 $target = 'add_' . $item->get_match_type();
251
252 if ( method_exists( $this, $target ) && $item->is_enabled() ) {
253 $this->$target( $item, $item->match );
254 }
255 }
256
257 public function get( $existing = false ) {
258 $text = $this->generate();
259
260 if ( $existing ) {
261 if ( preg_match( self::INSERT_REGEX, $existing ) > 0 ) {
262 $text = preg_replace( self::INSERT_REGEX, str_replace( '$', '\\$', $text ), $existing );
263 } else {
264 $text = $text . "\n" . trim( $existing );
265 }
266 }
267
268 return trim( $text );
269 }
270
271 public function sanitize_redirect( $text ) {
272 return str_replace( [ '<?', '>' ], '', $text );
273 }
274
275 public function sanitize_filename( $filename ) {
276 return str_replace( '.php', '', $filename );
277 }
278
279 public function save( $filename, $content_to_save = false ) {
280 $existing = false;
281 $filename = $this->sanitize_filename( $filename );
282
283 if ( file_exists( $filename ) ) {
284 $existing = file_get_contents( $filename );
285 }
286
287 $file = @fopen( $filename, 'w' );
288 if ( $file ) {
289 $result = fwrite( $file, $this->get( $existing ) );
290 fclose( $file );
291
292 return $result !== false;
293 }
294
295 return false;
296 }
297 }
298