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 / header.php

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

237 lines 6.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Manages HTTP headers for site-wide and redirect-specific use
5 *
6 * @phpstan-type HeaderSettingChoice array{
7 * label: string,
8 * value: string
9 * }
10 * @phpstan-type HeaderSettings array<string, string|array<HeaderSettingChoice>>
11 * @phpstan-type HeaderData array{
12 * type: string,
13 * headerName: string,
14 * headerValue: string,
15 * location: string,
16 * headerSettings: HeaderSettings
17 * }
18 * @phpstan-type RawHeaderInput array{
19 * headerName?: string,
20 * type?: string,
21 * headerValue?: string,
22 * location?: string,
23 * headerSettings?: array<string, mixed>
24 * }
25 */
26 class Red_Http_Headers {
27 /**
28 * Normalized headers
29 *
30 * @var array<HeaderData>
31 */
32 private $headers = [];
33
34 /**
35 * Constructor
36 *
37 * @param array<RawHeaderInput> $options Raw header configuration.
38 */
39 public function __construct( $options = [] ) {
40 // @phpstan-ignore function.alreadyNarrowedType
41 if ( is_array( $options ) ) {
42 $this->headers = array_filter( array_map( [ $this, 'normalize' ], $options ) );
43 }
44 }
45
46 /**
47 * Normalize a raw header input
48 *
49 * @param RawHeaderInput $header Raw header data.
50 * @return HeaderData|null Normalized header or null if invalid.
51 */
52 private function normalize( $header ) {
53 $location = 'site';
54 if ( isset( $header['location'] ) && $header['location'] === 'redirect' ) {
55 $location = 'redirect';
56 }
57
58 $name = $this->sanitize( isset( $header['headerName'] ) ? sanitize_text_field( $header['headerName'] ) : '' );
59 $type = $this->sanitize( isset( $header['type'] ) ? sanitize_text_field( $header['type'] ) : '' );
60 $value = $this->sanitize( isset( $header['headerValue'] ) ? sanitize_text_field( $header['headerValue'] ) : '' );
61 $settings = [];
62
63 // @phpstan-ignore booleanAnd.rightAlwaysTrue
64 if ( isset( $header['headerSettings'] ) && is_array( $header['headerSettings'] ) ) {
65 foreach ( $header['headerSettings'] as $key => $setting_value ) {
66 if ( is_array( $setting_value ) ) {
67 if ( isset( $setting_value['value'] ) ) {
68 $settings[ $this->sanitize( sanitize_text_field( $key ) ) ] = $this->sanitize( $setting_value['value'] );
69 } elseif ( isset( $setting_value['choices'] ) ) {
70 $settings[ $this->sanitize( sanitize_text_field( $key ) ) ] = array_map(
71 function ( $choice ) {
72 return [
73 'label' => $this->sanitize( isset( $choice['label'] ) ? $choice['label'] : '' ),
74 'value' => $this->sanitize( isset( $choice['value'] ) ? $choice['value'] : '' ),
75 ];
76 },
77 $setting_value['choices']
78 );
79 }
80 } else {
81 $settings[ $this->sanitize( sanitize_text_field( $key ) ) ] = $this->sanitize( $setting_value );
82 }
83 }
84 }
85
86 if ( strlen( $name ) > 0 && strlen( $type ) > 0 ) {
87 return [
88 'type' => $this->dash_case( $type ),
89 'headerName' => $this->dash_case( $name ),
90 'headerValue' => $value,
91 'location' => $location,
92 'headerSettings' => $settings,
93 ];
94 }
95
96 return null;
97 }
98
99 /**
100 * Get headers as JSON
101 *
102 * @return array<HeaderData>
103 */
104 public function get_json() {
105 return $this->headers;
106 }
107
108 /**
109 * Convert a string to Dash-Case format
110 *
111 * @param string $name Input string.
112 * @return string Dash-Case formatted string.
113 */
114 private function dash_case( $name ) {
115 $name = (string) preg_replace( '/[^A-Za-z0-9]/', ' ', $name );
116 $name = (string) preg_replace( '/\s{2,}/', ' ', $name );
117 $name = trim( $name, ' ' );
118 $name = ucwords( $name );
119 $name = str_replace( ' ', '-', $name );
120
121 return $name;
122 }
123
124 /**
125 * Remove duplicate headers by name
126 *
127 * @param array<HeaderData> $headers Headers to deduplicate.
128 * @return array<HeaderData>
129 */
130 private function remove_dupes( $headers ) {
131 $new_headers = [];
132
133 foreach ( $headers as $header ) {
134 $new_headers[ $header['headerName'] ] = $header;
135 }
136
137 return array_values( $new_headers );
138 }
139
140 /**
141 * Get headers for site-wide application
142 *
143 * @return array<HeaderData>
144 */
145 public function get_site_headers() {
146 $headers = array_values( $this->remove_dupes( array_filter( $this->headers, [ $this, 'is_site_header' ] ) ) );
147
148 return apply_filters( 'redirection_headers_site', $headers );
149 }
150
151 /**
152 * Get headers for redirects (combines site and redirect-specific headers)
153 *
154 * @return array<HeaderData>
155 */
156 public function get_redirect_headers() {
157 // Site ones first, then redirect - redirect will override any site ones
158 $headers = $this->get_site_headers();
159 $headers = array_merge( $headers, array_values( array_filter( $this->headers, [ $this, 'is_redirect_header' ] ) ) );
160 $headers = array_values( $this->remove_dupes( $headers ) );
161
162 return apply_filters( 'redirection_headers_redirect', $headers );
163 }
164
165 /**
166 * Check if header is a site header
167 *
168 * @param HeaderData $header Header to check.
169 * @return bool
170 */
171 private function is_site_header( $header ) {
172 return $header['location'] === 'site';
173 }
174
175 /**
176 * Check if header is a redirect header
177 *
178 * @param HeaderData $header Header to check.
179 * @return bool
180 */
181 private function is_redirect_header( $header ) {
182 return $header['location'] === 'redirect';
183 }
184
185 /**
186 * Apply headers to the current request
187 *
188 * @param array<HeaderData> $headers Headers to apply.
189 * @return void
190 */
191 public function run( $headers ) {
192 $done = [];
193
194 foreach ( $headers as $header ) {
195 if ( ! in_array( $header['headerName'], $done, true ) ) {
196 $name = $this->sanitize( $this->dash_case( $header['headerName'] ) );
197 $value = $this->sanitize( $header['headerValue'] );
198
199 // Trigger some other action
200 do_action( 'redirection_header', $name, $value );
201
202 header( sprintf( '%s: %s', $name, $value ) );
203 $done[] = $header['headerName'];
204 }
205 }
206 }
207
208 /**
209 * Sanitize a string for use in headers
210 *
211 * @param string|array<mixed> $text Text to sanitize.
212 * @return string Sanitized text.
213 */
214 private function sanitize( $text ) {
215 if ( is_array( $text ) ) {
216 return '';
217 }
218
219 // No new lines
220 $text = (string) preg_replace( "/[\r\n\t].*?$/s", '', $text );
221
222 // Clean control codes
223 $text = (string) preg_replace( '/[^\PC\s]/u', '', $text );
224
225 // Try and remove bad decoding
226 // @phpstan-ignore function.alreadyNarrowedType
227 if ( function_exists( 'iconv' ) && is_string( $text ) ) {
228 $converted = @iconv( 'UTF-8', 'UTF-8//IGNORE', $text );
229 if ( $converted !== false ) {
230 $text = $converted;
231 }
232 }
233
234 return $text;
235 }
236 }
237