| 1 |
<?php |
| 2 |
/** |
| 3 |
* HeaderSelector |
| 4 |
* PHP version 7.4 |
| 5 |
* |
| 6 |
* @category Class |
| 7 |
* @package Plausible\Analytics\WP\Client |
| 8 |
* @author OpenAPI Generator team |
| 9 |
* @link https://openapi-generator.tech |
| 10 |
*/ |
| 11 |
|
| 12 |
/** |
| 13 |
* Plausible Plugins API |
| 14 |
* |
| 15 |
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) |
| 16 |
* |
| 17 |
* The version of the OpenAPI document: 1.0-rc |
| 18 |
* Generated by: https://openapi-generator.tech |
| 19 |
* OpenAPI Generator version: 7.0.1 |
| 20 |
*/ |
| 21 |
|
| 22 |
/** |
| 23 |
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). |
| 24 |
* https://openapi-generator.tech |
| 25 |
* Do not edit the class manually. |
| 26 |
*/ |
| 27 |
|
| 28 |
namespace Plausible\Analytics\WP\Client; |
| 29 |
|
| 30 |
/** |
| 31 |
* HeaderSelector Class Doc Comment |
| 32 |
* |
| 33 |
* @category Class |
| 34 |
* @package Plausible\Analytics\WP\Client |
| 35 |
* @author OpenAPI Generator team |
| 36 |
* @link https://openapi-generator.tech |
| 37 |
*/ |
| 38 |
class HeaderSelector { |
| 39 |
/** |
| 40 |
* @param string[] $accept |
| 41 |
* @param string $contentType |
| 42 |
* @param bool $isMultipart |
| 43 |
* |
| 44 |
* @return string[] |
| 45 |
*/ |
| 46 |
public function selectHeaders( array $accept, string $contentType, bool $isMultipart ): array { |
| 47 |
$headers = []; |
| 48 |
|
| 49 |
$accept = $this->selectAcceptHeader( $accept ); |
| 50 |
if ( $accept !== null ) { |
| 51 |
$headers['Accept'] = $accept; |
| 52 |
} |
| 53 |
|
| 54 |
if ( ! $isMultipart ) { |
| 55 |
if ( $contentType === '' ) { |
| 56 |
$contentType = 'application/json'; |
| 57 |
} |
| 58 |
|
| 59 |
$headers['Content-Type'] = $contentType; |
| 60 |
} |
| 61 |
|
| 62 |
return $headers; |
| 63 |
} |
| 64 |
|
| 65 |
/** |
| 66 |
* Return the header 'Accept' based on an array of Accept provided. |
| 67 |
* |
| 68 |
* @param string[] $accept Array of header |
| 69 |
* |
| 70 |
* @return null|string Accept (e.g. application/json) |
| 71 |
*/ |
| 72 |
private function selectAcceptHeader( array $accept ): ?string { |
| 73 |
# filter out empty entries |
| 74 |
$accept = array_filter( $accept ); |
| 75 |
|
| 76 |
if ( count( $accept ) === 0 ) { |
| 77 |
return null; |
| 78 |
} |
| 79 |
|
| 80 |
# If there's only one Accept header, just use it |
| 81 |
if ( count( $accept ) === 1 ) { |
| 82 |
return reset( $accept ); |
| 83 |
} |
| 84 |
|
| 85 |
# If none of the available Accept headers is of type "json", then just use all them |
| 86 |
$headersWithJson = preg_grep( '~(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$~', $accept ); |
| 87 |
if ( count( $headersWithJson ) === 0 ) { |
| 88 |
return implode( ',', $accept ); |
| 89 |
} |
| 90 |
|
| 91 |
# If we got here, then we need add quality values (weight), as described in IETF RFC 9110, Items 12.4.2/12.5.1, |
| 92 |
# to give the highest priority to json-like headers - recalculating the existing ones, if needed |
| 93 |
return $this->getAcceptHeaderWithAdjustedWeight( $accept, $headersWithJson ); |
| 94 |
} |
| 95 |
|
| 96 |
/** |
| 97 |
* Create an Accept header string from the given "Accept" headers array, recalculating all weights |
| 98 |
* |
| 99 |
* @param string[] $accept Array of Accept Headers |
| 100 |
* @param string[] $headersWithJson Array of Accept Headers of type "json" |
| 101 |
* |
| 102 |
* @return string "Accept" Header (e.g. "application/json, text/html; q=0.9") |
| 103 |
*/ |
| 104 |
private function getAcceptHeaderWithAdjustedWeight( array $accept, array $headersWithJson ): string { |
| 105 |
$processedHeaders = [ |
| 106 |
'withApplicationJson' => [], |
| 107 |
'withJson' => [], |
| 108 |
'withoutJson' => [], |
| 109 |
]; |
| 110 |
|
| 111 |
foreach ( $accept as $header ) { |
| 112 |
|
| 113 |
$headerData = $this->getHeaderAndWeight( $header ); |
| 114 |
|
| 115 |
if ( stripos( $headerData['header'], 'application/json' ) === 0 ) { |
| 116 |
$processedHeaders['withApplicationJson'][] = $headerData; |
| 117 |
} elseif ( in_array( $header, $headersWithJson, true ) ) { |
| 118 |
$processedHeaders['withJson'][] = $headerData; |
| 119 |
} else { |
| 120 |
$processedHeaders['withoutJson'][] = $headerData; |
| 121 |
} |
| 122 |
} |
| 123 |
|
| 124 |
$acceptHeaders = []; |
| 125 |
$currentWeight = 1000; |
| 126 |
|
| 127 |
$hasMoreThan28Headers = count( $accept) > 28; |
| 128 |
|
| 129 |
foreach ( $processedHeaders as $headers ) { |
| 130 |
if ( count( $headers ) > 0 ) { |
| 131 |
$acceptHeaders[] = $this->adjustWeight( $headers, $currentWeight, $hasMoreThan28Headers ); |
| 132 |
} |
| 133 |
} |
| 134 |
|
| 135 |
$acceptHeaders = array_merge( ...$acceptHeaders ); |
| 136 |
|
| 137 |
return implode( ',', $acceptHeaders ); |
| 138 |
} |
| 139 |
|
| 140 |
/** |
| 141 |
* Given an Accept header, returns an associative array splitting the header and its weight |
| 142 |
* |
| 143 |
* @param string $header "Accept" Header |
| 144 |
* |
| 145 |
* @return array with the header and its weight |
| 146 |
*/ |
| 147 |
private function getHeaderAndWeight( string $header ): array { |
| 148 |
# matches headers with weight, splitting the header and the weight in $outputArray |
| 149 |
if ( preg_match( '/(.*);\s*q=(1(?:\.0+)?|0\.\d+)$/', $header, $outputArray ) === 1 ) { |
| 150 |
$headerData = [ |
| 151 |
'header' => $outputArray[1], |
| 152 |
'weight' => (int) ( $outputArray[2] * 1000 ), |
| 153 |
]; |
| 154 |
} else { |
| 155 |
$headerData = [ |
| 156 |
'header' => trim( $header ), |
| 157 |
'weight' => 1000, |
| 158 |
]; |
| 159 |
} |
| 160 |
|
| 161 |
return $headerData; |
| 162 |
} |
| 163 |
|
| 164 |
/** |
| 165 |
* @param array[] $headers |
| 166 |
* @param float $currentWeight |
| 167 |
* @param bool $hasMoreThan28Headers |
| 168 |
* |
| 169 |
* @return string[] array of adjusted "Accept" headers |
| 170 |
*/ |
| 171 |
private function adjustWeight( array $headers, float &$currentWeight, bool $hasMoreThan28Headers ): array { |
| 172 |
usort( $headers, function ( array $a, array $b ) { |
| 173 |
return $b['weight'] - $a['weight']; |
| 174 |
} ); |
| 175 |
|
| 176 |
$acceptHeaders = []; |
| 177 |
foreach ( $headers as $index => $header ) { |
| 178 |
if ( $index > 0 && $headers[ $index - 1 ]['weight'] > $header['weight'] ) { |
| 179 |
$currentWeight = $this->getNextWeight( $currentWeight, $hasMoreThan28Headers ); |
| 180 |
} |
| 181 |
|
| 182 |
$weight = $currentWeight; |
| 183 |
|
| 184 |
$acceptHeaders[] = $this->buildAcceptHeader( $header['header'], $weight ); |
| 185 |
} |
| 186 |
|
| 187 |
$currentWeight = $this->getNextWeight( $currentWeight, $hasMoreThan28Headers ); |
| 188 |
|
| 189 |
return $acceptHeaders; |
| 190 |
} |
| 191 |
|
| 192 |
/** |
| 193 |
* @param string $header |
| 194 |
* @param int $weight |
| 195 |
* |
| 196 |
* @return string |
| 197 |
*/ |
| 198 |
private function buildAcceptHeader( string $header, int $weight ): string { |
| 199 |
if ( $weight === 1000 ) { |
| 200 |
return $header; |
| 201 |
} |
| 202 |
|
| 203 |
return trim( $header, '; ' ) . ';q=' . rtrim( sprintf( '%0.3f', $weight / 1000 ), '0' ); |
| 204 |
} |
| 205 |
|
| 206 |
/** |
| 207 |
* Calculate the next weight, based on the current one. |
| 208 |
* |
| 209 |
* If there are less than 28 "Accept" headers, the weights will be decreased by 1 on its highest significant digit, using the |
| 210 |
* following formula: |
| 211 |
* |
| 212 |
* next weight = current weight - 10 ^ (floor(log(current weight - 1))) |
| 213 |
* |
| 214 |
* ( current weight minus ( 10 raised to the power of ( floor of (log to the base 10 of ( current weight minus 1 ) ) ) ) ) |
| 215 |
* |
| 216 |
* Starting from 1000, this generates the following series: |
| 217 |
* |
| 218 |
* 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 |
| 219 |
* |
| 220 |
* The resulting quality codes are closer to the average "normal" usage of them (like "q=0.9", "q=0.8" and so on), but it only works |
| 221 |
* if there is a maximum of 28 "Accept" headers. If we have more than that (which is extremely unlikely), then we fall back to a 1-by-1 |
| 222 |
* decrement rule, which will result in quality codes like "q=0.999", "q=0.998" etc. |
| 223 |
* |
| 224 |
* @param int $currentWeight varying from 1 to 1000 (will be divided by 1000 to build the quality value) |
| 225 |
* @param bool $hasMoreThan28Headers |
| 226 |
* |
| 227 |
* @return int |
| 228 |
*/ |
| 229 |
public function getNextWeight( int $currentWeight, bool $hasMoreThan28Headers ): int { |
| 230 |
if ( $currentWeight <= 1 ) { |
| 231 |
return 1; |
| 232 |
} |
| 233 |
|
| 234 |
if ( $hasMoreThan28Headers ) { |
| 235 |
return $currentWeight - 1; |
| 236 |
} |
| 237 |
|
| 238 |
return $currentWeight - 10 ** floor( log10($currentWeight - 1) ); |
| 239 |
} |
| 240 |
} |
| 241 |
|