PluginProbe
Redirection / 5.9.0
Redirection v5.9.0
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 / options.php

options.php in Redirection 5.9.0, at models/options.php

495 lines 14.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Redirection options manager - pure static utility class.
5 *
6 * Usage: Red_Options::get() returns a fully typed array.
7 *
8 * @phpstan-type RedirectionOptions array{
9 * support: bool,
10 * token: string,
11 * monitor_post: int,
12 * monitor_types: array<string>,
13 * associated_redirect: string,
14 * auto_target: string,
15 * expire_redirect: int,
16 * expire_404: int,
17 * log_external: bool,
18 * log_header: bool,
19 * track_hits: bool,
20 * modules: array<int, mixed>,
21 * redirect_cache: int,
22 * ip_logging: int,
23 * ip_headers: array<string>,
24 * ip_proxy: array<string>,
25 * last_group_id: int,
26 * rest_api: int,
27 * https: bool,
28 * headers: array<mixed>,
29 * database: string,
30 * relocate: string,
31 * preferred_domain: string,
32 * aliases: array<string>,
33 * permalinks: array<mixed>,
34 * cache_key: int,
35 * plugin_update: string,
36 * update_notice: int,
37 * flag_query: 'ignore'|'exact'|'pass'|'exactorder',
38 * flag_case: bool,
39 * flag_trailing: bool,
40 * flag_regex: bool,
41 * database_stage?: array{stage?: string|false, stages?: array<mixed>, status?: string|false},
42 * location?: string
43 * }
44 */
45 class Red_Options {
46 /**
47 * Options DB key. Previously defined by REDIRECTION_OPTION.
48 */
49 public const OPTION_KEY = 'redirection_options';
50
51 /**
52 * Portable settings that can be safely imported/exported.
53 *
54 * @var array<int, string>
55 */
56 private const IMPORT_EXPORT_KEYS = [
57 'support',
58 'monitor_types',
59 'auto_target',
60 'expire_redirect',
61 'expire_404',
62 'log_external',
63 'log_header',
64 'track_hits',
65 'redirect_cache',
66 'ip_logging',
67 'https',
68 'headers',
69 'plugin_update',
70 'permalinks',
71 'flag_query',
72 'flag_case',
73 'flag_trailing',
74 'flag_regex',
75 ];
76
77 /**
78 * REST API location constants. Previously REDIRECTION_API_JSON*, now centralized here.
79 */
80 public const API_JSON = 0;
81 public const API_JSON_INDEX = 1;
82 public const API_JSON_RELATIVE = 3;
83
84 /**
85 * In-memory cache for build_options result.
86 *
87 * @var RedirectionOptions|null
88 */
89 private static $options_cache = null;
90
91 /**
92 * Prevent instantiation - this is a static utility class.
93 */
94 private function __construct() {
95 }
96
97 /**
98 * Load current options and return as a typed array.
99 *
100 * @phpstan-return RedirectionOptions
101 * @return array
102 */
103 public static function get(): array {
104 return self::build_options();
105 }
106
107 /**
108 * Get settings that are safe to import/export.
109 *
110 * @return array<string, mixed>
111 */
112 public static function get_import_export_options(): array {
113 return self::filter_import_export_options( self::build_options() );
114 }
115
116 /**
117 * Filter a settings array to only include portable options.
118 *
119 * @param array<string, mixed> $settings
120 * @return array<string, mixed>
121 */
122 public static function filter_import_export_options( array $settings ): array {
123 return array_intersect_key( $settings, array_fill_keys( self::IMPORT_EXPORT_KEYS, true ) );
124 }
125
126 /**
127 * Reset the options cache.
128 * @return void
129 */
130 public static function reset() {
131 self::$options_cache = null;
132 }
133
134 /**
135 * Build Redirection options (array form).
136 * Mirrors legacy red_get_options() behavior.
137 *
138 * @phpstan-return RedirectionOptions
139 * @return array
140 */
141 private static function build_options(): array {
142 // Return cached result if available
143 if ( self::$options_cache !== null ) {
144 return self::$options_cache;
145 }
146
147 $options = get_option( self::OPTION_KEY );
148 $fresh_install = false;
149
150 if ( $options === false ) {
151 $fresh_install = true;
152 }
153
154 if ( ! is_array( $options ) ) {
155 $options = [];
156 }
157
158 if ( red_is_disabled() ) {
159 $options['https'] = false;
160 }
161
162 $defaults = self::get_default_options();
163
164 foreach ( $defaults as $key => $value ) {
165 if ( ! isset( $options[ $key ] ) ) {
166 $options[ $key ] = $value;
167 }
168 }
169
170 if ( $fresh_install ) {
171 // Default flags for new installs - ignore case and trailing slashes
172 $options['flag_case'] = true;
173 $options['flag_trailing'] = true;
174 }
175
176 // Back-compat. If monitor_post is set without types then it's from an older Redirection
177 if ( $options['monitor_post'] > 0 && count( $options['monitor_types'] ) === 0 ) {
178 $options['monitor_types'] = [ 'post' ];
179 }
180
181 // Remove old options not in get_default_options()
182 foreach ( array_keys( $options ) as $key ) {
183 if ( ! isset( $defaults[ $key ] ) && $key !== 'database_stage' ) {
184 unset( $options[ $key ] );
185 }
186 }
187
188 // Back-compat fix
189 if ( $options['rest_api'] === false || ! in_array( $options['rest_api'], [ self::API_JSON, self::API_JSON_INDEX, self::API_JSON_RELATIVE ], true ) ) {
190 $options['rest_api'] = self::API_JSON;
191 }
192
193 if ( isset( $options['modules'] ) && isset( $options['modules']['2'] ) && isset( $options['modules']['2']['location'] ) ) {
194 $options['location'] = $options['modules']['2']['location'];
195 }
196
197 /** @var RedirectionOptions $options */
198 // Cache the result before returning
199 self::$options_cache = $options;
200 return $options;
201 }
202
203 /**
204 * Get default options. Contains all valid options.
205 *
206 * @phpstan-return RedirectionOptions
207 * @return array<string, mixed>
208 */
209 public static function get_default_options() {
210 $flags = new Red_Source_Flags();
211 $defaults = [
212 'support' => false,
213 'token' => md5( uniqid() ),
214 'monitor_post' => 0,
215 'monitor_types' => [],
216 'associated_redirect' => '',
217 'auto_target' => '',
218 'expire_redirect' => 7,
219 'expire_404' => 7,
220 'log_external' => false,
221 'log_header' => false,
222 'track_hits' => true,
223 'modules' => [],
224 'redirect_cache' => 1,
225 'ip_logging' => 0,
226 'ip_headers' => [],
227 'ip_proxy' => [],
228 'last_group_id' => 0,
229 'rest_api' => self::API_JSON,
230 'https' => false,
231 'headers' => [],
232 'database' => '',
233 'relocate' => '',
234 'preferred_domain' => '',
235 'aliases' => [],
236 'permalinks' => [],
237 'cache_key' => 0,
238 'plugin_update' => 'prompt',
239 'update_notice' => 0,
240 ];
241
242 $defaults = array_merge( $defaults, $flags->get_json() );
243
244 return apply_filters( 'red_default_options', $defaults );
245 }
246
247 /**
248 * Persist option values using the same sanitization/merge rules as legacy red_set_options.
249 * This is a static method that saves options and returns the updated array.
250 *
251 * @param array<string, mixed> $settings Partial settings to apply.
252 * @phpstan-return RedirectionOptions
253 * @return array
254 */
255 public static function save( array $settings ): array {
256 // Clear the cache before applying settings
257 self::$options_cache = null;
258
259 $options = self::apply_settings( $settings );
260 update_option( self::OPTION_KEY, apply_filters( 'redirection_save_options', $options ) );
261
262 // Clear the cache after saving to ensure fresh data on next get()
263 self::$options_cache = null;
264
265 /** @var RedirectionOptions $options */
266 return $options;
267 }
268
269 /**
270 * Apply settings and return the updated options array without saving.
271 *
272 * @param array<string, mixed> $settings Partial settings to apply.
273 * @phpstan-return RedirectionOptions
274 * @return array
275 */
276 private static function apply_settings( array $settings ): array {
277 $options = self::build_options();
278 $monitor_types = [];
279
280 if ( isset( $settings['database'] ) ) {
281 $options['database'] = sanitize_text_field( $settings['database'] );
282 }
283
284 if ( array_key_exists( 'database_stage', $settings ) ) {
285 if ( $settings['database_stage'] === false ) {
286 unset( $options['database_stage'] );
287 } else {
288 $options['database_stage'] = $settings['database_stage'];
289 }
290 }
291
292 if ( isset( $settings['ip_proxy'] ) && is_array( $settings['ip_proxy'] ) ) {
293 $options['ip_proxy'] = array_map(
294 function ( $ip ) {
295 $ip = new Redirection_IP( $ip );
296 return $ip->get();
297 },
298 $settings['ip_proxy']
299 );
300
301 $options['ip_proxy'] = array_values( array_filter( $options['ip_proxy'] ) );
302 }
303
304 if ( isset( $settings['ip_headers'] ) && is_array( $settings['ip_headers'] ) ) {
305 $available = Redirection_Request::get_ip_headers();
306 $options['ip_headers'] = array_filter(
307 $settings['ip_headers'],
308 function ( $header ) use ( $available ) {
309 return in_array( $header, $available, true );
310 }
311 );
312 $options['ip_headers'] = array_values( $options['ip_headers'] );
313 }
314
315 if ( isset( $settings['rest_api'] ) && in_array( intval( $settings['rest_api'], 10 ), array( 0, 1, 2, 3, 4 ), true ) ) {
316 $options['rest_api'] = intval( $settings['rest_api'], 10 );
317 }
318
319 if ( isset( $settings['monitor_types'] ) && is_array( $settings['monitor_types'] ) ) {
320 $allowed = red_get_post_types( false );
321
322 foreach ( $settings['monitor_types'] as $type ) {
323 if ( in_array( $type, $allowed, true ) ) {
324 $monitor_types[] = $type;
325 }
326 }
327
328 $options['monitor_types'] = $monitor_types;
329 }
330
331 if ( isset( $settings['associated_redirect'] ) && is_string( $settings['associated_redirect'] ) ) {
332 $options['associated_redirect'] = '';
333
334 if ( strlen( $settings['associated_redirect'] ) > 0 ) {
335 $sanitizer = new Red_Item_Sanitize();
336 $options['associated_redirect'] = trim( $sanitizer->sanitize_url( $settings['associated_redirect'] ) );
337 }
338 }
339
340 if ( isset( $settings['monitor_types'] ) && count( $monitor_types ) === 0 ) {
341 $options['monitor_post'] = 0;
342 $options['associated_redirect'] = '';
343 } elseif ( isset( $settings['monitor_post'] ) ) {
344 $options['monitor_post'] = max( 0, intval( $settings['monitor_post'], 10 ) );
345
346 if ( Red_Group::get( $options['monitor_post'] ) === false && $options['monitor_post'] !== 0 ) {
347 $groups = Red_Group::get_all();
348
349 if ( count( $groups ) > 0 ) {
350 $options['monitor_post'] = $groups[0]['id'];
351 } else {
352 $options['monitor_post'] = 0;
353 }
354 }
355 }
356
357 if ( isset( $settings['auto_target'] ) && is_string( $settings['auto_target'] ) ) {
358 $options['auto_target'] = sanitize_text_field( $settings['auto_target'] );
359 }
360
361 if ( isset( $settings['last_group_id'] ) ) {
362 $options['last_group_id'] = max( 0, intval( $settings['last_group_id'], 10 ) );
363
364 if ( Red_Group::get( $options['last_group_id'] ) === false ) {
365 $groups = Red_Group::get_all();
366 $options['last_group_id'] = count( $groups ) > 0 ? $groups[0]['id'] : 0;
367 }
368 }
369
370 if ( isset( $settings['token'] ) && is_string( $settings['token'] ) ) {
371 $options['token'] = sanitize_text_field( $settings['token'] );
372 }
373
374 if ( isset( $settings['token'] ) && trim( $options['token'] ) === '' ) {
375 $options['token'] = md5( uniqid() );
376 }
377
378 // Boolean settings
379 foreach ( [ 'support', 'https', 'log_external', 'log_header', 'track_hits' ] as $name ) {
380 if ( isset( $settings[ $name ] ) ) {
381 $options[ $name ] = $settings[ $name ] ? true : false;
382 }
383 }
384
385 if ( isset( $settings['expire_redirect'] ) ) {
386 $options['expire_redirect'] = max( -1, min( intval( $settings['expire_redirect'], 10 ), 60 ) );
387 }
388
389 if ( isset( $settings['expire_404'] ) ) {
390 $options['expire_404'] = max( -1, min( intval( $settings['expire_404'], 10 ), 60 ) );
391 }
392
393 if ( isset( $settings['ip_logging'] ) ) {
394 $options['ip_logging'] = max( 0, min( 2, intval( $settings['ip_logging'], 10 ) ) );
395 }
396
397 if ( isset( $settings['redirect_cache'] ) ) {
398 $options['redirect_cache'] = intval( $settings['redirect_cache'], 10 );
399
400 if ( ! in_array( $options['redirect_cache'], array( -1, 0, 1, 24, 24 * 7 ), true ) ) {
401 $options['redirect_cache'] = 1;
402 }
403 }
404
405 if ( isset( $settings['location'] ) && ( ! isset( $options['location'] ) || $options['location'] !== $settings['location'] ) ) {
406 $module = Red_Module::get( 2 );
407
408 if ( $module !== false ) {
409 $options['modules'][2] = $module->update( $settings );
410 }
411 }
412
413 if ( ! empty( $options['monitor_post'] ) && count( $options['monitor_types'] ) === 0 ) {
414 // If we have a monitor_post set, but no types, then blank everything
415 $options['monitor_post'] = 0;
416 $options['associated_redirect'] = '';
417 }
418
419 if ( isset( $settings['plugin_update'] ) && in_array( $settings['plugin_update'], [ 'prompt', 'admin' ], true ) ) {
420 $options['plugin_update'] = sanitize_text_field( $settings['plugin_update'] );
421 }
422
423 $flags = new Red_Source_Flags();
424 $flags_present = [];
425
426 foreach ( array_keys( $flags->get_json() ) as $flag ) {
427 if ( isset( $settings[ $flag ] ) ) {
428 $flags_present[ $flag ] = $settings[ $flag ];
429 }
430 }
431
432 if ( count( $flags_present ) > 0 ) {
433 $flags->set_flags( $flags_present );
434 $options = array_merge( $options, $flags->get_json() );
435 }
436
437 if ( isset( $settings['headers'] ) ) {
438 $headers = new Red_Http_Headers( $settings['headers'] );
439 $options['headers'] = $headers->get_json();
440 }
441
442 if ( isset( $settings['aliases'] ) && is_array( $settings['aliases'] ) ) {
443 $options['aliases'] = array_map( 'sanitize_text_field', $settings['aliases'] );
444 $options['aliases'] = array_values( array_filter( array_map( 'red_parse_domain_only', $settings['aliases'] ) ) );
445 $options['aliases'] = array_slice( $options['aliases'], 0, 20 ); // Max 20
446 }
447
448 if ( isset( $settings['permalinks'] ) && is_array( $settings['permalinks'] ) ) {
449 $options['permalinks'] = array_map(
450 function ( $permalink ) {
451 return sanitize_option( 'permalink_structure', $permalink );
452 },
453 $settings['permalinks']
454 );
455 $options['permalinks'] = array_values( array_filter( array_map( 'trim', $options['permalinks'] ) ) );
456 $options['permalinks'] = array_slice( $options['permalinks'], 0, 10 ); // Max 10
457 }
458
459 if ( isset( $settings['preferred_domain'] ) && in_array( $settings['preferred_domain'], [ '', 'www', 'nowww' ], true ) ) {
460 $options['preferred_domain'] = sanitize_text_field( $settings['preferred_domain'] );
461 }
462
463 if ( isset( $settings['relocate'] ) && is_string( $settings['relocate'] ) ) {
464 $options['relocate'] = red_parse_domain_path( sanitize_text_field( $settings['relocate'] ) );
465
466 if ( strlen( $options['relocate'] ) > 0 ) {
467 $options['preferred_domain'] = '';
468 $options['aliases'] = [];
469 $options['https'] = false;
470 }
471 }
472
473 if ( isset( $settings['cache_key'] ) ) {
474 $key = intval( $settings['cache_key'], 10 );
475
476 if ( $settings['cache_key'] === true ) {
477 $key = time();
478 } elseif ( $settings['cache_key'] === false ) {
479 $key = 0;
480 }
481
482 $options['cache_key'] = $key;
483 }
484
485 if ( isset( $settings['update_notice'] ) ) {
486 $major_version = explode( '-', REDIRECTION_VERSION )[0]; // Remove any beta suffix
487 $major_version = implode( '.', array_slice( explode( '.', $major_version ), 0, 2 ) );
488 $options['update_notice'] = $major_version;
489 }
490
491 /** @var RedirectionOptions $options */
492 return $options;
493 }
494 }
495