| 1 |
<?php |
| 2 |
|
| 3 |
namespace Kibo\Phast\HTTP; |
| 4 |
|
| 5 |
class Response |
| 6 |
{ |
| 7 |
/** |
| 8 |
* @var int |
| 9 |
*/ |
| 10 |
private $code = 200; |
| 11 |
/** |
| 12 |
* @var array |
| 13 |
*/ |
| 14 |
private $headers = array(); |
| 15 |
/** |
| 16 |
* @var string|iterable |
| 17 |
*/ |
| 18 |
private $content; |
| 19 |
/** |
| 20 |
* @return int |
| 21 |
*/ |
| 22 |
public function getCode() |
| 23 |
{ |
| 24 |
return $this->code; |
| 25 |
} |
| 26 |
/** |
| 27 |
* @param int $code |
| 28 |
*/ |
| 29 |
public function setCode($code) |
| 30 |
{ |
| 31 |
$this->code = $code; |
| 32 |
} |
| 33 |
/** |
| 34 |
* @return array |
| 35 |
*/ |
| 36 |
public function getHeaders() |
| 37 |
{ |
| 38 |
return $this->headers; |
| 39 |
} |
| 40 |
/** |
| 41 |
* @param string $name |
| 42 |
* @return string|null |
| 43 |
*/ |
| 44 |
public function getHeader($name) |
| 45 |
{ |
| 46 |
foreach ($this->headers as $k => $v) { |
| 47 |
if (strcasecmp($name, $k) === 0) { |
| 48 |
return $v; |
| 49 |
} |
| 50 |
} |
| 51 |
return null; |
| 52 |
} |
| 53 |
public function setHeaders(array $headers) |
| 54 |
{ |
| 55 |
$this->headers = $headers; |
| 56 |
} |
| 57 |
/** |
| 58 |
* @param $name |
| 59 |
* @param $value |
| 60 |
*/ |
| 61 |
public function setHeader($name, $value) |
| 62 |
{ |
| 63 |
$this->headers[$name] = $value; |
| 64 |
} |
| 65 |
/** |
| 66 |
* @return string|iterable |
| 67 |
*/ |
| 68 |
public function getContent() |
| 69 |
{ |
| 70 |
return $this->content; |
| 71 |
} |
| 72 |
/** |
| 73 |
* @param string|iterable $content |
| 74 |
*/ |
| 75 |
public function setContent($content) |
| 76 |
{ |
| 77 |
$this->content = $content; |
| 78 |
} |
| 79 |
public function isCompressible() |
| 80 |
{ |
| 81 |
return strpos($this->getHeader('Content-Type'), 'image/') === false; |
| 82 |
} |
| 83 |
} |
| 84 |
namespace Kibo\Phast\HTTP; |
| 85 |
|
| 86 |
interface Client |
| 87 |
{ |
| 88 |
/** |
| 89 |
* Retrieve a URL using the GET HTTP method |
| 90 |
* |
| 91 |
* @param URL $url |
| 92 |
* @param array $headers - headers to send in headerName => headerValue format |
| 93 |
* @return Response |
| 94 |
* @throws \Exception |
| 95 |
*/ |
| 96 |
public function get(\Kibo\Phast\ValueObjects\URL $url, array $headers = array()); |
| 97 |
/** |
| 98 |
* Send data to a URL using the POST HTTP method |
| 99 |
* |
| 100 |
* @param URL $url |
| 101 |
* @param array|string $data - if array, it will be encoded as form data, if string - will be sent as is |
| 102 |
* @param array $headers - headers to send in headerName => headerValue format |
| 103 |
* @return Response |
| 104 |
* @throws \Exception |
| 105 |
*/ |
| 106 |
public function post(\Kibo\Phast\ValueObjects\URL $url, $data, array $headers = array()); |
| 107 |
} |
| 108 |
namespace Kibo\Phast\HTTP; |
| 109 |
|
| 110 |
class CURLClient implements \Kibo\Phast\HTTP\Client |
| 111 |
{ |
| 112 |
public function get(\Kibo\Phast\ValueObjects\URL $url, array $headers = array()) |
| 113 |
{ |
| 114 |
$this->checkCURL(); |
| 115 |
return $this->request($url, $headers); |
| 116 |
} |
| 117 |
public function post(\Kibo\Phast\ValueObjects\URL $url, $data, array $headers = array()) |
| 118 |
{ |
| 119 |
$this->checkCURL(); |
| 120 |
return $this->request($url, $headers, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $data]); |
| 121 |
} |
| 122 |
private function checkCURL() |
| 123 |
{ |
| 124 |
if (!function_exists('curl_init')) { |
| 125 |
throw new \Kibo\Phast\HTTP\Exceptions\NetworkError('cURL is not installed'); |
| 126 |
} |
| 127 |
} |
| 128 |
private function request(\Kibo\Phast\ValueObjects\URL $url, array $headers = array(), array $opts = array()) |
| 129 |
{ |
| 130 |
$response = new \Kibo\Phast\HTTP\Response(); |
| 131 |
$readHeader = function ($_, $headerLine) use($response) { |
| 132 |
if (strpos($headerLine, 'HTTP/') === 0) { |
| 133 |
$response->setHeaders([]); |
| 134 |
} else { |
| 135 |
list($name, $value) = explode(':', $headerLine, 2); |
| 136 |
if (trim($name) !== '') { |
| 137 |
$response->setHeader($name, trim($value)); |
| 138 |
} |
| 139 |
} |
| 140 |
return strlen($headerLine); |
| 141 |
}; |
| 142 |
$ch = curl_init((string) $url); |
| 143 |
curl_setopt_array($ch, $opts + [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $this->makeHeaders($headers), CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 5, CURLOPT_HEADERFUNCTION => $readHeader, CURLOPT_CAINFO => __DIR__ . '/cacert.pem', CURLOPT_ENCODING => '']); |
| 144 |
$responseText = @curl_exec($ch); |
| 145 |
if ($responseText === false) { |
| 146 |
throw new \Kibo\Phast\HTTP\Exceptions\NetworkError(curl_error($ch), curl_errno($ch)); |
| 147 |
} |
| 148 |
$info = curl_getinfo($ch); |
| 149 |
if (!preg_match('/^2/', $info['http_code'])) { |
| 150 |
throw new \Kibo\Phast\HTTP\Exceptions\HTTPError($info['http_code']); |
| 151 |
} |
| 152 |
$response->setCode($info['http_code']); |
| 153 |
$response->setContent($responseText); |
| 154 |
return $response; |
| 155 |
} |
| 156 |
private function makeHeaders(array $headers) |
| 157 |
{ |
| 158 |
$result = []; |
| 159 |
foreach ($headers as $k => $v) { |
| 160 |
$result[] = "{$k}: {$v}"; |
| 161 |
} |
| 162 |
return $result; |
| 163 |
} |
| 164 |
} |
| 165 |
namespace Kibo\Phast\HTTP; |
| 166 |
|
| 167 |
class Request |
| 168 |
{ |
| 169 |
/** |
| 170 |
* @var array |
| 171 |
*/ |
| 172 |
private $env; |
| 173 |
/** |
| 174 |
* @var array |
| 175 |
*/ |
| 176 |
private $cookie; |
| 177 |
/** |
| 178 |
* @var string |
| 179 |
*/ |
| 180 |
private $query; |
| 181 |
private function __construct() |
| 182 |
{ |
| 183 |
} |
| 184 |
public static function fromGlobals() |
| 185 |
{ |
| 186 |
$instance = new self(); |
| 187 |
$instance->env = $_SERVER; |
| 188 |
$instance->cookie = $_COOKIE; |
| 189 |
return $instance; |
| 190 |
} |
| 191 |
public static function fromArray(array $get = array(), array $env = array(), array $cookie = array()) |
| 192 |
{ |
| 193 |
if ($get) { |
| 194 |
$url = isset($env['REQUEST_URI']) ? $env['REQUEST_URI'] : ''; |
| 195 |
$env['REQUEST_URI'] = \Kibo\Phast\ValueObjects\URL::fromString($url)->withQuery(http_build_query($get))->toString(); |
| 196 |
} |
| 197 |
$instance = new self(); |
| 198 |
$instance->env = $env; |
| 199 |
$instance->cookie = $cookie; |
| 200 |
return $instance; |
| 201 |
} |
| 202 |
/** |
| 203 |
* @return array |
| 204 |
*/ |
| 205 |
public function getGet() |
| 206 |
{ |
| 207 |
return $this->getQuery()->toAssoc(); |
| 208 |
} |
| 209 |
/** |
| 210 |
* @return Query |
| 211 |
*/ |
| 212 |
public function getQuery() |
| 213 |
{ |
| 214 |
return \Kibo\Phast\ValueObjects\Query::fromString($this->getQueryString()); |
| 215 |
} |
| 216 |
/** |
| 217 |
* @param $name string |
| 218 |
* @return string|null |
| 219 |
*/ |
| 220 |
public function getHeader($name) |
| 221 |
{ |
| 222 |
$key = 'HTTP_' . strtoupper(str_replace('-', '_', $name)); |
| 223 |
return $this->getEnvValue($key); |
| 224 |
} |
| 225 |
public function getPathInfo() |
| 226 |
{ |
| 227 |
$pathInfo = $this->getEnvValue('PATH_INFO'); |
| 228 |
if ($pathInfo) { |
| 229 |
return $pathInfo; |
| 230 |
} |
| 231 |
$script = $this->getEnvValue('PHP_SELF'); |
| 232 |
$uri = $this->getEnvValue('DOCUMENT_URI'); |
| 233 |
if ($script !== null && $uri !== null && strpos($uri, $script . '/') === 0) { |
| 234 |
return substr($uri, strlen($script)); |
| 235 |
} |
| 236 |
} |
| 237 |
public function getCookie($name) |
| 238 |
{ |
| 239 |
if (isset($this->cookie[$name])) { |
| 240 |
return $this->cookie[$name]; |
| 241 |
} |
| 242 |
} |
| 243 |
public function getQueryString() |
| 244 |
{ |
| 245 |
$parsed = parse_url($this->getEnvValue('REQUEST_URI')); |
| 246 |
if (isset($parsed['query'])) { |
| 247 |
return $parsed['query']; |
| 248 |
} |
| 249 |
} |
| 250 |
public function getAbsoluteURI() |
| 251 |
{ |
| 252 |
return ($this->getEnvValue('HTTPS') ? 'https' : 'http') . '://' . $this->getHost() . $this->getURI(); |
| 253 |
} |
| 254 |
public function getHost() |
| 255 |
{ |
| 256 |
return $this->getHeader('Host'); |
| 257 |
} |
| 258 |
public function getURI() |
| 259 |
{ |
| 260 |
return $this->getEnvValue('REQUEST_URI'); |
| 261 |
} |
| 262 |
public function getEnvValue($key) |
| 263 |
{ |
| 264 |
if (isset($this->env[$key])) { |
| 265 |
return $this->env[$key]; |
| 266 |
} |
| 267 |
} |
| 268 |
public function getDocumentRoot() |
| 269 |
{ |
| 270 |
$scriptName = (string) $this->getEnvValue('SCRIPT_NAME'); |
| 271 |
$scriptFilename = $this->normalizePath((string) $this->getEnvValue('SCRIPT_FILENAME')); |
| 272 |
if (strpos($scriptName, '/') === 0 && $this->isAbsolutePath($scriptFilename) && $this->isSuffix($scriptName, $scriptFilename)) { |
| 273 |
return substr($scriptFilename, 0, strlen($scriptFilename) - strlen($scriptName)); |
| 274 |
} |
| 275 |
return $this->getEnvValue('DOCUMENT_ROOT'); |
| 276 |
} |
| 277 |
private function normalizePath($path) |
| 278 |
{ |
| 279 |
return str_replace('\\', '/', $path); |
| 280 |
} |
| 281 |
private function isAbsolutePath($path) |
| 282 |
{ |
| 283 |
return preg_match('~^/|^[a-z]:/~i', $path); |
| 284 |
} |
| 285 |
private function isSuffix($suffix, $string) |
| 286 |
{ |
| 287 |
return substr($string, -strlen($suffix)) === $suffix; |
| 288 |
} |
| 289 |
public function isCloudflare() |
| 290 |
{ |
| 291 |
return !!$this->getHeader('CF-Ray'); |
| 292 |
} |
| 293 |
} |
| 294 |
namespace Kibo\Phast\HTTP; |
| 295 |
|
| 296 |
class ClientFactory |
| 297 |
{ |
| 298 |
const CONFIG_KEY = 'httpClient'; |
| 299 |
/** |
| 300 |
* @param array $config |
| 301 |
* @return Client |
| 302 |
*/ |
| 303 |
public function make(array $config) |
| 304 |
{ |
| 305 |
$spec = $config[self::CONFIG_KEY]; |
| 306 |
if (is_callable($spec)) { |
| 307 |
$client = $spec(); |
| 308 |
} elseif (class_exists($spec)) { |
| 309 |
$client = new $spec(); |
| 310 |
} else { |
| 311 |
throw new \Kibo\Phast\Exceptions\RuntimeException(self::CONFIG_KEY . ' config value must be either callable or a class name'); |
| 312 |
} |
| 313 |
return $client; |
| 314 |
} |
| 315 |
} |
| 316 |
namespace Kibo\Phast\HTTP\Exceptions; |
| 317 |
|
| 318 |
class HTTPError extends \RuntimeException |
| 319 |
{ |
| 320 |
} |
| 321 |
namespace Kibo\Phast\HTTP\Exceptions; |
| 322 |
|
| 323 |
class NetworkError extends \RuntimeException |
| 324 |
{ |
| 325 |
} |
| 326 |
namespace Kibo\Phast\Environment; |
| 327 |
|
| 328 |
class DefaultConfiguration |
| 329 |
{ |
| 330 |
public static function get() |
| 331 |
{ |
| 332 |
$request = \Kibo\Phast\HTTP\Request::fromGlobals(); |
| 333 |
return ['securityToken' => null, 'retrieverMap' => [$request->getHost() => $request->getDocumentRoot()], 'httpClient' => \Kibo\Phast\HTTP\CURLClient::class, 'cache' => ['cacheRoot' => sys_get_temp_dir() . '/phast-cache-' . (new \Kibo\Phast\Common\System())->getUserId(), 'shardingDepth' => 1, 'garbageCollection' => ['maxItems' => 100, 'probability' => 0.1, 'maxAge' => 86400 * 365], 'diskCleanup' => ['maxSize' => 500 * pow(1024, 2), 'probability' => 0.02, 'portionToFree' => 0.5]], 'servicesUrl' => '/phast.php', 'serviceRequestFormat' => \Kibo\Phast\Services\ServiceRequest::FORMAT_PATH, 'compressServiceResponse' => true, 'optimizeHTMLDocumentsOnly' => true, 'outputServerSideStats' => true, 'documents' => ['maxBufferSizeToApply' => pow(1024, 2), 'baseUrl' => $request->getAbsoluteURI(), 'filters' => [\Kibo\Phast\Filters\HTML\CommentsRemoval\Filter::class => [], \Kibo\Phast\Filters\HTML\MetaCharset\Filter::class => [], \Kibo\Phast\Filters\HTML\Minify\Filter::class => [], \Kibo\Phast\Filters\HTML\MinifyScripts\Filter::class => [], \Kibo\Phast\Filters\HTML\BaseURLSetter\Filter::class => [], \Kibo\Phast\Filters\HTML\ImagesOptimizationService\Tags\Filter::class => [], \Kibo\Phast\Filters\HTML\LazyImageLoading\Filter::class => [], \Kibo\Phast\Filters\HTML\CSSInlining\Filter::class => ['optimizerSizeDiffThreshold' => 1024, 'whitelist' => ['~^https?://fonts\\.googleapis\\.com/~' => ['ieCompatible' => false], '~^https?://ajax\\.googleapis\\.com/ajax/libs/jqueryui/~', '~^https?://maxcdn\\.bootstrapcdn\\.com/[^?#]*\\.css~', '~^https?://idangero\\.us/~', '~^https?://[^/]*\\.github\\.io/~', '~^https?://\\w+\\.typekit\\.net/~' => ['ieCompatible' => false], '~^https?://stackpath\\.bootstrapcdn\\.com/~', '~^https?://cdnjs\\.cloudflare\\.com/~']], \Kibo\Phast\Filters\HTML\ImagesOptimizationService\CSS\Filter::class => [], \Kibo\Phast\Filters\HTML\DelayedIFrameLoading\Filter::class => [], \Kibo\Phast\Filters\HTML\ScriptsProxyService\Filter::class => ['urlRefreshTime' => 7200], \Kibo\Phast\Filters\HTML\Diagnostics\Filter::class => ['enabled' => 'diagnostics'], \Kibo\Phast\Filters\HTML\ScriptsDeferring\Filter::class => [], \Kibo\Phast\Filters\HTML\PhastScriptsCompiler\Filter::class => []]], 'images' => ['enable-cache' => 'imgcache', 'api-mode' => false, 'factory' => \Kibo\Phast\Filters\Image\ImageFactory::class, 'maxImageInliningSize' => 512, 'whitelist' => ['~^https?://ajax\\.googleapis\\.com/ajax/libs/jqueryui/~'], 'filters' => [\Kibo\Phast\Filters\Image\ImageAPIClient\Filter::class => ['api-url' => 'https://optimize.phast.io/?service=images', 'host-name' => $request->getHost(), 'request-uri' => $request->getURI(), 'plugin-version' => 'phast-core-1.0']]], 'styles' => ['filters' => [\Kibo\Phast\Filters\Text\Decode\Filter::class => [], \Kibo\Phast\Filters\CSS\ImportsStripper\Filter::class => [], \Kibo\Phast\Filters\CSS\CSSMinifier\Filter::class => [], \Kibo\Phast\Filters\CSS\CSSURLRewriter\Filter::class => [], \Kibo\Phast\Filters\CSS\ImageURLRewriter\Filter::class => ['maxImageInliningSize' => 512], \Kibo\Phast\Filters\CSS\FontSwap\Filter::class => []]], 'logging' => ['logWriters' => [['class' => \Kibo\Phast\Logging\LogWriters\PHPError\Writer::class, 'levelMask' => \Kibo\Phast\Logging\LogLevel::EMERGENCY | \Kibo\Phast\Logging\LogLevel::ALERT | \Kibo\Phast\Logging\LogLevel::CRITICAL | \Kibo\Phast\Logging\LogLevel::ERROR | \Kibo\Phast\Logging\LogLevel::WARNING], ['enabled' => 'diagnostics', 'class' => \Kibo\Phast\Logging\LogWriters\JSONLFile\Writer::class, 'logRoot' => sys_get_temp_dir() . '/phast-logs']]], 'switches' => ['phast' => true, 'diagnostics' => false], 'scripts' => ['removeLicenseHeaders' => false, 'whitelist' => ['~^https?://' . preg_quote($request->getHost(), '~') . '/~']]]; |
| 334 |
} |
| 335 |
} |
| 336 |
namespace Kibo\Phast\Environment; |
| 337 |
|
| 338 |
class Package |
| 339 |
{ |
| 340 |
/** |
| 341 |
* @var string |
| 342 |
*/ |
| 343 |
protected $type; |
| 344 |
/** |
| 345 |
* @var string |
| 346 |
*/ |
| 347 |
protected $namespace; |
| 348 |
/** |
| 349 |
* @param $className |
| 350 |
* @param string|null $type |
| 351 |
* @return Package |
| 352 |
*/ |
| 353 |
public static function fromPackageClass($className, $type = null) |
| 354 |
{ |
| 355 |
$instance = new self(); |
| 356 |
$lastSeparatorPosition = strrpos($className, '\\'); |
| 357 |
$instance->type = empty($type) ? substr($className, $lastSeparatorPosition + 1) : $type; |
| 358 |
$instance->namespace = substr($className, 0, $lastSeparatorPosition); |
| 359 |
return $instance; |
| 360 |
} |
| 361 |
/** |
| 362 |
* @return string |
| 363 |
*/ |
| 364 |
public function getType() |
| 365 |
{ |
| 366 |
return $this->type; |
| 367 |
} |
| 368 |
/** |
| 369 |
* @return string |
| 370 |
*/ |
| 371 |
public function getNamespace() |
| 372 |
{ |
| 373 |
return $this->namespace; |
| 374 |
} |
| 375 |
/** |
| 376 |
* @return bool |
| 377 |
*/ |
| 378 |
public function hasFactory() |
| 379 |
{ |
| 380 |
return $this->classExists($this->getFactoryClassName()); |
| 381 |
} |
| 382 |
/** |
| 383 |
* @return mixed |
| 384 |
*/ |
| 385 |
public function getFactory() |
| 386 |
{ |
| 387 |
if ($this->hasFactory()) { |
| 388 |
$class = $this->getFactoryClassName(); |
| 389 |
return new $class(); |
| 390 |
} |
| 391 |
throw new \Kibo\Phast\Environment\Exceptions\PackageHasNoFactoryException("Package {$this->namespace} has no factory"); |
| 392 |
} |
| 393 |
/** |
| 394 |
* @return bool |
| 395 |
*/ |
| 396 |
public function hasDiagnostics() |
| 397 |
{ |
| 398 |
return $this->classExists($this->getDiagnosticsClassName()); |
| 399 |
} |
| 400 |
/** |
| 401 |
* @return Diagnostics |
| 402 |
*/ |
| 403 |
public function getDiagnostics() |
| 404 |
{ |
| 405 |
if ($this->hasDiagnostics()) { |
| 406 |
$class = $this->getDiagnosticsClassName(); |
| 407 |
return new $class(); |
| 408 |
} |
| 409 |
throw new \Kibo\Phast\Environment\Exceptions\PackageHasNoDiagnosticsException("Package {$this->namespace} has no diagnostics"); |
| 410 |
} |
| 411 |
private function getFactoryClassName() |
| 412 |
{ |
| 413 |
return $this->getClassName('Factory'); |
| 414 |
} |
| 415 |
private function getDiagnosticsClassName() |
| 416 |
{ |
| 417 |
return $this->getClassName('Diagnostics'); |
| 418 |
} |
| 419 |
private function getClassName($class) |
| 420 |
{ |
| 421 |
return $this->namespace . '\\' . $class; |
| 422 |
} |
| 423 |
private function classExists($class) |
| 424 |
{ |
| 425 |
// Don't trigger any autoloaders if Phast has been compiled into a |
| 426 |
// single file, and avoid triggering Magento code generation. |
| 427 |
$useAutoloader = basename(__FILE__) == 'Package.php'; |
| 428 |
return class_exists($class, $useAutoloader); |
| 429 |
} |
| 430 |
} |
| 431 |
namespace Kibo\Phast\Environment; |
| 432 |
|
| 433 |
class Switches |
| 434 |
{ |
| 435 |
const SWITCH_PHAST = 'phast'; |
| 436 |
const SWITCH_DIAGNOSTICS = 'diagnostics'; |
| 437 |
private static $defaults = array(self::SWITCH_PHAST => true, self::SWITCH_DIAGNOSTICS => false); |
| 438 |
private $switches = array(); |
| 439 |
public static function fromArray(array $switches) |
| 440 |
{ |
| 441 |
$instance = new self(); |
| 442 |
$instance->switches = array_merge($instance->switches, $switches); |
| 443 |
return $instance; |
| 444 |
} |
| 445 |
public static function fromString($switches) |
| 446 |
{ |
| 447 |
$instance = new self(); |
| 448 |
if (empty($switches)) { |
| 449 |
return $instance; |
| 450 |
} |
| 451 |
foreach (explode(',', $switches) as $switch) { |
| 452 |
if ($switch[0] == '-') { |
| 453 |
$instance->switches[substr($switch, 1)] = false; |
| 454 |
} else { |
| 455 |
$instance->switches[$switch] = true; |
| 456 |
} |
| 457 |
} |
| 458 |
return $instance; |
| 459 |
} |
| 460 |
public function merge(\Kibo\Phast\Environment\Switches $switches) |
| 461 |
{ |
| 462 |
$instance = new self(); |
| 463 |
$instance->switches = array_merge($this->switches, $switches->switches); |
| 464 |
return $instance; |
| 465 |
} |
| 466 |
public function isOn($switch) |
| 467 |
{ |
| 468 |
if (isset($this->switches[$switch])) { |
| 469 |
return (bool) $this->switches[$switch]; |
| 470 |
} |
| 471 |
if (isset(self::$defaults[$switch])) { |
| 472 |
return (bool) self::$defaults[$switch]; |
| 473 |
} |
| 474 |
return true; |
| 475 |
} |
| 476 |
public function toArray() |
| 477 |
{ |
| 478 |
return array_merge(self::$defaults, $this->switches); |
| 479 |
} |
| 480 |
} |
| 481 |
namespace Kibo\Phast\Environment; |
| 482 |
|
| 483 |
class Configuration |
| 484 |
{ |
| 485 |
/** |
| 486 |
* @var array |
| 487 |
*/ |
| 488 |
private $sourceConfig; |
| 489 |
/** |
| 490 |
* @var Switches |
| 491 |
*/ |
| 492 |
private $switches; |
| 493 |
/** |
| 494 |
* @return Configuration |
| 495 |
*/ |
| 496 |
public static function fromDefaults() |
| 497 |
{ |
| 498 |
return new self(\Kibo\Phast\Environment\DefaultConfiguration::get()); |
| 499 |
} |
| 500 |
/** |
| 501 |
* Configuration constructor. |
| 502 |
* @param array $sourceConfig |
| 503 |
*/ |
| 504 |
public function __construct(array $sourceConfig) |
| 505 |
{ |
| 506 |
$this->sourceConfig = $sourceConfig; |
| 507 |
if (!isset($this->sourceConfig['switches'])) { |
| 508 |
$this->switches = new \Kibo\Phast\Environment\Switches(); |
| 509 |
} else { |
| 510 |
$this->switches = \Kibo\Phast\Environment\Switches::fromArray($this->sourceConfig['switches']); |
| 511 |
} |
| 512 |
} |
| 513 |
/** |
| 514 |
* @param Configuration $config |
| 515 |
* @return $this |
| 516 |
*/ |
| 517 |
public function withUserConfiguration(\Kibo\Phast\Environment\Configuration $config) |
| 518 |
{ |
| 519 |
$result = $this->recursiveMerge($this->sourceConfig, $config->sourceConfig); |
| 520 |
return new self($result); |
| 521 |
} |
| 522 |
public function withServiceRequest(\Kibo\Phast\Services\ServiceRequest $request) |
| 523 |
{ |
| 524 |
$clone = clone $this; |
| 525 |
$clone->switches = $this->switches->merge($request->getSwitches()); |
| 526 |
return $clone; |
| 527 |
} |
| 528 |
public function getRuntimeConfig() |
| 529 |
{ |
| 530 |
$config = $this->sourceConfig; |
| 531 |
$switchables = [&$config['documents']['filters'], &$config['images']['filters'], &$config['logging']['logWriters'], &$config['styles']['filters']]; |
| 532 |
foreach ($switchables as &$switchable) { |
| 533 |
if (!is_array($switchable)) { |
| 534 |
continue; |
| 535 |
} |
| 536 |
$switchable = array_filter($switchable, function ($item) { |
| 537 |
if (!isset($item['enabled'])) { |
| 538 |
return true; |
| 539 |
} |
| 540 |
if ($item['enabled'] === false) { |
| 541 |
return false; |
| 542 |
} |
| 543 |
return $this->switches->isOn($item['enabled']); |
| 544 |
}); |
| 545 |
} |
| 546 |
if (isset($config['images']['enable-cache']) && is_string($config['images']['enable-cache'])) { |
| 547 |
$config['images']['enable-cache'] = $this->switches->isOn($config['images']['enable-cache']); |
| 548 |
} |
| 549 |
$config['switches'] = $this->switches->toArray(); |
| 550 |
return new \Kibo\Phast\Environment\Configuration($config); |
| 551 |
} |
| 552 |
public function toArray() |
| 553 |
{ |
| 554 |
return $this->sourceConfig; |
| 555 |
} |
| 556 |
private function recursiveMerge(array $a1, array $a2) |
| 557 |
{ |
| 558 |
foreach ($a2 as $key => $value) { |
| 559 |
if (isset($a1[$key]) && is_array($a1[$key]) && is_array($value)) { |
| 560 |
$a1[$key] = $this->recursiveMerge($a1[$key], $value); |
| 561 |
} elseif (is_string($key)) { |
| 562 |
$a1[$key] = $value; |
| 563 |
} else { |
| 564 |
$a1[] = $value; |
| 565 |
} |
| 566 |
} |
| 567 |
return $a1; |
| 568 |
} |
| 569 |
} |
| 570 |
namespace Kibo\Phast\Cache; |
| 571 |
|
| 572 |
interface Cache |
| 573 |
{ |
| 574 |
/** |
| 575 |
* @param string $key |
| 576 |
* @param callable|null $cached |
| 577 |
* @param int $expiresIn |
| 578 |
* @return mixed |
| 579 |
*/ |
| 580 |
public function get($key, callable $cached = null, $expiresIn = 0); |
| 581 |
/** |
| 582 |
* @param string $key |
| 583 |
* @param mixed $value |
| 584 |
* @param int $expiresIn |
| 585 |
* @return mixed |
| 586 |
*/ |
| 587 |
public function set($key, $value, $expiresIn = 0); |
| 588 |
} |
| 589 |
namespace Kibo\Phast\Cache\File; |
| 590 |
|
| 591 |
abstract class ProbabilisticExecutor |
| 592 |
{ |
| 593 |
/** |
| 594 |
* @var string |
| 595 |
*/ |
| 596 |
protected $cacheRoot; |
| 597 |
/** |
| 598 |
* @var float |
| 599 |
*/ |
| 600 |
protected $probability = 0; |
| 601 |
/** |
| 602 |
* @var ObjectifiedFunctions |
| 603 |
*/ |
| 604 |
protected $functions; |
| 605 |
protected abstract function execute(); |
| 606 |
protected function __construct(array $config, \Kibo\Phast\Common\ObjectifiedFunctions $functions = null) |
| 607 |
{ |
| 608 |
$this->cacheRoot = $config['cacheRoot']; |
| 609 |
$this->functions = is_null($functions) ? new \Kibo\Phast\Common\ObjectifiedFunctions() : $functions; |
| 610 |
} |
| 611 |
public function __destruct() |
| 612 |
{ |
| 613 |
if ($this->shouldExecute()) { |
| 614 |
$this->execute(); |
| 615 |
} |
| 616 |
} |
| 617 |
private function shouldExecute() |
| 618 |
{ |
| 619 |
if (!$this->functions->file_exists($this->cacheRoot)) { |
| 620 |
return false; |
| 621 |
} |
| 622 |
if ($this->probability <= 0) { |
| 623 |
return false; |
| 624 |
} |
| 625 |
if ($this->probability >= 1) { |
| 626 |
return true; |
| 627 |
} |
| 628 |
return $this->functions->mt_rand(1, round(1 / $this->probability)) == 1; |
| 629 |
} |
| 630 |
protected function getCacheFiles($path) |
| 631 |
{ |
| 632 |
/** @var \SplFileInfo $item */ |
| 633 |
foreach ($this->makeFileSystemIterator($path) as $item) { |
| 634 |
if ($this->isShard($item)) { |
| 635 |
foreach ($this->getCacheFiles($item->getRealPath()) as $item) { |
| 636 |
(yield $item); |
| 637 |
} |
| 638 |
} elseif ($this->isCacheEntry($item)) { |
| 639 |
(yield $item); |
| 640 |
} |
| 641 |
} |
| 642 |
} |
| 643 |
/** |
| 644 |
* @return \Iterator |
| 645 |
*/ |
| 646 |
protected function makeFileSystemIterator($path) |
| 647 |
{ |
| 648 |
try { |
| 649 |
$items = iterator_to_array(new \FilesystemIterator($path)); |
| 650 |
shuffle($items); |
| 651 |
return new \ArrayIterator($items); |
| 652 |
} catch (\Exception $e) { |
| 653 |
return new \ArrayIterator([]); |
| 654 |
} |
| 655 |
} |
| 656 |
protected function isShard(\SplFileInfo $item) |
| 657 |
{ |
| 658 |
return $item->isDir() && !$item->isLink() && preg_match('/^[a-f\\d]{2}$/', $item->getFilename()); |
| 659 |
} |
| 660 |
protected function isCacheEntry(\SplFileInfo $item) |
| 661 |
{ |
| 662 |
return $item->isFile() && preg_match('/^[a-f\\d]{32}-/', $item->getFilename()); |
| 663 |
} |
| 664 |
} |
| 665 |
namespace Kibo\Phast\Cache\File; |
| 666 |
|
| 667 |
class GarbageCollector extends \Kibo\Phast\Cache\File\ProbabilisticExecutor |
| 668 |
{ |
| 669 |
/** |
| 670 |
* @var integer |
| 671 |
*/ |
| 672 |
private $shardingDepth; |
| 673 |
/** |
| 674 |
* @var integer |
| 675 |
*/ |
| 676 |
private $gcMaxAge; |
| 677 |
/** |
| 678 |
* @var integer |
| 679 |
*/ |
| 680 |
private $gcMaxItems; |
| 681 |
public function __construct(array $config, \Kibo\Phast\Common\ObjectifiedFunctions $functions = null) |
| 682 |
{ |
| 683 |
$this->shardingDepth = $config['shardingDepth']; |
| 684 |
$this->gcMaxAge = $config['garbageCollection']['maxAge']; |
| 685 |
$this->gcMaxItems = $config['garbageCollection']['maxItems']; |
| 686 |
$this->probability = $config['garbageCollection']['probability']; |
| 687 |
parent::__construct($config, $functions); |
| 688 |
} |
| 689 |
protected function execute() |
| 690 |
{ |
| 691 |
$files = $this->getCacheFiles($this->cacheRoot); |
| 692 |
$deleted = 0; |
| 693 |
/** @var \SplFileInfo $file */ |
| 694 |
foreach ($this->filterOldFiles($files) as $file) { |
| 695 |
@$this->functions->unlink($file->getRealPath()); |
| 696 |
$deleted++; |
| 697 |
if ($deleted == $this->gcMaxItems) { |
| 698 |
break; |
| 699 |
} |
| 700 |
} |
| 701 |
} |
| 702 |
/** |
| 703 |
* @param \Iterator $files |
| 704 |
* @return \Generator |
| 705 |
*/ |
| 706 |
private function filterOldFiles(\Iterator $files) |
| 707 |
{ |
| 708 |
$maxTimeModified = time() - $this->gcMaxAge; |
| 709 |
/** @var \SplFileInfo $file */ |
| 710 |
foreach ($files as $file) { |
| 711 |
if ($file->getMTime() < $maxTimeModified) { |
| 712 |
(yield $file); |
| 713 |
} |
| 714 |
} |
| 715 |
} |
| 716 |
} |
| 717 |
namespace Kibo\Phast\Cache\File; |
| 718 |
|
| 719 |
class Cache implements \Kibo\Phast\Cache\Cache |
| 720 |
{ |
| 721 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 722 |
const VERSION = '3'; |
| 723 |
/** |
| 724 |
* @var GarbageCollector |
| 725 |
*/ |
| 726 |
private static $garbageCollector; |
| 727 |
/** |
| 728 |
* @var DiskCleanup |
| 729 |
*/ |
| 730 |
private static $diskCleanup; |
| 731 |
/** |
| 732 |
* @var string |
| 733 |
*/ |
| 734 |
private $cacheRoot; |
| 735 |
/** |
| 736 |
* @var string |
| 737 |
*/ |
| 738 |
private $cacheNS; |
| 739 |
/** |
| 740 |
* @var integer |
| 741 |
*/ |
| 742 |
private $shardingDepth; |
| 743 |
/** |
| 744 |
* @var integer |
| 745 |
*/ |
| 746 |
private $gcMaxAge; |
| 747 |
/** |
| 748 |
* @var ObjectifiedFunctions |
| 749 |
*/ |
| 750 |
private $functions; |
| 751 |
/** |
| 752 |
* @var System |
| 753 |
*/ |
| 754 |
private $system; |
| 755 |
public function __construct(array $config, $cacheNamespace, \Kibo\Phast\Common\ObjectifiedFunctions $functions = null) |
| 756 |
{ |
| 757 |
$this->cacheRoot = $config['cacheRoot']; |
| 758 |
$this->shardingDepth = $config['shardingDepth']; |
| 759 |
$this->gcMaxAge = $config['garbageCollection']['maxAge']; |
| 760 |
$this->cacheNS = $cacheNamespace; |
| 761 |
if ($functions) { |
| 762 |
$this->functions = $functions; |
| 763 |
} else { |
| 764 |
$this->functions = new \Kibo\Phast\Common\ObjectifiedFunctions(); |
| 765 |
} |
| 766 |
$this->system = new \Kibo\Phast\Common\System($this->functions); |
| 767 |
if (!isset(self::$garbageCollector)) { |
| 768 |
self::$garbageCollector = new \Kibo\Phast\Cache\File\GarbageCollector($config, $this->functions); |
| 769 |
self::$diskCleanup = new \Kibo\Phast\Cache\File\DiskCleanup($config, $this->functions); |
| 770 |
} |
| 771 |
} |
| 772 |
public function get($key, callable $cached = null, $expiresIn = 0) |
| 773 |
{ |
| 774 |
$contents = $this->getFromCache($key); |
| 775 |
if (!is_null($contents)) { |
| 776 |
return $contents; |
| 777 |
} |
| 778 |
if (is_null($cached)) { |
| 779 |
return null; |
| 780 |
} |
| 781 |
$contents = $cached(); |
| 782 |
$this->storeCache($key, $contents, $expiresIn); |
| 783 |
return $contents; |
| 784 |
} |
| 785 |
public function set($key, $value, $expiresIn = 0) |
| 786 |
{ |
| 787 |
$this->storeCache($key, $value, $expiresIn); |
| 788 |
} |
| 789 |
/** |
| 790 |
* @return GarbageCollector |
| 791 |
*/ |
| 792 |
public function getGarbageCollector() |
| 793 |
{ |
| 794 |
return self::$garbageCollector; |
| 795 |
} |
| 796 |
/** |
| 797 |
* @return DiskCleanup |
| 798 |
*/ |
| 799 |
public function getDiskCleanup() |
| 800 |
{ |
| 801 |
return self::$diskCleanup; |
| 802 |
} |
| 803 |
private function getCacheDir($key) |
| 804 |
{ |
| 805 |
$hashedKey = $this->getHashedKey($key); |
| 806 |
$parts = [$this->cacheRoot]; |
| 807 |
for ($i = 0; $i < $this->shardingDepth * 2; $i += 2) { |
| 808 |
$parts[] = substr($hashedKey, $i, 2); |
| 809 |
} |
| 810 |
return join('/', $parts); |
| 811 |
} |
| 812 |
private function getCacheFilename($key) |
| 813 |
{ |
| 814 |
return $this->getCacheDir($key) . '/' . $this->getHashedKey($key) . '-' . ltrim($this->cacheNS, '/'); |
| 815 |
} |
| 816 |
private function getHashedKey($key) |
| 817 |
{ |
| 818 |
return md5($key); |
| 819 |
} |
| 820 |
private function storeCache($key, $contents, $expiresIn) |
| 821 |
{ |
| 822 |
$dir = $this->getCacheDir($key); |
| 823 |
if (!file_exists($dir)) { |
| 824 |
@mkdir($dir, 0700, true); |
| 825 |
} |
| 826 |
if (($uid = $this->system->getUserId()) && $uid !== $this->functions->fileowner($this->cacheRoot)) { |
| 827 |
$this->logger()->critical('Phast: FileCache: Cache root {cacheRoot} owned by {fileOwner}, but process user is {userId}!', ['cacheRoot' => $this->cacheRoot, 'fileOwner' => fileowner($this->cacheRoot), 'userId' => $uid]); |
| 828 |
return; |
| 829 |
} |
| 830 |
$file = $this->getCacheFilename($key); |
| 831 |
$expirationTime = $expiresIn > 0 ? $this->functions->time() + $expiresIn : 0; |
| 832 |
$serialized = serialize($contents); |
| 833 |
$serialized = implode(' ', [$expirationTime, self::VERSION, md5($serialized), $serialized]); |
| 834 |
$result = @$this->functions->file_put_contents($file, $serialized); |
| 835 |
if ($result === false) { |
| 836 |
@chmod($file, 0600); |
| 837 |
@unlink($file); |
| 838 |
$result = @$this->functions->file_put_contents($file, $serialized); |
| 839 |
} |
| 840 |
if ($result !== strlen($serialized)) { |
| 841 |
$this->logger()->critical('Phast: FileCache: Error writing to file {filename}. {written} of {total} bytes written!', ['filename' => $file, 'written' => json_encode($result), 'total' => strlen($serialized)]); |
| 842 |
} |
| 843 |
} |
| 844 |
private function getFromCache($key) |
| 845 |
{ |
| 846 |
$file = $this->getCacheFilename($key); |
| 847 |
$contents = @$this->functions->file_get_contents($file); |
| 848 |
if ($contents === false) { |
| 849 |
return null; |
| 850 |
} |
| 851 |
@(list($expirationTime, $version, $data) = explode(' ', $contents, 3)); |
| 852 |
if ($version === '2') { |
| 853 |
$data = unserialize($data); |
| 854 |
} elseif ($version === self::VERSION) { |
| 855 |
@(list($hash, $data) = explode(' ', $data, 2)); |
| 856 |
if (md5($data) != $hash) { |
| 857 |
$this->logger()->error('Phast: FileCache: Cache file was corrupted: {file}', ['file' => $file]); |
| 858 |
return null; |
| 859 |
} |
| 860 |
$data = unserialize($data); |
| 861 |
} else { |
| 862 |
$this->logger()->debug('Phast: FileCache: Refusing to read old cache file {file}', ['file' => $file]); |
| 863 |
return null; |
| 864 |
} |
| 865 |
if ($expirationTime > $this->functions->time() || $expirationTime == 0) { |
| 866 |
if ($this->functions->time() - @$this->functions->filectime($file) >= round($this->gcMaxAge / 10)) { |
| 867 |
@$this->functions->touch($file); |
| 868 |
} |
| 869 |
return $data; |
| 870 |
} |
| 871 |
return null; |
| 872 |
} |
| 873 |
} |
| 874 |
namespace Kibo\Phast\Cache\File; |
| 875 |
|
| 876 |
class DiskCleanup extends \Kibo\Phast\Cache\File\ProbabilisticExecutor |
| 877 |
{ |
| 878 |
/** |
| 879 |
* @var integer |
| 880 |
*/ |
| 881 |
private $maxSize; |
| 882 |
/** |
| 883 |
* @var float |
| 884 |
*/ |
| 885 |
private $portionToFree; |
| 886 |
public function __construct(array $config, \Kibo\Phast\Common\ObjectifiedFunctions $functions = null) |
| 887 |
{ |
| 888 |
$this->maxSize = $config['diskCleanup']['maxSize']; |
| 889 |
$this->probability = $config['diskCleanup']['probability']; |
| 890 |
$this->portionToFree = $config['diskCleanup']['portionToFree']; |
| 891 |
parent::__construct($config, $functions); |
| 892 |
} |
| 893 |
protected function execute() |
| 894 |
{ |
| 895 |
$usedSpace = $this->calculateUsedSpace(); |
| 896 |
$neededSpace = round($this->portionToFree * $this->maxSize); |
| 897 |
$bytesToDelete = $usedSpace - $this->maxSize + $neededSpace; |
| 898 |
$deletedBytes = 0; |
| 899 |
/** @var \SplFileInfo $file */ |
| 900 |
foreach ($this->getCacheFiles($this->cacheRoot) as $file) { |
| 901 |
if ($deletedBytes >= $bytesToDelete) { |
| 902 |
break; |
| 903 |
} |
| 904 |
$deletedBytes += $file->getSize(); |
| 905 |
@unlink($file->getRealPath()); |
| 906 |
} |
| 907 |
} |
| 908 |
private function calculateUsedSpace() |
| 909 |
{ |
| 910 |
$size = 0; |
| 911 |
/** @var \SplFileInfo $file */ |
| 912 |
foreach ($this->getCacheFiles($this->cacheRoot) as $file) { |
| 913 |
$size += $file->getSize(); |
| 914 |
} |
| 915 |
return $size; |
| 916 |
} |
| 917 |
} |
| 918 |
namespace Kibo\Phast; |
| 919 |
|
| 920 |
class PhastDocumentFilters |
| 921 |
{ |
| 922 |
const DOCUMENT_PATTERN = "~\n \\s* (<\\?xml[^>]*>)?\n (\\s* <!--(.*?)-->)*\n \\s* (<!doctype\\s+html[^>]*>)?\n (\\s* <!--(.*?)-->)*\n \\s* <html (?<amp> [^>]* \\s ( amp | \342\232\241 ) [\\s=>] )?\n .*\n ( </body> | </html> )\n ~xsiA"; |
| 923 |
/** |
| 924 |
* @return ?OutputBufferHandler |
| 925 |
*/ |
| 926 |
public static function deploy(array $userConfig = array()) |
| 927 |
{ |
| 928 |
$runtimeConfig = self::configure($userConfig); |
| 929 |
if (!$runtimeConfig) { |
| 930 |
return null; |
| 931 |
} |
| 932 |
$handler = new \Kibo\Phast\Common\OutputBufferHandler($runtimeConfig['documents']['maxBufferSizeToApply'], function ($html, $applyCheckBuffer) use($runtimeConfig) { |
| 933 |
return self::applyWithRuntimeConfig($html, $runtimeConfig, $applyCheckBuffer); |
| 934 |
}); |
| 935 |
$handler->install(); |
| 936 |
\Kibo\Phast\Logging\Log::info('Phast deployed!'); |
| 937 |
return $handler; |
| 938 |
} |
| 939 |
public static function apply($html, array $userConfig) |
| 940 |
{ |
| 941 |
$runtimeConfig = self::configure($userConfig); |
| 942 |
if (!$runtimeConfig) { |
| 943 |
return $html; |
| 944 |
} |
| 945 |
return self::applyWithRuntimeConfig($html, $runtimeConfig); |
| 946 |
} |
| 947 |
private static function configure(array $userConfig) |
| 948 |
{ |
| 949 |
$request = \Kibo\Phast\Services\ServiceRequest::fromHTTPRequest(\Kibo\Phast\HTTP\Request::fromGlobals()); |
| 950 |
$runtimeConfig = \Kibo\Phast\Environment\Configuration::fromDefaults()->withUserConfiguration(new \Kibo\Phast\Environment\Configuration($userConfig))->withServiceRequest($request)->getRuntimeConfig()->toArray(); |
| 951 |
\Kibo\Phast\Logging\Log::init($runtimeConfig['logging'], $request, 'dom-filters'); |
| 952 |
\Kibo\Phast\Services\ServiceRequest::setDefaultSerializationMode($runtimeConfig['serviceRequestFormat']); |
| 953 |
if ($request->hasRequestSwitchesSet()) { |
| 954 |
\Kibo\Phast\Logging\Log::info('Request has switches set! Sending "noindex" header!'); |
| 955 |
header('X-Robots-Tag: noindex'); |
| 956 |
} |
| 957 |
if (!$runtimeConfig['switches']['phast']) { |
| 958 |
\Kibo\Phast\Logging\Log::info('Phast is off. Skipping document filter deployment!'); |
| 959 |
return; |
| 960 |
} |
| 961 |
return $runtimeConfig; |
| 962 |
} |
| 963 |
private static function applyWithRuntimeConfig($buffer, $runtimeConfig, $applyCheckBuffer = null) |
| 964 |
{ |
| 965 |
if (is_null($applyCheckBuffer)) { |
| 966 |
$applyCheckBuffer = $buffer; |
| 967 |
} |
| 968 |
if (!self::shouldApply($applyCheckBuffer, $runtimeConfig)) { |
| 969 |
\Kibo\Phast\Logging\Log::info("Buffer ({bufferSize} bytes) doesn't look like html! Not applying filters", ['bufferSize' => strlen($applyCheckBuffer)]); |
| 970 |
return $buffer; |
| 971 |
} |
| 972 |
$compositeFilter = (new \Kibo\Phast\Filters\HTML\Composite\Factory())->make($runtimeConfig); |
| 973 |
if (self::isAMP($applyCheckBuffer)) { |
| 974 |
$compositeFilter->selectFilters(function ($filter) { |
| 975 |
return $filter instanceof \Kibo\Phast\Filters\HTML\AMPCompatibleFilter; |
| 976 |
}); |
| 977 |
} |
| 978 |
return $compositeFilter->apply($buffer); |
| 979 |
} |
| 980 |
private static function shouldApply($buffer, $runtimeConfig) |
| 981 |
{ |
| 982 |
if ($runtimeConfig['optimizeHTMLDocumentsOnly']) { |
| 983 |
return preg_match(self::DOCUMENT_PATTERN, $buffer); |
| 984 |
} |
| 985 |
return strpos($buffer, '<') !== false; |
| 986 |
} |
| 987 |
private static function isAMP($buffer) |
| 988 |
{ |
| 989 |
return preg_match(self::DOCUMENT_PATTERN, $buffer, $match) && !empty($match['amp']); |
| 990 |
} |
| 991 |
} |
| 992 |
namespace Kibo\Phast\Diagnostics; |
| 993 |
|
| 994 |
interface Diagnostics |
| 995 |
{ |
| 996 |
/** |
| 997 |
* @param array $config |
| 998 |
*/ |
| 999 |
public function diagnose(array $config); |
| 1000 |
} |
| 1001 |
namespace Kibo\Phast\Diagnostics; |
| 1002 |
|
| 1003 |
class SystemDiagnostics |
| 1004 |
{ |
| 1005 |
/** |
| 1006 |
* @param array $userConfigArr |
| 1007 |
* @return Status[] |
| 1008 |
*/ |
| 1009 |
public function run(array $userConfigArr) |
| 1010 |
{ |
| 1011 |
$results = []; |
| 1012 |
$userConfig = new \Kibo\Phast\Environment\Configuration($userConfigArr); |
| 1013 |
$config = \Kibo\Phast\Environment\Configuration::fromDefaults()->withUserConfiguration($userConfig); |
| 1014 |
foreach ($this->getExaminedItems($config) as $type => $group) { |
| 1015 |
foreach ($group['items'] as $name) { |
| 1016 |
$enabled = call_user_func($group['enabled'], $name); |
| 1017 |
$package = \Kibo\Phast\Environment\Package::fromPackageClass($name, $type); |
| 1018 |
try { |
| 1019 |
$diagnostic = $package->getDiagnostics(); |
| 1020 |
$diagnostic->diagnose($config->toArray()); |
| 1021 |
$results[] = new \Kibo\Phast\Diagnostics\Status($package, true, '', $enabled); |
| 1022 |
} catch (\Kibo\Phast\Environment\Exceptions\PackageHasNoDiagnosticsException $e) { |
| 1023 |
$results[] = new \Kibo\Phast\Diagnostics\Status($package, true, '', $enabled); |
| 1024 |
} catch (\Kibo\Phast\Exceptions\RuntimeException $e) { |
| 1025 |
$results[] = new \Kibo\Phast\Diagnostics\Status($package, false, $e->getMessage(), $enabled); |
| 1026 |
} catch (\Exception $e) { |
| 1027 |
$results[] = new \Kibo\Phast\Diagnostics\Status($package, false, sprintf('Unknown error: Exception: %s, Message: %s, Code: %s', get_class($e), $e->getMessage(), $e->getCode()), $enabled); |
| 1028 |
} |
| 1029 |
} |
| 1030 |
} |
| 1031 |
return $results; |
| 1032 |
} |
| 1033 |
private function getExaminedItems(\Kibo\Phast\Environment\Configuration $config) |
| 1034 |
{ |
| 1035 |
$runtimeConfig = $config->getRuntimeConfig()->toArray(); |
| 1036 |
$configArr = $config->toArray(); |
| 1037 |
return ['HTMLFilter' => ['items' => array_keys($configArr['documents']['filters']), 'enabled' => function ($filter) use($runtimeConfig) { |
| 1038 |
return isset($runtimeConfig['documents']['filters'][$filter]); |
| 1039 |
}], 'ImageFilter' => ['items' => array_keys($configArr['images']['filters']), 'enabled' => function ($filter) use($runtimeConfig) { |
| 1040 |
return isset($runtimeConfig['images']['filters'][$filter]); |
| 1041 |
}], 'Cache' => ['items' => [\Kibo\Phast\Cache\File\Cache::class], 'enabled' => function () { |
| 1042 |
return true; |
| 1043 |
}]]; |
| 1044 |
} |
| 1045 |
} |
| 1046 |
namespace Kibo\Phast\Diagnostics; |
| 1047 |
|
| 1048 |
class Status implements \JsonSerializable |
| 1049 |
{ |
| 1050 |
/** |
| 1051 |
* @var Package |
| 1052 |
*/ |
| 1053 |
private $package; |
| 1054 |
/** |
| 1055 |
* @var bool |
| 1056 |
*/ |
| 1057 |
private $available; |
| 1058 |
/** |
| 1059 |
* @var string |
| 1060 |
*/ |
| 1061 |
private $reason; |
| 1062 |
/** |
| 1063 |
* @var bool |
| 1064 |
*/ |
| 1065 |
private $enabled; |
| 1066 |
/** |
| 1067 |
* Status constructor. |
| 1068 |
* @param Package $package |
| 1069 |
* @param bool $available |
| 1070 |
* @param string $reason |
| 1071 |
* @param bool $enabled |
| 1072 |
*/ |
| 1073 |
public function __construct(\Kibo\Phast\Environment\Package $package, $available, $reason, $enabled) |
| 1074 |
{ |
| 1075 |
$this->package = $package; |
| 1076 |
$this->available = $available; |
| 1077 |
$this->reason = $reason; |
| 1078 |
$this->enabled = $enabled; |
| 1079 |
} |
| 1080 |
/** |
| 1081 |
* @return Package |
| 1082 |
*/ |
| 1083 |
public function getPackage() |
| 1084 |
{ |
| 1085 |
return $this->package; |
| 1086 |
} |
| 1087 |
/** |
| 1088 |
* @return bool |
| 1089 |
*/ |
| 1090 |
public function isAvailable() |
| 1091 |
{ |
| 1092 |
return $this->available; |
| 1093 |
} |
| 1094 |
/** |
| 1095 |
* @return string |
| 1096 |
*/ |
| 1097 |
public function getReason() |
| 1098 |
{ |
| 1099 |
return $this->reason; |
| 1100 |
} |
| 1101 |
/** |
| 1102 |
* @return bool |
| 1103 |
*/ |
| 1104 |
public function isEnabled() |
| 1105 |
{ |
| 1106 |
return $this->enabled; |
| 1107 |
} |
| 1108 |
/** |
| 1109 |
* @return array |
| 1110 |
*/ |
| 1111 |
public function toArray() |
| 1112 |
{ |
| 1113 |
return ['package' => ['type' => $this->package->getType(), 'name' => $this->package->getNamespace()], 'available' => $this->available, 'reason' => $this->reason, 'enabled' => $this->enabled]; |
| 1114 |
} |
| 1115 |
public function jsonSerialize() |
| 1116 |
{ |
| 1117 |
return $this->toArray(); |
| 1118 |
} |
| 1119 |
} |
| 1120 |
namespace Kibo\Phast\Retrievers; |
| 1121 |
|
| 1122 |
interface Retriever |
| 1123 |
{ |
| 1124 |
/** |
| 1125 |
* @param URL $url |
| 1126 |
* @return string|bool |
| 1127 |
*/ |
| 1128 |
public function retrieve(\Kibo\Phast\ValueObjects\URL $url); |
| 1129 |
/** |
| 1130 |
* @param URL $url |
| 1131 |
* @return integer|bool |
| 1132 |
*/ |
| 1133 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\URL $url); |
| 1134 |
} |
| 1135 |
namespace Kibo\Phast\Retrievers; |
| 1136 |
|
| 1137 |
trait DynamicCacheSaltTrait |
| 1138 |
{ |
| 1139 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\URL $url) |
| 1140 |
{ |
| 1141 |
return md5($url->toString()) . '-' . floor(time() / 7200); |
| 1142 |
} |
| 1143 |
} |
| 1144 |
namespace Kibo\Phast\Retrievers; |
| 1145 |
|
| 1146 |
class RemoteRetriever implements \Kibo\Phast\Retrievers\Retriever |
| 1147 |
{ |
| 1148 |
use \Kibo\Phast\Retrievers\DynamicCacheSaltTrait; |
| 1149 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 1150 |
private $client; |
| 1151 |
public function __construct(\Kibo\Phast\HTTP\Client $client) |
| 1152 |
{ |
| 1153 |
$this->client = $client; |
| 1154 |
} |
| 1155 |
public function retrieve(\Kibo\Phast\ValueObjects\URL $url) |
| 1156 |
{ |
| 1157 |
$cdnLoop = ['Phast']; |
| 1158 |
if (!empty($_SERVER['HTTP_CDN_LOOP'])) { |
| 1159 |
$cdnLoop[] = $_SERVER['HTTP_CDN_LOOP']; |
| 1160 |
} |
| 1161 |
try { |
| 1162 |
$response = $this->client->get($url, ['User-Agent' => 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:56.0) Gecko/20100101 Firefox/56.0', 'CDN-Loop' => implode(', ', $cdnLoop)]); |
| 1163 |
} catch (\Exception $e) { |
| 1164 |
$this->logger()->warning('Caught {cls} while fetching {url}: ({code}) {message}', ['cls' => get_class($e), 'url' => (string) $url, 'code' => $e->getCode(), 'message' => $e->getMessage()]); |
| 1165 |
return false; |
| 1166 |
} |
| 1167 |
return $response->getContent(); |
| 1168 |
} |
| 1169 |
} |
| 1170 |
namespace Kibo\Phast\Retrievers; |
| 1171 |
|
| 1172 |
class RemoteRetrieverFactory |
| 1173 |
{ |
| 1174 |
public function make(array $config) |
| 1175 |
{ |
| 1176 |
return new \Kibo\Phast\Retrievers\RemoteRetriever((new \Kibo\Phast\HTTP\ClientFactory())->make($config)); |
| 1177 |
} |
| 1178 |
} |
| 1179 |
namespace Kibo\Phast\Retrievers; |
| 1180 |
|
| 1181 |
class UniversalRetriever implements \Kibo\Phast\Retrievers\Retriever |
| 1182 |
{ |
| 1183 |
/** |
| 1184 |
* @var Retriever[] |
| 1185 |
*/ |
| 1186 |
private $retrievers = array(); |
| 1187 |
public function retrieve(\Kibo\Phast\ValueObjects\URL $url) |
| 1188 |
{ |
| 1189 |
return $this->iterateRetrievers(function (\Kibo\Phast\Retrievers\Retriever $retriever) use($url) { |
| 1190 |
return $retriever->retrieve($url); |
| 1191 |
}); |
| 1192 |
} |
| 1193 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\URL $url) |
| 1194 |
{ |
| 1195 |
return $this->iterateRetrievers(function (\Kibo\Phast\Retrievers\Retriever $retriever) use($url) { |
| 1196 |
return $retriever->getCacheSalt($url); |
| 1197 |
}); |
| 1198 |
} |
| 1199 |
private function iterateRetrievers(callable $callback) |
| 1200 |
{ |
| 1201 |
foreach ($this->retrievers as $retriever) { |
| 1202 |
$result = $callback($retriever); |
| 1203 |
if ($result !== false) { |
| 1204 |
return $result; |
| 1205 |
} |
| 1206 |
} |
| 1207 |
return false; |
| 1208 |
} |
| 1209 |
public function addRetriever(\Kibo\Phast\Retrievers\Retriever $retriever) |
| 1210 |
{ |
| 1211 |
$this->retrievers[] = $retriever; |
| 1212 |
} |
| 1213 |
} |
| 1214 |
namespace Kibo\Phast\Retrievers; |
| 1215 |
|
| 1216 |
class LocalRetriever implements \Kibo\Phast\Retrievers\Retriever |
| 1217 |
{ |
| 1218 |
/** |
| 1219 |
* @var array |
| 1220 |
*/ |
| 1221 |
private $map; |
| 1222 |
/** |
| 1223 |
* @var ObjectifiedFunctions |
| 1224 |
*/ |
| 1225 |
private $funcs; |
| 1226 |
/** |
| 1227 |
* LocalRetriever constructor. |
| 1228 |
* |
| 1229 |
* @param array $map |
| 1230 |
* @param ObjectifiedFunctions|null $functions |
| 1231 |
*/ |
| 1232 |
public function __construct(array $map, \Kibo\Phast\Common\ObjectifiedFunctions $functions = null) |
| 1233 |
{ |
| 1234 |
$this->map = $map; |
| 1235 |
if ($functions) { |
| 1236 |
$this->funcs = $functions; |
| 1237 |
} else { |
| 1238 |
$this->funcs = new \Kibo\Phast\Common\ObjectifiedFunctions(); |
| 1239 |
} |
| 1240 |
} |
| 1241 |
public static function getAllowedExtensions() |
| 1242 |
{ |
| 1243 |
return ['css', 'js', 'bmp', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'ico', 'svg', 'txt']; |
| 1244 |
} |
| 1245 |
public function retrieve(\Kibo\Phast\ValueObjects\URL $url) |
| 1246 |
{ |
| 1247 |
return $this->guard($url, function ($file) { |
| 1248 |
return @$this->funcs->file_get_contents($file); |
| 1249 |
}); |
| 1250 |
} |
| 1251 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\URL $url) |
| 1252 |
{ |
| 1253 |
return $this->guard($url, function ($file) { |
| 1254 |
$size = @$this->funcs->filesize($file); |
| 1255 |
$mtime = @$this->funcs->filectime($file); |
| 1256 |
if ($size === false && $mtime === false) { |
| 1257 |
return ''; |
| 1258 |
} |
| 1259 |
return "{$mtime}-{$size}"; |
| 1260 |
}); |
| 1261 |
} |
| 1262 |
public function getSize(\Kibo\Phast\ValueObjects\URL $url) |
| 1263 |
{ |
| 1264 |
return $this->guard($url, function ($file) { |
| 1265 |
return @$this->funcs->filesize($file); |
| 1266 |
}); |
| 1267 |
} |
| 1268 |
private function guard(\Kibo\Phast\ValueObjects\URL $url, callable $cb) |
| 1269 |
{ |
| 1270 |
if (!in_array($this->getExtensionForURL($url), self::getAllowedExtensions())) { |
| 1271 |
return false; |
| 1272 |
} |
| 1273 |
$file = $this->getFileForURL($url); |
| 1274 |
if ($file === false) { |
| 1275 |
return false; |
| 1276 |
} |
| 1277 |
return $cb($file); |
| 1278 |
} |
| 1279 |
private function getExtensionForURL(\Kibo\Phast\ValueObjects\URL $url) |
| 1280 |
{ |
| 1281 |
$dotPosition = strrpos($url->getDecodedPath(), '.'); |
| 1282 |
if ($dotPosition === false) { |
| 1283 |
return ''; |
| 1284 |
} |
| 1285 |
return strtolower(substr($url->getDecodedPath(), $dotPosition + 1)); |
| 1286 |
} |
| 1287 |
private function getFileForURL(\Kibo\Phast\ValueObjects\URL $url) |
| 1288 |
{ |
| 1289 |
if (!isset($this->map[$url->getHost()])) { |
| 1290 |
return false; |
| 1291 |
} |
| 1292 |
$submap = $this->map[$url->getHost()]; |
| 1293 |
if (!is_array($submap)) { |
| 1294 |
return $this->appendNormalized($submap, $url->getDecodedPath()); |
| 1295 |
} |
| 1296 |
$selectedPath = null; |
| 1297 |
$selectedRoot = null; |
| 1298 |
foreach ($submap as $prefix => $root) { |
| 1299 |
$pattern = '~^(?=/)/*?(?:' . str_replace('~', '\\~', $prefix) . ')(?<path>/*(?<=/).*)~'; |
| 1300 |
if (preg_match($pattern, $url->getDecodedPath(), $match) && ($selectedPath === null || strlen($match['path']) < strlen($selectedPath))) { |
| 1301 |
$selectedRoot = $root; |
| 1302 |
$selectedPath = $match['path']; |
| 1303 |
} |
| 1304 |
} |
| 1305 |
if ($selectedPath === null) { |
| 1306 |
return false; |
| 1307 |
} |
| 1308 |
return $this->appendNormalized($selectedRoot, $selectedPath); |
| 1309 |
} |
| 1310 |
private function appendNormalized($target, $appended) |
| 1311 |
{ |
| 1312 |
$appended = explode("\0", $appended)[0]; |
| 1313 |
$appended = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $appended); |
| 1314 |
$absolutes = []; |
| 1315 |
foreach (explode(DIRECTORY_SEPARATOR, $appended) as $part) { |
| 1316 |
if ($part == '' || $part == '.') { |
| 1317 |
} elseif ($part == '..') { |
| 1318 |
if (array_pop($absolutes) === null) { |
| 1319 |
return false; |
| 1320 |
} |
| 1321 |
} else { |
| 1322 |
$absolutes[] = $part; |
| 1323 |
} |
| 1324 |
} |
| 1325 |
return $target . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $absolutes); |
| 1326 |
} |
| 1327 |
} |
| 1328 |
namespace Kibo\Phast\Retrievers; |
| 1329 |
|
| 1330 |
class CachingRetriever implements \Kibo\Phast\Retrievers\Retriever |
| 1331 |
{ |
| 1332 |
use \Kibo\Phast\Retrievers\DynamicCacheSaltTrait { |
| 1333 |
getCacheSalt as getDynamicCacheSalt; |
| 1334 |
} |
| 1335 |
/** |
| 1336 |
* @var Cache |
| 1337 |
*/ |
| 1338 |
private $cache; |
| 1339 |
/** |
| 1340 |
* @var Retriever |
| 1341 |
*/ |
| 1342 |
private $retriever; |
| 1343 |
/** |
| 1344 |
* CachingRetriever constructor. |
| 1345 |
* |
| 1346 |
* @param Retriever $retriever |
| 1347 |
* @param Cache $cache |
| 1348 |
* @param int $defaultCacheTime |
| 1349 |
*/ |
| 1350 |
public function __construct(\Kibo\Phast\Cache\Cache $cache, \Kibo\Phast\Retrievers\Retriever $retriever = null, $defaultCacheTime = 0) |
| 1351 |
{ |
| 1352 |
$this->cache = $cache; |
| 1353 |
$this->retriever = $retriever; |
| 1354 |
} |
| 1355 |
public function retrieve(\Kibo\Phast\ValueObjects\URL $url) |
| 1356 |
{ |
| 1357 |
if ($this->retriever) { |
| 1358 |
return $this->getCachedWithRetriever($url); |
| 1359 |
} |
| 1360 |
return $this->getFromCacheOnly($url); |
| 1361 |
} |
| 1362 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\URL $url) |
| 1363 |
{ |
| 1364 |
if ($this->retriever) { |
| 1365 |
return $this->retriever->getCacheSalt($url); |
| 1366 |
} |
| 1367 |
return $this->getDynamicCacheSalt($url); |
| 1368 |
} |
| 1369 |
private function getCachedWithRetriever(\Kibo\Phast\ValueObjects\URL $url) |
| 1370 |
{ |
| 1371 |
return $this->cache->get($this->getCacheKey($url), function () use($url) { |
| 1372 |
return $this->retriever->retrieve($url); |
| 1373 |
}); |
| 1374 |
} |
| 1375 |
private function getFromCacheOnly(\Kibo\Phast\ValueObjects\URL $url) |
| 1376 |
{ |
| 1377 |
$cached = $this->cache->get($this->getCacheKey($url)); |
| 1378 |
if (!$cached) { |
| 1379 |
return false; |
| 1380 |
} |
| 1381 |
return $cached; |
| 1382 |
} |
| 1383 |
private function getCacheKey(\Kibo\Phast\ValueObjects\URL $url) |
| 1384 |
{ |
| 1385 |
return $url . '-' . $this->getCacheSalt($url); |
| 1386 |
} |
| 1387 |
} |
| 1388 |
namespace Kibo\Phast\Retrievers; |
| 1389 |
|
| 1390 |
class PostDataRetriever implements \Kibo\Phast\Retrievers\Retriever |
| 1391 |
{ |
| 1392 |
/** |
| 1393 |
* @var ObjectifiedFunctions |
| 1394 |
*/ |
| 1395 |
private $funcs; |
| 1396 |
private $content; |
| 1397 |
/** |
| 1398 |
* PostDataRetriever constructor. |
| 1399 |
* @param ObjectifiedFunctions $funcs |
| 1400 |
*/ |
| 1401 |
public function __construct(\Kibo\Phast\Common\ObjectifiedFunctions $funcs = null) |
| 1402 |
{ |
| 1403 |
$this->funcs = is_null($funcs) ? new \Kibo\Phast\Common\ObjectifiedFunctions() : $funcs; |
| 1404 |
} |
| 1405 |
public function retrieve(\Kibo\Phast\ValueObjects\URL $url) |
| 1406 |
{ |
| 1407 |
if (!isset($this->content)) { |
| 1408 |
$this->content = $this->funcs->file_get_contents('php://input'); |
| 1409 |
} |
| 1410 |
return $this->content; |
| 1411 |
} |
| 1412 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\URL $url) |
| 1413 |
{ |
| 1414 |
return md5($this->retrieve($url)); |
| 1415 |
} |
| 1416 |
} |
| 1417 |
namespace Kibo\Phast\Filters\HTML\Composite; |
| 1418 |
|
| 1419 |
class Factory |
| 1420 |
{ |
| 1421 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 1422 |
public function make(array $config) |
| 1423 |
{ |
| 1424 |
$composite = new \Kibo\Phast\Filters\HTML\Composite\Filter(\Kibo\Phast\ValueObjects\URL::fromString($config['documents']['baseUrl']), $config['outputServerSideStats']); |
| 1425 |
foreach (array_keys($config['documents']['filters']) as $class) { |
| 1426 |
$package = \Kibo\Phast\Environment\Package::fromPackageClass($class); |
| 1427 |
if ($package->hasFactory()) { |
| 1428 |
$filter = $package->getFactory()->make($config); |
| 1429 |
} elseif (!class_exists($class)) { |
| 1430 |
$this->logger(__METHOD__, __LINE__)->error("Skipping non-existent filter class: {$class}"); |
| 1431 |
continue; |
| 1432 |
} else { |
| 1433 |
$filter = new $class(); |
| 1434 |
} |
| 1435 |
$composite->addHTMLFilter($filter); |
| 1436 |
} |
| 1437 |
return $composite; |
| 1438 |
} |
| 1439 |
} |
| 1440 |
namespace Kibo\Phast\Filters\HTML\Composite; |
| 1441 |
|
| 1442 |
class Filter |
| 1443 |
{ |
| 1444 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 1445 |
/** |
| 1446 |
* @var URL |
| 1447 |
*/ |
| 1448 |
private $baseUrl; |
| 1449 |
private $outputStats; |
| 1450 |
/** |
| 1451 |
* @var HTMLStreamFilter[] |
| 1452 |
*/ |
| 1453 |
private $filters = array(); |
| 1454 |
private $timings = array(); |
| 1455 |
/** |
| 1456 |
* Filter constructor. |
| 1457 |
* @param URL $baseUrl |
| 1458 |
* @param $outputStats |
| 1459 |
*/ |
| 1460 |
public function __construct(\Kibo\Phast\ValueObjects\URL $baseUrl, $outputStats) |
| 1461 |
{ |
| 1462 |
$this->baseUrl = $baseUrl; |
| 1463 |
$this->outputStats = $outputStats; |
| 1464 |
} |
| 1465 |
/** |
| 1466 |
* @param string $buffer |
| 1467 |
* @return string |
| 1468 |
*/ |
| 1469 |
public function apply($buffer) |
| 1470 |
{ |
| 1471 |
$timeStart = microtime(true); |
| 1472 |
try { |
| 1473 |
return $this->tryToApply($buffer, $timeStart); |
| 1474 |
} catch (\Exception $e) { |
| 1475 |
$this->logger()->critical('Phast: CompositeHTMLFilter: {exception} Msg: {message}, Code: {code}, File: {file}, Line: {line}', ['exception' => get_class($e), 'message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile(), 'line' => $e->getLine()]); |
| 1476 |
return $buffer; |
| 1477 |
} |
| 1478 |
} |
| 1479 |
public function addHTMLFilter(\Kibo\Phast\Filters\HTML\HTMLStreamFilter $filter) |
| 1480 |
{ |
| 1481 |
$this->filters[] = $filter; |
| 1482 |
} |
| 1483 |
private function tryToApply($buffer, $timeStart) |
| 1484 |
{ |
| 1485 |
$context = new \Kibo\Phast\Filters\HTML\HTMLPageContext($this->baseUrl); |
| 1486 |
$elements = (new \Kibo\Phast\Parsing\HTML\PCRETokenizer())->tokenize($buffer); |
| 1487 |
foreach ($this->filters as $filter) { |
| 1488 |
$this->logger()->info('Starting {filter}', ['filter' => get_class($filter)]); |
| 1489 |
$elements = $filter->transformElements($elements, $context); |
| 1490 |
} |
| 1491 |
$output = ''; |
| 1492 |
foreach ($elements as $element) { |
| 1493 |
$output .= $element; |
| 1494 |
} |
| 1495 |
$timeDelta = microtime(true) - $timeStart; |
| 1496 |
if ($this->outputStats) { |
| 1497 |
$output .= sprintf("\n<!-- [Phast] Document optimized in %dms -->\n", $timeDelta * 1000); |
| 1498 |
} |
| 1499 |
return $output; |
| 1500 |
} |
| 1501 |
public function selectFilters($callback) |
| 1502 |
{ |
| 1503 |
$this->filters = array_filter($this->filters, $callback); |
| 1504 |
} |
| 1505 |
} |
| 1506 |
namespace Kibo\Phast\Filters\HTML; |
| 1507 |
|
| 1508 |
interface AMPCompatibleFilter |
| 1509 |
{ |
| 1510 |
} |
| 1511 |
namespace Kibo\Phast\Filters\HTML; |
| 1512 |
|
| 1513 |
interface HTMLStreamFilter |
| 1514 |
{ |
| 1515 |
/** |
| 1516 |
* @param \Traversable $elements |
| 1517 |
* @param HTMLPageContext $context |
| 1518 |
* @return \Traversable |
| 1519 |
*/ |
| 1520 |
public function transformElements(\Traversable $elements, \Kibo\Phast\Filters\HTML\HTMLPageContext $context); |
| 1521 |
} |
| 1522 |
namespace Kibo\Phast\Filters\HTML\MinifyScripts; |
| 1523 |
|
| 1524 |
class Filter implements \Kibo\Phast\Filters\HTML\HTMLStreamFilter |
| 1525 |
{ |
| 1526 |
use \Kibo\Phast\Filters\HTML\Helpers\JSDetectorTrait; |
| 1527 |
private $cache; |
| 1528 |
public function __construct(\Kibo\Phast\Cache\File\Cache $cache) |
| 1529 |
{ |
| 1530 |
$this->cache = $cache; |
| 1531 |
} |
| 1532 |
public function transformElements(\Traversable $elements, \Kibo\Phast\Filters\HTML\HTMLPageContext $context) |
| 1533 |
{ |
| 1534 |
foreach ($elements as $element) { |
| 1535 |
if ($element instanceof \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag && $element->getTagName() === 'script' && ($content = $element->getTextContent()) !== '') { |
| 1536 |
$content = trim($content); |
| 1537 |
if ($this->isJSElement($element) && preg_match('~[()[\\]{};]\\s~', $content)) { |
| 1538 |
$content = preg_replace('~^\\s*<!--\\s*\\n(.*)\\n\\s*-->\\s*$~s', '$1', $content); |
| 1539 |
$content = $this->cache->get(md5($content), function () use($content) { |
| 1540 |
return (new \Kibo\Phast\Common\JSMinifier($content, true))->min(); |
| 1541 |
}); |
| 1542 |
} elseif (($data = @json_decode($content)) !== null && ($newContent = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) !== false) { |
| 1543 |
$content = str_replace('</', '<\\/', $newContent); |
| 1544 |
} |
| 1545 |
$element->setTextContent($content); |
| 1546 |
} |
| 1547 |
(yield $element); |
| 1548 |
} |
| 1549 |
} |
| 1550 |
} |
| 1551 |
namespace Kibo\Phast\Filters\HTML\MinifyScripts; |
| 1552 |
|
| 1553 |
class Factory |
| 1554 |
{ |
| 1555 |
public function make(array $config) |
| 1556 |
{ |
| 1557 |
return new \Kibo\Phast\Filters\HTML\MinifyScripts\Filter(new \Kibo\Phast\Cache\File\Cache($config['cache'], 'minified-inline-scripts')); |
| 1558 |
} |
| 1559 |
} |
| 1560 |
namespace Kibo\Phast\Filters\HTML\ImagesOptimizationService; |
| 1561 |
|
| 1562 |
class ImageURLRewriterFactory |
| 1563 |
{ |
| 1564 |
public function make(array $config, $filterClass = '') |
| 1565 |
{ |
| 1566 |
$signature = (new \Kibo\Phast\Security\ServiceSignatureFactory())->make($config); |
| 1567 |
if (isset($config['documents']['filters'][$filterClass])) { |
| 1568 |
$classConfig = $config['documents']['filters'][$filterClass]; |
| 1569 |
} elseif (isset($config['styles']['filters'][$filterClass])) { |
| 1570 |
$classConfig = $config['styles']['filters'][$filterClass]; |
| 1571 |
} else { |
| 1572 |
$classConfig = []; |
| 1573 |
} |
| 1574 |
if (isset($classConfig['serviceUrl'])) { |
| 1575 |
$serviceUrl = $classConfig['serviceUrl']; |
| 1576 |
} else { |
| 1577 |
$serviceUrl = $config['servicesUrl'] . '?service=images'; |
| 1578 |
} |
| 1579 |
return new \Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageURLRewriter($signature, new \Kibo\Phast\Retrievers\LocalRetriever($config['retrieverMap']), (new \Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageInliningManagerFactory())->make($config), \Kibo\Phast\ValueObjects\URL::fromString($config['documents']['baseUrl']), \Kibo\Phast\ValueObjects\URL::fromString($serviceUrl), $config['images']['whitelist']); |
| 1580 |
} |
| 1581 |
} |
| 1582 |
namespace Kibo\Phast\Filters\HTML\ImagesOptimizationService; |
| 1583 |
|
| 1584 |
class ImageInliningManager |
| 1585 |
{ |
| 1586 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 1587 |
/** |
| 1588 |
* @var Cache |
| 1589 |
*/ |
| 1590 |
private $cache; |
| 1591 |
/** |
| 1592 |
* @var int |
| 1593 |
*/ |
| 1594 |
private $maxImageInliningSize; |
| 1595 |
/** |
| 1596 |
* ImageInliningManager constructor. |
| 1597 |
* @param Cache $cache |
| 1598 |
* @param int $maxImageInliningSize |
| 1599 |
*/ |
| 1600 |
public function __construct(\Kibo\Phast\Cache\Cache $cache, $maxImageInliningSize) |
| 1601 |
{ |
| 1602 |
$this->cache = $cache; |
| 1603 |
$this->maxImageInliningSize = $maxImageInliningSize; |
| 1604 |
} |
| 1605 |
/** |
| 1606 |
* @return int |
| 1607 |
*/ |
| 1608 |
public function getMaxImageInliningSize() |
| 1609 |
{ |
| 1610 |
return $this->maxImageInliningSize; |
| 1611 |
} |
| 1612 |
/** |
| 1613 |
* @param Resource $resource |
| 1614 |
* @return string|null |
| 1615 |
*/ |
| 1616 |
public function getUrlForInlining(\Kibo\Phast\ValueObjects\Resource $resource) |
| 1617 |
{ |
| 1618 |
if ($resource->getMimeType() !== 'image/svg+xml') { |
| 1619 |
return $this->cache->get($this->getCacheKey($resource)); |
| 1620 |
} |
| 1621 |
try { |
| 1622 |
if ($this->hasSizeForInlining($resource)) { |
| 1623 |
return $resource->toDataURL(); |
| 1624 |
} |
| 1625 |
} catch (\Kibo\Phast\Exceptions\ItemNotFoundException $e) { |
| 1626 |
$this->logger()->warning('Could not fetch contents for {url}. Message is {message}', ['url' => $resource->getUrl()->toString(), 'message' => $e->getMessage()]); |
| 1627 |
} |
| 1628 |
return null; |
| 1629 |
} |
| 1630 |
public function maybeStoreForInlining(\Kibo\Phast\ValueObjects\Resource $resource) |
| 1631 |
{ |
| 1632 |
if ($this->shouldStoreForInlining($resource)) { |
| 1633 |
$this->logger()->info('Storing {url} for inlining', ['url' => $resource->getUrl()->toString()]); |
| 1634 |
$this->cache->set($this->getCacheKey($resource), $resource->toDataURL()); |
| 1635 |
} else { |
| 1636 |
$this->logger()->info('Not storing {url} for inlining', ['url' => $resource->getUrl()->toString()]); |
| 1637 |
} |
| 1638 |
} |
| 1639 |
private function shouldStoreForInlining(\Kibo\Phast\ValueObjects\Resource $resource) |
| 1640 |
{ |
| 1641 |
return $this->hasSizeForInlining($resource) && strpos($resource->getMimeType(), 'image/') === 0 && $resource->getMimeType() !== 'image/webp'; |
| 1642 |
} |
| 1643 |
private function hasSizeForInlining(\Kibo\Phast\ValueObjects\Resource $resource) |
| 1644 |
{ |
| 1645 |
$size = $resource->getSize(); |
| 1646 |
return $size !== false && $size <= $this->maxImageInliningSize; |
| 1647 |
} |
| 1648 |
private function getCacheKey(\Kibo\Phast\ValueObjects\Resource $resource) |
| 1649 |
{ |
| 1650 |
return $resource->getUrl()->toString() . '|' . $resource->getCacheSalt() . '|' . $this->maxImageInliningSize; |
| 1651 |
} |
| 1652 |
} |
| 1653 |
namespace Kibo\Phast\Filters\HTML\ImagesOptimizationService; |
| 1654 |
|
| 1655 |
class ImageURLRewriter |
| 1656 |
{ |
| 1657 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 1658 |
/** |
| 1659 |
* @var ServiceSignature |
| 1660 |
*/ |
| 1661 |
protected $signature; |
| 1662 |
/** |
| 1663 |
* @var Retriever |
| 1664 |
*/ |
| 1665 |
protected $retriever; |
| 1666 |
/** |
| 1667 |
* @var ImageInliningManager |
| 1668 |
*/ |
| 1669 |
protected $inliningManager; |
| 1670 |
/** |
| 1671 |
* @var URL |
| 1672 |
*/ |
| 1673 |
protected $baseUrl; |
| 1674 |
/** |
| 1675 |
* @var URL |
| 1676 |
*/ |
| 1677 |
protected $serviceUrl; |
| 1678 |
/** |
| 1679 |
* @var string[] |
| 1680 |
*/ |
| 1681 |
protected $whitelist; |
| 1682 |
/** |
| 1683 |
* @var Resource[] |
| 1684 |
*/ |
| 1685 |
protected $inlinedResources; |
| 1686 |
/** |
| 1687 |
* ImageURLRewriter constructor. |
| 1688 |
* @param ServiceSignature $signature |
| 1689 |
* @param LocalRetriever $retriever |
| 1690 |
* @param ImageInliningManager $inliningManager |
| 1691 |
* @param URL $baseUrl |
| 1692 |
* @param URL $serviceUrl |
| 1693 |
* @param array $whitelist |
| 1694 |
*/ |
| 1695 |
public function __construct(\Kibo\Phast\Security\ServiceSignature $signature, \Kibo\Phast\Retrievers\LocalRetriever $retriever, \Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageInliningManager $inliningManager, \Kibo\Phast\ValueObjects\URL $baseUrl, \Kibo\Phast\ValueObjects\URL $serviceUrl, array $whitelist) |
| 1696 |
{ |
| 1697 |
$this->signature = $signature; |
| 1698 |
$this->retriever = $retriever; |
| 1699 |
$this->inliningManager = $inliningManager; |
| 1700 |
$this->baseUrl = $baseUrl; |
| 1701 |
$this->serviceUrl = $serviceUrl; |
| 1702 |
$this->whitelist = $whitelist; |
| 1703 |
} |
| 1704 |
/** |
| 1705 |
* @param string $url |
| 1706 |
* @param URL|null $baseUrl |
| 1707 |
* @param array $params |
| 1708 |
* @param bool $mustExist |
| 1709 |
* @return string |
| 1710 |
*/ |
| 1711 |
public function rewriteUrl($url, \Kibo\Phast\ValueObjects\URL $baseUrl = null, array $params = array(), $mustExist = false) |
| 1712 |
{ |
| 1713 |
if (strpos($url, '#') === 0) { |
| 1714 |
return $url; |
| 1715 |
} |
| 1716 |
$this->inlinedResources = []; |
| 1717 |
$absolute = $this->makeURLAbsoluteToBase($url, $baseUrl); |
| 1718 |
if (!$this->shouldRewriteUrl($absolute)) { |
| 1719 |
return $url; |
| 1720 |
} |
| 1721 |
$resource = \Kibo\Phast\ValueObjects\Resource::makeWithRetriever($absolute, $this->retriever); |
| 1722 |
if ($mustExist && $resource->getSize() === false) { |
| 1723 |
return $url; |
| 1724 |
} |
| 1725 |
$dataUrl = $this->inliningManager->getUrlForInlining($resource); |
| 1726 |
if ($dataUrl) { |
| 1727 |
$this->inlinedResources = [$resource]; |
| 1728 |
return $dataUrl; |
| 1729 |
} |
| 1730 |
$params['src'] = $absolute->toString(); |
| 1731 |
return $this->makeSignedUrl($params); |
| 1732 |
} |
| 1733 |
/** |
| 1734 |
* @param $styleContent |
| 1735 |
* @return string |
| 1736 |
*/ |
| 1737 |
public function rewriteStyle($styleContent) |
| 1738 |
{ |
| 1739 |
$allInlined = []; |
| 1740 |
$result = preg_replace_callback('~ |
| 1741 |
(\\b (?: image | background ):) |
| 1742 |
([^;}]*) |
| 1743 |
~xiS', function ($match) use(&$allInlined) { |
| 1744 |
return $match[1] . $this->rewriteStyleRule($match[2], $allInlined); |
| 1745 |
}, $styleContent); |
| 1746 |
$this->inlinedResources = array_values($allInlined); |
| 1747 |
return $result; |
| 1748 |
} |
| 1749 |
private function rewriteStyleRule($ruleContent, &$allInlined) |
| 1750 |
{ |
| 1751 |
return preg_replace_callback('~ |
| 1752 |
( \\b url \\( [\'"]? ) |
| 1753 |
( [^\'")] ++ ) |
| 1754 |
~xiS', function ($match) use(&$allInlined) { |
| 1755 |
$url = $match[1] . $this->rewriteUrl($match[2]); |
| 1756 |
if (!empty($this->inlinedResources)) { |
| 1757 |
$inlined = $this->inlinedResources[0]; |
| 1758 |
$allInlined[$inlined->getUrl()->toString()] = $inlined; |
| 1759 |
} |
| 1760 |
return $url; |
| 1761 |
}, $ruleContent); |
| 1762 |
} |
| 1763 |
/** |
| 1764 |
* @return Resource[] |
| 1765 |
*/ |
| 1766 |
public function getInlinedResources() |
| 1767 |
{ |
| 1768 |
return $this->inlinedResources; |
| 1769 |
} |
| 1770 |
/** |
| 1771 |
* @return string |
| 1772 |
*/ |
| 1773 |
public function getCacheSalt() |
| 1774 |
{ |
| 1775 |
$parts = array_merge([$this->signature->getCacheSalt(), $this->baseUrl->toString(), $this->serviceUrl->toString(), $this->inliningManager->getMaxImageInliningSize(), '20180413'], array_keys($this->whitelist), array_values($this->whitelist)); |
| 1776 |
return join('-', $parts); |
| 1777 |
} |
| 1778 |
/** |
| 1779 |
* @param string $url |
| 1780 |
* @param URL|null $baseUrl |
| 1781 |
* @return URL |
| 1782 |
*/ |
| 1783 |
private function makeURLAbsoluteToBase($url, \Kibo\Phast\ValueObjects\URL $baseUrl = null) |
| 1784 |
{ |
| 1785 |
$url = trim($url); |
| 1786 |
if (!$url || substr($url, 0, 5) === 'data:') { |
| 1787 |
return null; |
| 1788 |
} |
| 1789 |
$this->logger()->info('Rewriting img {url}', ['url' => $url]); |
| 1790 |
$baseUrl = is_null($baseUrl) ? $this->baseUrl : $baseUrl; |
| 1791 |
return \Kibo\Phast\ValueObjects\URL::fromString($url)->withBase($baseUrl); |
| 1792 |
} |
| 1793 |
/** |
| 1794 |
* @param string $url |
| 1795 |
* @return bool |
| 1796 |
*/ |
| 1797 |
private function shouldRewriteUrl($url) |
| 1798 |
{ |
| 1799 |
if (!$url) { |
| 1800 |
return false; |
| 1801 |
} |
| 1802 |
foreach ($this->whitelist as $pattern) { |
| 1803 |
if (preg_match($pattern, $url)) { |
| 1804 |
return true; |
| 1805 |
} |
| 1806 |
} |
| 1807 |
$urlObject = \Kibo\Phast\ValueObjects\URL::fromString($url); |
| 1808 |
if (preg_match('~\\.(jpe?g|gif|png)$~i', $urlObject->getPath()) && $this->retriever->getCacheSalt($urlObject)) { |
| 1809 |
return true; |
| 1810 |
} |
| 1811 |
return false; |
| 1812 |
} |
| 1813 |
/** |
| 1814 |
* @param array $params |
| 1815 |
* @return string |
| 1816 |
*/ |
| 1817 |
private function makeSignedUrl(array $params) |
| 1818 |
{ |
| 1819 |
$params['cacheMarker'] = $this->retriever->getCacheSalt(\Kibo\Phast\ValueObjects\URL::fromString($params['src'])); |
| 1820 |
return (new \Kibo\Phast\Services\ServiceRequest())->withParams($params)->withUrl($this->serviceUrl)->sign($this->signature)->serialize(); |
| 1821 |
} |
| 1822 |
} |
| 1823 |
namespace Kibo\Phast\Filters\HTML\ImagesOptimizationService\Tags; |
| 1824 |
|
| 1825 |
class Filter implements \Kibo\Phast\Filters\HTML\HTMLStreamFilter, \Kibo\Phast\Filters\HTML\AMPCompatibleFilter |
| 1826 |
{ |
| 1827 |
const IMG_SRC_ATTR_PATTERN = '~^(|data-(|lazy-|wood-))src$~i'; |
| 1828 |
const IMG_SRCSET_ATTR_PATTERN = '~^(|data-(|lazy-|wood-))srcset$~i'; |
| 1829 |
/** |
| 1830 |
* @var ImageURLRewriter |
| 1831 |
*/ |
| 1832 |
private $rewriter; |
| 1833 |
private $inPictureTag = false; |
| 1834 |
private $inBody = false; |
| 1835 |
private $imagePathPattern; |
| 1836 |
/** |
| 1837 |
* Filter constructor. |
| 1838 |
* @param ImageURLRewriter $rewriter |
| 1839 |
*/ |
| 1840 |
public function __construct(\Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageURLRewriter $rewriter) |
| 1841 |
{ |
| 1842 |
$this->rewriter = $rewriter; |
| 1843 |
$this->imagePathPattern = $this->makeImagePathPattern(); |
| 1844 |
} |
| 1845 |
public function transformElements(\Traversable $elements, \Kibo\Phast\Filters\HTML\HTMLPageContext $context) |
| 1846 |
{ |
| 1847 |
foreach ($elements as $element) { |
| 1848 |
if ($element instanceof \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag) { |
| 1849 |
$this->handleTag($element, $context); |
| 1850 |
} elseif ($element instanceof \Kibo\Phast\Parsing\HTML\HTMLStreamElements\ClosingTag) { |
| 1851 |
$this->handleClosingTag($element); |
| 1852 |
} |
| 1853 |
(yield $element); |
| 1854 |
} |
| 1855 |
} |
| 1856 |
private function handleTag(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag, \Kibo\Phast\Filters\HTML\HTMLPageContext $context) |
| 1857 |
{ |
| 1858 |
$isImage = false; |
| 1859 |
if ($tag->getTagName() == 'img' || $this->inPictureTag && $tag->getTagName() == 'source' || $tag->getTagName() == 'amp-img') { |
| 1860 |
$isImage = true; |
| 1861 |
} elseif ($tag->getTagName() == 'picture') { |
| 1862 |
$this->inPictureTag = true; |
| 1863 |
} elseif ($tag->getTagName() == 'video' || $tag->getTagName() == 'audio') { |
| 1864 |
$this->inPictureTag = false; |
| 1865 |
} elseif ($tag->getTagName() == 'body') { |
| 1866 |
$this->inBody = true; |
| 1867 |
} elseif ($tag->getTagName() == 'meta') { |
| 1868 |
return; |
| 1869 |
} |
| 1870 |
foreach ($tag->getAttributes() as $k => $v) { |
| 1871 |
if (!$v) { |
| 1872 |
continue; |
| 1873 |
} |
| 1874 |
if ($isImage && preg_match(self::IMG_SRC_ATTR_PATTERN, $k)) { |
| 1875 |
$this->rewriteSrc($tag, $context, $k); |
| 1876 |
} elseif ($isImage && preg_match(self::IMG_SRCSET_ATTR_PATTERN, $k)) { |
| 1877 |
$this->rewriteSrcset($tag, $context, $k); |
| 1878 |
} elseif ($this->inBody && preg_match($this->imagePathPattern, parse_url($v, PHP_URL_PATH))) { |
| 1879 |
$this->rewriteArbitraryAttribute($tag, $context, $k); |
| 1880 |
} |
| 1881 |
} |
| 1882 |
} |
| 1883 |
private function makeImagePathPattern() |
| 1884 |
{ |
| 1885 |
$pieces = []; |
| 1886 |
foreach (\Kibo\Phast\ValueObjects\Resource::EXTENSION_TO_MIME_TYPE as $ext => $mime) { |
| 1887 |
if (strpos($mime, 'image/') === 0) { |
| 1888 |
$pieces[] = preg_quote($ext, '~'); |
| 1889 |
} |
| 1890 |
} |
| 1891 |
return '~\\.(?:' . implode('|', $pieces) . ')$~'; |
| 1892 |
} |
| 1893 |
private function handleClosingTag(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\ClosingTag $closingTag) |
| 1894 |
{ |
| 1895 |
if ($closingTag->getTagName() == 'picture') { |
| 1896 |
$this->inPictureTag = false; |
| 1897 |
} |
| 1898 |
} |
| 1899 |
private function rewriteSrc(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $img, \Kibo\Phast\Filters\HTML\HTMLPageContext $context, $attribute) |
| 1900 |
{ |
| 1901 |
$url = $img->getAttribute($attribute); |
| 1902 |
$newURL = $this->rewriter->rewriteUrl($url, $context->getBaseUrl()); |
| 1903 |
$img->setAttribute($attribute, $newURL); |
| 1904 |
} |
| 1905 |
private function rewriteSrcset(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $img, \Kibo\Phast\Filters\HTML\HTMLPageContext $context, $attribute) |
| 1906 |
{ |
| 1907 |
$srcset = $img->getAttribute($attribute); |
| 1908 |
$rewritten = preg_replace_callback('/([^,\\s]+)(\\s+(?:[^,]+))?/', function ($match) use($context) { |
| 1909 |
$url = $this->rewriter->rewriteUrl($match[1], $context->getBaseUrl()); |
| 1910 |
if (isset($match[2])) { |
| 1911 |
return $url . $match[2]; |
| 1912 |
} |
| 1913 |
return $url; |
| 1914 |
}, $srcset); |
| 1915 |
$img->setAttribute($attribute, $rewritten); |
| 1916 |
} |
| 1917 |
private function rewriteArbitraryAttribute(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $element, \Kibo\Phast\Filters\HTML\HTMLPageContext $context, $attribute) |
| 1918 |
{ |
| 1919 |
$url = $element->getAttribute($attribute); |
| 1920 |
$newUrl = $this->rewriter->rewriteUrl($url, $context->getBaseUrl(), [], true); |
| 1921 |
$element->setAttribute($attribute, $newUrl); |
| 1922 |
} |
| 1923 |
} |
| 1924 |
namespace Kibo\Phast\Filters\HTML\ImagesOptimizationService; |
| 1925 |
|
| 1926 |
class ImageInliningManagerFactory |
| 1927 |
{ |
| 1928 |
public function make(array $config) |
| 1929 |
{ |
| 1930 |
$cache = new \Kibo\Phast\Cache\File\Cache($config['cache'], 'inline-images-1'); |
| 1931 |
return new \Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageInliningManager($cache, $config['images']['maxImageInliningSize']); |
| 1932 |
} |
| 1933 |
} |
| 1934 |
namespace Kibo\Phast\Filters\HTML; |
| 1935 |
|
| 1936 |
class HTMLPageContext |
| 1937 |
{ |
| 1938 |
/** |
| 1939 |
* @var URL |
| 1940 |
*/ |
| 1941 |
private $baseUrl; |
| 1942 |
/** |
| 1943 |
* @var PhastJavaScript[] |
| 1944 |
*/ |
| 1945 |
private $phastJavaScripts = array(); |
| 1946 |
/** |
| 1947 |
* HTMLPageContext constructor. |
| 1948 |
* @param URL $baseUrl |
| 1949 |
*/ |
| 1950 |
public function __construct(\Kibo\Phast\ValueObjects\URL $baseUrl) |
| 1951 |
{ |
| 1952 |
$this->baseUrl = $baseUrl; |
| 1953 |
} |
| 1954 |
/** |
| 1955 |
* @param URL $baseUrl |
| 1956 |
*/ |
| 1957 |
public function setBaseUrl(\Kibo\Phast\ValueObjects\URL $baseUrl) |
| 1958 |
{ |
| 1959 |
$this->baseUrl = $baseUrl; |
| 1960 |
} |
| 1961 |
/** |
| 1962 |
* @return URL |
| 1963 |
*/ |
| 1964 |
public function getBaseUrl() |
| 1965 |
{ |
| 1966 |
return $this->baseUrl; |
| 1967 |
} |
| 1968 |
/** |
| 1969 |
* @param PhastJavaScript $script |
| 1970 |
*/ |
| 1971 |
public function addPhastJavascript(\Kibo\Phast\ValueObjects\PhastJavaScript $script) |
| 1972 |
{ |
| 1973 |
$this->phastJavaScripts[] = $script; |
| 1974 |
} |
| 1975 |
/** |
| 1976 |
* @return PhastJavaScript[] |
| 1977 |
*/ |
| 1978 |
public function getPhastJavaScripts() |
| 1979 |
{ |
| 1980 |
return $this->phastJavaScripts; |
| 1981 |
} |
| 1982 |
} |
| 1983 |
namespace Kibo\Phast\Filters\HTML\Helpers; |
| 1984 |
|
| 1985 |
trait JSDetectorTrait |
| 1986 |
{ |
| 1987 |
/** |
| 1988 |
* @param Tag $element |
| 1989 |
* @return bool |
| 1990 |
*/ |
| 1991 |
private function isJSElement(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $element) |
| 1992 |
{ |
| 1993 |
if (!$element->hasAttribute('type')) { |
| 1994 |
return true; |
| 1995 |
} |
| 1996 |
return (bool) preg_match('~^(text|application)/javascript(;|$)~i', $element->getAttribute('type')); |
| 1997 |
} |
| 1998 |
} |
| 1999 |
namespace Kibo\Phast\Filters\HTML; |
| 2000 |
|
| 2001 |
abstract class BaseHTMLStreamFilter implements \Kibo\Phast\Filters\HTML\HTMLStreamFilter |
| 2002 |
{ |
| 2003 |
/** |
| 2004 |
* @var HTMLPageContext |
| 2005 |
*/ |
| 2006 |
protected $context; |
| 2007 |
/** |
| 2008 |
* @var \Traversable |
| 2009 |
*/ |
| 2010 |
protected $elements; |
| 2011 |
/** |
| 2012 |
* @param Tag $tag |
| 2013 |
* @return Element[]|\Generator |
| 2014 |
*/ |
| 2015 |
protected abstract function handleTag(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag); |
| 2016 |
public function transformElements(\Traversable $elements, \Kibo\Phast\Filters\HTML\HTMLPageContext $context) |
| 2017 |
{ |
| 2018 |
$this->context = $context; |
| 2019 |
$this->elements = $elements; |
| 2020 |
$this->beforeLoop(); |
| 2021 |
foreach ($this->elements as $element) { |
| 2022 |
if ($element instanceof \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag && $this->isTagOfInterest($element)) { |
| 2023 |
foreach ($this->handleTag($element) as $item) { |
| 2024 |
(yield $item); |
| 2025 |
} |
| 2026 |
} else { |
| 2027 |
(yield $element); |
| 2028 |
} |
| 2029 |
} |
| 2030 |
$this->afterLoop(); |
| 2031 |
} |
| 2032 |
/** |
| 2033 |
* @param Tag $tag |
| 2034 |
* @return bool |
| 2035 |
*/ |
| 2036 |
protected function isTagOfInterest(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag) |
| 2037 |
{ |
| 2038 |
return true; |
| 2039 |
} |
| 2040 |
protected function beforeLoop() |
| 2041 |
{ |
| 2042 |
} |
| 2043 |
protected function afterLoop() |
| 2044 |
{ |
| 2045 |
} |
| 2046 |
} |
| 2047 |
namespace Kibo\Phast\Filters\HTML\PhastScriptsCompiler; |
| 2048 |
|
| 2049 |
class Filter implements \Kibo\Phast\Filters\HTML\HTMLStreamFilter |
| 2050 |
{ |
| 2051 |
/** |
| 2052 |
* @var PhastJavaScriptCompiler |
| 2053 |
*/ |
| 2054 |
private $compiler; |
| 2055 |
/** |
| 2056 |
* Filter constructor. |
| 2057 |
* @param PhastJavaScriptCompiler $compiler |
| 2058 |
*/ |
| 2059 |
public function __construct(\Kibo\Phast\Filters\HTML\PhastScriptsCompiler\PhastJavaScriptCompiler $compiler) |
| 2060 |
{ |
| 2061 |
$this->compiler = $compiler; |
| 2062 |
} |
| 2063 |
public function transformElements(\Traversable $elements, \Kibo\Phast\Filters\HTML\HTMLPageContext $context) |
| 2064 |
{ |
| 2065 |
$buffered = []; |
| 2066 |
$buffering = false; |
| 2067 |
foreach ($elements as $element) { |
| 2068 |
if ($this->isClosingBodyTag($element)) { |
| 2069 |
if ($buffering) { |
| 2070 |
foreach ($buffered as $bufElement) { |
| 2071 |
(yield $bufElement); |
| 2072 |
} |
| 2073 |
$buffered = []; |
| 2074 |
} |
| 2075 |
$buffering = true; |
| 2076 |
} |
| 2077 |
if ($buffering) { |
| 2078 |
$buffered[] = $element; |
| 2079 |
} else { |
| 2080 |
(yield $element); |
| 2081 |
} |
| 2082 |
} |
| 2083 |
$scripts = $context->getPhastJavaScripts(); |
| 2084 |
if (!empty($scripts)) { |
| 2085 |
(yield $this->compileScript($scripts)); |
| 2086 |
} |
| 2087 |
foreach ($buffered as $element) { |
| 2088 |
(yield $element); |
| 2089 |
} |
| 2090 |
} |
| 2091 |
/** |
| 2092 |
* @param PhastJavaScript[] $scripts |
| 2093 |
* @return Tag |
| 2094 |
*/ |
| 2095 |
private function compileScript(array $scripts) |
| 2096 |
{ |
| 2097 |
$names = array_map(function (\Kibo\Phast\ValueObjects\PhastJavaScript $script) { |
| 2098 |
$matches = []; |
| 2099 |
preg_match('~[^/]*?\\/?[^/]+$~', $script->getFilename(), $matches); |
| 2100 |
return $matches[0]; |
| 2101 |
}, $scripts); |
| 2102 |
$script = new \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag('script'); |
| 2103 |
$script->setAttribute('data-phast-compiled-js-names', join(',', $names)); |
| 2104 |
$compiled = $this->compiler->compileScriptsWithConfig($scripts); |
| 2105 |
$script->setTextContent($compiled); |
| 2106 |
return $script; |
| 2107 |
} |
| 2108 |
private function isClosingBodyTag(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Element $element) |
| 2109 |
{ |
| 2110 |
return $element instanceof \Kibo\Phast\Parsing\HTML\HTMLStreamElements\ClosingTag && $element->getTagName() == 'body'; |
| 2111 |
} |
| 2112 |
} |
| 2113 |
namespace Kibo\Phast\Filters\HTML\PhastScriptsCompiler; |
| 2114 |
|
| 2115 |
class PhastJavaScriptCompiler |
| 2116 |
{ |
| 2117 |
/** |
| 2118 |
* @var Cache |
| 2119 |
*/ |
| 2120 |
private $cache; |
| 2121 |
/** |
| 2122 |
* @var string |
| 2123 |
*/ |
| 2124 |
private $serviceUrl; |
| 2125 |
private $serviceRequestFormat; |
| 2126 |
/** |
| 2127 |
* @var \stdClass |
| 2128 |
*/ |
| 2129 |
private $lastCompiledConfig; |
| 2130 |
/** |
| 2131 |
* PhastJavaScriptCompiler constructor. |
| 2132 |
* @param Cache $cache |
| 2133 |
* @param string $serviceUrl |
| 2134 |
*/ |
| 2135 |
public function __construct(\Kibo\Phast\Cache\Cache $cache, $serviceUrl, $serviceRequestFormat) |
| 2136 |
{ |
| 2137 |
$this->cache = $cache; |
| 2138 |
$this->serviceUrl = (new \Kibo\Phast\Services\ServiceRequest())->withUrl(\Kibo\Phast\ValueObjects\URL::fromString((string) $serviceUrl))->serialize(\Kibo\Phast\Services\ServiceRequest::FORMAT_QUERY); |
| 2139 |
$this->serviceRequestFormat = $serviceRequestFormat; |
| 2140 |
} |
| 2141 |
/** |
| 2142 |
* @return \stdClass|null |
| 2143 |
*/ |
| 2144 |
public function getLastCompiledConfig() |
| 2145 |
{ |
| 2146 |
return $this->lastCompiledConfig; |
| 2147 |
} |
| 2148 |
/** |
| 2149 |
* @param PhastJavaScript[] $scripts |
| 2150 |
* @return string |
| 2151 |
*/ |
| 2152 |
public function compileScripts(array $scripts) |
| 2153 |
{ |
| 2154 |
return $this->cache->get($this->getCacheKey($scripts), function () use($scripts) { |
| 2155 |
return $this->performCompilation($scripts); |
| 2156 |
}); |
| 2157 |
} |
| 2158 |
/** |
| 2159 |
* @param PhastJavaScript[] $scripts |
| 2160 |
* @return string |
| 2161 |
*/ |
| 2162 |
public function compileScriptsWithConfig(array $scripts) |
| 2163 |
{ |
| 2164 |
$bundlerMappings = \Kibo\Phast\Services\Bundler\ShortBundlerParamsParser::getParamsMappings(); |
| 2165 |
$jsMappings = array_combine(array_values($bundlerMappings), array_keys($bundlerMappings)); |
| 2166 |
$resourcesLoader = \Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/PhastScriptsCompiler/resources-loader.js', "var Promise=phast.ES6Promise.Promise;phast.ResourceLoader=function(a,b){this.get=function(c){return b.get(c).then(function(d){if(typeof d!==\"string\"){throw new Error(\"response should be string\")}return d}).catch(function(){var e=a.get(c);e.then(function(f){b.set(c,f)});return e})}};phast.ResourceLoader.RequestParams={};phast.ResourceLoader.RequestParams.FaultyParams={};phast.ResourceLoader.RequestParams.fromString=function(g){try{return JSON.parse(g)}catch(h){return phast.ResourceLoader.RequestParams.FaultyParams}};phast.ResourceLoader.BundlerServiceClient=function(i,j,k){var l=phast.ResourceLoader.BundlerServiceClient.RequestsPack;var m=l.PackItem;var n;this.get=function(q){if(q===phast.ResourceLoader.RequestParams.FaultyParams){return Promise.reject(new Error(\"Parameters did not parse as JSON\"))}return new Promise(function(r,s){if(n===undefined){n=new l(j)}n.add(new m({success:r,error:s},q));setTimeout(o);if(n.toQuery().length>4500){console.log(\"[Phast] Resource loader: Pack got too big; flushing early...\");o()}})};function o(){if(n===undefined){return}var t=n;n=undefined;p(t)}function p(u){var v=phast.buildServiceUrl({serviceUrl:i,pathInfo:k},\"service=bundler&\"+u.toQuery());var w=function(){console.error(\"[Phast] Request to bundler failed with status\",y.status);console.log(\"URL:\",v);u.handleError()};var x=function(){if(y.status>=200&&y.status<300){u.handleResponse(y.responseText)}else{u.handleError()}};var y=new XMLHttpRequest;y.open(\"GET\",v);y.addEventListener(\"error\",w);y.addEventListener(\"abort\",w);y.addEventListener(\"load\",x);y.send()}};phast.ResourceLoader.BundlerServiceClient.RequestsPack=function(z){var A={};this.getLength=function(){var F=0;for(var G in A){F++}return F};this.add=function(H){var I;if(H.params.token){I=\"token=\"+H.params.token}else if(H.params.ref){I=\"ref=\"+H.params.ref}else{I=\"\"}if(!A[I]){A[I]={params:H.params,requests:[H.request]}}else{A[I].requests.push(H.request)}};this.toQuery=function(){var J=[],K=[],L=\"\";B().forEach(function(M){var N,O;for(var P in A[M].params){if(P===\"cacheMarker\"){K.push(A[M].params.cacheMarker);continue}N=z[P]?z[P]:P;if(P===\"strip-imports\"){O=encodeURIComponent(N)}else if(P===\"src\"){O=encodeURIComponent(N)+\"=\"+encodeURIComponent(C(A[M].params.src,L));L=A[M].params.src}else{O=encodeURIComponent(N)+\"=\"+encodeURIComponent(A[M].params[P])}J.push(O)}});if(K.length>0){J.unshift(\"c=\"+phast.hash(K.join(\"|\"),23045))}return E(J.join(\"&\"))};function B(){return Object.keys(A).sort(function(R,S){return Q(R,S)?1:Q(S,R)?-1:0});function Q(T,U){if(typeof A[T].params.src!==\"undefined\"&&typeof A[U].params.src!==\"undefined\"){return A[T].params.src>A[U].params.src}return T>U}}function C(V,W){var X=0,Y=Math.pow(36,2)-1;while(X<W.length&&V[X]===W[X]){X++}X=Math.min(X,Y);return D(X)+\"\"+V.substr(X)}function D(Z){var \$=[\"0\",\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\",\"9\",\"a\",\"b\",\"c\",\"d\",\"e\",\"f\",\"g\",\"h\",\"i\",\"j\",\"k\",\"l\",\"m\",\"n\",\"o\",\"p\",\"q\",\"r\",\"s\",\"t\",\"u\",\"v\",\"w\",\"x\",\"y\",\"z\"];var _=Z%36;var aa=Math.floor((Z-_)/36);return \$[aa]+\$[_]}function E(ba){if(!/(^|&)s=/.test(ba)){return ba}return ba.replace(/(%..)|([A-M])|([N-Z])/gi,function(ca,da,ea,fa){if(da){return ca}return String.fromCharCode(ca.charCodeAt(0)+(ea?13:-13))})}this.handleResponse=function(ga){try{var ha=JSON.parse(ga)}catch(ja){this.handleError();return}var ia=B();if(ha.length!==ia.length){console.error(\"[Phast] Requested\",ia.length,\"items from bundler, but got\",ha.length,\"response(s)\");this.handleError();return}ha.forEach(function(ka,la){if(ka.status===200){A[ia[la]].requests.forEach(function(ma){ma.success(ka.content)})}else{A[ia[la]].requests.forEach(function(na){na.error(new Error(\"Got from bundler: \"+JSON.stringify(ka)))})}})}.bind(this);this.handleError=function(){for(var oa in A){A[oa].requests.forEach(function(pa){pa.error()})}}};phast.ResourceLoader.BundlerServiceClient.RequestsPack.PackItem=function(qa,ra){this.request=qa;this.params=ra};phast.ResourceLoader.IndexedDBStorage=function(sa){var ta=phast.ResourceLoader.IndexedDBStorage;var ua=ta.logPrefix;var va=ta.requestToPromise;var wa;Ba();this.get=function(Ca){return xa(\"readonly\").then(function(Da){return va(Da.get(Ca)).catch(ya(\"reading from store\"))})};this.store=function(Ea){return xa(\"readwrite\").then(function(Fa){return va(Fa.put(Ea)).catch(ya(\"writing to store\"))})};this.clear=function(){return xa(\"readwrite\").then(function(Ga){return va(Ga.clear())})};this.iterateOnAll=function(Ha){return xa(\"readonly\").then(function(Ia){return za(Ha,Ia.openCursor()).catch(ya(\"iterating on all\"))})};function xa(Ja){return wa.get().then(function(Ka){try{return Ka.transaction(sa.storeName,Ja).objectStore(sa.storeName)}catch(La){console.error(ua,\"Could not open store; recreating database:\",La);Aa();throw La}})}function ya(Ma){return function(Na){console.error(ua,\"Error \"+Ma+\":\",Na);Aa();throw Na}}function za(Oa,Pa){return new Promise(function(Qa,Ra){Pa.onsuccess=function(Sa){var Ta=Sa.target.result;if(Ta){Oa(Ta.value);Ta.continue()}else{Qa()}};Pa.onerror=Ra})}function Aa(){var Ua=wa.dropDB().then(Ba);wa={get:function(){return Promise.reject(new Error(\"Database is being dropped and recreated\"))},dropDB:function(){return Ua}}}function Ba(){wa=new phast.ResourceLoader.IndexedDBStorage.Connection(sa)}};phast.ResourceLoader.IndexedDBStorage.logPrefix=\"[Phast] Resource loader:\";phast.ResourceLoader.IndexedDBStorage.requestToPromise=function(Va){return new Promise(function(Wa,Xa){Va.onsuccess=function(){Wa(Va.result)};Va.onerror=function(){Xa(Va.error)}})};phast.ResourceLoader.IndexedDBStorage.ConnectionParams=function(){this.dbName=\"phastResourcesCache\";this.dbVersion=1;this.storeName=\"resources\"};phast.ResourceLoader.IndexedDBStorage.StoredResource=function(Ya,Za){this.token=Ya;this.content=Za};phast.ResourceLoader.IndexedDBStorage.Connection=function(\$a){var _a=phast.ResourceLoader.IndexedDBStorage.logPrefix;var ab=phast.ResourceLoader.IndexedDBStorage.requestToPromise;var bb;this.get=cb;this.dropDB=db;function cb(){if(!bb){bb=eb(\$a)}return bb}function db(){return cb().then(function(gb){console.error(_a,\"Dropping DB\");gb.close();bb=null;return ab(window.indexedDB.deleteDatabase(\$a.dbName))})}function eb(hb){if(typeof window.indexedDB===\"undefined\"){return Promise.reject(new Error(\"IndexedDB is not available\"))}var ib=window.indexedDB.open(hb.dbName,hb.dbVersion);ib.onupgradeneeded=function(){fb(ib.result,hb)};return ab(ib).then(function(jb){jb.onversionchange=function(){console.debug(_a,\"Closing DB\");jb.close();if(bb){bb=null}};return jb}).catch(function(kb){console.log(_a,\"IndexedDB cache is not available. This is usually due to using private browsing mode.\");throw kb})}function fb(lb,mb){lb.createObjectStore(mb.storeName,{keyPath:\"token\"})}};phast.ResourceLoader.StorageCache=function(nb,ob){var pb=phast.ResourceLoader.IndexedDBStorage.StoredResource;this.get=function(xb){return sb(rb(xb))};this.set=function(yb,zb){return tb(rb(yb),zb,false)};var qb=null;function rb(Ab){return JSON.stringify(Ab)}function sb(Bb){return ob.get(Bb).then(function(Cb){if(Cb){return Promise.resolve(Cb.content)}return Promise.resolve()})}function tb(Db,Eb,Fb){return wb().then(function(Gb){var Hb=Eb.length+Gb;if(Hb>nb.maxStorageSize){return Fb||Eb.length>nb.maxStorageSize?Promise.reject(new Error(\"Storage quota will be exceeded\")):ub(Db,Eb)}qb=Hb;var Ib=new pb(Db,Eb);return ob.store(Ib)})}function ub(Jb,Kb){return vb().then(function(){return tb(Jb,Kb,true)})}function vb(){return ob.clear().then(function(){qb=0})}function wb(){if(qb!==null){return Promise.resolve(qb)}var Lb=0;return ob.iterateOnAll(function(Mb){Lb+=Mb.content.length}).then(function(){qb=Lb;return Promise.resolve(qb)})}};phast.ResourceLoader.StorageCache.StorageCacheParams=function(){this.maxStorageSize=4.5*1024*1024};phast.ResourceLoader.BlackholeCache=function(){this.get=function(){return Promise.reject()};this.set=function(){return Promise.reject()}};phast.ResourceLoader.make=function(Nb,Ob,Pb){var Qb=Sb();var Rb=new phast.ResourceLoader.BundlerServiceClient(Nb,Ob,Pb);return new phast.ResourceLoader(Rb,Qb);function Sb(){var Tb=window.navigator.userAgent;if(/safari/i.test(Tb)&&!/chrome|android/i.test(Tb)){console.log(\"[Phast] Not using IndexedDB cache on Safari\");return new phast.ResourceLoader.BlackholeCache}else{var Ub=new phast.ResourceLoader.IndexedDBStorage.ConnectionParams;var Vb=new phast.ResourceLoader.IndexedDBStorage(Ub);var Wb=new phast.ResourceLoader.StorageCache.StorageCacheParams;return new phast.ResourceLoader.StorageCache(Wb,Vb)}}};\n"); |
| 2167 |
$resourcesLoader->setConfig('resourcesLoader', ['serviceUrl' => (string) $this->serviceUrl, 'shortParamsMappings' => $jsMappings, 'pathInfo' => $this->serviceRequestFormat === \Kibo\Phast\Services\ServiceRequest::FORMAT_PATH]); |
| 2168 |
$scripts = array_merge([\Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/PhastScriptsCompiler/runner.js', "phast.config=JSON.parse(atob(phast.config));while(phast.scripts.length){phast.scripts.shift()()}\n"), \Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/PhastScriptsCompiler/es6-promise.js', "(function(a,b){typeof exports===\"object\"&&typeof module!==\"undefined\"?module.exports=b():typeof define===\"function\"&&define.amd?define(b):a.ES6Promise=b()})(phast,function(){\"use strict\";function c(ia){var ja=typeof ia;return ia!==null&&(ja===\"object\"||ja===\"function\")}function d(ka){return typeof ka===\"function\"}var e=void 0;if(Array.isArray){e=Array.isArray}else{e=function(la){return Object.prototype.toString.call(la)===\"[object Array]\"}}var f=e;var g=0;var h=void 0;var i=void 0;var j=function ma(na,oa){w[g]=na;w[g+1]=oa;g+=2;if(g===2){if(i){i(x)}else{z()}}};function k(pa){i=pa}function l(qa){j=qa}var m=typeof window!==\"undefined\"?window:undefined;var n=m||{};var o=n.MutationObserver||n.WebKitMutationObserver;var p=typeof self===\"undefined\"&&typeof process!==\"undefined\"&&{}.toString.call(process)===\"[object process]\";var q=typeof Uint8ClampedArray!==\"undefined\"&&typeof importScripts!==\"undefined\"&&typeof MessageChannel!==\"undefined\";function r(){return function(){return process.nextTick(x)}}function s(){if(typeof h!==\"undefined\"){return function(){h(x)}}return v()}function t(){var ra=0;var sa=new o(x);var ta=document.createTextNode(\"\");sa.observe(ta,{characterData:true});return function(){ta.data=ra=++ra%2}}function u(){var ua=new MessageChannel;ua.port1.onmessage=x;return function(){return ua.port2.postMessage(0)}}function v(){var va=setTimeout;return function(){return va(x,1)}}var w=new Array(1e3);function x(){for(var wa=0;wa<g;wa+=2){var xa=w[wa];var ya=w[wa+1];xa(ya);w[wa]=undefined;w[wa+1]=undefined}g=0}function y(){try{var za=Function(\"return this\")().require(\"vertx\");h=za.runOnLoop||za.runOnContext;return s()}catch(Aa){return v()}}var z=void 0;if(p){z=r()}else if(o){z=t()}else if(q){z=u()}else if(m===undefined&&typeof require===\"function\"){z=y()}else{z=v()}function A(Ba,Ca){var Da=this;var Ea=new this.constructor(D);if(Ea[C]===undefined){\$(Ea)}var Fa=Da._state;if(Fa){var Ga=arguments[Fa-1];j(function(){return W(Fa,Ea,Ga,Da._result)})}else{T(Da,Ea,Ba,Ca)}return Ea}function B(Ha){var Ia=this;if(Ha&&typeof Ha===\"object\"&&Ha.constructor===Ia){return Ha}var Ja=new Ia(D);P(Ja,Ha);return Ja}var C=Math.random().toString(36).substring(2);function D(){}var E=void 0;var F=1;var G=2;var H={error:null};function I(){return new TypeError(\"You cannot resolve a promise with itself\")}function J(){return new TypeError(\"A promises callback cannot return that same promise.\")}function K(Ka){try{return Ka.then}catch(La){H.error=La;return H}}function L(Ma,Na,Oa,Pa){try{Ma.call(Na,Oa,Pa)}catch(Qa){return Qa}}function M(Ra,Sa,Ta){j(function(Ua){var Va=false;var Wa=L(Ta,Sa,function(Xa){if(Va){return}Va=true;if(Sa!==Xa){P(Ua,Xa)}else{R(Ua,Xa)}},function(Ya){if(Va){return}Va=true;S(Ua,Ya)},\"Settle: \"+(Ua._label||\" unknown promise\"));if(!Va&&Wa){Va=true;S(Ua,Wa)}},Ra)}function N(Za,\$a){if(\$a._state===F){R(Za,\$a._result)}else if(\$a._state===G){S(Za,\$a._result)}else{T(\$a,undefined,function(_a){return P(Za,_a)},function(ab){return S(Za,ab)})}}function O(bb,cb,db){if(cb.constructor===bb.constructor&&db===A&&cb.constructor.resolve===B){N(bb,cb)}else{if(db===H){S(bb,H.error);H.error=null}else if(db===undefined){R(bb,cb)}else if(d(db)){M(bb,cb,db)}else{R(bb,cb)}}}function P(eb,fb){if(eb===fb){S(eb,I())}else if(c(fb)){O(eb,fb,K(fb))}else{R(eb,fb)}}function Q(gb){if(gb._onerror){gb._onerror(gb._result)}U(gb)}function R(hb,ib){if(hb._state!==E){return}hb._result=ib;hb._state=F;if(hb._subscribers.length!==0){j(U,hb)}}function S(jb,kb){if(jb._state!==E){return}jb._state=G;jb._result=kb;j(Q,jb)}function T(lb,mb,nb,ob){var pb=lb._subscribers;var qb=pb.length;lb._onerror=null;pb[qb]=mb;pb[qb+F]=nb;pb[qb+G]=ob;if(qb===0&&lb._state){j(U,lb)}}function U(rb){var sb=rb._subscribers;var tb=rb._state;if(sb.length===0){return}var ub=void 0,vb=void 0,wb=rb._result;for(var xb=0;xb<sb.length;xb+=3){ub=sb[xb];vb=sb[xb+tb];if(ub){W(tb,ub,vb,wb)}else{vb(wb)}}rb._subscribers.length=0}function V(yb,zb){try{return yb(zb)}catch(Ab){H.error=Ab;return H}}function W(Bb,Cb,Db,Eb){var Fb=d(Db),Gb=void 0,Hb=void 0,Ib=void 0,Jb=void 0;if(Fb){Gb=V(Db,Eb);if(Gb===H){Jb=true;Hb=Gb.error;Gb.error=null}else{Ib=true}if(Cb===Gb){S(Cb,J());return}}else{Gb=Eb;Ib=true}if(Cb._state!==E){}else if(Fb&&Ib){P(Cb,Gb)}else if(Jb){S(Cb,Hb)}else if(Bb===F){R(Cb,Gb)}else if(Bb===G){S(Cb,Gb)}}function X(Kb,Lb){try{Lb(function Mb(Nb){P(Kb,Nb)},function Ob(Pb){S(Kb,Pb)})}catch(Qb){S(Kb,Qb)}}var Y=0;function Z(){return Y++}function \$(Rb){Rb[C]=Y++;Rb._state=undefined;Rb._result=undefined;Rb._subscribers=[]}function _(){return new Error(\"Array Methods must be provided an Array\")}var aa=function(){function Sb(Tb,Ub){this._instanceConstructor=Tb;this.promise=new Tb(D);if(!this.promise[C]){\$(this.promise)}if(f(Ub)){this.length=Ub.length;this._remaining=Ub.length;this._result=new Array(this.length);if(this.length===0){R(this.promise,this._result)}else{this.length=this.length||0;this._enumerate(Ub);if(this._remaining===0){R(this.promise,this._result)}}}else{S(this.promise,_())}}Sb.prototype._enumerate=function Vb(Wb){for(var Xb=0;this._state===E&&Xb<Wb.length;Xb++){this._eachEntry(Wb[Xb],Xb)}};Sb.prototype._eachEntry=function Yb(Zb,\$b){var _b=this._instanceConstructor;var ac=_b.resolve;if(ac===B){var bc=K(Zb);if(bc===A&&Zb._state!==E){this._settledAt(Zb._state,\$b,Zb._result)}else if(typeof bc!==\"function\"){this._remaining--;this._result[\$b]=Zb}else if(_b===ga){var cc=new _b(D);O(cc,Zb,bc);this._willSettleAt(cc,\$b)}else{this._willSettleAt(new _b(function(dc){return dc(Zb)}),\$b)}}else{this._willSettleAt(ac(Zb),\$b)}};Sb.prototype._settledAt=function ec(fc,gc,hc){var ic=this.promise;if(ic._state===E){this._remaining--;if(fc===G){S(ic,hc)}else{this._result[gc]=hc}}if(this._remaining===0){R(ic,this._result)}};Sb.prototype._willSettleAt=function jc(kc,lc){var mc=this;T(kc,undefined,function(nc){return mc._settledAt(F,lc,nc)},function(oc){return mc._settledAt(G,lc,oc)})};return Sb}();function ba(pc){return new aa(this,pc).promise}function ca(qc){var rc=this;if(!f(qc)){return new rc(function(sc,tc){return tc(new TypeError(\"You must pass an array to race.\"))})}else{return new rc(function(uc,vc){var wc=qc.length;for(var xc=0;xc<wc;xc++){rc.resolve(qc[xc]).then(uc,vc)}})}}function da(yc){var zc=this;var Ac=new zc(D);S(Ac,yc);return Ac}function ea(){throw new TypeError(\"You must pass a resolver function as the first argument to the promise constructor\")}function fa(){throw new TypeError(\"Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.\")}var ga=function(){function Bc(Cc){this[C]=Z();this._result=this._state=undefined;this._subscribers=[];if(D!==Cc){typeof Cc!==\"function\"&&ea();this instanceof Bc?X(this,Cc):fa()}}Bc.prototype.catch=function Dc(Ec){return this.then(null,Ec)};Bc.prototype.finally=function Fc(Gc){var Hc=this;var Ic=Hc.constructor;return Hc.then(function(Jc){return Ic.resolve(Gc()).then(function(){return Jc})},function(Kc){return Ic.resolve(Gc()).then(function(){throw Kc})})};return Bc}();ga.prototype.then=A;ga.all=ba;ga.race=ca;ga.resolve=B;ga.reject=da;ga._setScheduler=k;ga._setAsap=l;ga._asap=j;function ha(){var Lc=void 0;if(typeof global!==\"undefined\"){Lc=global}else if(typeof self!==\"undefined\"){Lc=self}else{try{Lc=Function(\"return this\")()}catch(Oc){throw new Error(\"polyfill failed because global object is unavailable in this environment\")}}var Mc=Lc.Promise;if(Mc){var Nc=null;try{Nc=Object.prototype.toString.call(Mc.resolve())}catch(Pc){}if(Nc===\"[object Promise]\"&&!Mc.cast){return}}Lc.Promise=ga}ga.polyfill=ha;ga.Promise=ga;return ga});\n"), \Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/PhastScriptsCompiler/hash.js', "function murmurhash3_32_gc(a,b){var c,d,e,f,g,h,i,j,k,l;c=a.length&3;d=a.length-c;e=b;g=3432918353;i=461845907;l=0;while(l<d){k=a.charCodeAt(l)&255|(a.charCodeAt(++l)&255)<<8|(a.charCodeAt(++l)&255)<<16|(a.charCodeAt(++l)&255)<<24;++l;k=(k&65535)*g+(((k>>>16)*g&65535)<<16)&4294967295;k=k<<15|k>>>17;k=(k&65535)*i+(((k>>>16)*i&65535)<<16)&4294967295;e^=k;e=e<<13|e>>>19;f=(e&65535)*5+(((e>>>16)*5&65535)<<16)&4294967295;e=(f&65535)+27492+(((f>>>16)+58964&65535)<<16)}k=0;switch(c){case 3:k^=(a.charCodeAt(l+2)&255)<<16;case 2:k^=(a.charCodeAt(l+1)&255)<<8;case 1:k^=a.charCodeAt(l)&255;k=(k&65535)*g+(((k>>>16)*g&65535)<<16)&4294967295;k=k<<15|k>>>17;k=(k&65535)*i+(((k>>>16)*i&65535)<<16)&4294967295;e^=k}e^=a.length;e^=e>>>16;e=(e&65535)*2246822507+(((e>>>16)*2246822507&65535)<<16)&4294967295;e^=e>>>13;e=(e&65535)*3266489909+(((e>>>16)*3266489909&65535)<<16)&4294967295;e^=e>>>16;return e>>>0}phast.hash=murmurhash3_32_gc;\n"), \Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/PhastScriptsCompiler/service-url.js', "phast.buildServiceUrl=function(a,b){if(a.pathInfo){return appendPathInfo(a.serviceUrl,buildQuery(b))}else{return appendQueryString(a.serviceUrl,buildQuery(b))}};function buildQuery(c){if(typeof c===\"string\"){return c}var d=[];for(var e in c){if(c.hasOwnProperty(e)){d.push(encodeURIComponent(e)+\"=\"+encodeURIComponent(c[e]))}}return d.join(\"&\")}function appendPathInfo(f,g){var h=btoa(g).replace(/=/g,\"\").replace(/\\//g,\"_\").replace(/\\+/g,\"-\");var i=j(h+\".q.js\");return f.replace(/\\?.*\$/,\"\").replace(/\\/__p__\\.js\$/,\"\")+\"/\"+i;function j(l){return k(k(l).match(/[\\s\\S]{1,255}/g).join(\"/\"))}function k(m){return m.split(\"\").reverse().join(\"\")}}function appendQueryString(n,o){var p=n.indexOf(\"?\")>-1?\"&\":\"?\";return n+p+o}\n"), $resourcesLoader, \Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/PhastScriptsCompiler/phast.js', "var Promise=phast.ES6Promise;phast.ResourceLoader.instance=phast.ResourceLoader.make(phast.config.resourcesLoader.serviceUrl,phast.config.resourcesLoader.shortParamsMappings,phast.config.resourcesLoader.pathInfo);phast.forEachSelectedElement=function(a,b){Array.prototype.forEach.call(window.document.querySelectorAll(a),b)};phast.once=function(c){var d=false;return function(){if(!d){d=true;c.apply(this,Array.prototype.slice(arguments))}}};phast.on=function(e,f){return new Promise(function(g){e.addEventListener(f,g)})};phast.wait=function(h){return new Promise(function(i){setTimeout(i,h)})};phast.on(document,\"DOMContentLoaded\").then(function(){var j,k;function l(n){return n&&n.nodeType===8&&/^\\s*\\[Phast\\]/.test(n.textContent)}function m(o){while(o){if(l(o)){return o}o=o.nextSibling}return false}k=m(document.documentElement.nextSibling);if(k===false){k=m(document.body.firstChild)}if(k){j=k.textContent.replace(/^\\s+|\\s+\$/g,\"\").split(\"\\n\");console.groupCollapsed(j.shift());console.log(j.join(\"\\n\"));console.groupEnd()}});phast.on(document,\"DOMContentLoaded\").then(function(){var p=performance.timing;var q=[];q.push([\"Downloading phases:\"]);q.push([\" Look up hostname in DNS + %s ms\",t(p.domainLookupEnd-p.fetchStart)]);q.push([\" Establish connection + %s ms\",t(p.connectEnd-p.domainLookupEnd)]);q.push([\" Send request + %s ms\",t(p.requestStart-p.connectEnd)]);q.push([\" Receive first byte + %s ms\",t(p.responseStart-p.requestStart)]);q.push([\" Download page + %s ms\",t(p.responseEnd-p.responseStart)]);q.push([\"\"]);q.push([\"Totals:\"]);q.push([\" Time to first byte %s ms\",t(p.responseStart-p.fetchStart)]);q.push([\" (since request start) %s ms\",t(p.responseStart-p.requestStart)]);q.push([\" Total request time %s ms\",t(p.responseEnd-p.fetchStart)]);q.push([\" (since request start) %s ms\",t(p.responseEnd-p.requestStart)]);q.push([\" \"]);var r=[];var s=[];q.forEach(function(u){r.push(u.shift());s=s.concat(u)});console.groupCollapsed(\"[Phast] Client-side performance metrics\");console.log.apply(console,[r.join(\"\\n\")].concat(s));console.groupEnd();function t(v){v=\"\"+v;while(v.length<4){v=\" \"+v}return v}});\n")], $scripts); |
| 2169 |
$compiled = $this->compileScripts($scripts); |
| 2170 |
return '(' . $compiled . ')(' . $this->compileConfig($scripts) . ');'; |
| 2171 |
} |
| 2172 |
/** |
| 2173 |
* @param PhastJavaScript[] $scripts |
| 2174 |
* @return string |
| 2175 |
*/ |
| 2176 |
private function performCompilation(array $scripts) |
| 2177 |
{ |
| 2178 |
$compiled = implode(',', array_map(function (\Kibo\Phast\ValueObjects\PhastJavaScript $script) { |
| 2179 |
return $this->interpolate($script->getContents()); |
| 2180 |
}, $scripts)); |
| 2181 |
return 'function phastScripts(phast){phast.scripts=[' . $compiled . '];(phast.scripts.shift())();}'; |
| 2182 |
} |
| 2183 |
/** |
| 2184 |
* @param PhastJavaScript[] $scripts |
| 2185 |
* @return string |
| 2186 |
*/ |
| 2187 |
private function compileConfig(array $scripts) |
| 2188 |
{ |
| 2189 |
$config = new \stdClass(); |
| 2190 |
foreach ($scripts as $script) { |
| 2191 |
if ($script->hasConfig()) { |
| 2192 |
$config->{$script->getConfigKey()} = $script->getConfig(); |
| 2193 |
} |
| 2194 |
} |
| 2195 |
$this->lastCompiledConfig = $config; |
| 2196 |
return \Kibo\Phast\Common\JSON::encode(['config' => base64_encode(\Kibo\Phast\Common\JSON::encode($config))]); |
| 2197 |
} |
| 2198 |
/** |
| 2199 |
* @param string $script |
| 2200 |
* @return string |
| 2201 |
*/ |
| 2202 |
private function interpolate($script) |
| 2203 |
{ |
| 2204 |
return sprintf('(function(){%s})', $script); |
| 2205 |
} |
| 2206 |
/** |
| 2207 |
* @param PhastJavaScript[] $scripts |
| 2208 |
* @return string |
| 2209 |
*/ |
| 2210 |
private function getCacheKey(array $scripts) |
| 2211 |
{ |
| 2212 |
return array_reduce($scripts, function ($carry, \Kibo\Phast\ValueObjects\PhastJavaScript $script) { |
| 2213 |
$carry .= $script->getFilename() . '-' . $script->getCacheSalt() . "\n"; |
| 2214 |
return $carry; |
| 2215 |
}, ''); |
| 2216 |
} |
| 2217 |
} |
| 2218 |
namespace Kibo\Phast\Filters\HTML\LazyImageLoading; |
| 2219 |
|
| 2220 |
class Filter extends \Kibo\Phast\Filters\HTML\BaseHTMLStreamFilter |
| 2221 |
{ |
| 2222 |
protected function handleTag(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag) |
| 2223 |
{ |
| 2224 |
if (!$tag->hasAttribute('loading')) { |
| 2225 |
$tag->setAttribute('loading', 'lazy'); |
| 2226 |
} |
| 2227 |
(yield $tag); |
| 2228 |
} |
| 2229 |
protected function isTagOfInterest(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag) |
| 2230 |
{ |
| 2231 |
return $tag->getTagName() == 'img'; |
| 2232 |
} |
| 2233 |
} |
| 2234 |
namespace Kibo\Phast\Filters\HTML\ScriptsProxyService; |
| 2235 |
|
| 2236 |
class Filter extends \Kibo\Phast\Filters\HTML\BaseHTMLStreamFilter |
| 2237 |
{ |
| 2238 |
use \Kibo\Phast\Filters\HTML\Helpers\JSDetectorTrait, \Kibo\Phast\Logging\LoggingTrait; |
| 2239 |
/** |
| 2240 |
* @var array |
| 2241 |
*/ |
| 2242 |
private $config; |
| 2243 |
/** |
| 2244 |
* @var ServiceSignature |
| 2245 |
*/ |
| 2246 |
private $signature; |
| 2247 |
/** |
| 2248 |
* @var LocalRetriever |
| 2249 |
*/ |
| 2250 |
private $retriever; |
| 2251 |
private $tokenRefMaker; |
| 2252 |
/** |
| 2253 |
* @var ObjectifiedFunctions |
| 2254 |
*/ |
| 2255 |
private $functions; |
| 2256 |
/** |
| 2257 |
* @var bool |
| 2258 |
*/ |
| 2259 |
private $didInject = false; |
| 2260 |
public function __construct(array $config, \Kibo\Phast\Security\ServiceSignature $signature, \Kibo\Phast\Retrievers\LocalRetriever $retriever, \Kibo\Phast\Services\Bundler\TokenRefMaker $tokenRefMaker, \Kibo\Phast\Common\ObjectifiedFunctions $functions = null) |
| 2261 |
{ |
| 2262 |
$this->config = $config; |
| 2263 |
$this->signature = $signature; |
| 2264 |
$this->retriever = $retriever; |
| 2265 |
$this->tokenRefMaker = $tokenRefMaker; |
| 2266 |
$this->functions = is_null($functions) ? new \Kibo\Phast\Common\ObjectifiedFunctions() : $functions; |
| 2267 |
} |
| 2268 |
protected function isTagOfInterest(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag) |
| 2269 |
{ |
| 2270 |
return $tag->getTagName() == 'script' && $this->isJSElement($tag); |
| 2271 |
} |
| 2272 |
protected function handleTag(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $script) |
| 2273 |
{ |
| 2274 |
$this->rewriteScriptSource($script); |
| 2275 |
if (!$this->didInject) { |
| 2276 |
$this->addScript(); |
| 2277 |
$this->didInject = true; |
| 2278 |
} |
| 2279 |
(yield $script); |
| 2280 |
} |
| 2281 |
private function rewriteScriptSource(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $element) |
| 2282 |
{ |
| 2283 |
if (!$element->hasAttribute('src')) { |
| 2284 |
return; |
| 2285 |
} |
| 2286 |
$src = trim($element->getAttribute('src')); |
| 2287 |
$url = $this->getAbsoluteURL($src); |
| 2288 |
$cacheMarker = $this->retriever->getCacheSalt($url); |
| 2289 |
if (!$cacheMarker) { |
| 2290 |
return; |
| 2291 |
} |
| 2292 |
$cacheMarker .= '-' . \Kibo\Phast\Filters\JavaScript\Minification\JSMinifierFilter::VERSION; |
| 2293 |
$element->setAttribute('src', $this->makeProxiedURL($url, $cacheMarker)); |
| 2294 |
$element->setAttribute('data-phast-original-src', (string) $url); |
| 2295 |
$element->setAttribute('data-phast-params', $this->makeServiceParams($url, $cacheMarker)); |
| 2296 |
} |
| 2297 |
private function makeProxiedURL(\Kibo\Phast\ValueObjects\URL $url, $cacheMarker) |
| 2298 |
{ |
| 2299 |
$params = ['service' => 'scripts', 'src' => (string) $url->withoutQuery(), 'cacheMarker' => $cacheMarker]; |
| 2300 |
return (new \Kibo\Phast\Services\ServiceRequest())->withUrl(\Kibo\Phast\ValueObjects\URL::fromString($this->config['serviceUrl']))->withParams($params)->serialize(); |
| 2301 |
} |
| 2302 |
private function makeServiceParams(\Kibo\Phast\ValueObjects\URL $url, $cacheMarker) |
| 2303 |
{ |
| 2304 |
return \Kibo\Phast\Services\Bundler\ServiceParams::fromArray(['src' => (string) $url->withoutQuery(), 'cacheMarker' => $cacheMarker, 'isScript' => '1'])->sign($this->signature)->replaceByTokenRef($this->tokenRefMaker)->serialize(); |
| 2305 |
} |
| 2306 |
private function addScript() |
| 2307 |
{ |
| 2308 |
$config = ['serviceUrl' => $this->config['serviceUrl'], 'pathInfo' => \Kibo\Phast\Services\ServiceRequest::getDefaultSerializationMode() === \Kibo\Phast\Services\ServiceRequest::FORMAT_PATH, 'urlRefreshTime' => $this->config['urlRefreshTime'], 'whitelist' => $this->config['match']]; |
| 2309 |
$script = \Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/ScriptsProxyService/rewrite-function.js', "var config=phast.config[\"script-proxy-service\"];var urlPattern=/^(https?:)?\\/\\//;var cacheMarker=Math.floor((new Date).getTime()/1e3/config.urlRefreshTime);var whitelist=compileWhitelistPatterns(config.whitelist);phast.scripts.push(function(){overrideDOMMethod(\"appendChild\");overrideDOMMethod(\"insertBefore\")});function compileWhitelistPatterns(a){var b=/^(.)(.*)\\1([a-z]*)\$/i;var c=[];a.forEach(function(d){var e=b.exec(d);if(!e){window.console&&window.console.log(\"Phast: Not a pattern:\",d);return}try{c.push(new RegExp(e[2],e[3]))}catch(f){window.console&&window.console.log(\"Phast: Failed to compile pattern:\",d)}});return c}function checkWhitelist(g){for(var h=0;h<whitelist.length;h++){if(whitelist[h].exec(g)){return true}}return false}function overrideDOMMethod(i){var j=Element.prototype[i];var k=function(){var l=processNode(arguments[0]);var m=j.apply(this,arguments);l();return m};Element.prototype[i]=k;window.addEventListener(\"load\",function(){if(Element.prototype[i]===k){delete Element.prototype[i]}})}function processNode(n){if(!n||n.nodeType!==Node.ELEMENT_NODE||n.tagName!==\"SCRIPT\"||!urlPattern.test(n.src)||n.src.substr(0,config.serviceUrl.length)===config.serviceUrl||!checkWhitelist(n.src)){return function(){}}var o=n.src;n.src=phast.buildServiceUrl(config,{service:\"scripts\",src:o,cacheMarker:cacheMarker});return function(){n.src=o}}\n"); |
| 2310 |
$script->setConfig('script-proxy-service', $config); |
| 2311 |
$this->context->addPhastJavaScript($script); |
| 2312 |
} |
| 2313 |
private function getAbsoluteURL($url) |
| 2314 |
{ |
| 2315 |
return \Kibo\Phast\ValueObjects\URL::fromString($url)->withBase($this->context->getBaseUrl()); |
| 2316 |
} |
| 2317 |
} |
| 2318 |
namespace Kibo\Phast\Filters\HTML\MetaCharset; |
| 2319 |
|
| 2320 |
class Filter implements \Kibo\Phast\Filters\HTML\HTMLStreamFilter |
| 2321 |
{ |
| 2322 |
public function transformElements(\Traversable $elements, \Kibo\Phast\Filters\HTML\HTMLPageContext $context) |
| 2323 |
{ |
| 2324 |
$didYield = false; |
| 2325 |
foreach ($elements as $element) { |
| 2326 |
if ($element instanceof \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag) { |
| 2327 |
if ($element->tagName == 'meta' && array_keys($element->getAttributes()) == ['charset']) { |
| 2328 |
continue; |
| 2329 |
} |
| 2330 |
if (!$didYield && !in_array($element->tagName, ['html', 'head'])) { |
| 2331 |
(yield new \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag('meta', ['charset' => 'utf-8'])); |
| 2332 |
$didYield = true; |
| 2333 |
} |
| 2334 |
} |
| 2335 |
(yield $element); |
| 2336 |
} |
| 2337 |
} |
| 2338 |
} |
| 2339 |
namespace Kibo\Phast\Filters\HTML\BaseURLSetter; |
| 2340 |
|
| 2341 |
class Filter extends \Kibo\Phast\Filters\HTML\BaseHTMLStreamFilter |
| 2342 |
{ |
| 2343 |
protected function isTagOfInterest(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag) |
| 2344 |
{ |
| 2345 |
return $tag->getTagName() == 'base' && $tag->hasAttribute('href'); |
| 2346 |
} |
| 2347 |
protected function handleTag(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag) |
| 2348 |
{ |
| 2349 |
$base = \Kibo\Phast\ValueObjects\URL::fromString($tag->getAttribute('href')); |
| 2350 |
$current = $this->context->getBaseUrl(); |
| 2351 |
$this->context->setBaseUrl($base->withBase($current)); |
| 2352 |
(yield $tag); |
| 2353 |
} |
| 2354 |
} |
| 2355 |
namespace Kibo\Phast\Filters\HTML\CSSInlining; |
| 2356 |
|
| 2357 |
class OptimizerFactory |
| 2358 |
{ |
| 2359 |
/** |
| 2360 |
* @var Cache |
| 2361 |
*/ |
| 2362 |
private $cache; |
| 2363 |
public function __construct(array $config) |
| 2364 |
{ |
| 2365 |
$this->cache = new \Kibo\Phast\Cache\File\Cache($config['cache'], 'css-optimizitor'); |
| 2366 |
} |
| 2367 |
/** |
| 2368 |
* @param \Traversable $elements |
| 2369 |
* @return Optimizer |
| 2370 |
*/ |
| 2371 |
public function makeForElements(\Traversable $elements) |
| 2372 |
{ |
| 2373 |
return new \Kibo\Phast\Filters\HTML\CSSInlining\Optimizer($elements, $this->cache); |
| 2374 |
} |
| 2375 |
} |
| 2376 |
namespace Kibo\Phast\Filters\HTML\CSSInlining; |
| 2377 |
|
| 2378 |
class Filter extends \Kibo\Phast\Filters\HTML\BaseHTMLStreamFilter |
| 2379 |
{ |
| 2380 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 2381 |
const CSS_IMPORTS_REGEXP = '~ |
| 2382 |
@import \\s++ |
| 2383 |
( url \\( )?+ # url() is optional |
| 2384 |
( (?(1) ["\']?+ | ["\'] ) ) # without url() a quote is necessary |
| 2385 |
\\s*+ (?<url>[A-Za-z0-9_/.:?&=+%,-]++) \\s*+ |
| 2386 |
\\2 # match ending quote |
| 2387 |
(?(1)\\)) # match closing paren if url( was used |
| 2388 |
\\s*+ ; |
| 2389 |
~xi'; |
| 2390 |
/** |
| 2391 |
* @var ServiceSignature |
| 2392 |
*/ |
| 2393 |
private $signature; |
| 2394 |
/** |
| 2395 |
* @var int |
| 2396 |
*/ |
| 2397 |
private $maxInlineDepth = 2; |
| 2398 |
/** |
| 2399 |
* @var URL |
| 2400 |
*/ |
| 2401 |
private $baseURL; |
| 2402 |
/** |
| 2403 |
* @var string[] |
| 2404 |
*/ |
| 2405 |
private $whitelist = array(); |
| 2406 |
/** |
| 2407 |
* @var string |
| 2408 |
*/ |
| 2409 |
private $serviceUrl; |
| 2410 |
/** |
| 2411 |
* @var int |
| 2412 |
*/ |
| 2413 |
private $optimizerSizeDiffThreshold; |
| 2414 |
/** |
| 2415 |
* @var Retriever |
| 2416 |
*/ |
| 2417 |
private $localRetriever; |
| 2418 |
/** |
| 2419 |
* @var Retriever |
| 2420 |
*/ |
| 2421 |
private $retriever; |
| 2422 |
/** |
| 2423 |
* @var OptimizerFactory |
| 2424 |
*/ |
| 2425 |
private $optimizerFactory; |
| 2426 |
/** |
| 2427 |
* @var ServiceFilter |
| 2428 |
*/ |
| 2429 |
private $cssFilter; |
| 2430 |
/** |
| 2431 |
* @var Optimizer |
| 2432 |
*/ |
| 2433 |
private $optimizer; |
| 2434 |
/** |
| 2435 |
* @var TokenRefMaker |
| 2436 |
*/ |
| 2437 |
private $tokenRefMaker; |
| 2438 |
/** |
| 2439 |
* @var string[] |
| 2440 |
*/ |
| 2441 |
private $cacheMarkers = array(); |
| 2442 |
public function __construct(\Kibo\Phast\Security\ServiceSignature $signature, \Kibo\Phast\ValueObjects\URL $baseURL, array $config, \Kibo\Phast\Retrievers\Retriever $localRetriever, \Kibo\Phast\Retrievers\Retriever $retriever, \Kibo\Phast\Filters\HTML\CSSInlining\OptimizerFactory $optimizerFactory, \Kibo\Phast\Services\ServiceFilter $cssFilter, \Kibo\Phast\Services\Bundler\TokenRefMaker $tokenRefMaker) |
| 2443 |
{ |
| 2444 |
$this->signature = $signature; |
| 2445 |
$this->baseURL = $baseURL; |
| 2446 |
$this->serviceUrl = \Kibo\Phast\ValueObjects\URL::fromString((string) $config['serviceUrl']); |
| 2447 |
$this->optimizerSizeDiffThreshold = (int) $config['optimizerSizeDiffThreshold']; |
| 2448 |
$this->localRetriever = $localRetriever; |
| 2449 |
$this->retriever = $retriever; |
| 2450 |
$this->optimizerFactory = $optimizerFactory; |
| 2451 |
$this->cssFilter = $cssFilter; |
| 2452 |
$this->tokenRefMaker = $tokenRefMaker; |
| 2453 |
foreach ($config['whitelist'] as $key => $value) { |
| 2454 |
if (!is_array($value)) { |
| 2455 |
$this->whitelist[$value] = ['ieCompatible' => true]; |
| 2456 |
$key = $value; |
| 2457 |
} else { |
| 2458 |
$this->whitelist[$key] = $value; |
| 2459 |
} |
| 2460 |
} |
| 2461 |
} |
| 2462 |
protected function beforeLoop() |
| 2463 |
{ |
| 2464 |
$this->elements = iterator_to_array($this->elements); |
| 2465 |
$this->optimizer = $this->optimizerFactory->makeForElements(new \ArrayIterator($this->elements)); |
| 2466 |
} |
| 2467 |
protected function isTagOfInterest(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag) |
| 2468 |
{ |
| 2469 |
return $tag->getTagName() == 'style' || $tag->getTagName() == 'link' && $tag->getAttribute('rel') == 'stylesheet' && $tag->hasAttribute('href'); |
| 2470 |
} |
| 2471 |
protected function handleTag(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag) |
| 2472 |
{ |
| 2473 |
if ($tag->getTagName() == 'link') { |
| 2474 |
return $this->inlineLink($tag, $this->context->getBaseUrl()); |
| 2475 |
} |
| 2476 |
return $this->inlineStyle($tag); |
| 2477 |
} |
| 2478 |
protected function afterLoop() |
| 2479 |
{ |
| 2480 |
$this->addIEFallbackScript(); |
| 2481 |
$this->addInlinedRetrieverScript(); |
| 2482 |
} |
| 2483 |
private function inlineLink(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $link, \Kibo\Phast\ValueObjects\URL $baseUrl) |
| 2484 |
{ |
| 2485 |
$href = trim($link->getAttribute('href')); |
| 2486 |
if (trim($href, '/') == '') { |
| 2487 |
return [$link]; |
| 2488 |
} |
| 2489 |
$location = \Kibo\Phast\ValueObjects\URL::fromString($href)->withBase($baseUrl); |
| 2490 |
if (!$this->findInWhitelist($location) && !$this->localRetriever->getCacheSalt($location)) { |
| 2491 |
return [$link]; |
| 2492 |
} |
| 2493 |
$media = $link->getAttribute('media'); |
| 2494 |
if (preg_match('~^\\s*(this\\.)?media\\s*=\\s*(?<q>[\'"])(?<m>((?!\\k<q>).)+?)\\k<q>\\s*(;|$)~', $link->getAttribute('onload'), $match)) { |
| 2495 |
$media = $match['m']; |
| 2496 |
} |
| 2497 |
$elements = $this->inlineURL($location, $media); |
| 2498 |
return is_null($elements) ? [$link] : $elements; |
| 2499 |
} |
| 2500 |
private function inlineStyle(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $style) |
| 2501 |
{ |
| 2502 |
$processed = $this->cssFilter->apply(\Kibo\Phast\ValueObjects\Resource::makeWithContent($this->baseURL, $style->textContent), [])->getContent(); |
| 2503 |
$elements = $this->inlineCSS($this->baseURL, $processed, $style->getAttribute('media'), false); |
| 2504 |
if (($id = $style->getAttribute('id')) != '') { |
| 2505 |
if (sizeof($elements) == 1) { |
| 2506 |
$elements[0]->setAttribute('id', $id); |
| 2507 |
} else { |
| 2508 |
foreach ($elements as $element) { |
| 2509 |
$element->setAttribute('data-phast-original-id', $id); |
| 2510 |
} |
| 2511 |
} |
| 2512 |
} |
| 2513 |
return $elements; |
| 2514 |
} |
| 2515 |
private function findInWhitelist(\Kibo\Phast\ValueObjects\URL $url) |
| 2516 |
{ |
| 2517 |
$stringUrl = (string) $url; |
| 2518 |
foreach ($this->whitelist as $pattern => $settings) { |
| 2519 |
if (preg_match($pattern, $stringUrl)) { |
| 2520 |
return $settings; |
| 2521 |
} |
| 2522 |
} |
| 2523 |
return false; |
| 2524 |
} |
| 2525 |
/** |
| 2526 |
* @param URL $url |
| 2527 |
* @param string $media |
| 2528 |
* @param boolean $ieCompatible |
| 2529 |
* @param int $currentLevel |
| 2530 |
* @param string[] $seen |
| 2531 |
* @return Tag[]|null |
| 2532 |
* @throws \Kibo\Phast\Exceptions\ItemNotFoundException |
| 2533 |
*/ |
| 2534 |
private function inlineURL(\Kibo\Phast\ValueObjects\URL $url, $media, $ieCompatible = true, $currentLevel = 0, $seen = array()) |
| 2535 |
{ |
| 2536 |
$whitelistEntry = $this->findInWhitelist($url); |
| 2537 |
if (!$whitelistEntry) { |
| 2538 |
$whitelistEntry = !!$this->localRetriever->getCacheSalt($url); |
| 2539 |
} |
| 2540 |
if (!$whitelistEntry) { |
| 2541 |
$this->logger()->info('Not inlining {url}. Not in whitelist', ['url' => $url]); |
| 2542 |
return [$this->makeLink($url, $media)]; |
| 2543 |
} |
| 2544 |
if (isset($whitelistEntry['ieCompatible']) && !$whitelistEntry['ieCompatible']) { |
| 2545 |
$ieFallbackUrl = $ieCompatible ? $url : null; |
| 2546 |
$ieCompatible = false; |
| 2547 |
} else { |
| 2548 |
$ieFallbackUrl = null; |
| 2549 |
} |
| 2550 |
if (in_array($url, $seen)) { |
| 2551 |
return []; |
| 2552 |
} |
| 2553 |
if ($currentLevel > $this->maxInlineDepth) { |
| 2554 |
return $this->addIEFallback($ieFallbackUrl, [$this->makeLink($url, $media)]); |
| 2555 |
} |
| 2556 |
$seen[] = $url; |
| 2557 |
$this->logger()->info('Inlining {url}.', ['url' => (string) $url]); |
| 2558 |
$content = $this->retriever->retrieve($url); |
| 2559 |
if ($content === false) { |
| 2560 |
return $this->addIEFallback($ieFallbackUrl, [$this->makeServiceLink($url, $media)]); |
| 2561 |
} |
| 2562 |
$content = $this->cssFilter->apply(\Kibo\Phast\ValueObjects\Resource::makeWithContent($url, $content), [])->getContent(); |
| 2563 |
$this->cacheMarkers[$url->toString()] = \Kibo\Phast\Common\Base64url::shortHash(implode("\0", [$this->retriever->getCacheSalt($url), $content])); |
| 2564 |
$optimized = $this->optimizer->optimizeCSS($content); |
| 2565 |
if ($optimized === null) { |
| 2566 |
$this->logger()->error('CSS optimizer failed for {url}', ['url' => (string) $url]); |
| 2567 |
return null; |
| 2568 |
} |
| 2569 |
$isOptimized = false; |
| 2570 |
if (strlen($content) - strlen($optimized) > $this->optimizerSizeDiffThreshold) { |
| 2571 |
$content = $optimized; |
| 2572 |
$isOptimized = true; |
| 2573 |
} |
| 2574 |
$elements = $this->inlineCSS($url, $content, $media, $isOptimized, $ieCompatible, $currentLevel, $seen); |
| 2575 |
$this->addIEFallback($ieFallbackUrl, $elements); |
| 2576 |
return $elements; |
| 2577 |
} |
| 2578 |
private function inlineCSS(\Kibo\Phast\ValueObjects\URL $url, $content, $media, $optimized, $ieCompatible = true, $currentLevel = 0, $seen = array()) |
| 2579 |
{ |
| 2580 |
$urlMatches = $this->getImportedURLs($content); |
| 2581 |
$elements = []; |
| 2582 |
foreach ($urlMatches as $match) { |
| 2583 |
$matchedUrl = \Kibo\Phast\ValueObjects\URL::fromString($match['url'])->withBase($url); |
| 2584 |
$replacement = $this->inlineURL($matchedUrl, $media, $ieCompatible, $currentLevel + 1, $seen); |
| 2585 |
if ($replacement !== null) { |
| 2586 |
$content = str_replace($match[0], '', $content); |
| 2587 |
$elements = array_merge($elements, $replacement); |
| 2588 |
} |
| 2589 |
} |
| 2590 |
$elements[] = $this->makeStyle($url, $content, $media, $optimized); |
| 2591 |
return $elements; |
| 2592 |
} |
| 2593 |
private function addIEFallback(\Kibo\Phast\ValueObjects\URL $fallbackUrl = null, array $elements = null) |
| 2594 |
{ |
| 2595 |
if ($fallbackUrl === null || !$elements) { |
| 2596 |
return $elements; |
| 2597 |
} |
| 2598 |
foreach ($elements as $element) { |
| 2599 |
$element->setAttribute('data-phast-nested-inlined', ''); |
| 2600 |
} |
| 2601 |
$element->setAttribute('data-phast-ie-fallback-url', (string) $fallbackUrl); |
| 2602 |
$element->removeAttribute('data-phast-nested-inlined'); |
| 2603 |
$this->logger()->info('Set {url} as IE fallback URL', ['url' => (string) $fallbackUrl]); |
| 2604 |
return $elements; |
| 2605 |
} |
| 2606 |
private function addIEFallbackScript() |
| 2607 |
{ |
| 2608 |
$this->logger()->info('Adding IE fallback script'); |
| 2609 |
$this->context->addPhastJavaScript(\Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/CSSInlining/ie-fallback.js', "(function(){var a=window.navigator.userAgent;if(a.indexOf(\"MSIE \")===-1&&a.indexOf(\"Trident/\")===-1){return}Array.prototype.forEach.call(document.querySelectorAll(\"style[data-phast-ie-fallback-url]\"),function(b){var c=document.createElement(\"link\");if(b.hasAttribute(\"media\")){c.setAttribute(\"media\",b.getAttribute(\"media\"))}c.setAttribute(\"rel\",\"stylesheet\");c.setAttribute(\"href\",b.getAttribute(\"data-phast-ie-fallback-url\"));b.parentNode.insertBefore(c,b);b.parentNode.removeChild(b)});Array.prototype.forEach.call(document.querySelectorAll(\"style[data-phast-nested-inlined]\"),function(d){d.parentNode.removeChild(d)})})();\n")); |
| 2610 |
} |
| 2611 |
private function addInlinedRetrieverScript() |
| 2612 |
{ |
| 2613 |
$this->logger()->info('Adding inlined retriever script'); |
| 2614 |
$this->context->addPhastJavaScript(\Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/CSSInlining/inlined-css-retriever.js', "phast.stylesLoading=0;var resourceLoader=phast.ResourceLoader.instance;phast.forEachSelectedElement(\"style[data-phast-params]\",function(a){var b=a.getAttribute(\"data-phast-params\");var c=phast.ResourceLoader.RequestParams.fromString(b);phast.stylesLoading++;resourceLoader.get(c).then(function(d){a.textContent=d;a.removeAttribute(\"data-phast-params\")}).catch(function(e){console.warn(\"[Phast] Failed to load CSS\",c,e);var f=a.getAttribute(\"data-phast-original-src\");if(!f){console.error(\"[Phast] No data-phast-original-src on <style>!\",a);return}console.info(\"[Phast] Falling back to <link> element for\",f);var g=document.createElement(\"link\");g.href=f;g.media=a.media;g.rel=\"stylesheet\";g.addEventListener(\"load\",function(){if(a.parentNode){a.parentNode.removeChild(a)}});a.parentNode.insertBefore(g,a.nextSibling)}).finally(function(){phast.stylesLoading--;if(phast.stylesLoading===0&&phast.onStylesLoaded){phast.onStylesLoaded()}})});(function(){var h=[];phast.forEachSelectedElement(\"style[data-phast-original-id]\",function(i){var j=i.getAttribute(\"data-phast-original-id\");if(h[j]){return}h[j]=true;console.warn(\"[Phast] The style element with id\",j,\"has been split into multiple style tags due to @import statements and the id attribute has been removed. Normally, this does not cause any issues.\")})})();\n")); |
| 2615 |
} |
| 2616 |
private function getImportedURLs($cssContent) |
| 2617 |
{ |
| 2618 |
preg_match_all(self::CSS_IMPORTS_REGEXP, $cssContent, $matches, PREG_SET_ORDER); |
| 2619 |
return $matches; |
| 2620 |
} |
| 2621 |
private function makeStyle(\Kibo\Phast\ValueObjects\URL $url, $content, $media, $optimized, $stripImports = true) |
| 2622 |
{ |
| 2623 |
$style = new \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag('style'); |
| 2624 |
if ($media !== '' && $media !== 'all') { |
| 2625 |
$style->setAttribute('media', $media); |
| 2626 |
} |
| 2627 |
if ($optimized) { |
| 2628 |
$style->setAttribute('data-phast-original-src', $url->toString()); |
| 2629 |
$style->setAttribute('data-phast-params', $this->makeServiceParams($url, $stripImports)); |
| 2630 |
} |
| 2631 |
$content = preg_replace('~(</)(style)~i', '$1 $2', $content); |
| 2632 |
$style->setTextContent($content); |
| 2633 |
return $style; |
| 2634 |
} |
| 2635 |
private function makeLink(\Kibo\Phast\ValueObjects\URL $url, $media) |
| 2636 |
{ |
| 2637 |
$link = new \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag('link', ['rel' => 'stylesheet', 'href' => (string) $url]); |
| 2638 |
if ($media !== '') { |
| 2639 |
$link->setAttribute('media', $media); |
| 2640 |
} |
| 2641 |
return $link; |
| 2642 |
} |
| 2643 |
private function makeServiceLink(\Kibo\Phast\ValueObjects\URL $location, $media) |
| 2644 |
{ |
| 2645 |
$url = $this->makeServiceURL($location); |
| 2646 |
return $this->makeLink(\Kibo\Phast\ValueObjects\URL::fromString($url), $media); |
| 2647 |
} |
| 2648 |
protected function makeServiceParams(\Kibo\Phast\ValueObjects\URL $originalLocation, $stripImports = false) |
| 2649 |
{ |
| 2650 |
if (isset($this->cacheMarkers[$originalLocation->toString()])) { |
| 2651 |
$cacheMarker = $this->cacheMarkers[$originalLocation->toString()]; |
| 2652 |
} else { |
| 2653 |
$cacheMarker = $this->retriever->getCacheSalt($originalLocation); |
| 2654 |
} |
| 2655 |
$src = $originalLocation; |
| 2656 |
if ($this->localRetriever->getCacheSalt($src)) { |
| 2657 |
$src = $originalLocation->withoutQuery(); |
| 2658 |
} |
| 2659 |
$params = ['src' => (string) $src, 'cacheMarker' => $cacheMarker]; |
| 2660 |
if ($stripImports) { |
| 2661 |
$params['strip-imports'] = 1; |
| 2662 |
} |
| 2663 |
return \Kibo\Phast\Services\Bundler\ServiceParams::fromArray($params)->sign($this->signature)->replaceByTokenRef($this->tokenRefMaker)->serialize(); |
| 2664 |
} |
| 2665 |
protected function makeServiceURL(\Kibo\Phast\ValueObjects\URL $originalLocation) |
| 2666 |
{ |
| 2667 |
$params = ['service' => 'css', 'src' => (string) $originalLocation, 'cacheMarker' => $this->retriever->getCacheSalt($originalLocation)]; |
| 2668 |
return (new \Kibo\Phast\Services\ServiceRequest())->withUrl($this->serviceUrl)->withParams($params)->sign($this->signature)->serialize(); |
| 2669 |
} |
| 2670 |
} |
| 2671 |
namespace Kibo\Phast\Filters\HTML\CSSInlining; |
| 2672 |
|
| 2673 |
class Optimizer |
| 2674 |
{ |
| 2675 |
private $classNamePattern = '-?[_a-zA-Z]++[_a-zA-Z0-9-]*+'; |
| 2676 |
/** |
| 2677 |
* @var array |
| 2678 |
*/ |
| 2679 |
private $usedClasses; |
| 2680 |
/** |
| 2681 |
* @var Cache |
| 2682 |
*/ |
| 2683 |
private $cache; |
| 2684 |
public function __construct(\Traversable $elements, \Kibo\Phast\Cache\Cache $cache) |
| 2685 |
{ |
| 2686 |
$this->usedClasses = $this->getUsedClasses($elements); |
| 2687 |
$this->cache = $cache; |
| 2688 |
} |
| 2689 |
public function optimizeCSS($css) |
| 2690 |
{ |
| 2691 |
$stylesheet = $this->cache->get(md5($css), function () use($css) { |
| 2692 |
return $this->parseCSS($css); |
| 2693 |
}); |
| 2694 |
if ($stylesheet === null) { |
| 2695 |
return; |
| 2696 |
} |
| 2697 |
$output = ''; |
| 2698 |
$selectors = null; |
| 2699 |
foreach ($stylesheet as $element) { |
| 2700 |
if (is_array($element)) { |
| 2701 |
if ($selectors === null) { |
| 2702 |
$selectors = []; |
| 2703 |
} |
| 2704 |
foreach ($element as $i => $class) { |
| 2705 |
if ($i !== 0 && !isset($this->usedClasses[$class])) { |
| 2706 |
continue 2; |
| 2707 |
} |
| 2708 |
} |
| 2709 |
$selectors[] = $element[0]; |
| 2710 |
} elseif ($selectors !== null) { |
| 2711 |
if (isset($selectors[0])) { |
| 2712 |
$output .= implode(',', $selectors) . $element; |
| 2713 |
} |
| 2714 |
$selectors = null; |
| 2715 |
} else { |
| 2716 |
$output .= $element; |
| 2717 |
} |
| 2718 |
} |
| 2719 |
$output = $this->removeEmptyMediaQueries($output); |
| 2720 |
return trim($output); |
| 2721 |
} |
| 2722 |
/** |
| 2723 |
* Parse a stylesheet into an array of segments |
| 2724 |
* |
| 2725 |
* Each string segment is preceded by zero or more arrays encoding selectors |
| 2726 |
* parsed by parseSelector (see below). |
| 2727 |
* |
| 2728 |
* @param $css |
| 2729 |
* @return array|void |
| 2730 |
*/ |
| 2731 |
private function parseCSS($css) |
| 2732 |
{ |
| 2733 |
$re_simple_selector_chars = "[A-Z0-9_.#*:>+\\~\\s-]"; |
| 2734 |
$re_selector = "(?: {$re_simple_selector_chars} | \\[[a-z]++\\] )++"; |
| 2735 |
$re_rule = "~\n (?<= ^ | [;{}] ) \\s*+\n ( (?: {$re_selector} , )*+ {$re_selector} )\n ( { [^}]*+ } )\n ~xi"; |
| 2736 |
if (preg_match_all($re_rule, $css, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE) === false) { |
| 2737 |
// This is an error condition |
| 2738 |
return; |
| 2739 |
} |
| 2740 |
$offset = 0; |
| 2741 |
$stylesheet = []; |
| 2742 |
foreach ($matches as $match) { |
| 2743 |
$selectors = $this->parseSelectors($match[1][0]); |
| 2744 |
if ($selectors === null) { |
| 2745 |
continue; |
| 2746 |
} |
| 2747 |
if ($match[0][1] > $offset) { |
| 2748 |
$stylesheet[] = substr($css, $offset, $match[0][1] - $offset); |
| 2749 |
} |
| 2750 |
foreach ($selectors as $selector) { |
| 2751 |
$stylesheet[] = $selector; |
| 2752 |
} |
| 2753 |
$stylesheet[] = $match[2][0]; |
| 2754 |
$offset = $match[0][1] + strlen($match[0][0]); |
| 2755 |
} |
| 2756 |
if ($offset < strlen($css)) { |
| 2757 |
$stylesheet[] = substr($css, $offset); |
| 2758 |
} |
| 2759 |
return $stylesheet; |
| 2760 |
} |
| 2761 |
/** |
| 2762 |
* Parse the selector part of a CSS rule into an array of selectors. |
| 2763 |
* |
| 2764 |
* Each selector will be an array with at offset 0, the string contents of |
| 2765 |
* the selector. The rest of the array will be the class names (if any) that |
| 2766 |
* must be present in the document for this selector to match. |
| 2767 |
* |
| 2768 |
* Null is returned if none of the selectors use classes, and can therefore |
| 2769 |
* not be optimized. |
| 2770 |
* |
| 2771 |
* @param string $selectors |
| 2772 |
* @return array|void |
| 2773 |
*/ |
| 2774 |
private function parseSelectors($selectors) |
| 2775 |
{ |
| 2776 |
$newSelectors = []; |
| 2777 |
$anyClasses = false; |
| 2778 |
foreach (explode(',', $selectors) as $selector) { |
| 2779 |
$classes = [$selector]; |
| 2780 |
if (preg_match_all("~\\.({$this->classNamePattern})~", $selector, $matches)) { |
| 2781 |
foreach ($matches[1] as $class) { |
| 2782 |
$classes[] = $class; |
| 2783 |
$anyClasses = true; |
| 2784 |
} |
| 2785 |
} |
| 2786 |
$newSelectors[] = $classes; |
| 2787 |
} |
| 2788 |
if (!$anyClasses) { |
| 2789 |
return; |
| 2790 |
} |
| 2791 |
return $newSelectors; |
| 2792 |
} |
| 2793 |
private function getUsedClasses(\Traversable $elements) |
| 2794 |
{ |
| 2795 |
$classes = []; |
| 2796 |
/** @var Tag $tag */ |
| 2797 |
foreach ($elements as $tag) { |
| 2798 |
if (!$tag instanceof \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag) { |
| 2799 |
continue; |
| 2800 |
} |
| 2801 |
foreach (preg_split('/\\s+/', $tag->getAttribute('class')) as $cls) { |
| 2802 |
if ($cls != '' && !isset($classes[$cls]) && preg_match("/^{$this->classNamePattern}\$/", $cls)) { |
| 2803 |
$classes[$cls] = true; |
| 2804 |
} |
| 2805 |
} |
| 2806 |
} |
| 2807 |
return $classes; |
| 2808 |
} |
| 2809 |
private function removeEmptyMediaQueries($css) |
| 2810 |
{ |
| 2811 |
return preg_replace('~@media\\s++[A-Z0-9():,\\s-]++\\s*+{}~i', '', $css); |
| 2812 |
} |
| 2813 |
} |
| 2814 |
namespace Kibo\Phast\Filters\HTML\Minify; |
| 2815 |
|
| 2816 |
class Filter implements \Kibo\Phast\Filters\HTML\HTMLStreamFilter |
| 2817 |
{ |
| 2818 |
public function transformElements(\Traversable $elements, \Kibo\Phast\Filters\HTML\HTMLPageContext $context) |
| 2819 |
{ |
| 2820 |
$inTags = ['pre' => 0, 'textarea' => 0]; |
| 2821 |
foreach ($elements as $element) { |
| 2822 |
if ($element instanceof \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag && isset($inTags[$element->getTagName()])) { |
| 2823 |
$inTags[$element->getTagName()]++; |
| 2824 |
} elseif ($element instanceof \Kibo\Phast\Parsing\HTML\HTMLStreamElements\ClosingTag && !empty($inTags[$element->getTagName()])) { |
| 2825 |
$inTags[$element->getTagName()]--; |
| 2826 |
} elseif ($element instanceof \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Junk && !array_sum($inTags)) { |
| 2827 |
$element->originalString = preg_replace_callback('~\\s++~', function ($match) { |
| 2828 |
return strpos($match[0], "\n") === false ? ' ' : "\n"; |
| 2829 |
}, $element->originalString); |
| 2830 |
} |
| 2831 |
(yield $element); |
| 2832 |
} |
| 2833 |
} |
| 2834 |
} |
| 2835 |
namespace Kibo\Phast\Filters\HTML\DelayedIFrameLoading; |
| 2836 |
|
| 2837 |
class Filter extends \Kibo\Phast\Filters\HTML\BaseHTMLStreamFilter |
| 2838 |
{ |
| 2839 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 2840 |
protected $addScript = false; |
| 2841 |
private $ignoredUrlPattern = '~ |
| 2842 |
^about: | |
| 2843 |
^data: |
| 2844 |
~ix'; |
| 2845 |
protected function isTagOfInterest(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag) |
| 2846 |
{ |
| 2847 |
return $tag->getTagName() == 'iframe' && $tag->hasAttribute('src'); |
| 2848 |
} |
| 2849 |
protected function handleTag(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $iframe) |
| 2850 |
{ |
| 2851 |
$src = trim($iframe->getAttribute('src')); |
| 2852 |
if (preg_match($this->ignoredUrlPattern, $src)) { |
| 2853 |
(yield $iframe); |
| 2854 |
return; |
| 2855 |
} |
| 2856 |
$this->logger()->info('Delaying iframe {src}', ['src' => $src]); |
| 2857 |
$iframe->setAttribute('data-phast-src', $src); |
| 2858 |
$iframe->setAttribute('src', 'about:blank'); |
| 2859 |
$this->addScript = true; |
| 2860 |
(yield $iframe); |
| 2861 |
} |
| 2862 |
protected function afterLoop() |
| 2863 |
{ |
| 2864 |
if ($this->addScript) { |
| 2865 |
$this->context->addPhastJavaScript(\Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/DelayedIFrameLoading/iframe-loader.js', "window.addEventListener(\"load\",function(){window.setTimeout(loadIframes,30)});function loadIframes(){phast.forEachSelectedElement(\"iframe[data-phast-src]\",function(a){var b=a.getAttribute(\"data-phast-src\");a.removeAttribute(\"data-phast-src\");if(a.getAttribute(\"src\")===\"about:blank\"){a.setAttribute(\"src\",b)}})}\n")); |
| 2866 |
} |
| 2867 |
} |
| 2868 |
} |
| 2869 |
namespace Kibo\Phast\Filters\HTML\Diagnostics; |
| 2870 |
|
| 2871 |
class Filter implements \Kibo\Phast\Filters\HTML\HTMLStreamFilter |
| 2872 |
{ |
| 2873 |
private $serviceUrl; |
| 2874 |
public function __construct($serviceUrl) |
| 2875 |
{ |
| 2876 |
$this->serviceUrl = $serviceUrl; |
| 2877 |
} |
| 2878 |
public function transformElements(\Traversable $elements, \Kibo\Phast\Filters\HTML\HTMLPageContext $context) |
| 2879 |
{ |
| 2880 |
$url = (new \Kibo\Phast\Services\ServiceRequest())->withUrl(\Kibo\Phast\ValueObjects\URL::fromString($this->serviceUrl))->serialize(); |
| 2881 |
$script = \Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/Diagnostics/diagnostics.js', "window.addEventListener(\"load\",function(){var a=phast.config.diagnostics.serviceUrl;var b=new XMLHttpRequest;b.open(\"GET\",a);b.responseType=\"json\";b.onload=function(){var c=b.response;var d={};var e=[];c.forEach(function(g){var h=g.context.requestId;if(!d[h]){d[h]={title:g.context.service,timestamp:g.context.timestamp,errorsCnt:0,warningsCnt:0,longestPrefixLength:0,entries:[]};e.push(d[h])}if(g.level>8){d[h].errorsCnt++}else if(g.level===8){d[h].warningsCnt++}var i=(g.context.timestamp-d[h].timestamp).toFixed(3);if(g.context.class){i+=\" \"+g.context.class}if(g.context.class&&g.context.method){i+=\"::\"}if(g.context.method){i+=g.context.method+\"()\"}if(g.context.line){i+=\" Line: \"+g.context.line}var j=g.message.replace(/\\{([a-z0-9_.]*)\\}/gi,function(l,m){return g.context[m]});var k;if(g.level>8){k=console.error}else if(g.level===8){k=console.warn}else if(g.level>1){k=console.info}else{k=console.log}d[h].entries.push({prefix:i,message:j,cb:k});if(i.length>d[h].longestPrefixLength){d[h].longestPrefixLength=i.length}});if(e.length===0){return}e.sort(function(n,o){return n.timestamp<o.timestamp?-1:1});var f=e[0].timestamp;console.group(\"Phast diagnostics log\");e.forEach(function(p){var q=(p.timestamp-f).toFixed(3);var r=q+\" - \"+p.title+\" (entries: \"+p.entries.length;if(p.errorsCnt>0){r+=\", errors: \"+p.errorsCnt}if(p.warningsCnt>0){r+=\", warnings: \"+p.warningsCnt}r+=\")\";console.groupCollapsed(r);p.entries.forEach(function(s){var t=s.prefix;var u=p.longestPrefixLength-t.length;for(var v=0;v<u;v++){t+=\" \"}s.cb(t+\" \"+s.message)});console.groupEnd()});console.groupEnd()};b.send()});\n"); |
| 2882 |
$script->setConfig('diagnostics', ['serviceUrl' => $url]); |
| 2883 |
$context->addPhastJavaScript($script); |
| 2884 |
foreach ($elements as $element) { |
| 2885 |
(yield $element); |
| 2886 |
} |
| 2887 |
} |
| 2888 |
} |
| 2889 |
namespace Kibo\Phast\Filters\HTML; |
| 2890 |
|
| 2891 |
interface HTMLFilterFactory |
| 2892 |
{ |
| 2893 |
/** |
| 2894 |
* @param array $config |
| 2895 |
* @return HTMLStreamFilter |
| 2896 |
*/ |
| 2897 |
public function make(array $config); |
| 2898 |
} |
| 2899 |
namespace Kibo\Phast\Filters\Image; |
| 2900 |
|
| 2901 |
interface ImageFilter |
| 2902 |
{ |
| 2903 |
/** |
| 2904 |
* @param array $request |
| 2905 |
* @return string |
| 2906 |
*/ |
| 2907 |
public function getCacheSalt(array $request); |
| 2908 |
/** |
| 2909 |
* @param Image $image |
| 2910 |
* @param array $request |
| 2911 |
* @return Image |
| 2912 |
*/ |
| 2913 |
public function transformImage(\Kibo\Phast\Filters\Image\Image $image, array $request); |
| 2914 |
} |
| 2915 |
namespace Kibo\Phast\Filters\Image; |
| 2916 |
|
| 2917 |
interface ImageFilterFactory |
| 2918 |
{ |
| 2919 |
/** |
| 2920 |
* @param array $config |
| 2921 |
* @return ImageFilter |
| 2922 |
*/ |
| 2923 |
public function make(array $config); |
| 2924 |
} |
| 2925 |
namespace Kibo\Phast\Filters\Image\Composite; |
| 2926 |
|
| 2927 |
class Factory |
| 2928 |
{ |
| 2929 |
/** |
| 2930 |
* @var array |
| 2931 |
*/ |
| 2932 |
private $config; |
| 2933 |
/** |
| 2934 |
* CompositeImageFilterFactory constructor. |
| 2935 |
* |
| 2936 |
* @param array $config |
| 2937 |
*/ |
| 2938 |
public function __construct(array $config) |
| 2939 |
{ |
| 2940 |
$this->config = $config; |
| 2941 |
} |
| 2942 |
public function make() |
| 2943 |
{ |
| 2944 |
$imageFactoryClass = $this->config['images']['factory']; |
| 2945 |
if (!class_exists($imageFactoryClass)) { |
| 2946 |
throw new \Kibo\Phast\Exceptions\LogicException("No such class: {$imageFactoryClass}"); |
| 2947 |
} |
| 2948 |
$composite = new \Kibo\Phast\Filters\Image\Composite\Filter(new $imageFactoryClass($this->config), (new \Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageInliningManagerFactory())->make($this->config)); |
| 2949 |
foreach ($this->config['images']['filters'] as $class => $config) { |
| 2950 |
if ($config === null) { |
| 2951 |
continue; |
| 2952 |
} |
| 2953 |
$package = \Kibo\Phast\Environment\Package::fromPackageClass($class); |
| 2954 |
$filter = $package->getFactory()->make($this->config); |
| 2955 |
$composite->addImageFilter($filter); |
| 2956 |
} |
| 2957 |
if ($this->config['images']['enable-cache']) { |
| 2958 |
return new \Kibo\Phast\Filters\Service\CachingServiceFilter(new \Kibo\Phast\Cache\File\Cache($this->config['cache'], 'images-1'), $composite, new \Kibo\Phast\Retrievers\LocalRetriever($this->config['retrieverMap'])); |
| 2959 |
} |
| 2960 |
return $composite; |
| 2961 |
} |
| 2962 |
} |
| 2963 |
namespace Kibo\Phast\Filters\Image; |
| 2964 |
|
| 2965 |
class ImageFactory |
| 2966 |
{ |
| 2967 |
private $config; |
| 2968 |
public function __construct(array $config) |
| 2969 |
{ |
| 2970 |
$this->config = $config; |
| 2971 |
} |
| 2972 |
/** |
| 2973 |
* @param URL $url |
| 2974 |
* @return Image |
| 2975 |
*/ |
| 2976 |
public function getForURL(\Kibo\Phast\ValueObjects\URL $url) |
| 2977 |
{ |
| 2978 |
$retriever = new \Kibo\Phast\Retrievers\UniversalRetriever(); |
| 2979 |
$retriever->addRetriever(new \Kibo\Phast\Retrievers\LocalRetriever($this->config['retrieverMap'])); |
| 2980 |
$retriever->addRetriever((new \Kibo\Phast\Retrievers\RemoteRetrieverFactory())->make($this->config)); |
| 2981 |
return new \Kibo\Phast\Filters\Image\ImageImplementations\DefaultImage($url, $retriever); |
| 2982 |
} |
| 2983 |
/** |
| 2984 |
* @param Resource $resource |
| 2985 |
* @return Image |
| 2986 |
*/ |
| 2987 |
public function getForResource(\Kibo\Phast\ValueObjects\Resource $resource) |
| 2988 |
{ |
| 2989 |
return $this->getForURL($resource->getUrl()); |
| 2990 |
} |
| 2991 |
} |
| 2992 |
namespace Kibo\Phast\Filters\Image\CommonDiagnostics; |
| 2993 |
|
| 2994 |
class DiagnosticsRetriever implements \Kibo\Phast\Retrievers\Retriever |
| 2995 |
{ |
| 2996 |
/** |
| 2997 |
* @var string |
| 2998 |
*/ |
| 2999 |
private $file; |
| 3000 |
/** |
| 3001 |
* DiagnosticsRetriever constructor. |
| 3002 |
* @param string $file |
| 3003 |
*/ |
| 3004 |
public function __construct($file) |
| 3005 |
{ |
| 3006 |
$this->file = $file; |
| 3007 |
} |
| 3008 |
public function retrieve(\Kibo\Phast\ValueObjects\URL $url) |
| 3009 |
{ |
| 3010 |
return file_get_contents($this->file); |
| 3011 |
} |
| 3012 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\URL $url) |
| 3013 |
{ |
| 3014 |
return ''; |
| 3015 |
} |
| 3016 |
} |
| 3017 |
namespace Kibo\Phast\Filters\Image; |
| 3018 |
|
| 3019 |
interface Image |
| 3020 |
{ |
| 3021 |
const TYPE_JPEG = 'image/jpeg'; |
| 3022 |
const TYPE_PNG = 'image/png'; |
| 3023 |
const TYPE_WEBP = 'image/webp'; |
| 3024 |
/** |
| 3025 |
* @return integer |
| 3026 |
*/ |
| 3027 |
public function getWidth(); |
| 3028 |
/** |
| 3029 |
* @return integer |
| 3030 |
*/ |
| 3031 |
public function getHeight(); |
| 3032 |
/** |
| 3033 |
* @return string |
| 3034 |
*/ |
| 3035 |
public function getType(); |
| 3036 |
/** |
| 3037 |
* @return string |
| 3038 |
*/ |
| 3039 |
public function getAsString(); |
| 3040 |
/** |
| 3041 |
* @return integer |
| 3042 |
*/ |
| 3043 |
public function getSizeAsString(); |
| 3044 |
/** |
| 3045 |
* @param integer $width |
| 3046 |
* @param integer $height |
| 3047 |
* @return Image |
| 3048 |
*/ |
| 3049 |
public function resize($width, $height); |
| 3050 |
/** |
| 3051 |
* @param integer $compression |
| 3052 |
* @return Image |
| 3053 |
*/ |
| 3054 |
public function compress($compression); |
| 3055 |
/** |
| 3056 |
* @param string $type - One of Image::TYPE_JPEG, Image::TYPE_PNG or Image::TYPE_WEBP |
| 3057 |
* @return Image |
| 3058 |
*/ |
| 3059 |
public function encodeTo($type); |
| 3060 |
} |
| 3061 |
namespace Kibo\Phast\Filters\Image\ImageAPIClient; |
| 3062 |
|
| 3063 |
class Diagnostics implements \Kibo\Phast\Diagnostics\Diagnostics |
| 3064 |
{ |
| 3065 |
public function diagnose(array $config) |
| 3066 |
{ |
| 3067 |
$package = \Kibo\Phast\Environment\Package::fromPackageClass(get_class($this)); |
| 3068 |
/** @var ImageFilter $filter */ |
| 3069 |
$filter = $package->getFactory()->make($config); |
| 3070 |
$imageData = @"\211PNG\r\n\32\n\0\0\0\rIHDR\0\0\1h\0\0\1h\10\2\0\0\0\365\207\366\202\0\0\0\31tEXtSoftware\0Adobe ImageReadyq\311e<\0\0\3\$iTXtXML:com.adobe.xmp\0\0\0\0\0<?xpacket begin=\"\357\273\277\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?> <x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27 \"> <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"> <rdf:Description rdf:about=\"\" xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\" xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\" xmlns:stRef=\"http://ns.adobe.com/xap/1.0/sType/ResourceRef#\" xmp:CreatorTool=\"Adobe Photoshop CS6 (Macintosh)\" xmpMM:InstanceID=\"xmp.iid:0E913E46F5A911E5B20EF2CD3E8D574E\" xmpMM:DocumentID=\"xmp.did:0E913E47F5A911E5B20EF2CD3E8D574E\"> <xmpMM:DerivedFrom stRef:instanceID=\"xmp.iid:CCC4537FF57711E5B20EF2CD3E8D574E\" stRef:documentID=\"xmp.did:CCC45380F57711E5B20EF2CD3E8D574E\"/> </rdf:Description> </rdf:RDF> </x:xmpmeta> <?xpacket end=\"r\"?>\10\f\316\"\0\0!^IDATx\332\354\235{lT\327\235\307g\346\316\353\216I\370\3\\\330\215\f!\17\r\201\$\305\20\300\255\275y\264\252]\247\213\332\30\323f\225\332\33\233\0006*\304\260\305P\333\332\222\312\266\10T@q\4\256\223\340\10oTR\273\356\37\220&f\265\$\$8\261\351\202+S\10\243<xX\221\332uF-\221=\343y\334\231=\327\3.\1\0033\366=\257;\337\217PB\22\342\271s\317\275\237\363\373\235s~\347X//^d\1\0\200T\260\341\26\0\0 \16\0\0\304\1\0\2008\0\0\20\7\0\0\342\0\0\0\210\3\0\0q\0\0 \16\0\0\304\1\0\2008\0\0\0\342\0\0@\34\0\0^\330q\v@2(Ks\334?Y\307\370CG^j\322z{p\363\345\26GF\333\353\214/n\270\344\31\223\335nGY\271\363[\337\26\347z\202\365\277\210\371|\311\374I\353\324\251\212\327\313\370\362\310\207\342\25\225^\34\354\237\33\363YC\255\\+\2205\366\355M\322\32\0 U\2015,\361`0P\275\tY\0\2300\30\34\2055\0\2008`\rX\3@\34\260\206\201h\3\3\260\0060\4\214q\244\2235*V\307\7\7\321.\0\21\7\254\1k\0\210\3\326\2005\0\304\1kp'\322}|x\371S\260\6\2008`\215\24\254\21\334P\205F\1\20\7\254\1k\0\376`V\305\234\326\30i;\20n\332\203F\1\20\207\330\367\261x\2058\326\10\356\333\33i\335\217F\1HU\204FY\232\243\256[\17k\0D\34 \5kx\266\357\260\252*\367+\211\7\203#\257\265\302\32\0\342\2005R\260\6\226\223\3\244*\260\6\254\1 \16X\3\326\0\20\7\254!\2105\264\201\201\341\325\317\301\32\2001\30\343\220\333\32(B\1\2108`\rX\3@\34\260\0065\242}}\260\6@\252\2k\244\0\212P\0\"\16X\3\326\0\20\7\254\1k\0\244*@\34k\240\10\5 \342\2005`\r\0q\300\32\260\6@\252\2\4\261F<\30\f6\324G\217t\241E\0\304\1k\$k\r\24\241\0\244*\222\334\21\257\27\326\0\0\342H\1kf\246\332\270\215\2735b~?\254\1\220\252Hc\rOs\213\222\225\305\3672P\204\2\20q\300\32\260\6\2008`\rX\3\0\210C\34kD\272\217\303\32@\26\322}\214C\34k\240\10\5 \342\2005`\r\0q\300\32\324\10uv\302\32\0\251\n\254\221\2(B\1\2108`\rX\3@\34\260\6\254\1\0R\25A\254\201\"\24\200\210\3\326\2005\0\304\1k\300\32\0@\34\342XC_N\16k\0\263`\3761\16Q\254\201\345\344\0\21\7\254\1k\0\210\303\264\360\267\206\317\7k\0\244*2\241\356\332\315\327\32(B\1\2108\344\263\206#7\17\326\0\0\342\2005\0@\252bRk\214\264\35\0107\355\301\263\5 \16X#YP\204\2\220\252\300\32\260\6\0\246\2168\370ZC?\253\261iO\264\243\35\217\24\2008`\215d\255\201\345\344\0\251\212d8\312\312a\r\0 \216\324\254\241V\256\2055\0@\252\"\2075P\204\2\20q\310\207\2624\7\326\0\0\342H\315\32\236\355;`\r\0 \216\324\254aUU.\237\36\355\353\2035@\232#\337\30\20753\323]\275\231\2275P\204\2\200|\21\7\337\215y`\r\0\244\24\207g'\267-6\302G\272`\r\0\344KU\364\215y\274^.\37\35\352\354\fmk\304\343\2\200d\21\207kK\r\307\345\241\316\302B\222%\341q\1@&q8\312\312]EE\34/\300\252\252\236\346\26\270\3\0i\304\301w\241\327?.#+K\255G\266\2\200\f\342\260y\275\34\27z]\207=;\333]\337\200\207\6\0\241\305AR\3\265q\33\257%\33\343\342\314/ y\23\236\33\220\346\10=\253r\307\233o\txU\$o\212\376y\364H\27\236\36\200\210\3\244\342\216\332:ei\16\356\3\2008@*9\224\252\352\313\3361\311\2 \16\220\22JV\226\247\271\5\367\1@\34 ew\250\273v\343>\0\210\3\244\206#7\317\265\245\6\367\1@\34 5\\EE\230\240\5\20\7H\31\367\263e\230d\1\20\7H\r\275\222e\373\16\33\247\312]\0 \16\211\335\241/r\305\4-\2008@J\240\n\16@\34`\"\330\263\2631A\v \16\2202\216\334<L\262\0\210C2\342\301`\250\263\223\374\225\3435\250\225k\355\371\5x\266\0\304!\2155\2\325\233B\333\32\203\r\365|\257\4Up\0\342\220\311\32\211\363\237\243G\272F\332\16p\274\30}\222e\353\v\230d\1\20\207\320h\3\3C\305E\327\236\32\37n\332\23\351>\316\363\316N\233\206mJ\1\304!\2645\306=\2231\270\241\212\374'\216\27\246de\271kj\361\220\1\210C8n}\222\253\376\237\270\16\224\242\n\16@\34\302A\222\221\300\232U\2678\377\231\374\247@\365&\276\356@\25\34\2008\4\"\3113\31\265\336\236\221\327Z\371^\252Z\271\26\223,\0\342\340Op\337\336\221\272d\207\17\"\255\373\303\274\367\26F\25\34\2008\370[\203\270 \245\377\205X&\332\327\307\361\232Q\5\7 \16n\304\203\301\341u?I\325\32WtSW\303}\222\305\263\23\225,\0\342`n\215\261%^\23\371\337\7\7\2035[\370\16\224*^/\252\340\0\304\301\16}\261\306\$\254\221 \346\363q_\215\216*8\0q0\264F\305\352IZ#A\364HWp\337^\276_G\257\202+^\201\207\17@\0344\255\341\363\335b\211\327\4\210\264\356\347\273\32]w\307\272\365\230\240\5\20\7-\310\33>\\\362\214\201\326H\300}5:\252\340\0\304A\321\32\311,\361\232\30\$\212\211\371\375<o=\252\340\0\304A\3z\326\260\$&Y\266\376\234\363\$\v\252\340\0\304!\35ZoO\260i\17\337kp\344\346\271\353\33\360 \2\210C&\242\35\355\241\316N\276\327\340\314/\300\4-\2008\$#\264\255\221\377\$\v\252\340\0\304!\35#\215\r|'Y,\243Upp\7\2008dB\204\325\350VUuWo\306\$\v\2008d\"\346\363\5\2527\361\275\6T\301\1\210C>\364I\26\336\253\321Q\5\7 \16\371\20a5\272#7\317\271n=\332\2@\0342\241\257F\367\371\370^\203\273\244\24Up\0\342\220\214\300\306*\356\223,\250\202\3\20\207d\304\7\7G\266\277\310}\222\305\263}\7&Y\0\304!\23\"\254F\327\335\201*8\0q\310E\264\243\235\357\1\264\26T\301\1\210CF\270\37@kA\25\34\2008dD\204\325\350\250\202\3\20\207d\350\207H\362>\200\326\222\330\2464\277\0\315\1 \16\251\334\301{5\272\356\216\332:L\320\2\210C&DX\215\216*8\0q\310\207\10\7\320\352Up\315-h\v\0q\310\4\367\3h\23\356@\25\34\2008\$\203\373\1\264\26T\301\1\210C:DX\215n\31\255\202\303\4-\2008dB\37(\345}\0\255\356\216g\3130\311\2 \16\231\210\36\351\342\276\32\35Up\0\342\220\17\21V\243\243\n\16@\34\362\301\375\0ZKb\222\245\276\21m\1 \16\231\340~\0-\301\236\235\215*8\0q\310\204\10\7\320ZP\5\7 \16\351\320z{F^k\345~\31\250\202\3\20\207dDZ\367s?\200\326\202*8\0qHGh[#\367\325\350VUU\267\276\200I\26\0q\310\204\10\253\321m\323\246\241\n\16@\0342!\302\1\264\26T\301\1\210C:b>\237\10\253\321\35\271y\256-5h\16\0qHC\364H\27\367-\10\256\242\"L\320\2\210C&D8\200\326\202*8\0qH\207\10\7\320&\252\340l^/\232\3@\34\322\20\330X\305}5\272>A\333\270\r\23\264\0\342\220\6AV\243\243\n\16@\34\222!\302\1\264\226\321*8L\320\2\210C&\242\35\355\"\254Fw\344\346a\222\5@\0342\21\332\326(\302\$\v\252\340\0\304!\31\"\34@kA\25\34\2008\344B\220\3hQ\5\7 \16\t\335!\300\1\264\211*8\270\3@\34\322 \302\1\264\226\321\tZwM-\232\3@\34\322 \302\1\264\26T\301\1\210C:F\352j\271\257F\267\240\n\16@\34\322\21\330X%\304\$K\345ZL\262\0\210C\32\49\200\226\200*8\0q\310\204 \253\321Q\5\7 \16\311\210v\264s?\200\3262:\311\342\331\211J\26\0q\310\203\10\7\320\352\356\360zQ\5\7R\302\216[\300\227\340\206*\333\357~O\272}\276\227\341\310\315\213\226\225GZ\367\337\354\17\304.^`_\255G>\24O\210\364\342\20\241\312\323\224\4*V;W\256\342\37|\316\230y\253w\330\347\vm\303\276\36\340\n\326\313\213\27\341.\0\0R\353fp\v\0\0\20\7\0\0\342\0\0@\34\0\0\210\3\0\0q\0\0\0\304\1\0\2008\0\0\20\7\0\0\342\0\0@\34\0\0000>\334\252c\257=U\314z\327]\327UX)s\346X=\236\361U7k\226UU\223\377\240x0\30\273t\351\306\37=sf\354\367\332'\37[\276\374R\377M\337\251\370\340 \36\v\211\260ff*\331\vo\361\7b\27/\304\4\330\344\325l\267\335\360\"\267+F\270\363N\345\276\373\365\17\230\222\241\314\276{b\357<G\22\373\t'\344\242\235:\31\277|Y\353\355\301\343\302-0\366zm\263\357&\265fL\261\317\237?\261gi\254\v\321.^\210\17\r\243Y9\210\343\332\206\264\315\370\232mz\246\305\343\341\276\251\4mb~?y\362\264\363\347I\204\22;\335\217~\214\252)\224o\346*\367\336Kz\35\205\362\256\250\332\300@\354\322E\355\263\317\264\23'\340\21*\342P\226\346\270\2537\233^\20)y\$\372\347\323x\340\f\224\205#\347\33\312\334\271\34#S\22l\222H3\372\316Q\264\251a\342 i\210\247\276\1wm\33408z\352d\264\277?z\370\20FIR\202\364F\216e\313\224\7\346\211\326!%\3324\322\335\35\355hG3A\34\324\211\366\365Ez>\204An\33_8KJ\355\213\36\261M\233&E\257\20y\353\255\250\0\247\360A\34i\21\203\340i\273\21GY\271\363[\337V\$<\317\205\$\247\221c\307\302\257\276\214.\1\342\240\2373\17\f\204\17\37\272\305>\300\351\362\250ef:W\256r\26\26\3122\263v\v\"\335\307C\315\373\322yt\34\342@g\305\"+qUT:r\363\314\326%\370|\241\266\3\351\31QB\34\254\363\227PG{X\2003\334\240\f\3\3651\362RS\272M\301(?\273\353\237Sx\16\356\275\317\361\255o\343\375\237\270\247\35\16\373\327\277\356(Z\36w:c\3523wb\342Z_\245n\374\17\345\236{M.\307\351\323\235O>i\2337/v\341|\334\357\2078 \16j/\225\307\343X\274\230\350#\26\n\305\316\2365\337\27t\224\225\253u\377\351X\264\210\2102]^\244Y\263\34\205OZg\376\223v\374}\210\3\342\240\254\217\334<\373\243\217ig\317\230\246\247\"\271\211g\367\36\347w\voVjd\362p\362\201\7\354\337-\214\377\375\357\261O?\2058 \16\272\201.\351\251,w\334\241\235\350\225\375\2738\327\255W7\377\3146sfZ7\350\324\251\372;2m\272\271C\17\224\325\v\320S\251\252\273\2444\243\355ukf\246\274\201\6\271~\362-L0\325j\10\256\242\242\214\337\375^Y\232\3q\0\312\261\37y\367\16\374\227\214\217\232\275xEF\313+2.\350\242\333\240YY\236\355;\34e\345HU\220\252P\16=<\36\307\23O\304\206\206\$\0321u\3277\270K\377=}\6ASkP\207\303\261x\261m\336\274h\327\333\2108\0\335\264\305S\275Y\212n\212\$V\$=q^\263!\23\30\27Gn\36I[\344\315C!\16iP+\327\n\356\16\222R\351\211\25\322\223\344\323\226\346\0263\ry@\34\342\272C\330\347\214\\\30\311\336\305\257j\25\316\35\333w\230\306\35\20\207\270\210\371\234\221P\210\\\30fO&\230\207\232\305\35v\271.7\346\367\307\277\370b\354\37\343\201\200v\376\374\355\355\230\330\3340\361{y\366=\325\247i\2537\7*V\213S\27\247/\t\255\\\v\5L\322\35\201\352M\262\327\266\210+\216\304\16n\211\375\307\r\337\250:\261gjbwu\373\374\371\302\332\204\304\267\256\347\253F\352ja\r\270\3\342H\n\252\5\313DCc&\n]U\211\362\315\\\373\303\17+s\37\20*{w\346\27D\337{\217{\3556\254\1wH\234\252PL\202FU\22I\364\363\243\273`\212\263\253\235kM\5_q\330\363\vD\263F\"i\275r~\305\325cqn\214+\307v\341\27m\2Hvw@\34\343eI\275=\211\346\264\27\257p}\377\7\334\2379\222\260\220\16\237\327\36bD\243jm\235@\331\353\251\223\311j\364\253\214|\21e\311\22\373\203\17\361\335E\375Zw\2106\206\225\302\305\v\273\221O\240\256V\220\275\225\310\267v\225\224\362\325\7\351`\207\n9,\265\322\213PZ^\341\373\232\321\330\374\231t\t\216\334\\\373\302E\334\rBl8\\\362\214t\342\20w\311y\344\350QAj\223\311eD~\337\251\375\355o\372s\306im\265\325\343\211E\243\214\367\376\261ffzv\375\212W\276F\\\31~\373\355\340O7F\3368\250\361@\300\310\37~\366l\264\353\355\310\233\207c_~\251\334s\17\307M\0l\323\247[g\317\216\36=*\2278\260\216#\351~\257\243}\250\270H\343\267?\255\223y\225\220\273\246\226\313Y'D\31\301}{I\204\25\332\326H5\214'?\234\$\200\344\203\2\333_\324\6\6\270\265l~\201t\265p\20Gj\317\31\211*#\335\307\371\4\207^\257\215a\272\344\\\267\236\375^\241\372\236\254\235\235\344Mf<\240Cz\205\341\345O\21[\221\v\340\322\270\356g\313lR\255\337\2078R&\270\241\212\227;\354\254*\312\364\263>KJ\331\217e\220\230\216D\31\274ZV\217>\212\213\302<F\326\254\252\2526n\2038\340\16*8\226,e3\264\241n}\201q\240A\222\205\300\232U\334\347\27\310\5\214\324\325\6\352j\331\207\36\372b\277-5\20\207\371\335\301>+f3\263\343\256\251e9 Jn\343\360\352\347\204:\2375z\244\213\313x\226\253\250H\226J\26\210c\342\214l\321|\331\312\350<%\273\241\r\22\270\5*V\vx\$Zb<\213}\332\342\256\336\fq\230\34\255\267\207}\302Bu\10MOR\326\255g\366]\310kI\0027\221\227?\221\264%\270o/\22\26\210\303`B\315\373\30\2425c\n\305P\371\371*f\v\242\310\v)H\361\336mb\242\326\375\214\335\341,,\24\206\5\342\230\24\$\306\216\3661]\224e\237?\237^\22\304l\37@\362*Jt\n7cw\20w\273**!\16\223\23\351\371\320\34_\304\305j\376\225d(\22Y\203\213;\364\223\272\304\336\314\25\342\230,\332\7\335&\370\26\216\262r6S6\221\356\343Rd(\343\272\203\345X\251\213\371:\32\210\203u\266\22\223\377\0G\327\17\304B\262\3\3\301\rU\362\336%\242<f\231)\361\270\310A\7\304a\0\327\356f(\2455\266\3240X\270\21\17\6\2035[do\353`]\r\263\365;\"\7\35\20\207\21\257\204\241\205\233\214\261ff:\v\vY\274rM{\4\\\257\221r[\17\0162[\277#r\320\1q\30\21\201'\261a\262\2608W\256b0\5\33>\322%\324\332\320I5wo\317H\333\2014\17: \16\331\236\332\213\27\f\26\7\375p#\346\367\207~\265\333L\255\20n\332\303fA:\t:\304\\\204\16q\30\321\272s\346\260\v\225\207\206\r\374i\216\262r\6\341\306\310\256\2352\356\216w\233/\365R\23\243\220\360\351\247!\16s\302r\377(\355\324I##a\372\223)\321\276>A\266\2004<a\tuv2\370 Gn\236\200\347\316B\34F\334\304Y\263\330=\257}\247\214\372Q\366\374\2\6\223)#;i\326v\17\277\3722\233\352{\307\323\377\6q\230.OY\232\303\254\276\203\344\325\6\306\374\316\345\305\324_\255#]&\230I\271i\332888\362Z+\vq<\376\4\304a:q,Y\302\354\263\"'z\rkx\257\327\236\235M\367\275\n\6M6&:N\213\264\356g\260\374O\311\312\22m\210\24\342\230t\277\375\344\367\230}\226\201\203\5\f\26\10D\336\317|c\2427\22\372\355\33,\202\216e\313 \16\363\300f\230`,O10\354g\340\2730\253\305\16\351\20t\330\27=\2q\230\7\226\353sB\306\275\207\$\356\245\355\273H\367q\23\217n\\\257\310?\274I\375E\2356M\250l\5\342\230D\247\275n=\263\343\335\264\201\1\3\363\24\6qo\370\340\301\364y\22\"\7\303`zE\250l\5\342\230x\247\355*^\301.\334\370u\263Dq/\321\234\274\347\260O\200\370\340`\324\320\3655\342g+\20\307D\260ff\272\2537\263\234\20550\334`\221\247\274\373N\272=\22\f\",\322j\342l)\10qL\304\32\236\346\26fg#\352\325\350\365\27702V\242?LB\367t{*H\204\305\240\334^\234bY\210Chk\350IJG\273\261\243\214\264W\23E\373\372\322a\26\226K\234e\360!\210C>H\220\317\330\32\344%\f7\3551V|\264\257\3374\233\260\246\334X\364Kr\224\271s!\16\311p\256[\357\331\276\203\2455\364\215\366\352\f>bCy\354q\352\357\317\341C\351\371\204\220\300\220v\266bUUA&e!\216\244\2\215\214\266\327\335%\245\314FC-W7\3323<\346\267/X@[v\351\231\247\\\221\346\37\377H\375\215\2357O\204oj\207\27n\325H^\257\253\242\222\345\221\210c\326\10To\242\261\200Jy`\236\354o\216\320\342x\347\250\253\250\210\356\33\373\360\303\21\210CX\354\371\5\216\302B\366\312\30\263\6\245u\20\264S-\215\376r\6\221!\255F\232\217jdj\233.\304\336\34\20\307\365!\206~\240\331\223\337cy\\;3k0\230\3143\345\236=\251\271\343\3349\252e\307\212\30K9 \216+o\224\375\321GI\30\317r\354s\334\1\202`\315\26z%\36\312\302Et\257?m\212Sn\245\316?\237\246\275_\201\2624\207\373\302\334t\24\207>%\231\275\220\4\27\266\0313\270\313b\f\375\210\263\306\6\252#\213\266\31_\243\373\316\2349\3q0(\355\263\222'\26\342\270U\326`\304\17I\34\357n\235\222\241\314\276[\234H\357\272\364\$\324\321n\354z\215\361\357\306\254\331t#\216O>\2068\364d\255\276\201n\304q\337\375Q\244*7\303-\366\331\231\6\246'#\333_d\23y\322\216\255b\247\373!\216D\233R\275\325\264#G\244*B\303,\320\30K\214i\235\30\3068\22\2\275t\221\2568\4\230X\2018\370\300`D\343\372\304x\352T\312o\313%4\353\225[\361\327\377\243\234r\316\342\376\35\261r\224y\34\353\363\r\225<\23\334P\305x\205%\365)\25\243\217\230\223\270\211)/fa\271\202\31\21\207\20QF\370\340A^\23i\326)\31tS\25C\217\230\223;\t\275|\231z\207\357\365\362M\f!\16\372\217Q0\30y\377\275p\333\1\276-\235\230T\222\267\233\225)\342\240\3377\330f\337\rq\230\226h__\244\347\303H\353~\334\2124\354-DH( \16\371|\21=|H\250:Q\332#j\6\236Mi\2b\227.Q]1d\275\353.\210\303TD\272\217G\373\373\265\17\272E\253.\247\335\1\246s5=\207n`\306L\210\303T8r\363\364\232\332\312\2651\277_;\367\21\221\210h\241\7`\200v\361\202\200k\224!\16\31\372\204i\323lW%\242\371|\221\23\275\372\351\33\234\fB{\365\27\312\333\256\217\277\314>\307\4q0yo\275^\362\313]R\312k\270\224\366\352/\220v\375\"n\1SOgg\253\225k\3578\366\276kK\215\315\324\241,\2008\200\321\375\277\252\272\212\212\246\264\275\256\356\332\r}\0\210\3\244\206#7\17\3720%\246\337a\0\342\20E\37\356\372\6kf\246\244_!\366\5\246\215\276\312\227_B\34\200\5\316\374\202)\35\235\216\262r)\305A\271\36\24@\34\340\246XUU\255\\\233\321\366:2\27\351\233\222\367\312N\210#\355P\274\336\214\226W\354\305+dz\214\4\330\223J\260\0332\323\344_\20m,f\350\341\251\336\354\246\274u\245\221\217\321\364L\264\32R\25 \4\316\374\2\222\266\310;b\nL\214\270+G\3u\265\206\234\3563v\n\21I;\23\1\244}\376|\213\220\333\235\217\233\266x\232[\250\36\266\2\200\251\304a\0247\332'4\226\21\\=`E\271\347\36\333\254\331\202\34\260r\275;\262\2622Z^\241w\274\33\220\221\330_\377\2qp#>8\250k\345\252Yt\217<\366\270}\301\2\373\242Gx\35\19.\372\220\307\366\35B\273\303\343\301\313\374\225\367j4\252\245\370\350~\3769\304!\222G:\332\311/\313\325Cd\35\217?!H\30B\334\341\256\336\34\250X=\261\372Z\332\273`\212\31\254\1z`p\364&\241\240\317\27n\3323\274\374\251\341u?\211t\37\217\7\203\"\344,\236\346\226\211\215\225\"\3151Y\10\306`?d\210cR\220W.\270\241j\250\270(\324\331\311]\37\304\35\356\232ZA\273 ,Zc\30\202q\357\t \216d\263\230\320\266F\21\364\241\35706\241e\351\264/\333Fy\27u\211H\207\31t\210#e}\f\257~.\332\327\307\3612\334\317\226M\240{\247}\322\232\351\27Y\247\20nd/\244\33n\01007\17q\244L\314\347\v\254Y\25\334\267\227W\350\241\227\264\324\375\247p]\220\331\27Y\303\241\20\207\1DZ\367\7\2527\305\374~>}\232\327\233j1K\364\314\31\272\2274g\16\236\n6\16\245\335\224\20\7]\264\336\236\341\322\37k\3\3|\22\226\225\317\211\325\315N\237\216G\"\1\365E\34\303C\20\207\334\304\7\7\3\25\253\271\270\3036mZJ\243\244\264\217h\304R\16f\16\215a\214\3034\356\3402\336\341\372\341\217R\270N\3723\377ceAim\215\314L\332\313\216E84\17\3420\306\35\301\206z.AG\362#\35,NB\306R\16\372S*\244\213\22\341|/\210\303\30\242G\272B\235\235\354?\327\371\235\374\24z*\312)\225\375\301\207\360\$(\v\27\321\315S(O\253C\34\254\t\277\3722\373I\26{vv\362\313\215b\227.\322}\230(\237k-\5\264GFE\230R\2018\fNXB\277}\203\303\223\372\257\313\222\2158>\373\214v\352\204l\205\366>/\202\34\274\0q\30I\244u?\373QRG\3167\222\2158\350\217\306\247\371\370(\203\235bc\247\373!\0163\272\343\375\367Xwqs\347&\33\345\32\261\243\332m\336\234\364\36\346\260/X@\327\32~\277 {\301A\34F\213\343\320!\306\237hU\325\344\23\4\352\343\243\251\214\271\230P\34\213\36\241\233\247\234\373H\220o\nq\30\335\264\275=\354\263\25\333C\17'{y\37\235\245\376\362\$=\346b2\224\2459\264WpD\373\373\5\371\262\20\7\205x\222\371\204\231r\337\375\311>y\372\23\355\213I~\314\305d8\226Q7\246\366A7\304a\336\240\343\342\5\326\255\230\364yH\332\261w\251G\34\351\232\255\320\316S\304\31\340\2008\250\20\37\32f\335\212I\237\207\24\37\34d\260\233C\32f+\366\374\2\352y\312\311\377\25\347\373B\34\24z\6\336[\327\337\232\310\211^\332\37\341L?q8\n\vi\4\2034\23\342\340\32q\360\336\272\3766\317\37\375IY%++\255\26t\220\324\314\221\233G\367\241\n\6\23\373\357C\34\200S@\344\3631\330\7\200A\17,\16\316\225\253\250\353\236\362\256\10\20\7h\2279\31\220\255\274\373\16uq\344\346\245\317\362s\307c\217Qo\262\356n\241\2762\304\221\2160\310V\10\256\212\312\264\260FY9\355a\321\230\337/T\236\2q\320\271\247IO\216\232<[I\217\240#\245\355\224&(z\221\346S \16j\367t\326l\306\237\30\17\4R\375_\302\207Y,\2157}\320\301 \334\320\33\253\355\0\304a~\330\357\276\251\235?\237r'v\370\20\203\245\361\$\3500\361\364\21253\323\375l\31\365\306\365\371\304Y\367\5q\320\202Aa\3658\21G\352\333^\353'l3\31\250w\225\224\232\265\255\235+WYU\225z\270q\364D\f\253\361\252\33\335\307\346\262\377\320\211\365H\341\203\7Y\304_^\257s\335z\23\306\225Ks\\EE\324[\326\357\217\264\356\2078L\16\211]\355<\346b'6K\242\365\366\2609L\320U\274\302|\243\244\356\352\315\f>%\374\2077\305\374\372\20\207|\261\353\365\357\377\$\346GBLF\335\304<\263rR*\334R\303`\$+\36\fF\16\376\6\3420\270\341\344\261\\r2[l\220P\205\315\6\313\$a!/\33\222\224\324\302\215\267\336\22\341\$\4\210\203r/\364|\25\373p\3032\351\332'f\33,\223\227\315\0043,\244{P\267\276\300\340\203H\270\21~\365ea\357\3\304a\f\344\225p\362x+&_\373\24i\335\317\354T\7\265\266N\366\301\16\317\316\335\f\26n\10\36n@\34\306\365B\265u\\>\332\220)\325\221];\31\335(UU\33\267\311\273\315\217\272k\267\302D|D\345\"\207\33\20\2071\326\3604\267pIR,\6M\251F\217ti\254\226\30)YY\372\355\222\320\35\316u\353i\327\316_\233?\212\34n@\34\306X\203\327A\355\344m7\352D\330\221\227\232\230]\266\214\356p\224\225\273Y\255d#\315*\346\332\r\210\303\f\326\260\30\272\246\220\10(\322}\34\356\270\2315\324\312\265\314>\216\245\304!\16\326\330\363\v\246ttr\264\20660`l\2774\322\330\300\362`\7Y\334\341\256o`i\2150I\33\r\212\"!\16\341pm\251\361\3247\360\32\327\270\222\6\377\272\331\330\37H\222\352`\323\36\226_\201\270C\227\357\322\34a#Ju\327n\226\223e1\277?\364\253\335R\274\2\20G\352\201\306[]l\326\377\334\202h_\37\215\315x\242\35\355\32\333BL\"\337\214\246\227\4,f!:#\1\21\263\321\320+A\337\256\235\202\217\211\376\343E\200\v\222W\206\253\244T\21`\31\2I(Fv\376\222\322\17\17l\254\"Q\0\343`\312]Rj\360\241`]\215 \257\r\21\231\253x\5\343\233@\222\0246;\263!\342`\204\243\254<\243\355u\222\233(b,^\nu\264\323\333\240\201}\302r\305\313\331\331DX\334C\17\233\327K\332\232\210\214\2615\264\201\201\221\272Z\231\372Qx\341\26\301\252c\3312\307\277<\312w,\343\306\$%L\371\305&\tK\$7\227q\224\236H[\310\33\353x\374\211\320\257\233\331\367\275\326\314L\327\363U\274\226\377\6k\266H\26\200C\20\327==\312c\217\333\27,\260/z\204\315\312\342\224\210\371\375\$\236g\221l76\3308\3154\353\263-\365\rZIi\250\355\0\33}\350\325\211+W9\v\vy\365\20\$\304\23p\217/\210\343\366\221\205m\336<\345\336{\225\7\346q\234^M\252_\332\372s6\243\0z\302R\263%\243\345\25^\357\22\311\n\211>b\0336\206~\373\206\276\313!\235oM\232\336\371\364\323\354c\253\257\$\236\235\235\242\355`\236\224m//Na\343\31{~\1iN6W\26\351>\256}\366\331\230\211\265\276S\223|zH\372j\233}\267\345\352\271'\366\371\363-\36\217\310\246\270\216@]-\343\0\236es\3376A\213\364|h\224A\22I\250\10A%y\310\203\33\252\244\214\315\205\25G2q{\374\213/n\363\365\246O\0270\343\230H4\273o/\227e\310\214\27M\336\26m`@\373\350\254\366\351\247\261\263g\223_(\245g\240\331\vI\207\241\314\231\243\314\235+\310\240\25\371.\303\313\237\222\364\201\2248U\321\215`\n)\10k\r\313h\321=I\342\234\302\354\243A\"\304k\203\304D\347\21\17\4n\334\347\335:%CI\4\230B\26\362\23k\4*V\313\373Lb\214Ch\364%\33\257\265\362-y\32\251\253\265fd\360\35\10\270m\347a\317\316\226\250Y\23\326\220e\255\327\370w\36/\247\310\326\10To\22\241P\222\344\341,K\340\314\215\t\254\1q\10\375x\r\257~N\234z'\270\3\326\2008D\207\274\242\344\361\22mn\37\356\2005 \16\201\323\223\355/\222WT\314\307\v\356\2300\321\276>\323X\3\342\20\254G\362\371Hz\"\370r \342\216\21\361\316@\226 \204\\\263\3124\326\260`VE\234@#\324\321\36\346Q]6\1\310u\306\207\206\334\317\226\tU\305#\256j\371\315\246C\34&\357\216\364\335\267\244\352\216\364C\25\316\236U\267\276`K\217\2454\23C\257-\332\372s)v\364B\252\"Yn\22\250\253\25vD\3436\27\337\3333\\\372c\222\272\243\35\307\205\334\31rLi\rD\34<\225\301\254\372\223b\20658HRw.\333\336\10\236xr_\266\7q\230\260#\n\377\256Cve\\K\270i\217v\342\204\273z\263D\25\203T\273\204`\375/\244+\223\2078\304\355\205\"\357\277\27n;`\312GJO[\226?\225\346\241G\314\357\37y\365\25\31k\344!\16QC\214\377>\222\16\317\23\t=H\$\345\336\370S\271*G\f!\324\331\31~\365e3M\270B\34\334|a\340\26\22\322\364\272>_`\315*}c\3475\25i\222\271D\272\217\207\232\367\231>7\2018\350\6\253\332\271\217\"\335\335\332\261w\323\312\27\327Kst\303nGY\271\353\207?2\361|mz*\3\3420R\26\321\376~\355\203\356\364|\206n\372^\265\356'\277L\251\217tV\6\3041A\342\301`\354\322\245\350\2313\332'\37\247yd\221\274>\354\305+\\\337\377\201\230{\352\244\324ID\216\35K\253\261\f\210cR\232\320.^\210\17\rk\247NN~\353\3234M^:\332\311/\233\327\353,)\25\355\304\211\244\256?mF\270\223\$\265=G-\243\333\216\352\273\363N\345\276\373\311\337m3\276f\233\256\237\33,\373\356\236c;\230\222P\202\374\2258\"\221\253\343\21\241\322_\25\257p~'_\374\311\227\364\34\341\246\"\216\24\344r\215_\22\214Y\346\37\377f\326,\252\235\317u\33\32'\2\207+\277\37U\3\354\300\363\341\33=\305\306\221\233k_\270H\234\30\204\304\230\321S'\243\375\375\360\5kq\30\205\2624\307:uj2\22\31\204\354\220\266V\226,\261?\370\20\227]\310\211,\264s\347\242>\255\2358a\326\352\2224\22\7H[\211\330\346\315\263\315\230i\237?\237RL\252\r\f\220PT;^\373\344\343\330\351~L\207A\34\300\204\$\222\337\304IZ\312\2349V\217\347\212bn>MC\324`\t\4\256d\243\243\343V\261\277\376%\376\371\347\261\213\27\240\t\210\3\0\300\1\354\307\1\0\2008\0\0\20\7\0\0\342\0\0@\34\0\0\210\3\0\0 \16\0\0\304\1\0\2008\0\0\222\362\377\2\f\0\330R\221^i(\247\250\0\0\0\0IEND\256B`\202"; |
| 3071 |
if ($imageData === false) { |
| 3072 |
throw new \Kibo\Phast\Exceptions\RuntimeException('Could not read testing image for ' . static::class . ' diagnostics.'); |
| 3073 |
} |
| 3074 |
$image = new \Kibo\Phast\Filters\Image\ImageImplementations\DummyImage(); |
| 3075 |
$image->setImageString($imageData); |
| 3076 |
$filter->transformImage($image, []); |
| 3077 |
} |
| 3078 |
} |
| 3079 |
namespace Kibo\Phast\Filters\Image\ImageAPIClient; |
| 3080 |
|
| 3081 |
class Factory implements \Kibo\Phast\Filters\Image\ImageFilterFactory |
| 3082 |
{ |
| 3083 |
public function make(array $config) |
| 3084 |
{ |
| 3085 |
$signature = new \Kibo\Phast\Security\ServiceSignature(new \Kibo\Phast\Cache\File\Cache($config['cache'], 'api-service-signature')); |
| 3086 |
return new \Kibo\Phast\Filters\Image\ImageAPIClient\Filter($config['images']['filters'][\Kibo\Phast\Filters\Image\ImageAPIClient\Filter::class], $signature, (new \Kibo\Phast\HTTP\ClientFactory())->make($config)); |
| 3087 |
} |
| 3088 |
} |
| 3089 |
namespace Kibo\Phast\Filters\Image\ImageAPIClient; |
| 3090 |
|
| 3091 |
class Filter implements \Kibo\Phast\Filters\Image\ImageFilter |
| 3092 |
{ |
| 3093 |
/** |
| 3094 |
* @var array |
| 3095 |
*/ |
| 3096 |
private $config; |
| 3097 |
/** |
| 3098 |
* @var ServiceSignature |
| 3099 |
*/ |
| 3100 |
private $signature; |
| 3101 |
/** |
| 3102 |
* @var Client |
| 3103 |
*/ |
| 3104 |
private $client; |
| 3105 |
/** |
| 3106 |
* Filter constructor. |
| 3107 |
* @param array $config |
| 3108 |
* @param ServiceSignature $signature |
| 3109 |
* @param Client $client |
| 3110 |
*/ |
| 3111 |
public function __construct(array $config, \Kibo\Phast\Security\ServiceSignature $signature, \Kibo\Phast\HTTP\Client $client) |
| 3112 |
{ |
| 3113 |
$this->config = $config; |
| 3114 |
$this->signature = $signature; |
| 3115 |
$this->client = $client; |
| 3116 |
$this->signature->setIdentities(''); |
| 3117 |
} |
| 3118 |
public function getCacheSalt(array $request) |
| 3119 |
{ |
| 3120 |
$result = 'api-call'; |
| 3121 |
foreach (['width', 'height', 'preferredType'] as $key) { |
| 3122 |
if (isset($request[$key])) { |
| 3123 |
$result .= "-{$key}-{$request[$key]}"; |
| 3124 |
} |
| 3125 |
} |
| 3126 |
return $result; |
| 3127 |
} |
| 3128 |
public function transformImage(\Kibo\Phast\Filters\Image\Image $image, array $request) |
| 3129 |
{ |
| 3130 |
$url = $this->getRequestURL($request); |
| 3131 |
$headers = $this->getRequestHeaders($image, $request); |
| 3132 |
$data = $image->getAsString(); |
| 3133 |
try { |
| 3134 |
$response = $this->client->post(\Kibo\Phast\ValueObjects\URL::fromString($url), $data, $headers); |
| 3135 |
} catch (\Exception $e) { |
| 3136 |
throw new \Kibo\Phast\Filters\Image\Exceptions\ImageProcessingException('Request exception: ' . get_class($e) . ' MSG: ' . $e->getMessage() . ' Code: ' . $e->getCode()); |
| 3137 |
} |
| 3138 |
if (strlen($response->getContent()) === 0) { |
| 3139 |
throw new \Kibo\Phast\Filters\Image\Exceptions\ImageProcessingException('Image API response is empty'); |
| 3140 |
} |
| 3141 |
$newImage = new \Kibo\Phast\Filters\Image\ImageImplementations\DummyImage(); |
| 3142 |
$newImage->setImageString($response->getContent()); |
| 3143 |
$headers = []; |
| 3144 |
foreach ($response->getHeaders() as $name => $value) { |
| 3145 |
$headers[strtolower($name)] = $value; |
| 3146 |
} |
| 3147 |
$newImage->setType($headers['content-type']); |
| 3148 |
return $newImage; |
| 3149 |
} |
| 3150 |
private function getRequestURL(array $request) |
| 3151 |
{ |
| 3152 |
$params = []; |
| 3153 |
foreach (['width', 'height'] as $key) { |
| 3154 |
if (isset($request[$key])) { |
| 3155 |
$params[$key] = $request[$key]; |
| 3156 |
} |
| 3157 |
} |
| 3158 |
return (new \Kibo\Phast\Services\ServiceRequest())->withUrl(\Kibo\Phast\ValueObjects\URL::fromString($this->config['api-url']))->withParams($params)->sign($this->signature)->serialize(\Kibo\Phast\Services\ServiceRequest::FORMAT_QUERY); |
| 3159 |
} |
| 3160 |
private function getRequestHeaders(\Kibo\Phast\Filters\Image\Image $image, array $request) |
| 3161 |
{ |
| 3162 |
$headers = ['X-Phast-Image-API-Client' => $this->getRequestToken(), 'Content-Type' => 'application/octet-stream']; |
| 3163 |
if (isset($request['preferredType']) && $request['preferredType'] == \Kibo\Phast\Filters\Image\Image::TYPE_WEBP) { |
| 3164 |
$headers['Accept'] = 'image/webp'; |
| 3165 |
} |
| 3166 |
return $headers; |
| 3167 |
} |
| 3168 |
private function getRequestToken() |
| 3169 |
{ |
| 3170 |
$token_parts = []; |
| 3171 |
foreach (['host-name', 'request-uri', 'plugin-version'] as $key) { |
| 3172 |
$token_parts[$key] = $this->config[$key]; |
| 3173 |
} |
| 3174 |
$token_parts['php'] = PHP_VERSION; |
| 3175 |
return json_encode($token_parts); |
| 3176 |
} |
| 3177 |
} |
| 3178 |
namespace Kibo\Phast\Filters\Image\ImageImplementations; |
| 3179 |
|
| 3180 |
abstract class BaseImage |
| 3181 |
{ |
| 3182 |
/** |
| 3183 |
* @var integer |
| 3184 |
*/ |
| 3185 |
protected $width; |
| 3186 |
/** |
| 3187 |
* @var integer |
| 3188 |
*/ |
| 3189 |
protected $height; |
| 3190 |
/** |
| 3191 |
* @var integer |
| 3192 |
*/ |
| 3193 |
protected $compression; |
| 3194 |
/** |
| 3195 |
* @var string |
| 3196 |
*/ |
| 3197 |
protected $type; |
| 3198 |
/** |
| 3199 |
* @return string |
| 3200 |
*/ |
| 3201 |
public abstract function getAsString(); |
| 3202 |
/** |
| 3203 |
* @return integer |
| 3204 |
*/ |
| 3205 |
public function getSizeAsString() |
| 3206 |
{ |
| 3207 |
return strlen($this->getAsString()); |
| 3208 |
} |
| 3209 |
/** |
| 3210 |
* @param $width |
| 3211 |
* @param $height |
| 3212 |
* @return static |
| 3213 |
*/ |
| 3214 |
public function resize($width, $height) |
| 3215 |
{ |
| 3216 |
$im = clone $this; |
| 3217 |
$im->width = $width; |
| 3218 |
$im->height = $height; |
| 3219 |
return $im; |
| 3220 |
} |
| 3221 |
/** |
| 3222 |
* @param $compression |
| 3223 |
* @return static |
| 3224 |
*/ |
| 3225 |
public function compress($compression) |
| 3226 |
{ |
| 3227 |
$im = clone $this; |
| 3228 |
$im->compression = $compression; |
| 3229 |
return $im; |
| 3230 |
} |
| 3231 |
/** |
| 3232 |
* @param $type |
| 3233 |
* @return static |
| 3234 |
*/ |
| 3235 |
public function encodeTo($type) |
| 3236 |
{ |
| 3237 |
$im = clone $this; |
| 3238 |
$im->type = $type; |
| 3239 |
return $im; |
| 3240 |
} |
| 3241 |
} |
| 3242 |
namespace Kibo\Phast\Filters\Image\ImageImplementations; |
| 3243 |
|
| 3244 |
class DefaultImage extends \Kibo\Phast\Filters\Image\ImageImplementations\BaseImage implements \Kibo\Phast\Filters\Image\Image |
| 3245 |
{ |
| 3246 |
/** |
| 3247 |
* @var URL |
| 3248 |
*/ |
| 3249 |
private $imageURL; |
| 3250 |
/** |
| 3251 |
* @var Retriever |
| 3252 |
*/ |
| 3253 |
private $retriever; |
| 3254 |
/** |
| 3255 |
* @var string |
| 3256 |
*/ |
| 3257 |
private $imageString; |
| 3258 |
/** |
| 3259 |
* @var array |
| 3260 |
*/ |
| 3261 |
private $imageInfo; |
| 3262 |
/** |
| 3263 |
* @var ObjectifiedFunctions |
| 3264 |
*/ |
| 3265 |
private $funcs; |
| 3266 |
public function __construct(\Kibo\Phast\ValueObjects\URL $imageURL, \Kibo\Phast\Retrievers\Retriever $retriever, \Kibo\Phast\Common\ObjectifiedFunctions $funcs = null) |
| 3267 |
{ |
| 3268 |
$this->imageURL = $imageURL; |
| 3269 |
$this->retriever = $retriever; |
| 3270 |
$this->funcs = is_null($funcs) ? new \Kibo\Phast\Common\ObjectifiedFunctions() : $funcs; |
| 3271 |
} |
| 3272 |
public function getWidth() |
| 3273 |
{ |
| 3274 |
return isset($this->width) ? $this->width : $this->getImageInfo()[0]; |
| 3275 |
} |
| 3276 |
public function getHeight() |
| 3277 |
{ |
| 3278 |
return isset($this->height) ? $this->height : $this->getImageInfo()[1]; |
| 3279 |
} |
| 3280 |
public function getType() |
| 3281 |
{ |
| 3282 |
if (isset($this->type)) { |
| 3283 |
return $this->type; |
| 3284 |
} |
| 3285 |
$type = @image_type_to_mime_type($this->getImageInfo()[2]); |
| 3286 |
if (!$type) { |
| 3287 |
throw new \Kibo\Phast\Filters\Image\Exceptions\ImageProcessingException('Could not determine image type'); |
| 3288 |
} |
| 3289 |
return $type; |
| 3290 |
} |
| 3291 |
public function getAsString() |
| 3292 |
{ |
| 3293 |
return $this->getImageString(); |
| 3294 |
} |
| 3295 |
private function getImageString() |
| 3296 |
{ |
| 3297 |
if (!isset($this->imageString)) { |
| 3298 |
$imageString = $this->retriever->retrieve($this->imageURL); |
| 3299 |
if ($imageString === false) { |
| 3300 |
throw new \Kibo\Phast\Exceptions\ItemNotFoundException('Could not find image: ' . $this->imageURL, 0, null, $this->imageURL); |
| 3301 |
} |
| 3302 |
$this->imageString = $imageString; |
| 3303 |
} |
| 3304 |
return $this->imageString; |
| 3305 |
} |
| 3306 |
private function getImageInfo() |
| 3307 |
{ |
| 3308 |
if (!isset($this->imageInfo)) { |
| 3309 |
if ($this->getImageString() === '') { |
| 3310 |
throw new \Kibo\Phast\Filters\Image\Exceptions\ImageProcessingException('Image is empty'); |
| 3311 |
} |
| 3312 |
$imageInfo = @getimagesizefromstring($this->getImageString()); |
| 3313 |
if ($imageInfo === false) { |
| 3314 |
throw new \Kibo\Phast\Filters\Image\Exceptions\ImageProcessingException('Could not read GD image info'); |
| 3315 |
} |
| 3316 |
$this->imageInfo = $imageInfo; |
| 3317 |
} |
| 3318 |
return $this->imageInfo; |
| 3319 |
} |
| 3320 |
protected function __clone() |
| 3321 |
{ |
| 3322 |
throw new \Kibo\Phast\Exceptions\LogicException('No operations may be performed on DefaultImage'); |
| 3323 |
} |
| 3324 |
} |
| 3325 |
namespace Kibo\Phast\Filters\CSS\CSSMinifier; |
| 3326 |
|
| 3327 |
class Factory |
| 3328 |
{ |
| 3329 |
public function make() |
| 3330 |
{ |
| 3331 |
return new \Kibo\Phast\Filters\CSS\CSSMinifier\Filter(); |
| 3332 |
} |
| 3333 |
} |
| 3334 |
namespace Kibo\Phast\Filters\CSS\CSSURLRewriter; |
| 3335 |
|
| 3336 |
class Factory |
| 3337 |
{ |
| 3338 |
public function make() |
| 3339 |
{ |
| 3340 |
return new \Kibo\Phast\Filters\CSS\CSSURLRewriter\Filter(); |
| 3341 |
} |
| 3342 |
} |
| 3343 |
namespace Kibo\Phast\Filters\CSS\ImageURLRewriter; |
| 3344 |
|
| 3345 |
class Factory |
| 3346 |
{ |
| 3347 |
public function make(array $config) |
| 3348 |
{ |
| 3349 |
return new \Kibo\Phast\Filters\CSS\ImageURLRewriter\Filter((new \Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageURLRewriterFactory())->make($config, \Kibo\Phast\Filters\CSS\ImageURLRewriter\Filter::class)); |
| 3350 |
} |
| 3351 |
} |
| 3352 |
namespace Kibo\Phast\Filters\CSS\Composite; |
| 3353 |
|
| 3354 |
class Factory |
| 3355 |
{ |
| 3356 |
/** |
| 3357 |
* @param array $config |
| 3358 |
* @return Filter |
| 3359 |
*/ |
| 3360 |
public function make(array $config) |
| 3361 |
{ |
| 3362 |
$class = \Kibo\Phast\Filters\HTML\ImagesOptimizationService\CSS\Filter::class; |
| 3363 |
if (isset($config['documents']['filters'][$class]['serviceUrl'])) { |
| 3364 |
$serviceUrl = $config['documents']['filters'][$class]['serviceUrl']; |
| 3365 |
} else { |
| 3366 |
$serviceUrl = $config['servicesUrl'] . '?service=images'; |
| 3367 |
} |
| 3368 |
$filter = new \Kibo\Phast\Filters\CSS\Composite\Filter($serviceUrl); |
| 3369 |
foreach (array_keys($config['styles']['filters']) as $filterClass) { |
| 3370 |
$filter->addFilter(\Kibo\Phast\Environment\Package::fromPackageClass($filterClass)->getFactory()->make($config)); |
| 3371 |
} |
| 3372 |
return $filter; |
| 3373 |
} |
| 3374 |
} |
| 3375 |
namespace Kibo\Phast\Filters\CSS\FontSwap; |
| 3376 |
|
| 3377 |
class Factory |
| 3378 |
{ |
| 3379 |
public function make() |
| 3380 |
{ |
| 3381 |
return new \Kibo\Phast\Filters\CSS\FontSwap\Filter(); |
| 3382 |
} |
| 3383 |
} |
| 3384 |
namespace Kibo\Phast\Filters\CSS\ImportsStripper; |
| 3385 |
|
| 3386 |
class Factory |
| 3387 |
{ |
| 3388 |
public function make() |
| 3389 |
{ |
| 3390 |
return new \Kibo\Phast\Filters\CSS\ImportsStripper\Filter(); |
| 3391 |
} |
| 3392 |
} |
| 3393 |
namespace Kibo\Phast\Filters\Text\Decode; |
| 3394 |
|
| 3395 |
class Factory |
| 3396 |
{ |
| 3397 |
public function make() |
| 3398 |
{ |
| 3399 |
return new \Kibo\Phast\Filters\Text\Decode\Filter(); |
| 3400 |
} |
| 3401 |
} |
| 3402 |
namespace Kibo\Phast\JSMin; |
| 3403 |
|
| 3404 |
class UnterminatedRegExpException extends \Exception |
| 3405 |
{ |
| 3406 |
} |
| 3407 |
namespace Kibo\Phast\JSMin; |
| 3408 |
|
| 3409 |
class UnterminatedCommentException extends \Exception |
| 3410 |
{ |
| 3411 |
} |
| 3412 |
namespace Kibo\Phast\JSMin; |
| 3413 |
|
| 3414 |
class UnterminatedStringException extends \Exception |
| 3415 |
{ |
| 3416 |
} |
| 3417 |
namespace Kibo\Phast\JSMin; |
| 3418 |
|
| 3419 |
/** |
| 3420 |
* JSMin.php - modified PHP implementation of Douglas Crockford's JSMin. |
| 3421 |
* |
| 3422 |
* <code> |
| 3423 |
* $minifiedJs = JSMin::minify($js); |
| 3424 |
* </code> |
| 3425 |
* |
| 3426 |
* This is a modified port of jsmin.c. Improvements: |
| 3427 |
* |
| 3428 |
* Does not choke on some regexp literals containing quote characters. E.g. /'/ |
| 3429 |
* |
| 3430 |
* Spaces are preserved after some add/sub operators, so they are not mistakenly |
| 3431 |
* converted to post-inc/dec. E.g. a + ++b -> a+ ++b |
| 3432 |
* |
| 3433 |
* Preserves multi-line comments that begin with /*! |
| 3434 |
* |
| 3435 |
* PHP 5 or higher is required. |
| 3436 |
* |
| 3437 |
* Permission is hereby granted to use this version of the library under the |
| 3438 |
* same terms as jsmin.c, which has the following license: |
| 3439 |
* |
| 3440 |
* -- |
| 3441 |
* Copyright (c) 2002 Douglas Crockford (www.crockford.com) |
| 3442 |
* |
| 3443 |
* Permission is hereby granted, free of charge, to any person obtaining a copy of |
| 3444 |
* this software and associated documentation files (the "Software"), to deal in |
| 3445 |
* the Software without restriction, including without limitation the rights to |
| 3446 |
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
| 3447 |
* of the Software, and to permit persons to whom the Software is furnished to do |
| 3448 |
* so, subject to the following conditions: |
| 3449 |
* |
| 3450 |
* The above copyright notice and this permission notice shall be included in all |
| 3451 |
* copies or substantial portions of the Software. |
| 3452 |
* |
| 3453 |
* The Software shall be used for Good, not Evil. |
| 3454 |
* |
| 3455 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 3456 |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 3457 |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 3458 |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 3459 |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 3460 |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 3461 |
* SOFTWARE. |
| 3462 |
* -- |
| 3463 |
* |
| 3464 |
* @package JSMin |
| 3465 |
* @author Ryan Grove <ryan@wonko.com> (PHP port) |
| 3466 |
* @author Steve Clay <steve@mrclay.org> (modifications + cleanup) |
| 3467 |
* @author Andrea Giammarchi <http://www.3site.eu> (spaceBeforeRegExp) |
| 3468 |
* @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c) |
| 3469 |
* @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port) |
| 3470 |
* @license http://opensource.org/licenses/mit-license.php MIT License |
| 3471 |
* @link http://code.google.com/p/jsmin-php/ |
| 3472 |
*/ |
| 3473 |
class JSMin |
| 3474 |
{ |
| 3475 |
const ACTION_KEEP_A = 1; |
| 3476 |
const ACTION_DELETE_A = 2; |
| 3477 |
const ACTION_DELETE_A_B = 3; |
| 3478 |
protected $a = "\n"; |
| 3479 |
protected $b = ''; |
| 3480 |
protected $input = ''; |
| 3481 |
protected $inputIndex = 0; |
| 3482 |
protected $inputLength = 0; |
| 3483 |
protected $lookAhead = null; |
| 3484 |
protected $output = ''; |
| 3485 |
protected $lastByteOut = ''; |
| 3486 |
protected $keptComment = ''; |
| 3487 |
/** |
| 3488 |
* Minify Javascript. |
| 3489 |
* |
| 3490 |
* @param string $js Javascript to be minified |
| 3491 |
* |
| 3492 |
* @return string |
| 3493 |
*/ |
| 3494 |
public static function minify($js) |
| 3495 |
{ |
| 3496 |
$jsmin = new \Kibo\Phast\JSMin\JSMin($js); |
| 3497 |
return $jsmin->min(); |
| 3498 |
} |
| 3499 |
/** |
| 3500 |
* @param string $input |
| 3501 |
*/ |
| 3502 |
public function __construct($input) |
| 3503 |
{ |
| 3504 |
$this->input = $input; |
| 3505 |
} |
| 3506 |
/** |
| 3507 |
* Perform minification, return result |
| 3508 |
* |
| 3509 |
* @return string |
| 3510 |
*/ |
| 3511 |
public function min() |
| 3512 |
{ |
| 3513 |
if ($this->output !== '') { |
| 3514 |
// min already run |
| 3515 |
return $this->output; |
| 3516 |
} |
| 3517 |
$mbIntEnc = null; |
| 3518 |
if (function_exists('mb_strlen') && (int) ini_get('mbstring.func_overload') & 2) { |
| 3519 |
$mbIntEnc = mb_internal_encoding(); |
| 3520 |
mb_internal_encoding('8bit'); |
| 3521 |
} |
| 3522 |
if (isset($this->input[0]) && $this->input[0] === "\357") { |
| 3523 |
$this->input = substr($this->input, 3); |
| 3524 |
} |
| 3525 |
$this->input = str_replace("\r\n", "\n", $this->input); |
| 3526 |
$this->inputLength = strlen($this->input); |
| 3527 |
$this->action(self::ACTION_DELETE_A_B); |
| 3528 |
while ($this->a !== null) { |
| 3529 |
// determine next command |
| 3530 |
$command = self::ACTION_KEEP_A; |
| 3531 |
// default |
| 3532 |
if ($this->isWhiteSpace($this->a)) { |
| 3533 |
if (($this->lastByteOut === '+' || $this->lastByteOut === '-') && $this->b === $this->lastByteOut) { |
| 3534 |
// Don't delete this space. If we do, the addition/subtraction |
| 3535 |
// could be parsed as a post-increment |
| 3536 |
} elseif (!$this->isAlphaNum($this->b)) { |
| 3537 |
$command = self::ACTION_DELETE_A; |
| 3538 |
} |
| 3539 |
} elseif ($this->isLineTerminator($this->a)) { |
| 3540 |
if ($this->isWhiteSpace($this->b)) { |
| 3541 |
$command = self::ACTION_DELETE_A_B; |
| 3542 |
// in case of mbstring.func_overload & 2, must check for null b, |
| 3543 |
// otherwise mb_strpos will give WARNING |
| 3544 |
} elseif ($this->b === null || false === strpos('{[(+-!~', $this->b) && !$this->isAlphaNum($this->b)) { |
| 3545 |
$command = self::ACTION_DELETE_A; |
| 3546 |
} |
| 3547 |
} elseif (!$this->isAlphaNum($this->a)) { |
| 3548 |
if ($this->isWhiteSpace($this->b) || $this->isLineTerminator($this->b) && false === strpos('}])+-"\'', $this->a)) { |
| 3549 |
$command = self::ACTION_DELETE_A_B; |
| 3550 |
} |
| 3551 |
} |
| 3552 |
$this->action($command); |
| 3553 |
} |
| 3554 |
$this->output = trim($this->output); |
| 3555 |
if ($mbIntEnc !== null) { |
| 3556 |
mb_internal_encoding($mbIntEnc); |
| 3557 |
} |
| 3558 |
return $this->output; |
| 3559 |
} |
| 3560 |
/** |
| 3561 |
* ACTION_KEEP_A = Output A. Copy B to A. Get the next B. |
| 3562 |
* ACTION_DELETE_A = Copy B to A. Get the next B. |
| 3563 |
* ACTION_DELETE_A_B = Get the next B. |
| 3564 |
* |
| 3565 |
* @param int $command |
| 3566 |
* @throws UnterminatedRegExpException|UnterminatedStringException |
| 3567 |
*/ |
| 3568 |
protected function action($command) |
| 3569 |
{ |
| 3570 |
// make sure we don't compress "a + ++b" to "a+++b", etc. |
| 3571 |
if ($command === self::ACTION_DELETE_A_B && $this->b === ' ' && ($this->a === '+' || $this->a === '-')) { |
| 3572 |
// Note: we're at an addition/substraction operator; the inputIndex |
| 3573 |
// will certainly be a valid index |
| 3574 |
if ($this->input[$this->inputIndex] === $this->a) { |
| 3575 |
// This is "+ +" or "- -". Don't delete the space. |
| 3576 |
$command = self::ACTION_KEEP_A; |
| 3577 |
} |
| 3578 |
} |
| 3579 |
switch ($command) { |
| 3580 |
case self::ACTION_KEEP_A: |
| 3581 |
// 1 |
| 3582 |
$this->output .= $this->a; |
| 3583 |
if ($this->keptComment) { |
| 3584 |
$this->output = rtrim($this->output, "\n"); |
| 3585 |
$this->output .= $this->keptComment; |
| 3586 |
$this->keptComment = ''; |
| 3587 |
} |
| 3588 |
$this->lastByteOut = $this->a; |
| 3589 |
// fallthrough intentional |
| 3590 |
case self::ACTION_DELETE_A: |
| 3591 |
// 2 |
| 3592 |
$this->a = $this->b; |
| 3593 |
if ($this->a === "'" || $this->a === '"' || $this->a === '`') { |
| 3594 |
// string/template literal |
| 3595 |
$delimiter = $this->a; |
| 3596 |
$str = $this->a; |
| 3597 |
// in case needed for exception |
| 3598 |
for (;;) { |
| 3599 |
$this->output .= $this->a; |
| 3600 |
$this->lastByteOut = $this->a; |
| 3601 |
$this->a = $this->get(); |
| 3602 |
if ($this->a === $this->b) { |
| 3603 |
// end quote |
| 3604 |
break; |
| 3605 |
} |
| 3606 |
if ($delimiter === '`' && $this->isLineTerminator($this->a)) { |
| 3607 |
// leave the newline |
| 3608 |
} elseif ($this->isEOF($this->a)) { |
| 3609 |
$byte = $this->inputIndex - 1; |
| 3610 |
throw new \Kibo\Phast\JSMin\UnterminatedStringException("JSMin: Unterminated String at byte {$byte}: {$str}"); |
| 3611 |
} |
| 3612 |
$str .= $this->a; |
| 3613 |
if ($this->a === '\\') { |
| 3614 |
$this->output .= $this->a; |
| 3615 |
$this->lastByteOut = $this->a; |
| 3616 |
$this->a = $this->get(); |
| 3617 |
$str .= $this->a; |
| 3618 |
} |
| 3619 |
} |
| 3620 |
} |
| 3621 |
// fallthrough intentional |
| 3622 |
case self::ACTION_DELETE_A_B: |
| 3623 |
// 3 |
| 3624 |
$this->b = $this->next(); |
| 3625 |
if ($this->b === '/' && $this->isRegexpLiteral()) { |
| 3626 |
$this->output .= $this->a . $this->b; |
| 3627 |
$pattern = '/'; |
| 3628 |
// keep entire pattern in case we need to report it in the exception |
| 3629 |
for (;;) { |
| 3630 |
$this->a = $this->get(); |
| 3631 |
$pattern .= $this->a; |
| 3632 |
if ($this->a === '[') { |
| 3633 |
for (;;) { |
| 3634 |
$this->output .= $this->a; |
| 3635 |
$this->a = $this->get(); |
| 3636 |
$pattern .= $this->a; |
| 3637 |
if ($this->a === ']') { |
| 3638 |
break; |
| 3639 |
} |
| 3640 |
if ($this->a === '\\') { |
| 3641 |
$this->output .= $this->a; |
| 3642 |
$this->a = $this->get(); |
| 3643 |
$pattern .= $this->a; |
| 3644 |
} |
| 3645 |
if ($this->isEOF($this->a)) { |
| 3646 |
throw new \Kibo\Phast\JSMin\UnterminatedRegExpException("JSMin: Unterminated set in RegExp at byte " . $this->inputIndex . ": {$pattern}"); |
| 3647 |
} |
| 3648 |
} |
| 3649 |
} |
| 3650 |
if ($this->a === '/') { |
| 3651 |
// end pattern |
| 3652 |
break; |
| 3653 |
// while (true) |
| 3654 |
} elseif ($this->a === '\\') { |
| 3655 |
$this->output .= $this->a; |
| 3656 |
$this->a = $this->get(); |
| 3657 |
$pattern .= $this->a; |
| 3658 |
} elseif ($this->isEOF($this->a)) { |
| 3659 |
$byte = $this->inputIndex - 1; |
| 3660 |
throw new \Kibo\Phast\JSMin\UnterminatedRegExpException("JSMin: Unterminated RegExp at byte {$byte}: {$pattern}"); |
| 3661 |
} |
| 3662 |
$this->output .= $this->a; |
| 3663 |
$this->lastByteOut = $this->a; |
| 3664 |
} |
| 3665 |
$this->b = $this->next(); |
| 3666 |
} |
| 3667 |
} |
| 3668 |
} |
| 3669 |
/** |
| 3670 |
* @return bool |
| 3671 |
*/ |
| 3672 |
protected function isRegexpLiteral() |
| 3673 |
{ |
| 3674 |
if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) { |
| 3675 |
// we can't divide after these tokens |
| 3676 |
return true; |
| 3677 |
} |
| 3678 |
// check if first non-ws token is "/" (see starts-regex.js) |
| 3679 |
$length = strlen($this->output); |
| 3680 |
if ($this->isWhiteSpace($this->a) || $this->isLineTerminator($this->a)) { |
| 3681 |
if ($length < 2) { |
| 3682 |
// weird edge case |
| 3683 |
return true; |
| 3684 |
} |
| 3685 |
} |
| 3686 |
// if the "/" follows a keyword, it must be a regexp, otherwise it's best to assume division |
| 3687 |
$subject = $this->output . trim($this->a); |
| 3688 |
if (!preg_match('/(?:case|else|in|return|typeof)$/', $subject, $m)) { |
| 3689 |
// not a keyword |
| 3690 |
return false; |
| 3691 |
} |
| 3692 |
// can't be sure it's a keyword yet (see not-regexp.js) |
| 3693 |
$charBeforeKeyword = substr($subject, 0 - strlen($m[0]) - 1, 1); |
| 3694 |
if ($this->isAlphaNum($charBeforeKeyword)) { |
| 3695 |
// this is really an identifier ending in a keyword, e.g. "xreturn" |
| 3696 |
return false; |
| 3697 |
} |
| 3698 |
// it's a regexp. Remove unneeded whitespace after keyword |
| 3699 |
if ($this->isWhiteSpace($this->a) || $this->isLineTerminator($this->a)) { |
| 3700 |
$this->a = ''; |
| 3701 |
} |
| 3702 |
return true; |
| 3703 |
} |
| 3704 |
/** |
| 3705 |
* Return the next character from stdin. Watch out for lookahead. If the character is a control character, |
| 3706 |
* translate it to a space or linefeed. |
| 3707 |
* |
| 3708 |
* @return string |
| 3709 |
*/ |
| 3710 |
protected function get() |
| 3711 |
{ |
| 3712 |
$c = $this->lookAhead; |
| 3713 |
$this->lookAhead = null; |
| 3714 |
if ($c === null) { |
| 3715 |
// getc(stdin) |
| 3716 |
if ($this->inputIndex < $this->inputLength) { |
| 3717 |
$c = $this->input[$this->inputIndex]; |
| 3718 |
$this->inputIndex += 1; |
| 3719 |
} else { |
| 3720 |
$c = null; |
| 3721 |
} |
| 3722 |
} |
| 3723 |
if ($c === "\r") { |
| 3724 |
return "\n"; |
| 3725 |
} |
| 3726 |
return $c; |
| 3727 |
} |
| 3728 |
/** |
| 3729 |
* Does $a indicate end of input? |
| 3730 |
* |
| 3731 |
* @param string $a |
| 3732 |
* @return bool |
| 3733 |
*/ |
| 3734 |
protected function isEOF($a) |
| 3735 |
{ |
| 3736 |
return $a === null || $this->isLineTerminator($a); |
| 3737 |
} |
| 3738 |
/** |
| 3739 |
* Get next char (without getting it). If is ctrl character, translate to a space or newline. |
| 3740 |
* |
| 3741 |
* @return string |
| 3742 |
*/ |
| 3743 |
protected function peek() |
| 3744 |
{ |
| 3745 |
$this->lookAhead = $this->get(); |
| 3746 |
return $this->lookAhead; |
| 3747 |
} |
| 3748 |
/** |
| 3749 |
* Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. |
| 3750 |
* |
| 3751 |
* @param string $c |
| 3752 |
* |
| 3753 |
* @return bool |
| 3754 |
*/ |
| 3755 |
protected function isAlphaNum($c) |
| 3756 |
{ |
| 3757 |
return preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126; |
| 3758 |
} |
| 3759 |
/** |
| 3760 |
* Consume a single line comment from input (possibly retaining it) |
| 3761 |
*/ |
| 3762 |
protected function consumeSingleLineComment() |
| 3763 |
{ |
| 3764 |
$comment = ''; |
| 3765 |
while (true) { |
| 3766 |
$get = $this->get(); |
| 3767 |
$comment .= $get; |
| 3768 |
if ($this->isEOF($get)) { |
| 3769 |
// if IE conditional comment |
| 3770 |
if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) { |
| 3771 |
$this->keptComment .= "/{$comment}"; |
| 3772 |
} |
| 3773 |
return; |
| 3774 |
} |
| 3775 |
} |
| 3776 |
} |
| 3777 |
/** |
| 3778 |
* Consume a multiple line comment from input (possibly retaining it) |
| 3779 |
* |
| 3780 |
* @throws UnterminatedCommentException |
| 3781 |
*/ |
| 3782 |
protected function consumeMultipleLineComment() |
| 3783 |
{ |
| 3784 |
$this->get(); |
| 3785 |
$comment = ''; |
| 3786 |
for (;;) { |
| 3787 |
$get = $this->get(); |
| 3788 |
if ($get === '*') { |
| 3789 |
if ($this->peek() === '/') { |
| 3790 |
// end of comment reached |
| 3791 |
$this->get(); |
| 3792 |
if (0 === strpos($comment, '!')) { |
| 3793 |
// preserved by YUI Compressor |
| 3794 |
if (!$this->keptComment) { |
| 3795 |
// don't prepend a newline if two comments right after one another |
| 3796 |
$this->keptComment = "\n"; |
| 3797 |
} |
| 3798 |
$this->keptComment .= "/*!" . substr($comment, 1) . "*/\n"; |
| 3799 |
} else { |
| 3800 |
if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) { |
| 3801 |
// IE conditional |
| 3802 |
$this->keptComment .= "/*{$comment}*/"; |
| 3803 |
} |
| 3804 |
} |
| 3805 |
return; |
| 3806 |
} |
| 3807 |
} elseif ($get === null) { |
| 3808 |
throw new \Kibo\Phast\JSMin\UnterminatedCommentException("JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}"); |
| 3809 |
} |
| 3810 |
$comment .= $get; |
| 3811 |
} |
| 3812 |
} |
| 3813 |
/** |
| 3814 |
* Get the next character, skipping over comments. Some comments may be preserved. |
| 3815 |
* |
| 3816 |
* @return string |
| 3817 |
*/ |
| 3818 |
protected function next() |
| 3819 |
{ |
| 3820 |
$get = $this->get(); |
| 3821 |
if ($get === '/') { |
| 3822 |
switch ($this->peek()) { |
| 3823 |
case '/': |
| 3824 |
$this->consumeSingleLineComment(); |
| 3825 |
$get = "\n"; |
| 3826 |
break; |
| 3827 |
case '*': |
| 3828 |
$this->consumeMultipleLineComment(); |
| 3829 |
$get = ' '; |
| 3830 |
break; |
| 3831 |
} |
| 3832 |
} |
| 3833 |
return $get; |
| 3834 |
} |
| 3835 |
protected function isWhiteSpace($s) |
| 3836 |
{ |
| 3837 |
// https://www.ecma-international.org/ecma-262/#sec-white-space |
| 3838 |
return $s !== null && strpos(" \t\v\f", $s) !== false; |
| 3839 |
} |
| 3840 |
protected function isLineTerminator($s) |
| 3841 |
{ |
| 3842 |
// https://www.ecma-international.org/ecma-262/#sec-line-terminators |
| 3843 |
return $s !== null && strpos("\n\r", $s) !== false; |
| 3844 |
} |
| 3845 |
} |
| 3846 |
namespace Kibo\Phast\Parsing\HTML; |
| 3847 |
|
| 3848 |
class PCRETokenizer |
| 3849 |
{ |
| 3850 |
private $mainPattern = '~ |
| 3851 |
# Allow duplicate names for subpatterns |
| 3852 |
(?J) |
| 3853 |
|
| 3854 |
( |
| 3855 |
@@COMMENT | |
| 3856 |
@@SCRIPT | |
| 3857 |
@@STYLE | |
| 3858 |
@@CLOSING_TAG | |
| 3859 |
@@TAG |
| 3860 |
) |
| 3861 |
~Xxsi'; |
| 3862 |
private $attributePattern = '~ |
| 3863 |
@attr |
| 3864 |
~Xxsi'; |
| 3865 |
private $subroutines = array('COMMENT' => ' |
| 3866 |
<!--.*?--> |
| 3867 |
', 'SCRIPT' => "\n (?= <script[\\s>]) @@TAG\n (?'body' .*? )\n (?'closing_tag' </script/?+(?:\\s[^a-z>]*+)?+> )\n ", 'STYLE' => "\n (?= <style[\\s>]) @@TAG\n (?'body' .*? )\n (?'closing_tag' </style/?+(?:\\s[^a-z>]*+)?+> )\n ", 'TAG' => "\n < @@tag_name \\s*+ @@attrs? @tag_end\n ", 'tag_name' => "\n [^\\s>]++\n ", 'attrs' => ' |
| 3868 |
(?: @attr )*+ |
| 3869 |
', 'attr' => "\n \\s*+\n @@attr_name\n (?: \\s*+ = \\s*+ @attr_value )?\n ", 'attr_name' => "\n [^\\s>][^\\s>=]*+\n ", 'attr_value' => "\n (?|\n \"(?'attr_value'[^\"]*+)\" |\n ' (?'attr_value' [^']*+) ' |\n (?'attr_value' [^\\s>]*+)\n )\n ", 'tag_end' => "\n \\s*+ >\n ", 'CLOSING_TAG' => ' |
| 3870 |
</ @@tag_name [^>]*+ > |
| 3871 |
'); |
| 3872 |
public function __construct() |
| 3873 |
{ |
| 3874 |
$this->mainPattern = $this->compilePattern($this->mainPattern, $this->subroutines); |
| 3875 |
$this->attributePattern = $this->compilePattern($this->attributePattern, $this->subroutines); |
| 3876 |
} |
| 3877 |
public function tokenize($data) |
| 3878 |
{ |
| 3879 |
$offset = 0; |
| 3880 |
while (preg_match($this->mainPattern, $data, $match, PREG_OFFSET_CAPTURE, $offset)) { |
| 3881 |
if ($match[0][1] > $offset) { |
| 3882 |
$element = new \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Junk(); |
| 3883 |
$element->originalString = substr($data, $offset, $match[0][1] - $offset); |
| 3884 |
(yield $element); |
| 3885 |
} |
| 3886 |
if (!empty($match['COMMENT'][0])) { |
| 3887 |
$element = new \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Comment(); |
| 3888 |
$element->originalString = $match[0][0]; |
| 3889 |
} elseif (!empty($match['TAG'][0]) || !empty($match['SCRIPT'][0]) || !empty($match['STYLE'][0])) { |
| 3890 |
$attributes = $match['attrs'][0] === '' ? [] : $this->parseAttributes($match['attrs'][0]); |
| 3891 |
$element = new \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag($match['tag_name'][0], $attributes); |
| 3892 |
$element->originalString = $match['TAG'][0]; |
| 3893 |
if (isset($match['body'][1]) && $match['body'][1] != -1) { |
| 3894 |
$element->setTextContent($match['body'][0]); |
| 3895 |
$element = $element->withClosingTag($match['closing_tag'][0]); |
| 3896 |
} |
| 3897 |
} elseif (!empty($match['CLOSING_TAG'][0])) { |
| 3898 |
$element = new \Kibo\Phast\Parsing\HTML\HTMLStreamElements\ClosingTag($match['tag_name'][0]); |
| 3899 |
$element->originalString = $match[0][0]; |
| 3900 |
} else { |
| 3901 |
throw new \Kibo\Phast\Exceptions\RuntimeException("Unhandled match:\n" . \Kibo\Phast\Common\JSON::prettyEncode($match)); |
| 3902 |
} |
| 3903 |
(yield $element); |
| 3904 |
$offset = $match[0][1] + strlen($match[0][0]); |
| 3905 |
} |
| 3906 |
if ($offset < strlen($data) - 1) { |
| 3907 |
$element = new \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Junk(); |
| 3908 |
$element->originalString = substr($data, $offset); |
| 3909 |
(yield $element); |
| 3910 |
} |
| 3911 |
} |
| 3912 |
private function parseAttributes($str) |
| 3913 |
{ |
| 3914 |
$matches = $this->repeatMatch($this->attributePattern, $str); |
| 3915 |
foreach ($matches as $match) { |
| 3916 |
(yield $match['attr_name'][0] => isset($match['attr_value'][0]) ? html_entity_decode($match['attr_value'][0], ENT_QUOTES, 'UTF-8') : ''); |
| 3917 |
} |
| 3918 |
} |
| 3919 |
private function repeatMatch($pattern, $subject) |
| 3920 |
{ |
| 3921 |
$offset = 0; |
| 3922 |
while (preg_match($pattern, $subject, $match, PREG_OFFSET_CAPTURE, $offset)) { |
| 3923 |
(yield $match); |
| 3924 |
$offset = $match[0][1] + strlen($match[0][0]); |
| 3925 |
} |
| 3926 |
if ($offset < strlen($subject) - 1) { |
| 3927 |
throw new \Kibo\Phast\Exceptions\RuntimeException('Unmatched part of subject: ' . substr($subject, $offset)); |
| 3928 |
} |
| 3929 |
} |
| 3930 |
/** |
| 3931 |
* Replace subroutines in patterns |
| 3932 |
*/ |
| 3933 |
private function compilePattern($pattern, array $subroutines) |
| 3934 |
{ |
| 3935 |
return preg_replace_callback('/@(@?)(\\w+)/', function ($match) use($subroutines) { |
| 3936 |
$capture = !empty($match[1]); |
| 3937 |
$ref = $match[2]; |
| 3938 |
if (!isset($subroutines[$ref])) { |
| 3939 |
throw new \Kibo\Phast\Exceptions\RuntimeException("Unknown pattern '{$ref}' used, or circular reference"); |
| 3940 |
} |
| 3941 |
$subroutine = $subroutines[$ref]; |
| 3942 |
unset($subroutines[$ref]); |
| 3943 |
$replace = $this->compilePattern($subroutine, $subroutines); |
| 3944 |
if ($capture) { |
| 3945 |
$replace = "(?'{$ref}'{$replace})"; |
| 3946 |
} else { |
| 3947 |
$replace = "(?:{$replace})"; |
| 3948 |
} |
| 3949 |
return $replace; |
| 3950 |
}, $pattern); |
| 3951 |
} |
| 3952 |
} |
| 3953 |
namespace Kibo\Phast\Parsing\HTML\HTMLStreamElements; |
| 3954 |
|
| 3955 |
class Element |
| 3956 |
{ |
| 3957 |
/** |
| 3958 |
* @var string |
| 3959 |
*/ |
| 3960 |
public $originalString; |
| 3961 |
/** |
| 3962 |
* @param string $originalString |
| 3963 |
*/ |
| 3964 |
public function setOriginalString($originalString) |
| 3965 |
{ |
| 3966 |
$this->originalString = $originalString; |
| 3967 |
} |
| 3968 |
public function __get($name) |
| 3969 |
{ |
| 3970 |
$method = 'get' . ucfirst($name); |
| 3971 |
if (method_exists($this, $method)) { |
| 3972 |
return call_user_func([$this, $method]); |
| 3973 |
} |
| 3974 |
} |
| 3975 |
public function __set($name, $value) |
| 3976 |
{ |
| 3977 |
$method = 'set' . ucfirst($name); |
| 3978 |
if (method_exists($this, $method)) { |
| 3979 |
return call_user_func([$this, $method], $value); |
| 3980 |
} |
| 3981 |
} |
| 3982 |
public function toString() |
| 3983 |
{ |
| 3984 |
return $this->__toString(); |
| 3985 |
} |
| 3986 |
public function __toString() |
| 3987 |
{ |
| 3988 |
return isset($this->originalString) ? $this->originalString : ''; |
| 3989 |
} |
| 3990 |
public function dump() |
| 3991 |
{ |
| 3992 |
return '<' . preg_replace('~^.*\\\\~', '', get_class($this)) . ' ' . $this->dumpValue() . '>'; |
| 3993 |
} |
| 3994 |
public function dumpValue() |
| 3995 |
{ |
| 3996 |
return \Kibo\Phast\Common\JSON::encode($this->originalString); |
| 3997 |
} |
| 3998 |
} |
| 3999 |
namespace Kibo\Phast\Parsing\HTML\HTMLStreamElements; |
| 4000 |
|
| 4001 |
class Junk extends \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Element |
| 4002 |
{ |
| 4003 |
} |
| 4004 |
namespace Kibo\Phast\Parsing\HTML\HTMLStreamElements; |
| 4005 |
|
| 4006 |
class Comment extends \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Element |
| 4007 |
{ |
| 4008 |
public function isIEConditional() |
| 4009 |
{ |
| 4010 |
return (bool) preg_match('/^<!--\\[if\\s/', $this->originalString); |
| 4011 |
} |
| 4012 |
} |
| 4013 |
/** |
| 4014 |
* Provide general element functions. |
| 4015 |
*/ |
| 4016 |
namespace Kibo\Phast\Parsing\HTML; |
| 4017 |
|
| 4018 |
/** |
| 4019 |
* This class provides general information about HTML5 elements, |
| 4020 |
* including syntactic and semantic issues. |
| 4021 |
* Parsers and serializers can |
| 4022 |
* use this class as a reference point for information about the rules |
| 4023 |
* of various HTML5 elements. |
| 4024 |
* |
| 4025 |
* @todo consider using a bitmask table lookup. There is enough overlap in |
| 4026 |
* naming that this could significantly shrink the size and maybe make it |
| 4027 |
* faster. See the Go teams implementation at https://code.google.com/p/go/source/browse/html/atom. |
| 4028 |
*/ |
| 4029 |
class HTMLInfo |
| 4030 |
{ |
| 4031 |
/** |
| 4032 |
* Indicates an element is described in the specification. |
| 4033 |
*/ |
| 4034 |
const KNOWN_ELEMENT = 1; |
| 4035 |
// From section 8.1.2: "script", "style" |
| 4036 |
// From 8.2.5.4.7 ("in body" insertion mode): "noembed" |
| 4037 |
// From 8.4 "style", "xmp", "iframe", "noembed", "noframes" |
| 4038 |
/** |
| 4039 |
* Indicates the contained text should be processed as raw text. |
| 4040 |
*/ |
| 4041 |
const TEXT_RAW = 2; |
| 4042 |
// From section 8.1.2: "textarea", "title" |
| 4043 |
/** |
| 4044 |
* Indicates the contained text should be processed as RCDATA. |
| 4045 |
*/ |
| 4046 |
const TEXT_RCDATA = 4; |
| 4047 |
/** |
| 4048 |
* Indicates the tag cannot have content. |
| 4049 |
*/ |
| 4050 |
const VOID_TAG = 8; |
| 4051 |
// "address", "article", "aside", "blockquote", "center", "details", "dialog", "dir", "div", "dl", |
| 4052 |
// "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "menu", |
| 4053 |
// "nav", "ol", "p", "section", "summary", "ul" |
| 4054 |
// "h1", "h2", "h3", "h4", "h5", "h6" |
| 4055 |
// "pre", "listing" |
| 4056 |
// "form" |
| 4057 |
// "plaintext" |
| 4058 |
/** |
| 4059 |
* Indicates that if a previous event is for a P tag, that element |
| 4060 |
* should be considered closed. |
| 4061 |
*/ |
| 4062 |
const AUTOCLOSE_P = 16; |
| 4063 |
/** |
| 4064 |
* Indicates that the text inside is plaintext (pre). |
| 4065 |
*/ |
| 4066 |
const TEXT_PLAINTEXT = 32; |
| 4067 |
// See https://developer.mozilla.org/en-US/docs/HTML/Block-level_elements |
| 4068 |
/** |
| 4069 |
* Indicates that the tag is a block. |
| 4070 |
*/ |
| 4071 |
const BLOCK_TAG = 64; |
| 4072 |
/** |
| 4073 |
* Indicates that the tag allows only inline elements as child nodes. |
| 4074 |
*/ |
| 4075 |
const BLOCK_ONLY_INLINE = 128; |
| 4076 |
/** |
| 4077 |
* The HTML5 elements as defined in http://dev.w3.org/html5/markup/elements.html. |
| 4078 |
* |
| 4079 |
* @var array |
| 4080 |
*/ |
| 4081 |
public static $html5 = array( |
| 4082 |
'a' => 1, |
| 4083 |
'abbr' => 1, |
| 4084 |
'address' => 65, |
| 4085 |
// NORMAL | BLOCK_TAG |
| 4086 |
'area' => 9, |
| 4087 |
// NORMAL | VOID_TAG |
| 4088 |
'article' => 81, |
| 4089 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4090 |
'aside' => 81, |
| 4091 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4092 |
'audio' => 65, |
| 4093 |
// NORMAL | BLOCK_TAG |
| 4094 |
'b' => 1, |
| 4095 |
'base' => 9, |
| 4096 |
// NORMAL | VOID_TAG |
| 4097 |
'bdi' => 1, |
| 4098 |
'bdo' => 1, |
| 4099 |
'blockquote' => 81, |
| 4100 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4101 |
'body' => 1, |
| 4102 |
'br' => 9, |
| 4103 |
// NORMAL | VOID_TAG |
| 4104 |
'button' => 1, |
| 4105 |
'canvas' => 65, |
| 4106 |
// NORMAL | BLOCK_TAG |
| 4107 |
'caption' => 1, |
| 4108 |
'cite' => 1, |
| 4109 |
'code' => 1, |
| 4110 |
'col' => 9, |
| 4111 |
// NORMAL | VOID_TAG |
| 4112 |
'colgroup' => 1, |
| 4113 |
'command' => 9, |
| 4114 |
// NORMAL | VOID_TAG |
| 4115 |
// "data" => 1, // This is highly experimental and only part of the whatwg spec (not w3c). See https://developer.mozilla.org/en-US/docs/HTML/Element/data |
| 4116 |
'datalist' => 1, |
| 4117 |
'dd' => 65, |
| 4118 |
// NORMAL | BLOCK_TAG |
| 4119 |
'del' => 1, |
| 4120 |
'details' => 17, |
| 4121 |
// NORMAL | AUTOCLOSE_P, |
| 4122 |
'dfn' => 1, |
| 4123 |
'dialog' => 17, |
| 4124 |
// NORMAL | AUTOCLOSE_P, |
| 4125 |
'div' => 81, |
| 4126 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4127 |
'dl' => 81, |
| 4128 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4129 |
'dt' => 1, |
| 4130 |
'em' => 1, |
| 4131 |
'embed' => 9, |
| 4132 |
// NORMAL | VOID_TAG |
| 4133 |
'fieldset' => 81, |
| 4134 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4135 |
'figcaption' => 81, |
| 4136 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4137 |
'figure' => 81, |
| 4138 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4139 |
'footer' => 81, |
| 4140 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4141 |
'form' => 81, |
| 4142 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4143 |
'h1' => 81, |
| 4144 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4145 |
'h2' => 81, |
| 4146 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4147 |
'h3' => 81, |
| 4148 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4149 |
'h4' => 81, |
| 4150 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4151 |
'h5' => 81, |
| 4152 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4153 |
'h6' => 81, |
| 4154 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4155 |
'head' => 1, |
| 4156 |
'header' => 81, |
| 4157 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4158 |
'hgroup' => 81, |
| 4159 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4160 |
'hr' => 73, |
| 4161 |
// NORMAL | VOID_TAG |
| 4162 |
'html' => 1, |
| 4163 |
'i' => 1, |
| 4164 |
'iframe' => 3, |
| 4165 |
// NORMAL | TEXT_RAW |
| 4166 |
'img' => 9, |
| 4167 |
// NORMAL | VOID_TAG |
| 4168 |
'input' => 9, |
| 4169 |
// NORMAL | VOID_TAG |
| 4170 |
'kbd' => 1, |
| 4171 |
'ins' => 1, |
| 4172 |
'keygen' => 9, |
| 4173 |
// NORMAL | VOID_TAG |
| 4174 |
'label' => 1, |
| 4175 |
'legend' => 1, |
| 4176 |
'li' => 1, |
| 4177 |
'link' => 9, |
| 4178 |
// NORMAL | VOID_TAG |
| 4179 |
'map' => 1, |
| 4180 |
'mark' => 1, |
| 4181 |
'menu' => 17, |
| 4182 |
// NORMAL | AUTOCLOSE_P, |
| 4183 |
'meta' => 9, |
| 4184 |
// NORMAL | VOID_TAG |
| 4185 |
'meter' => 1, |
| 4186 |
'nav' => 17, |
| 4187 |
// NORMAL | AUTOCLOSE_P, |
| 4188 |
'noscript' => 65, |
| 4189 |
// NORMAL | BLOCK_TAG |
| 4190 |
'object' => 1, |
| 4191 |
'ol' => 81, |
| 4192 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4193 |
'optgroup' => 1, |
| 4194 |
'option' => 1, |
| 4195 |
'output' => 65, |
| 4196 |
// NORMAL | BLOCK_TAG |
| 4197 |
'p' => 209, |
| 4198 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG | BLOCK_ONLY_INLINE |
| 4199 |
'param' => 9, |
| 4200 |
// NORMAL | VOID_TAG |
| 4201 |
'pre' => 81, |
| 4202 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4203 |
'progress' => 1, |
| 4204 |
'q' => 1, |
| 4205 |
'rp' => 1, |
| 4206 |
'rt' => 1, |
| 4207 |
'ruby' => 1, |
| 4208 |
's' => 1, |
| 4209 |
'samp' => 1, |
| 4210 |
'script' => 3, |
| 4211 |
// NORMAL | TEXT_RAW |
| 4212 |
'section' => 81, |
| 4213 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4214 |
'select' => 1, |
| 4215 |
'small' => 1, |
| 4216 |
'source' => 9, |
| 4217 |
// NORMAL | VOID_TAG |
| 4218 |
'span' => 1, |
| 4219 |
'strong' => 1, |
| 4220 |
'style' => 3, |
| 4221 |
// NORMAL | TEXT_RAW |
| 4222 |
'sub' => 1, |
| 4223 |
'summary' => 17, |
| 4224 |
// NORMAL | AUTOCLOSE_P, |
| 4225 |
'sup' => 1, |
| 4226 |
'table' => 65, |
| 4227 |
// NORMAL | BLOCK_TAG |
| 4228 |
'tbody' => 1, |
| 4229 |
'td' => 1, |
| 4230 |
'textarea' => 5, |
| 4231 |
// NORMAL | TEXT_RCDATA |
| 4232 |
'tfoot' => 65, |
| 4233 |
// NORMAL | BLOCK_TAG |
| 4234 |
'th' => 1, |
| 4235 |
'thead' => 1, |
| 4236 |
'time' => 1, |
| 4237 |
'title' => 5, |
| 4238 |
// NORMAL | TEXT_RCDATA |
| 4239 |
'tr' => 1, |
| 4240 |
'track' => 9, |
| 4241 |
// NORMAL | VOID_TAG |
| 4242 |
'u' => 1, |
| 4243 |
'ul' => 81, |
| 4244 |
// NORMAL | AUTOCLOSE_P | BLOCK_TAG |
| 4245 |
'var' => 1, |
| 4246 |
'video' => 65, |
| 4247 |
// NORMAL | BLOCK_TAG |
| 4248 |
'wbr' => 9, |
| 4249 |
// NORMAL | VOID_TAG |
| 4250 |
// Legacy? |
| 4251 |
'basefont' => 8, |
| 4252 |
// VOID_TAG |
| 4253 |
'bgsound' => 8, |
| 4254 |
// VOID_TAG |
| 4255 |
'noframes' => 2, |
| 4256 |
// RAW_TEXT |
| 4257 |
'frame' => 9, |
| 4258 |
// NORMAL | VOID_TAG |
| 4259 |
'frameset' => 1, |
| 4260 |
'center' => 16, |
| 4261 |
'dir' => 16, |
| 4262 |
'listing' => 16, |
| 4263 |
// AUTOCLOSE_P |
| 4264 |
'plaintext' => 48, |
| 4265 |
// AUTOCLOSE_P | TEXT_PLAINTEXT |
| 4266 |
'applet' => 0, |
| 4267 |
'marquee' => 0, |
| 4268 |
'isindex' => 8, |
| 4269 |
// VOID_TAG |
| 4270 |
'xmp' => 20, |
| 4271 |
// AUTOCLOSE_P | VOID_TAG | RAW_TEXT |
| 4272 |
'noembed' => 2, |
| 4273 |
); |
| 4274 |
/** |
| 4275 |
* The MathML elements. |
| 4276 |
* See http://www.w3.org/wiki/MathML/Elements. |
| 4277 |
* |
| 4278 |
* In our case we are only concerned with presentation MathML and not content |
| 4279 |
* MathML. There is a nice list of this subset at https://developer.mozilla.org/en-US/docs/MathML/Element. |
| 4280 |
* |
| 4281 |
* @var array |
| 4282 |
*/ |
| 4283 |
public static $mathml = array('maction' => 1, 'maligngroup' => 1, 'malignmark' => 1, 'math' => 1, 'menclose' => 1, 'merror' => 1, 'mfenced' => 1, 'mfrac' => 1, 'mglyph' => 1, 'mi' => 1, 'mlabeledtr' => 1, 'mlongdiv' => 1, 'mmultiscripts' => 1, 'mn' => 1, 'mo' => 1, 'mover' => 1, 'mpadded' => 1, 'mphantom' => 1, 'mroot' => 1, 'mrow' => 1, 'ms' => 1, 'mscarries' => 1, 'mscarry' => 1, 'msgroup' => 1, 'msline' => 1, 'mspace' => 1, 'msqrt' => 1, 'msrow' => 1, 'mstack' => 1, 'mstyle' => 1, 'msub' => 1, 'msup' => 1, 'msubsup' => 1, 'mtable' => 1, 'mtd' => 1, 'mtext' => 1, 'mtr' => 1, 'munder' => 1, 'munderover' => 1); |
| 4284 |
/** |
| 4285 |
* The svg elements. |
| 4286 |
* |
| 4287 |
* The Mozilla documentation has a good list at https://developer.mozilla.org/en-US/docs/SVG/Element. |
| 4288 |
* The w3c list appears to be lacking in some areas like filter effect elements. |
| 4289 |
* That list can be found at http://www.w3.org/wiki/SVG/Elements. |
| 4290 |
* |
| 4291 |
* Note, FireFox appears to do a better job rendering filter effects than chrome. |
| 4292 |
* While they are in the spec I'm not sure how widely implemented they are. |
| 4293 |
* |
| 4294 |
* @var array |
| 4295 |
*/ |
| 4296 |
public static $svg = array( |
| 4297 |
'a' => 1, |
| 4298 |
'altGlyph' => 1, |
| 4299 |
'altGlyphDef' => 1, |
| 4300 |
'altGlyphItem' => 1, |
| 4301 |
'animate' => 1, |
| 4302 |
'animateColor' => 1, |
| 4303 |
'animateMotion' => 1, |
| 4304 |
'animateTransform' => 1, |
| 4305 |
'circle' => 1, |
| 4306 |
'clipPath' => 1, |
| 4307 |
'color-profile' => 1, |
| 4308 |
'cursor' => 1, |
| 4309 |
'defs' => 1, |
| 4310 |
'desc' => 1, |
| 4311 |
'ellipse' => 1, |
| 4312 |
'feBlend' => 1, |
| 4313 |
'feColorMatrix' => 1, |
| 4314 |
'feComponentTransfer' => 1, |
| 4315 |
'feComposite' => 1, |
| 4316 |
'feConvolveMatrix' => 1, |
| 4317 |
'feDiffuseLighting' => 1, |
| 4318 |
'feDisplacementMap' => 1, |
| 4319 |
'feDistantLight' => 1, |
| 4320 |
'feFlood' => 1, |
| 4321 |
'feFuncA' => 1, |
| 4322 |
'feFuncB' => 1, |
| 4323 |
'feFuncG' => 1, |
| 4324 |
'feFuncR' => 1, |
| 4325 |
'feGaussianBlur' => 1, |
| 4326 |
'feImage' => 1, |
| 4327 |
'feMerge' => 1, |
| 4328 |
'feMergeNode' => 1, |
| 4329 |
'feMorphology' => 1, |
| 4330 |
'feOffset' => 1, |
| 4331 |
'fePointLight' => 1, |
| 4332 |
'feSpecularLighting' => 1, |
| 4333 |
'feSpotLight' => 1, |
| 4334 |
'feTile' => 1, |
| 4335 |
'feTurbulence' => 1, |
| 4336 |
'filter' => 1, |
| 4337 |
'font' => 1, |
| 4338 |
'font-face' => 1, |
| 4339 |
'font-face-format' => 1, |
| 4340 |
'font-face-name' => 1, |
| 4341 |
'font-face-src' => 1, |
| 4342 |
'font-face-uri' => 1, |
| 4343 |
'foreignObject' => 1, |
| 4344 |
'g' => 1, |
| 4345 |
'glyph' => 1, |
| 4346 |
'glyphRef' => 1, |
| 4347 |
'hkern' => 1, |
| 4348 |
'image' => 1, |
| 4349 |
'line' => 1, |
| 4350 |
'linearGradient' => 1, |
| 4351 |
'marker' => 1, |
| 4352 |
'mask' => 1, |
| 4353 |
'metadata' => 1, |
| 4354 |
'missing-glyph' => 1, |
| 4355 |
'mpath' => 1, |
| 4356 |
'path' => 1, |
| 4357 |
'pattern' => 1, |
| 4358 |
'polygon' => 1, |
| 4359 |
'polyline' => 1, |
| 4360 |
'radialGradient' => 1, |
| 4361 |
'rect' => 1, |
| 4362 |
'script' => 3, |
| 4363 |
// NORMAL | RAW_TEXT |
| 4364 |
'set' => 1, |
| 4365 |
'stop' => 1, |
| 4366 |
'style' => 3, |
| 4367 |
// NORMAL | RAW_TEXT |
| 4368 |
'svg' => 1, |
| 4369 |
'switch' => 1, |
| 4370 |
'symbol' => 1, |
| 4371 |
'text' => 1, |
| 4372 |
'textPath' => 1, |
| 4373 |
'title' => 1, |
| 4374 |
'tref' => 1, |
| 4375 |
'tspan' => 1, |
| 4376 |
'use' => 1, |
| 4377 |
'view' => 1, |
| 4378 |
'vkern' => 1, |
| 4379 |
); |
| 4380 |
/** |
| 4381 |
* Some attributes in SVG are case sensetitive. |
| 4382 |
* |
| 4383 |
* This map contains key/value pairs with the key as the lowercase attribute |
| 4384 |
* name and the value with the correct casing. |
| 4385 |
*/ |
| 4386 |
public static $svgCaseSensitiveAttributeMap = array('attributename' => 'attributeName', 'attributetype' => 'attributeType', 'basefrequency' => 'baseFrequency', 'baseprofile' => 'baseProfile', 'calcmode' => 'calcMode', 'clippathunits' => 'clipPathUnits', 'contentscripttype' => 'contentScriptType', 'contentstyletype' => 'contentStyleType', 'diffuseconstant' => 'diffuseConstant', 'edgemode' => 'edgeMode', 'externalresourcesrequired' => 'externalResourcesRequired', 'filterres' => 'filterRes', 'filterunits' => 'filterUnits', 'glyphref' => 'glyphRef', 'gradienttransform' => 'gradientTransform', 'gradientunits' => 'gradientUnits', 'kernelmatrix' => 'kernelMatrix', 'kernelunitlength' => 'kernelUnitLength', 'keypoints' => 'keyPoints', 'keysplines' => 'keySplines', 'keytimes' => 'keyTimes', 'lengthadjust' => 'lengthAdjust', 'limitingconeangle' => 'limitingConeAngle', 'markerheight' => 'markerHeight', 'markerunits' => 'markerUnits', 'markerwidth' => 'markerWidth', 'maskcontentunits' => 'maskContentUnits', 'maskunits' => 'maskUnits', 'numoctaves' => 'numOctaves', 'pathlength' => 'pathLength', 'patterncontentunits' => 'patternContentUnits', 'patterntransform' => 'patternTransform', 'patternunits' => 'patternUnits', 'pointsatx' => 'pointsAtX', 'pointsaty' => 'pointsAtY', 'pointsatz' => 'pointsAtZ', 'preservealpha' => 'preserveAlpha', 'preserveaspectratio' => 'preserveAspectRatio', 'primitiveunits' => 'primitiveUnits', 'refx' => 'refX', 'refy' => 'refY', 'repeatcount' => 'repeatCount', 'repeatdur' => 'repeatDur', 'requiredextensions' => 'requiredExtensions', 'requiredfeatures' => 'requiredFeatures', 'specularconstant' => 'specularConstant', 'specularexponent' => 'specularExponent', 'spreadmethod' => 'spreadMethod', 'startoffset' => 'startOffset', 'stddeviation' => 'stdDeviation', 'stitchtiles' => 'stitchTiles', 'surfacescale' => 'surfaceScale', 'systemlanguage' => 'systemLanguage', 'tablevalues' => 'tableValues', 'targetx' => 'targetX', 'targety' => 'targetY', 'textlength' => 'textLength', 'viewbox' => 'viewBox', 'viewtarget' => 'viewTarget', 'xchannelselector' => 'xChannelSelector', 'ychannelselector' => 'yChannelSelector', 'zoomandpan' => 'zoomAndPan'); |
| 4387 |
/** |
| 4388 |
* Some SVG elements are case sensetitive. |
| 4389 |
* This map contains these. |
| 4390 |
* |
| 4391 |
* The map contains key/value store of the name is lowercase as the keys and |
| 4392 |
* the correct casing as the value. |
| 4393 |
*/ |
| 4394 |
public static $svgCaseSensitiveElementMap = array('altglyph' => 'altGlyph', 'altglyphdef' => 'altGlyphDef', 'altglyphitem' => 'altGlyphItem', 'animatecolor' => 'animateColor', 'animatemotion' => 'animateMotion', 'animatetransform' => 'animateTransform', 'clippath' => 'clipPath', 'feblend' => 'feBlend', 'fecolormatrix' => 'feColorMatrix', 'fecomponenttransfer' => 'feComponentTransfer', 'fecomposite' => 'feComposite', 'feconvolvematrix' => 'feConvolveMatrix', 'fediffuselighting' => 'feDiffuseLighting', 'fedisplacementmap' => 'feDisplacementMap', 'fedistantlight' => 'feDistantLight', 'feflood' => 'feFlood', 'fefunca' => 'feFuncA', 'fefuncb' => 'feFuncB', 'fefuncg' => 'feFuncG', 'fefuncr' => 'feFuncR', 'fegaussianblur' => 'feGaussianBlur', 'feimage' => 'feImage', 'femerge' => 'feMerge', 'femergenode' => 'feMergeNode', 'femorphology' => 'feMorphology', 'feoffset' => 'feOffset', 'fepointlight' => 'fePointLight', 'fespecularlighting' => 'feSpecularLighting', 'fespotlight' => 'feSpotLight', 'fetile' => 'feTile', 'feturbulence' => 'feTurbulence', 'foreignobject' => 'foreignObject', 'glyphref' => 'glyphRef', 'lineargradient' => 'linearGradient', 'radialgradient' => 'radialGradient', 'textpath' => 'textPath'); |
| 4395 |
/** |
| 4396 |
* Check whether the given element meets the given criterion. |
| 4397 |
* |
| 4398 |
* Example: |
| 4399 |
* |
| 4400 |
* Elements::isA('script', Elements::TEXT_RAW); // Returns true. |
| 4401 |
* |
| 4402 |
* Elements::isA('script', Elements::TEXT_RCDATA); // Returns false. |
| 4403 |
* |
| 4404 |
* @param string $name |
| 4405 |
* The element name. |
| 4406 |
* @param int $mask |
| 4407 |
* One of the constants on this class. |
| 4408 |
* @return boolean true if the element matches the mask, false otherwise. |
| 4409 |
*/ |
| 4410 |
public static function isA($name, $mask) |
| 4411 |
{ |
| 4412 |
if (!static::isElement($name)) { |
| 4413 |
return false; |
| 4414 |
} |
| 4415 |
return (static::element($name) & $mask) == $mask; |
| 4416 |
} |
| 4417 |
/** |
| 4418 |
* Test if an element is a valid html5 element. |
| 4419 |
* |
| 4420 |
* @param string $name |
| 4421 |
* The name of the element. |
| 4422 |
* |
| 4423 |
* @return bool True if a html5 element and false otherwise. |
| 4424 |
*/ |
| 4425 |
public static function isHtml5Element($name) |
| 4426 |
{ |
| 4427 |
// html5 element names are case insensetitive. Forcing lowercase for the check. |
| 4428 |
// Do we need this check or will all data passed here already be lowercase? |
| 4429 |
return isset(static::$html5[strtolower($name)]); |
| 4430 |
} |
| 4431 |
/** |
| 4432 |
* Test if an element name is a valid MathML presentation element. |
| 4433 |
* |
| 4434 |
* @param string $name |
| 4435 |
* The name of the element. |
| 4436 |
* |
| 4437 |
* @return bool True if a MathML name and false otherwise. |
| 4438 |
*/ |
| 4439 |
public static function isMathMLElement($name) |
| 4440 |
{ |
| 4441 |
// MathML is case-sensetitive unlike html5 elements. |
| 4442 |
return isset(static::$mathml[$name]); |
| 4443 |
} |
| 4444 |
/** |
| 4445 |
* Test if an element is a valid SVG element. |
| 4446 |
* |
| 4447 |
* @param string $name |
| 4448 |
* The name of the element. |
| 4449 |
* |
| 4450 |
* @return boolean True if a SVG element and false otherise. |
| 4451 |
*/ |
| 4452 |
public static function isSvgElement($name) |
| 4453 |
{ |
| 4454 |
// SVG is case-sensetitive unlike html5 elements. |
| 4455 |
return isset(static::$svg[$name]); |
| 4456 |
} |
| 4457 |
/** |
| 4458 |
* Is an element name valid in an html5 document. |
| 4459 |
* |
| 4460 |
* This includes html5 elements along with other allowed embedded content |
| 4461 |
* such as svg and mathml. |
| 4462 |
* |
| 4463 |
* @param string $name |
| 4464 |
* The name of the element. |
| 4465 |
* |
| 4466 |
* @return bool True if valid and false otherwise. |
| 4467 |
*/ |
| 4468 |
public static function isElement($name) |
| 4469 |
{ |
| 4470 |
return static::isHtml5Element($name) || static::isMathMLElement($name) || static::isSvgElement($name); |
| 4471 |
} |
| 4472 |
/** |
| 4473 |
* Get the element mask for the given element name. |
| 4474 |
* |
| 4475 |
* @param string $name |
| 4476 |
* The name of the element. |
| 4477 |
* |
| 4478 |
* @return int|bool The element mask or false if element does not exist. |
| 4479 |
*/ |
| 4480 |
public static function element($name) |
| 4481 |
{ |
| 4482 |
if (isset(static::$html5[$name])) { |
| 4483 |
return static::$html5[$name]; |
| 4484 |
} |
| 4485 |
if (isset(static::$svg[$name])) { |
| 4486 |
return static::$svg[$name]; |
| 4487 |
} |
| 4488 |
if (isset(static::$mathml[$name])) { |
| 4489 |
return static::$mathml[$name]; |
| 4490 |
} |
| 4491 |
return false; |
| 4492 |
} |
| 4493 |
/** |
| 4494 |
* Normalize a SVG element name to its proper case and form. |
| 4495 |
* |
| 4496 |
* @param string $name |
| 4497 |
* The name of the element. |
| 4498 |
* |
| 4499 |
* @return string The normalized form of the element name. |
| 4500 |
*/ |
| 4501 |
public static function normalizeSvgElement($name) |
| 4502 |
{ |
| 4503 |
$name = strtolower($name); |
| 4504 |
if (isset(static::$svgCaseSensitiveElementMap[$name])) { |
| 4505 |
$name = static::$svgCaseSensitiveElementMap[$name]; |
| 4506 |
} |
| 4507 |
return $name; |
| 4508 |
} |
| 4509 |
/** |
| 4510 |
* Normalize a SVG attribute name to its proper case and form. |
| 4511 |
* |
| 4512 |
* @param string $name |
| 4513 |
* The name of the attribute. |
| 4514 |
* |
| 4515 |
* @return string The normalized form of the attribute name. |
| 4516 |
*/ |
| 4517 |
public static function normalizeSvgAttribute($name) |
| 4518 |
{ |
| 4519 |
$name = strtolower($name); |
| 4520 |
if (isset(static::$svgCaseSensitiveAttributeMap[$name])) { |
| 4521 |
$name = static::$svgCaseSensitiveAttributeMap[$name]; |
| 4522 |
} |
| 4523 |
return $name; |
| 4524 |
} |
| 4525 |
/** |
| 4526 |
* Normalize a MathML attribute name to its proper case and form. |
| 4527 |
* |
| 4528 |
* Note, all MathML element names are lowercase. |
| 4529 |
* |
| 4530 |
* @param string $name |
| 4531 |
* The name of the attribute. |
| 4532 |
* |
| 4533 |
* @return string The normalized form of the attribute name. |
| 4534 |
*/ |
| 4535 |
public static function normalizeMathMlAttribute($name) |
| 4536 |
{ |
| 4537 |
$name = strtolower($name); |
| 4538 |
// Only one attribute has a mixed case form for MathML. |
| 4539 |
if ($name == 'definitionurl') { |
| 4540 |
$name = 'definitionURL'; |
| 4541 |
} |
| 4542 |
return $name; |
| 4543 |
} |
| 4544 |
} |
| 4545 |
namespace Kibo\Phast; |
| 4546 |
|
| 4547 |
class PhastServices |
| 4548 |
{ |
| 4549 |
/** |
| 4550 |
* @param callable|null $getConfig |
| 4551 |
*/ |
| 4552 |
public static function serve(callable $getConfig = null) |
| 4553 |
{ |
| 4554 |
$httpRequest = \Kibo\Phast\HTTP\Request::fromGlobals(); |
| 4555 |
if ($httpRequest->getHeader('CDN-Loop') && preg_match('~(^|,)\\s*Phast\\b~', $httpRequest->getHeader('CDN-Loop'))) { |
| 4556 |
http_response_code(508); |
| 4557 |
die('Loop detected'); |
| 4558 |
} |
| 4559 |
$serviceRequest = \Kibo\Phast\Services\ServiceRequest::fromHTTPRequest($httpRequest); |
| 4560 |
$serviceParams = $serviceRequest->getParams(); |
| 4561 |
if (defined('PHAST_SERVICE')) { |
| 4562 |
$service = PHAST_SERVICE; |
| 4563 |
} elseif (!isset($serviceParams['service'])) { |
| 4564 |
http_response_code(404); |
| 4565 |
exit; |
| 4566 |
} else { |
| 4567 |
$service = $serviceParams['service']; |
| 4568 |
} |
| 4569 |
if (isset($serviceParams['src']) && !headers_sent()) { |
| 4570 |
if (self::isRewrittenRequest($httpRequest)) { |
| 4571 |
http_response_code(500); |
| 4572 |
} else { |
| 4573 |
http_response_code(301); |
| 4574 |
header('Location: ' . $serviceParams['src']); |
| 4575 |
header('Cache-Control: max-age=86400'); |
| 4576 |
} |
| 4577 |
} |
| 4578 |
if ($getConfig === null) { |
| 4579 |
$config = []; |
| 4580 |
} else { |
| 4581 |
$config = $getConfig(); |
| 4582 |
} |
| 4583 |
$userConfig = new \Kibo\Phast\Environment\Configuration($config); |
| 4584 |
$runtimeConfig = \Kibo\Phast\Environment\Configuration::fromDefaults()->withUserConfiguration($userConfig)->withServiceRequest($serviceRequest)->getRuntimeConfig()->toArray(); |
| 4585 |
\Kibo\Phast\Logging\Log::init($runtimeConfig['logging'], $serviceRequest, $service); |
| 4586 |
try { |
| 4587 |
\Kibo\Phast\Services\ServiceRequest::setDefaultSerializationMode($runtimeConfig['serviceRequestFormat']); |
| 4588 |
\Kibo\Phast\Logging\Log::info('Starting service'); |
| 4589 |
$response = (new \Kibo\Phast\Services\Factory())->make($service, $runtimeConfig)->serve($serviceRequest); |
| 4590 |
\Kibo\Phast\Logging\Log::info('Service completed'); |
| 4591 |
} catch (\Kibo\Phast\Exceptions\UnauthorizedException $e) { |
| 4592 |
echo "Unauthorized\n"; |
| 4593 |
\Kibo\Phast\Logging\Log::error('Unauthorized exception: {message}', ['message' => $e->getMessage()]); |
| 4594 |
exit; |
| 4595 |
} catch (\Kibo\Phast\Exceptions\ItemNotFoundException $e) { |
| 4596 |
echo "Item not found\n"; |
| 4597 |
\Kibo\Phast\Logging\Log::error('Item not found: {message}', ['message' => $e->getMessage()]); |
| 4598 |
exit; |
| 4599 |
} catch (\Exception $e) { |
| 4600 |
echo "Internal error, see logs\n"; |
| 4601 |
\Kibo\Phast\Logging\Log::critical('Unhandled exception: {type} Message: {message} File: {file} Line: {line}', ['type' => get_class($e), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine()]); |
| 4602 |
exit; |
| 4603 |
} |
| 4604 |
header_remove('Location'); |
| 4605 |
header_remove('Cache-Control'); |
| 4606 |
self::output($httpRequest, $response, $runtimeConfig); |
| 4607 |
} |
| 4608 |
public static function isRewrittenRequest() |
| 4609 |
{ |
| 4610 |
return !!\Kibo\Phast\Services\ServiceRequest::getRewrittenService(\Kibo\Phast\HTTP\Request::fromGlobals()); |
| 4611 |
} |
| 4612 |
public static function output(\Kibo\Phast\HTTP\Request $request, \Kibo\Phast\HTTP\Response $response, array $config, \Kibo\Phast\Common\ObjectifiedFunctions $funcs = null) |
| 4613 |
{ |
| 4614 |
if (is_null($funcs)) { |
| 4615 |
$funcs = new \Kibo\Phast\Common\ObjectifiedFunctions(); |
| 4616 |
} |
| 4617 |
$headers = $response->getHeaders(); |
| 4618 |
$content = $response->getContent(); |
| 4619 |
if (!self::isIterable($content)) { |
| 4620 |
$content = [$content]; |
| 4621 |
} |
| 4622 |
$fp = fopen('php://output', 'wb'); |
| 4623 |
$zipping = false; |
| 4624 |
if ($response->isCompressible() && self::shouldZip($request) && !empty($config['compressServiceResponse'])) { |
| 4625 |
$zipping = @$funcs->stream_filter_append($fp, 'zlib.deflate', STREAM_FILTER_WRITE, ['level' => 9, 'window' => 31]); |
| 4626 |
if ($zipping) { |
| 4627 |
$headers['Content-Encoding'] = 'gzip'; |
| 4628 |
} |
| 4629 |
} |
| 4630 |
$maxAge = 86400 * 365; |
| 4631 |
$headers += ['Vary' => 'Accept-Encoding', 'Cache-Control' => 'max-age=' . $maxAge, 'Expires' => self::formatHeaderDate(time() + $maxAge), 'X-Accel-Expires' => $maxAge, 'Access-Control-Allow-Origin' => '*', 'ETag' => self::generateETag($headers, $content), 'Last-Modified' => self::formatHeaderDate(time()), 'X-Content-Type-Options' => 'nosniff', 'Content-Security-Policy' => "default-src 'none'"]; |
| 4632 |
if (is_array($content) && !$zipping) { |
| 4633 |
$headers['Content-Length'] = (string) array_sum(array_map('strlen', $content)); |
| 4634 |
} |
| 4635 |
$funcs->http_response_code($response->getCode()); |
| 4636 |
foreach ($headers as $name => $value) { |
| 4637 |
$funcs->header($name . ': ' . $value); |
| 4638 |
} |
| 4639 |
foreach ($content as $part) { |
| 4640 |
fwrite($fp, $part); |
| 4641 |
} |
| 4642 |
fclose($fp); |
| 4643 |
} |
| 4644 |
private static function formatHeaderDate($time) |
| 4645 |
{ |
| 4646 |
return gmdate('D, d M Y H:i:s', $time) . ' GMT'; |
| 4647 |
} |
| 4648 |
private static function shouldZip(\Kibo\Phast\HTTP\Request $request) |
| 4649 |
{ |
| 4650 |
return !$request->isCloudflare() && strpos($request->getHeader('Accept-Encoding'), 'gzip') !== false; |
| 4651 |
} |
| 4652 |
private static function generateETag(array $headers, $content) |
| 4653 |
{ |
| 4654 |
$headersPart = http_build_query($headers); |
| 4655 |
$contentPart = self::isIterable($content) ? uniqid() : $content; |
| 4656 |
return '"' . md5($headersPart . "\0" . $contentPart) . '"'; |
| 4657 |
} |
| 4658 |
private static function isIterable($thing) |
| 4659 |
{ |
| 4660 |
return is_array($thing) || $thing instanceof \Iterator || $thing instanceof \Generator; |
| 4661 |
} |
| 4662 |
} |
| 4663 |
namespace Kibo\Phast\Common; |
| 4664 |
|
| 4665 |
class JSON |
| 4666 |
{ |
| 4667 |
public static function encode($value) |
| 4668 |
{ |
| 4669 |
return self::_encode($value, 0); |
| 4670 |
} |
| 4671 |
public static function prettyEncode($value) |
| 4672 |
{ |
| 4673 |
return self::_encode($value, JSON_PRETTY_PRINT); |
| 4674 |
} |
| 4675 |
private static function _encode($value, $flags) |
| 4676 |
{ |
| 4677 |
$flags |= JSON_UNESCAPED_SLASHES; |
| 4678 |
if (version_compare(PHP_VERSION, '7.2.0', '<')) { |
| 4679 |
return self::legacyEncode($value, $flags); |
| 4680 |
} |
| 4681 |
return json_encode($value, $flags | JSON_INVALID_UTF8_IGNORE | JSON_PARTIAL_OUTPUT_ON_ERROR); |
| 4682 |
} |
| 4683 |
private static function legacyEncode($value, $flags) |
| 4684 |
{ |
| 4685 |
$result = json_encode($value, $flags); |
| 4686 |
if ($result !== false || json_last_error() !== JSON_ERROR_UTF8) { |
| 4687 |
return $result; |
| 4688 |
} |
| 4689 |
self::cleanUTF8($value); |
| 4690 |
return json_encode($value, $flags | JSON_PARTIAL_OUTPUT_ON_ERROR); |
| 4691 |
} |
| 4692 |
private static function cleanUTF8(&$value) |
| 4693 |
{ |
| 4694 |
if (is_array($value)) { |
| 4695 |
array_walk_recursive($value, __METHOD__); |
| 4696 |
} elseif (is_string($value)) { |
| 4697 |
$value = preg_replace_callback('~ |
| 4698 |
[\\x00-\\x7F]++ # ASCII |
| 4699 |
| [\\xC2-\\xDF][\\x80-\\xBF] # non-overlong 2-byte |
| 4700 |
| \\xE0[\\xA0-\\xBF][\\x80-\\xBF] # excluding overlongs |
| 4701 |
| [\\xE1-\\xEC\\xEE\\xEF][\\x80-\\xBF]{2} # straight 3-byte |
| 4702 |
| \\xED[\\x80-\\x9F][\\x80-\\xBF] # excluding surrogates |
| 4703 |
| \\xF0[\\x90-\\xBF][\\x80-\\xBF]{2} # planes 1-3 |
| 4704 |
| [\\xF1-\\xF3][\\x80-\\xBF]{3} # planes 4-15 |
| 4705 |
| \\xF4[\\x80-\\x8F][\\x80-\\xBF]{2} # plane 16 |
| 4706 |
| (.) |
| 4707 |
~xs', function ($match) { |
| 4708 |
if (isset($match[1]) && strlen($match[1])) { |
| 4709 |
return ''; |
| 4710 |
} |
| 4711 |
return $match[0]; |
| 4712 |
}, $value); |
| 4713 |
} |
| 4714 |
} |
| 4715 |
} |
| 4716 |
namespace Kibo\Phast\Common; |
| 4717 |
|
| 4718 |
class Base64url |
| 4719 |
{ |
| 4720 |
public static function encode($data) |
| 4721 |
{ |
| 4722 |
return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); |
| 4723 |
} |
| 4724 |
public static function decode($data) |
| 4725 |
{ |
| 4726 |
return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); |
| 4727 |
} |
| 4728 |
public static function shortHash($data) |
| 4729 |
{ |
| 4730 |
return self::encode(substr(sha1($data, true), 0, 8)); |
| 4731 |
} |
| 4732 |
} |
| 4733 |
namespace Kibo\Phast\Common; |
| 4734 |
|
| 4735 |
class ObjectifiedFunctions |
| 4736 |
{ |
| 4737 |
/** |
| 4738 |
* @param string $name |
| 4739 |
* @param array $arguments |
| 4740 |
*/ |
| 4741 |
public function __call($name, array $arguments) |
| 4742 |
{ |
| 4743 |
if (isset($this->{$name}) && is_callable($this->{$name})) { |
| 4744 |
$fn = $this->{$name}; |
| 4745 |
return $fn(...$arguments); |
| 4746 |
} |
| 4747 |
if (function_exists($name)) { |
| 4748 |
return $name(...$arguments); |
| 4749 |
} |
| 4750 |
throw new \Kibo\Phast\Exceptions\UndefinedObjectifiedFunction("Undefined objectified function {$name}"); |
| 4751 |
} |
| 4752 |
} |
| 4753 |
namespace Kibo\Phast\Common; |
| 4754 |
|
| 4755 |
class System |
| 4756 |
{ |
| 4757 |
private $functions; |
| 4758 |
public function __construct(\Kibo\Phast\Common\ObjectifiedFunctions $functions = null) |
| 4759 |
{ |
| 4760 |
if ($functions === null) { |
| 4761 |
$functions = new \Kibo\Phast\Common\ObjectifiedFunctions(); |
| 4762 |
} |
| 4763 |
$this->functions = $functions; |
| 4764 |
} |
| 4765 |
public function getUserId() |
| 4766 |
{ |
| 4767 |
try { |
| 4768 |
return (int) $this->functions->posix_geteuid(); |
| 4769 |
} catch (\Kibo\Phast\Exceptions\UndefinedObjectifiedFunction $e) { |
| 4770 |
return 0; |
| 4771 |
} |
| 4772 |
} |
| 4773 |
} |
| 4774 |
namespace Kibo\Phast\Common; |
| 4775 |
|
| 4776 |
class JSMinifier extends \Kibo\Phast\JSMin\JSMin |
| 4777 |
{ |
| 4778 |
protected $removeLicenseHeaders; |
| 4779 |
public function __construct($input, $removeLicenseHeaders = false) |
| 4780 |
{ |
| 4781 |
parent::__construct($input); |
| 4782 |
$this->removeLicenseHeaders = $removeLicenseHeaders; |
| 4783 |
} |
| 4784 |
protected function consumeMultipleLineComment() |
| 4785 |
{ |
| 4786 |
parent::consumeMultipleLineComment(); |
| 4787 |
if ($this->removeLicenseHeaders) { |
| 4788 |
$this->keptComment = preg_replace('~/\\*!.*?\\*/~s', '', $this->keptComment); |
| 4789 |
} |
| 4790 |
} |
| 4791 |
} |
| 4792 |
namespace Kibo\Phast\Common; |
| 4793 |
|
| 4794 |
class OutputBufferHandler |
| 4795 |
{ |
| 4796 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 4797 |
const START_PATTERN = '~ |
| 4798 |
( |
| 4799 |
\\s*+ <!doctype\\s++html> | |
| 4800 |
\\s*+ <html> | |
| 4801 |
\\s*+ <head> | |
| 4802 |
\\s*+ <!--.*?--> |
| 4803 |
)++ |
| 4804 |
~xsiA'; |
| 4805 |
private $filterCb; |
| 4806 |
/** |
| 4807 |
* @var ?string |
| 4808 |
*/ |
| 4809 |
private $buffer = ''; |
| 4810 |
private $offset = 0; |
| 4811 |
/** |
| 4812 |
* @var integer |
| 4813 |
*/ |
| 4814 |
private $maxBufferSizeToApply; |
| 4815 |
private $canceled = false; |
| 4816 |
public function __construct($maxBufferSizeToApply, callable $filterCb) |
| 4817 |
{ |
| 4818 |
$this->maxBufferSizeToApply = $maxBufferSizeToApply; |
| 4819 |
$this->filterCb = $filterCb; |
| 4820 |
} |
| 4821 |
public function install() |
| 4822 |
{ |
| 4823 |
$ignoreHandlers = ['default output handler', 'ob_gzhandler']; |
| 4824 |
if (!array_diff(ob_list_handlers(), $ignoreHandlers)) { |
| 4825 |
while (@ob_end_clean()) { |
| 4826 |
} |
| 4827 |
} |
| 4828 |
ob_start([$this, 'handleChunk'], 2); |
| 4829 |
ob_implicit_flush(1); |
| 4830 |
} |
| 4831 |
public function handleChunk($chunk, $phase) |
| 4832 |
{ |
| 4833 |
if ($this->buffer === null) { |
| 4834 |
return $chunk; |
| 4835 |
} |
| 4836 |
$this->buffer .= $chunk; |
| 4837 |
if ($this->canceled) { |
| 4838 |
return $this->stop(); |
| 4839 |
} |
| 4840 |
if (strlen($this->buffer) > $this->maxBufferSizeToApply) { |
| 4841 |
$this->logger()->info('Buffer exceeds max. size ({buffersize} bytes). Not applying', ['buffersize' => $this->maxBufferSizeToApply]); |
| 4842 |
return $this->stop(); |
| 4843 |
} |
| 4844 |
$output = ''; |
| 4845 |
if (preg_match(self::START_PATTERN, $this->buffer, $match, 0, $this->offset)) { |
| 4846 |
$this->offset += strlen($match[0]); |
| 4847 |
$output .= $match[0]; |
| 4848 |
} |
| 4849 |
if ($phase & PHP_OUTPUT_HANDLER_FINAL) { |
| 4850 |
$output .= $this->finalize(); |
| 4851 |
} |
| 4852 |
if ($output !== '') { |
| 4853 |
@header_remove('Content-Length'); |
| 4854 |
} |
| 4855 |
return $output; |
| 4856 |
} |
| 4857 |
private function finalize() |
| 4858 |
{ |
| 4859 |
$input = substr($this->buffer, $this->offset); |
| 4860 |
$result = call_user_func($this->filterCb, $input, $this->buffer); |
| 4861 |
$this->buffer = null; |
| 4862 |
return $result; |
| 4863 |
} |
| 4864 |
private function stop() |
| 4865 |
{ |
| 4866 |
$output = $this->buffer; |
| 4867 |
$this->buffer = null; |
| 4868 |
return $output; |
| 4869 |
} |
| 4870 |
public function cancel() |
| 4871 |
{ |
| 4872 |
$this->canceled = true; |
| 4873 |
} |
| 4874 |
} |
| 4875 |
namespace Kibo\Phast\ValueObjects; |
| 4876 |
|
| 4877 |
class PhastJavaScript |
| 4878 |
{ |
| 4879 |
/** |
| 4880 |
* @var string |
| 4881 |
*/ |
| 4882 |
private $filename; |
| 4883 |
/** |
| 4884 |
* @var string |
| 4885 |
*/ |
| 4886 |
private $contents; |
| 4887 |
/** |
| 4888 |
* @var string |
| 4889 |
*/ |
| 4890 |
private $configKey; |
| 4891 |
/** |
| 4892 |
* @var mixed |
| 4893 |
*/ |
| 4894 |
private $config; |
| 4895 |
/** |
| 4896 |
* @var ObjectifiedFunctions |
| 4897 |
*/ |
| 4898 |
private $funcs; |
| 4899 |
/** |
| 4900 |
* @param string $filename |
| 4901 |
* @param string $contents |
| 4902 |
*/ |
| 4903 |
private function __construct($filename, $contents) |
| 4904 |
{ |
| 4905 |
$this->filename = $filename; |
| 4906 |
$this->contents = $contents; |
| 4907 |
} |
| 4908 |
/** |
| 4909 |
* @param string $filename |
| 4910 |
* @param ObjectifiedFunctions|null $funcs |
| 4911 |
*/ |
| 4912 |
public static function fromFile($filename, \Kibo\Phast\Common\ObjectifiedFunctions $funcs = null) |
| 4913 |
{ |
| 4914 |
$funcs = $funcs ? $funcs : new \Kibo\Phast\Common\ObjectifiedFunctions(); |
| 4915 |
$contents = $funcs->file_get_contents($filename); |
| 4916 |
if ($contents === false) { |
| 4917 |
throw new \RuntimeException("Failed to read script: {$filename}"); |
| 4918 |
} |
| 4919 |
$contents = (new \Kibo\Phast\Common\JSMinifier($contents))->min(); |
| 4920 |
return new self($filename, $contents); |
| 4921 |
} |
| 4922 |
/** |
| 4923 |
* @param string $filename |
| 4924 |
* @param string $contents |
| 4925 |
*/ |
| 4926 |
public static function fromString($filename, $contents) |
| 4927 |
{ |
| 4928 |
return new self($filename, $contents); |
| 4929 |
} |
| 4930 |
/** |
| 4931 |
* @return string |
| 4932 |
*/ |
| 4933 |
public function getFilename() |
| 4934 |
{ |
| 4935 |
return $this->filename; |
| 4936 |
} |
| 4937 |
/** |
| 4938 |
* @return bool|string |
| 4939 |
*/ |
| 4940 |
public function getContents() |
| 4941 |
{ |
| 4942 |
return $this->contents; |
| 4943 |
} |
| 4944 |
/** |
| 4945 |
* @return string |
| 4946 |
*/ |
| 4947 |
public function getCacheSalt() |
| 4948 |
{ |
| 4949 |
$hash = md5($this->getContents(), true); |
| 4950 |
return substr(preg_replace('/^[a-z0-9]/i', '', base64_encode($hash)), 0, 16); |
| 4951 |
} |
| 4952 |
/** |
| 4953 |
* @param string $configKey |
| 4954 |
* @param mixed $config |
| 4955 |
*/ |
| 4956 |
public function setConfig($configKey, $config) |
| 4957 |
{ |
| 4958 |
$this->configKey = $configKey; |
| 4959 |
$this->config = $config; |
| 4960 |
} |
| 4961 |
/** |
| 4962 |
* @return bool |
| 4963 |
*/ |
| 4964 |
public function hasConfig() |
| 4965 |
{ |
| 4966 |
return isset($this->configKey); |
| 4967 |
} |
| 4968 |
/** |
| 4969 |
* @return string |
| 4970 |
*/ |
| 4971 |
public function getConfigKey() |
| 4972 |
{ |
| 4973 |
return $this->configKey; |
| 4974 |
} |
| 4975 |
/** |
| 4976 |
* @return mixed |
| 4977 |
*/ |
| 4978 |
public function getConfig() |
| 4979 |
{ |
| 4980 |
return $this->config; |
| 4981 |
} |
| 4982 |
} |
| 4983 |
namespace Kibo\Phast\ValueObjects; |
| 4984 |
|
| 4985 |
class Resource |
| 4986 |
{ |
| 4987 |
const EXTENSION_TO_MIME_TYPE = array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'bmp' => 'image/bmp', 'webp' => 'image/webp', 'svg' => 'image/svg+xml', 'css' => 'text/css', 'js' => 'application/javascript', 'json' => 'application/json'); |
| 4988 |
/** |
| 4989 |
* @var URL |
| 4990 |
*/ |
| 4991 |
private $url; |
| 4992 |
/** |
| 4993 |
* @var Retriever |
| 4994 |
*/ |
| 4995 |
private $retriever; |
| 4996 |
/** |
| 4997 |
* @var string |
| 4998 |
*/ |
| 4999 |
private $content; |
| 5000 |
/** |
| 5001 |
* @var string |
| 5002 |
*/ |
| 5003 |
private $mimeType; |
| 5004 |
/** |
| 5005 |
* @var Resource[] |
| 5006 |
*/ |
| 5007 |
private $dependencies = array(); |
| 5008 |
private function __construct() |
| 5009 |
{ |
| 5010 |
} |
| 5011 |
public static function makeWithContent(\Kibo\Phast\ValueObjects\URL $url, $content, $mimeType = null) |
| 5012 |
{ |
| 5013 |
$instance = new self(); |
| 5014 |
$instance->url = $url; |
| 5015 |
$instance->mimeType = $mimeType; |
| 5016 |
$instance->content = $content; |
| 5017 |
return $instance; |
| 5018 |
} |
| 5019 |
public static function makeWithRetriever(\Kibo\Phast\ValueObjects\URL $url, \Kibo\Phast\Retrievers\Retriever $retriever, $mimeType = null) |
| 5020 |
{ |
| 5021 |
$instance = new self(); |
| 5022 |
$instance->url = $url; |
| 5023 |
$instance->mimeType = $mimeType; |
| 5024 |
$instance->retriever = $retriever; |
| 5025 |
return $instance; |
| 5026 |
} |
| 5027 |
/** |
| 5028 |
* @return URL |
| 5029 |
*/ |
| 5030 |
public function getUrl() |
| 5031 |
{ |
| 5032 |
return $this->url; |
| 5033 |
} |
| 5034 |
/** |
| 5035 |
* @return string |
| 5036 |
* @throws ItemNotFoundException |
| 5037 |
*/ |
| 5038 |
public function getContent() |
| 5039 |
{ |
| 5040 |
if (!isset($this->content)) { |
| 5041 |
$this->content = $this->retriever->retrieve($this->url); |
| 5042 |
if ($this->content === false) { |
| 5043 |
throw new \Kibo\Phast\Exceptions\ItemNotFoundException("Could not get {$this->url}"); |
| 5044 |
} |
| 5045 |
} |
| 5046 |
return $this->content; |
| 5047 |
} |
| 5048 |
/** |
| 5049 |
* @return string|null |
| 5050 |
*/ |
| 5051 |
public function getMimeType() |
| 5052 |
{ |
| 5053 |
if (!isset($this->mimeType)) { |
| 5054 |
$ext = strtolower($this->url->getExtension()); |
| 5055 |
$ext2mime = self::EXTENSION_TO_MIME_TYPE; |
| 5056 |
if (isset($ext2mime[$ext])) { |
| 5057 |
$this->mimeType = self::EXTENSION_TO_MIME_TYPE[$ext]; |
| 5058 |
} |
| 5059 |
} |
| 5060 |
return $this->mimeType; |
| 5061 |
} |
| 5062 |
/** |
| 5063 |
* @return bool|int |
| 5064 |
*/ |
| 5065 |
public function getSize() |
| 5066 |
{ |
| 5067 |
if (isset($this->retriever) && method_exists($this->retriever, 'getSize')) { |
| 5068 |
return $this->retriever->getSize($this->url); |
| 5069 |
} |
| 5070 |
if (isset($this->content)) { |
| 5071 |
return strlen($this->content); |
| 5072 |
} |
| 5073 |
return false; |
| 5074 |
} |
| 5075 |
public function toDataURL() |
| 5076 |
{ |
| 5077 |
$mime = $this->getMimeType(); |
| 5078 |
$content = $this->getContent(); |
| 5079 |
return "data:{$mime};base64," . base64_encode($content); |
| 5080 |
} |
| 5081 |
/** |
| 5082 |
* @return Resource[] |
| 5083 |
*/ |
| 5084 |
public function getDependencies() |
| 5085 |
{ |
| 5086 |
return $this->dependencies; |
| 5087 |
} |
| 5088 |
/** |
| 5089 |
* @return bool|int |
| 5090 |
*/ |
| 5091 |
public function getCacheSalt() |
| 5092 |
{ |
| 5093 |
return isset($this->retriever) ? $this->retriever->getCacheSalt($this->url) : 0; |
| 5094 |
} |
| 5095 |
/** |
| 5096 |
* @param string $content |
| 5097 |
* @param string|null $mimeType |
| 5098 |
* @return Resource |
| 5099 |
*/ |
| 5100 |
public function withContent($content, $mimeType = null) |
| 5101 |
{ |
| 5102 |
$new = clone $this; |
| 5103 |
$new->content = $content; |
| 5104 |
if (!is_null($mimeType)) { |
| 5105 |
$new->mimeType = $mimeType; |
| 5106 |
} |
| 5107 |
return $new; |
| 5108 |
} |
| 5109 |
/** |
| 5110 |
* @param Resource[] $dependencies |
| 5111 |
* @return Resource |
| 5112 |
*/ |
| 5113 |
public function withDependencies(array $dependencies) |
| 5114 |
{ |
| 5115 |
$new = clone $this; |
| 5116 |
$new->dependencies = $dependencies; |
| 5117 |
return $new; |
| 5118 |
} |
| 5119 |
} |
| 5120 |
namespace Kibo\Phast\ValueObjects; |
| 5121 |
|
| 5122 |
class Query implements \IteratorAggregate |
| 5123 |
{ |
| 5124 |
private $tuples = array(); |
| 5125 |
/** |
| 5126 |
* @param array $assoc |
| 5127 |
* @return Query |
| 5128 |
*/ |
| 5129 |
public static function fromAssoc($assoc) |
| 5130 |
{ |
| 5131 |
$result = new static(); |
| 5132 |
foreach ($assoc as $k => $v) { |
| 5133 |
$result->add($k, $v); |
| 5134 |
} |
| 5135 |
return $result; |
| 5136 |
} |
| 5137 |
/** |
| 5138 |
* @param string $string |
| 5139 |
* @return Query |
| 5140 |
*/ |
| 5141 |
public static function fromString($string) |
| 5142 |
{ |
| 5143 |
$result = new static(); |
| 5144 |
foreach (explode('&', $string) as $piece) { |
| 5145 |
if ($piece === '') { |
| 5146 |
continue; |
| 5147 |
} |
| 5148 |
$parts = array_map('urldecode', explode('=', $piece, 2)); |
| 5149 |
$result->add($parts[0], isset($parts[1]) ? $parts[1] : ''); |
| 5150 |
} |
| 5151 |
return $result; |
| 5152 |
} |
| 5153 |
public function add($key, $value) |
| 5154 |
{ |
| 5155 |
$this->tuples[] = [(string) $key, (string) $value]; |
| 5156 |
} |
| 5157 |
public function get($key, $default = null) |
| 5158 |
{ |
| 5159 |
foreach ($this->tuples as $tuple) { |
| 5160 |
if ($tuple[0] === (string) $key) { |
| 5161 |
return $tuple[1]; |
| 5162 |
} |
| 5163 |
} |
| 5164 |
return $default; |
| 5165 |
} |
| 5166 |
public function delete($key) |
| 5167 |
{ |
| 5168 |
$this->tuples = array_filter($this->tuples, function ($tuple) use($key) { |
| 5169 |
return $tuple[0] !== (string) $key; |
| 5170 |
}); |
| 5171 |
} |
| 5172 |
public function set($key, $value) |
| 5173 |
{ |
| 5174 |
$this->delete($key); |
| 5175 |
$this->add($key, $value); |
| 5176 |
} |
| 5177 |
public function has($key) |
| 5178 |
{ |
| 5179 |
foreach ($this->tuples as $tuple) { |
| 5180 |
if ($tuple[0] === (string) $key) { |
| 5181 |
return true; |
| 5182 |
} |
| 5183 |
} |
| 5184 |
return false; |
| 5185 |
} |
| 5186 |
public function update(\Kibo\Phast\ValueObjects\Query $source) |
| 5187 |
{ |
| 5188 |
foreach ($source as $key => $value) { |
| 5189 |
$this->delete($key); |
| 5190 |
} |
| 5191 |
foreach ($source as $key => $value) { |
| 5192 |
$this->add($key, $value); |
| 5193 |
} |
| 5194 |
} |
| 5195 |
public function toAssoc() |
| 5196 |
{ |
| 5197 |
$assoc = []; |
| 5198 |
foreach ($this->tuples as $tuple) { |
| 5199 |
if (!array_key_exists($tuple[0], $assoc)) { |
| 5200 |
$assoc[$tuple[0]] = $tuple[1]; |
| 5201 |
} |
| 5202 |
} |
| 5203 |
return $assoc; |
| 5204 |
} |
| 5205 |
public function getIterator() |
| 5206 |
{ |
| 5207 |
foreach ($this->tuples as $tuple) { |
| 5208 |
(yield $tuple[0] => $tuple[1]); |
| 5209 |
} |
| 5210 |
} |
| 5211 |
public function pop($key) |
| 5212 |
{ |
| 5213 |
$value = $this->get($key); |
| 5214 |
$this->delete($key); |
| 5215 |
return $value; |
| 5216 |
} |
| 5217 |
public function getAll($key) |
| 5218 |
{ |
| 5219 |
$result = []; |
| 5220 |
foreach ($this->tuples as $tuple) { |
| 5221 |
if ($tuple[0] === (string) $key) { |
| 5222 |
$result[] = $tuple[1]; |
| 5223 |
} |
| 5224 |
} |
| 5225 |
return $result; |
| 5226 |
} |
| 5227 |
} |
| 5228 |
namespace Kibo\Phast\ValueObjects; |
| 5229 |
|
| 5230 |
class URL |
| 5231 |
{ |
| 5232 |
/** |
| 5233 |
* @var string |
| 5234 |
*/ |
| 5235 |
private $scheme; |
| 5236 |
/** |
| 5237 |
* @var string |
| 5238 |
*/ |
| 5239 |
private $host; |
| 5240 |
/** |
| 5241 |
* @var string |
| 5242 |
*/ |
| 5243 |
private $port; |
| 5244 |
/** |
| 5245 |
* @var string |
| 5246 |
*/ |
| 5247 |
private $user; |
| 5248 |
/** |
| 5249 |
* @var string |
| 5250 |
*/ |
| 5251 |
private $pass; |
| 5252 |
/** |
| 5253 |
* @var string |
| 5254 |
*/ |
| 5255 |
private $path; |
| 5256 |
/** |
| 5257 |
* @var string |
| 5258 |
*/ |
| 5259 |
private $query; |
| 5260 |
/** |
| 5261 |
* @var string |
| 5262 |
*/ |
| 5263 |
private $fragment; |
| 5264 |
/** |
| 5265 |
* @param $string |
| 5266 |
* @return URL |
| 5267 |
*/ |
| 5268 |
public static function fromString($string) |
| 5269 |
{ |
| 5270 |
$components = parse_url($string); |
| 5271 |
if (!$components) { |
| 5272 |
return new self(); |
| 5273 |
} |
| 5274 |
return self::fromArray($components); |
| 5275 |
} |
| 5276 |
/** |
| 5277 |
* @param array $arr Should follow the format produced by parse_url() |
| 5278 |
* @return URL |
| 5279 |
* @see parse_url() |
| 5280 |
*/ |
| 5281 |
public static function fromArray(array $arr) |
| 5282 |
{ |
| 5283 |
$url = new self(); |
| 5284 |
foreach ($arr as $key => $value) { |
| 5285 |
$url->{$key} = $key == 'path' ? $url->normalizePath($value) : $value; |
| 5286 |
} |
| 5287 |
return $url; |
| 5288 |
} |
| 5289 |
/** |
| 5290 |
* If $this can be interpreted as relative to $base, |
| 5291 |
* will produce URL that is $base/$this. |
| 5292 |
* Otherwise the returned URL will point to the same place as $this |
| 5293 |
* |
| 5294 |
* @param URL $base |
| 5295 |
* @return URL |
| 5296 |
* |
| 5297 |
* @example this: www/htdocs + base: /var -> /var/www/htdocs |
| 5298 |
* @example this: /var + base: http://example.com -> http://example.com/var |
| 5299 |
* @example this: /var + base: /www -> /var |
| 5300 |
*/ |
| 5301 |
public function withBase(\Kibo\Phast\ValueObjects\URL $base) |
| 5302 |
{ |
| 5303 |
$new = clone $this; |
| 5304 |
foreach (['scheme', 'host', 'port', 'user', 'pass', 'path'] as $key) { |
| 5305 |
if ($key == 'path') { |
| 5306 |
$new->path = $this->resolvePath($base->path, $this->path); |
| 5307 |
} elseif (!isset($this->{$key}) && isset($base->{$key})) { |
| 5308 |
$new->{$key} = $base->{$key}; |
| 5309 |
} elseif (isset($this->{$key})) { |
| 5310 |
break; |
| 5311 |
} |
| 5312 |
} |
| 5313 |
return $new; |
| 5314 |
} |
| 5315 |
/** |
| 5316 |
* Tells whether $this can be interpreted as at the same host as $url |
| 5317 |
* |
| 5318 |
* @param URL $url |
| 5319 |
* @return bool |
| 5320 |
*/ |
| 5321 |
public function isLocalTo(\Kibo\Phast\ValueObjects\URL $url) |
| 5322 |
{ |
| 5323 |
return empty($this->host) || $this->host === $url->host; |
| 5324 |
} |
| 5325 |
/** |
| 5326 |
* @return string |
| 5327 |
*/ |
| 5328 |
public function toString() |
| 5329 |
{ |
| 5330 |
$scheme = isset($this->scheme) ? $this->scheme . '://' : ''; |
| 5331 |
$host = isset($this->host) ? $this->host : ''; |
| 5332 |
$port = isset($this->port) ? ':' . $this->port : ''; |
| 5333 |
$user = isset($this->user) ? $this->user : ''; |
| 5334 |
$pass = isset($this->pass) ? ':' . $this->pass : ''; |
| 5335 |
$pass = $user || $pass ? "{$pass}@" : ''; |
| 5336 |
$path = isset($this->path) ? $this->getPath() : ''; |
| 5337 |
$query = isset($this->query) ? '?' . $this->query : ''; |
| 5338 |
$fragment = isset($this->fragment) ? '#' . $this->fragment : ''; |
| 5339 |
return "{$scheme}{$user}{$pass}{$host}{$port}{$path}{$query}{$fragment}"; |
| 5340 |
} |
| 5341 |
private function normalizePath($path) |
| 5342 |
{ |
| 5343 |
$stack = []; |
| 5344 |
$head = null; |
| 5345 |
foreach (explode('/', $path) as $part) { |
| 5346 |
if ($part == '.' || $part == '') { |
| 5347 |
continue; |
| 5348 |
} |
| 5349 |
if (!is_null($head) && $part == '..' && $head != '..') { |
| 5350 |
array_pop($stack); |
| 5351 |
$head = empty($stack) ? null : $stack[count($stack) - 1]; |
| 5352 |
} else { |
| 5353 |
$stack[] = $head = $part; |
| 5354 |
} |
| 5355 |
} |
| 5356 |
$normalized = substr($path, 0, 1) == '/' ? '/' : ''; |
| 5357 |
if (!empty($stack)) { |
| 5358 |
$normalized .= join('/', $stack); |
| 5359 |
$normalized .= substr($path, -1) == '/' ? '/' : ''; |
| 5360 |
} |
| 5361 |
return $normalized; |
| 5362 |
} |
| 5363 |
private function resolvePath($base, $requested) |
| 5364 |
{ |
| 5365 |
if (!$requested) { |
| 5366 |
return $base; |
| 5367 |
} |
| 5368 |
if ($requested[0] == '/') { |
| 5369 |
return $requested; |
| 5370 |
} |
| 5371 |
if (substr($base, -1, 1) == '/') { |
| 5372 |
$usedBase = $base; |
| 5373 |
} else { |
| 5374 |
$usedBase = dirname($base); |
| 5375 |
} |
| 5376 |
return rtrim($usedBase, '/') . '/' . $requested; |
| 5377 |
} |
| 5378 |
/** |
| 5379 |
* @return string |
| 5380 |
*/ |
| 5381 |
public function getScheme() |
| 5382 |
{ |
| 5383 |
return $this->scheme; |
| 5384 |
} |
| 5385 |
/** |
| 5386 |
* @return string |
| 5387 |
*/ |
| 5388 |
public function getHost() |
| 5389 |
{ |
| 5390 |
return $this->host; |
| 5391 |
} |
| 5392 |
/** |
| 5393 |
* @return string |
| 5394 |
*/ |
| 5395 |
public function getPort() |
| 5396 |
{ |
| 5397 |
return $this->port; |
| 5398 |
} |
| 5399 |
/** |
| 5400 |
* @return string |
| 5401 |
*/ |
| 5402 |
public function getUser() |
| 5403 |
{ |
| 5404 |
return $this->user; |
| 5405 |
} |
| 5406 |
/** |
| 5407 |
* @return string |
| 5408 |
*/ |
| 5409 |
public function getPass() |
| 5410 |
{ |
| 5411 |
return $this->pass; |
| 5412 |
} |
| 5413 |
/** @return string */ |
| 5414 |
public function getDecodedPath() |
| 5415 |
{ |
| 5416 |
return urldecode($this->path); |
| 5417 |
} |
| 5418 |
/** |
| 5419 |
* @return string |
| 5420 |
*/ |
| 5421 |
public function getPath() |
| 5422 |
{ |
| 5423 |
return $this->path; |
| 5424 |
} |
| 5425 |
/** |
| 5426 |
* @return string |
| 5427 |
*/ |
| 5428 |
public function getQuery() |
| 5429 |
{ |
| 5430 |
return $this->query; |
| 5431 |
} |
| 5432 |
/** |
| 5433 |
* @return string |
| 5434 |
*/ |
| 5435 |
public function getExtension() |
| 5436 |
{ |
| 5437 |
$matches = []; |
| 5438 |
if (preg_match('/\\.([^.]*)$/', $this->path, $matches)) { |
| 5439 |
return $matches[1]; |
| 5440 |
} |
| 5441 |
return ''; |
| 5442 |
} |
| 5443 |
/** |
| 5444 |
* @return string |
| 5445 |
*/ |
| 5446 |
public function getFragment() |
| 5447 |
{ |
| 5448 |
return $this->fragment; |
| 5449 |
} |
| 5450 |
/** |
| 5451 |
* @param string $path |
| 5452 |
* @return self |
| 5453 |
*/ |
| 5454 |
public function withPath($path) |
| 5455 |
{ |
| 5456 |
$url = clone $this; |
| 5457 |
$url->path = (string) $path; |
| 5458 |
return $url; |
| 5459 |
} |
| 5460 |
/** |
| 5461 |
* @param string|null $query |
| 5462 |
* @return self |
| 5463 |
*/ |
| 5464 |
public function withQuery($query) |
| 5465 |
{ |
| 5466 |
$url = clone $this; |
| 5467 |
if ($query === null) { |
| 5468 |
$url->query = null; |
| 5469 |
} else { |
| 5470 |
$url->query = (string) $query; |
| 5471 |
} |
| 5472 |
return $url; |
| 5473 |
} |
| 5474 |
/** |
| 5475 |
* @return self |
| 5476 |
*/ |
| 5477 |
public function withoutQuery() |
| 5478 |
{ |
| 5479 |
$url = clone $this; |
| 5480 |
$url->query = null; |
| 5481 |
return $url; |
| 5482 |
} |
| 5483 |
public function __toString() |
| 5484 |
{ |
| 5485 |
return $this->toString(); |
| 5486 |
} |
| 5487 |
public function rewrite(\Kibo\Phast\ValueObjects\URL $from, \Kibo\Phast\ValueObjects\URL $to) |
| 5488 |
{ |
| 5489 |
$str_from = rtrim($from->toString(), '/'); |
| 5490 |
$str_to = rtrim($to->toString(), '/'); |
| 5491 |
return \Kibo\Phast\ValueObjects\URL::fromString(preg_replace('~^' . preg_quote($str_from, '~') . '(?=$|/)~', $str_to, $this->toString())); |
| 5492 |
} |
| 5493 |
} |
| 5494 |
namespace Kibo\Phast\Logging; |
| 5495 |
|
| 5496 |
class LogLevel |
| 5497 |
{ |
| 5498 |
const EMERGENCY = 128; |
| 5499 |
const ALERT = 64; |
| 5500 |
const CRITICAL = 32; |
| 5501 |
const ERROR = 16; |
| 5502 |
const WARNING = 8; |
| 5503 |
const NOTICE = 4; |
| 5504 |
const INFO = 2; |
| 5505 |
const DEBUG = 1; |
| 5506 |
public static function toString($level) |
| 5507 |
{ |
| 5508 |
switch ($level) { |
| 5509 |
case self::EMERGENCY: |
| 5510 |
return 'EMERGENCY'; |
| 5511 |
case self::ALERT: |
| 5512 |
return 'ALERT'; |
| 5513 |
case self::CRITICAL: |
| 5514 |
return 'CRITICAL'; |
| 5515 |
case self::ERROR: |
| 5516 |
return 'ERROR'; |
| 5517 |
case self::WARNING: |
| 5518 |
return 'WARNING'; |
| 5519 |
case self::NOTICE: |
| 5520 |
return 'NOTICE'; |
| 5521 |
case self::INFO: |
| 5522 |
return 'INFO'; |
| 5523 |
case self::DEBUG: |
| 5524 |
return 'DEBUG'; |
| 5525 |
default: |
| 5526 |
return 'UNKNOWN'; |
| 5527 |
} |
| 5528 |
} |
| 5529 |
} |
| 5530 |
namespace Kibo\Phast\Logging; |
| 5531 |
|
| 5532 |
class Log |
| 5533 |
{ |
| 5534 |
/** |
| 5535 |
* @var Logger |
| 5536 |
*/ |
| 5537 |
private static $logger; |
| 5538 |
public static function setLogger(\Kibo\Phast\Logging\Logger $logger) |
| 5539 |
{ |
| 5540 |
self::$logger = $logger; |
| 5541 |
} |
| 5542 |
public static function initWithDummy() |
| 5543 |
{ |
| 5544 |
self::$logger = new \Kibo\Phast\Logging\Logger(new \Kibo\Phast\Logging\LogWriters\Dummy\Writer()); |
| 5545 |
} |
| 5546 |
public static function init(array $config, \Kibo\Phast\Services\ServiceRequest $request, $service) |
| 5547 |
{ |
| 5548 |
$writer = (new \Kibo\Phast\Logging\LogWriters\Factory())->make($config, $request); |
| 5549 |
$logger = new \Kibo\Phast\Logging\Logger($writer); |
| 5550 |
self::$logger = $logger->withContext(['documentRequestId' => $request->getDocumentRequestId(), 'requestId' => mt_rand(0, 99999999), 'service' => $service]); |
| 5551 |
} |
| 5552 |
/** |
| 5553 |
* @return Logger |
| 5554 |
*/ |
| 5555 |
public static function get() |
| 5556 |
{ |
| 5557 |
if (!isset(self::$logger)) { |
| 5558 |
self::initWithDummy(); |
| 5559 |
} |
| 5560 |
return self::$logger; |
| 5561 |
} |
| 5562 |
/** |
| 5563 |
* @param array $context |
| 5564 |
* @return Logger |
| 5565 |
*/ |
| 5566 |
public static function context(array $context) |
| 5567 |
{ |
| 5568 |
return self::get()->withContext($context); |
| 5569 |
} |
| 5570 |
/** |
| 5571 |
* System is unusable. |
| 5572 |
* |
| 5573 |
* @param string $message |
| 5574 |
* @param array $context |
| 5575 |
* |
| 5576 |
* @return void |
| 5577 |
*/ |
| 5578 |
public static function emergency($message, array $context = array()) |
| 5579 |
{ |
| 5580 |
self::get()->emergency($message, $context); |
| 5581 |
} |
| 5582 |
/** |
| 5583 |
* Action must be taken immediately. |
| 5584 |
* |
| 5585 |
* Example: Entire website down, database unavailable, etc. This should |
| 5586 |
* trigger the SMS alerts and wake you up. |
| 5587 |
* |
| 5588 |
* @param string $message |
| 5589 |
* @param array $context |
| 5590 |
* |
| 5591 |
* @return void |
| 5592 |
*/ |
| 5593 |
public static function alert($message, array $context = array()) |
| 5594 |
{ |
| 5595 |
self::get()->alert($message, $context); |
| 5596 |
} |
| 5597 |
/** |
| 5598 |
* Critical conditions. |
| 5599 |
* |
| 5600 |
* Example: Application component unavailable, unexpected exception. |
| 5601 |
* |
| 5602 |
* @param string $message |
| 5603 |
* @param array $context |
| 5604 |
* |
| 5605 |
* @return void |
| 5606 |
*/ |
| 5607 |
public static function critical($message, array $context = array()) |
| 5608 |
{ |
| 5609 |
self::get()->critical($message, $context); |
| 5610 |
} |
| 5611 |
/** |
| 5612 |
* Runtime errors that do not require immediate action but should typically |
| 5613 |
* be logged and monitored. |
| 5614 |
* |
| 5615 |
* @param string $message |
| 5616 |
* @param array $context |
| 5617 |
* |
| 5618 |
* @return void |
| 5619 |
*/ |
| 5620 |
public static function error($message, array $context = array()) |
| 5621 |
{ |
| 5622 |
self::get()->error($message, $context); |
| 5623 |
} |
| 5624 |
/** |
| 5625 |
* Exceptional occurrences that are not errors. |
| 5626 |
* |
| 5627 |
* Example: Use of deprecated APIs, poor use of an API, undesirable things |
| 5628 |
* that are not necessarily wrong. |
| 5629 |
* |
| 5630 |
* @param string $message |
| 5631 |
* @param array $context |
| 5632 |
* |
| 5633 |
* @return void |
| 5634 |
*/ |
| 5635 |
public static function warning($message, array $context = array()) |
| 5636 |
{ |
| 5637 |
self::get()->warning($message, $context); |
| 5638 |
} |
| 5639 |
/** |
| 5640 |
* Normal but significant events. |
| 5641 |
* |
| 5642 |
* @param string $message |
| 5643 |
* @param array $context |
| 5644 |
* |
| 5645 |
* @return void |
| 5646 |
*/ |
| 5647 |
public static function notice($message, array $context = array()) |
| 5648 |
{ |
| 5649 |
self::get()->notice($message, $context); |
| 5650 |
} |
| 5651 |
/** |
| 5652 |
* Interesting events. |
| 5653 |
* |
| 5654 |
* Example: User logs in, SQL logs. |
| 5655 |
* |
| 5656 |
* @param string $message |
| 5657 |
* @param array $context |
| 5658 |
* |
| 5659 |
* @return void |
| 5660 |
*/ |
| 5661 |
public static function info($message, array $context = array()) |
| 5662 |
{ |
| 5663 |
self::get()->info($message, $context); |
| 5664 |
} |
| 5665 |
/** |
| 5666 |
* Detailed debug information. |
| 5667 |
* |
| 5668 |
* @param string $message |
| 5669 |
* @param array $context |
| 5670 |
* |
| 5671 |
* @return void |
| 5672 |
*/ |
| 5673 |
public static function debug($message, array $context = array()) |
| 5674 |
{ |
| 5675 |
self::get()->debug($message, $context); |
| 5676 |
} |
| 5677 |
} |
| 5678 |
namespace Kibo\Phast\Logging\LogWriters; |
| 5679 |
|
| 5680 |
class Factory |
| 5681 |
{ |
| 5682 |
public function make(array $config, \Kibo\Phast\Services\ServiceRequest $request) |
| 5683 |
{ |
| 5684 |
if (isset($config['logWriters']) && count($config['logWriters']) > 1) { |
| 5685 |
$class = \Kibo\Phast\Logging\LogWriters\Composite\Writer::class; |
| 5686 |
} elseif (isset($config['logWriters'])) { |
| 5687 |
$config = array_pop($config['logWriters']); |
| 5688 |
$class = $config['class']; |
| 5689 |
} else { |
| 5690 |
$class = $config['class']; |
| 5691 |
} |
| 5692 |
$package = \Kibo\Phast\Environment\Package::fromPackageClass($class); |
| 5693 |
$writer = $package->getFactory()->make($config, $request); |
| 5694 |
if (isset($config['levelMask'])) { |
| 5695 |
$writer->setLevelMask($config['levelMask']); |
| 5696 |
} |
| 5697 |
return $writer; |
| 5698 |
} |
| 5699 |
} |
| 5700 |
namespace Kibo\Phast\Logging\LogWriters\JSONLFile; |
| 5701 |
|
| 5702 |
class Factory |
| 5703 |
{ |
| 5704 |
public function make(array $config, \Kibo\Phast\Services\ServiceRequest $request) |
| 5705 |
{ |
| 5706 |
return new \Kibo\Phast\Logging\LogWriters\JSONLFile\Writer($config['logRoot'], $request->getDocumentRequestId()); |
| 5707 |
} |
| 5708 |
} |
| 5709 |
namespace Kibo\Phast\Logging\LogWriters\Composite; |
| 5710 |
|
| 5711 |
class Factory |
| 5712 |
{ |
| 5713 |
public function make(array $config, \Kibo\Phast\Services\ServiceRequest $request) |
| 5714 |
{ |
| 5715 |
$writer = new \Kibo\Phast\Logging\LogWriters\Composite\Writer(); |
| 5716 |
$factory = new \Kibo\Phast\Logging\LogWriters\Factory(); |
| 5717 |
foreach ($config['logWriters'] as $writerConfig) { |
| 5718 |
$writer->addWriter($factory->make($writerConfig, $request)); |
| 5719 |
} |
| 5720 |
return $writer; |
| 5721 |
} |
| 5722 |
} |
| 5723 |
namespace Kibo\Phast\Logging\LogWriters\RotatingTextFile; |
| 5724 |
|
| 5725 |
class Factory |
| 5726 |
{ |
| 5727 |
public function make(array $config, \Kibo\Phast\Services\ServiceRequest $request) |
| 5728 |
{ |
| 5729 |
return new \Kibo\Phast\Logging\LogWriters\RotatingTextFile\Writer($config); |
| 5730 |
} |
| 5731 |
} |
| 5732 |
namespace Kibo\Phast\Logging\LogWriters\PHPError; |
| 5733 |
|
| 5734 |
class Factory |
| 5735 |
{ |
| 5736 |
public function make(array $config, \Kibo\Phast\Services\ServiceRequest $request) |
| 5737 |
{ |
| 5738 |
return new \Kibo\Phast\Logging\LogWriters\PHPError\Writer($config); |
| 5739 |
} |
| 5740 |
} |
| 5741 |
namespace Kibo\Phast\Logging\Common; |
| 5742 |
|
| 5743 |
trait JSONLFileLogTrait |
| 5744 |
{ |
| 5745 |
/** |
| 5746 |
* @var string |
| 5747 |
*/ |
| 5748 |
private $dir; |
| 5749 |
/** |
| 5750 |
* @var string |
| 5751 |
*/ |
| 5752 |
private $filename; |
| 5753 |
/** |
| 5754 |
* JSONLFileLogWriter constructor. |
| 5755 |
* @param string $dir |
| 5756 |
* @param string $suffix |
| 5757 |
*/ |
| 5758 |
public function __construct($dir, $suffix) |
| 5759 |
{ |
| 5760 |
$this->dir = $dir; |
| 5761 |
$suffix = preg_replace('/[^0-9A-Za-z_-]/', '', (string) $suffix); |
| 5762 |
if (!empty($suffix)) { |
| 5763 |
$suffix = '-' . $suffix; |
| 5764 |
} |
| 5765 |
$this->filename = $this->dir . '/log' . $suffix . '.jsonl'; |
| 5766 |
} |
| 5767 |
} |
| 5768 |
namespace Kibo\Phast\Logging; |
| 5769 |
|
| 5770 |
class LogEntry implements \JsonSerializable |
| 5771 |
{ |
| 5772 |
/** |
| 5773 |
* @var int |
| 5774 |
*/ |
| 5775 |
private $level; |
| 5776 |
/** |
| 5777 |
* @var string |
| 5778 |
*/ |
| 5779 |
private $message; |
| 5780 |
/** |
| 5781 |
* @var array |
| 5782 |
*/ |
| 5783 |
private $context; |
| 5784 |
/** |
| 5785 |
* LogEntry constructor. |
| 5786 |
* @param int $level |
| 5787 |
* @param string $message |
| 5788 |
* @param array $context |
| 5789 |
*/ |
| 5790 |
public function __construct($level, $message, array $context) |
| 5791 |
{ |
| 5792 |
$this->level = (int) $level; |
| 5793 |
$this->message = $message; |
| 5794 |
$this->context = $context; |
| 5795 |
} |
| 5796 |
/** |
| 5797 |
* @return int |
| 5798 |
*/ |
| 5799 |
public function getLevel() |
| 5800 |
{ |
| 5801 |
return $this->level; |
| 5802 |
} |
| 5803 |
/** |
| 5804 |
* @return string |
| 5805 |
*/ |
| 5806 |
public function getMessage() |
| 5807 |
{ |
| 5808 |
return $this->message; |
| 5809 |
} |
| 5810 |
/** |
| 5811 |
* @return array |
| 5812 |
*/ |
| 5813 |
public function getContext() |
| 5814 |
{ |
| 5815 |
return $this->context; |
| 5816 |
} |
| 5817 |
public function toArray() |
| 5818 |
{ |
| 5819 |
return ['level' => $this->level, 'message' => $this->message, 'context' => $this->context]; |
| 5820 |
} |
| 5821 |
public function jsonSerialize() |
| 5822 |
{ |
| 5823 |
return $this->toArray(); |
| 5824 |
} |
| 5825 |
} |
| 5826 |
namespace Kibo\Phast\Logging; |
| 5827 |
|
| 5828 |
class Logger |
| 5829 |
{ |
| 5830 |
/** |
| 5831 |
* @var LogWriter |
| 5832 |
*/ |
| 5833 |
private $writer; |
| 5834 |
/** |
| 5835 |
* @var array |
| 5836 |
*/ |
| 5837 |
private $context = array(); |
| 5838 |
/** |
| 5839 |
* @var ObjectifiedFunctions |
| 5840 |
*/ |
| 5841 |
private $functions; |
| 5842 |
/** |
| 5843 |
* Logger constructor. |
| 5844 |
* @param LogWriter $writer |
| 5845 |
*/ |
| 5846 |
public function __construct(\Kibo\Phast\Logging\LogWriter $writer, \Kibo\Phast\Common\ObjectifiedFunctions $functions = null) |
| 5847 |
{ |
| 5848 |
$this->writer = $writer; |
| 5849 |
$this->functions = is_null($functions) ? new \Kibo\Phast\Common\ObjectifiedFunctions() : $functions; |
| 5850 |
} |
| 5851 |
/** |
| 5852 |
* Returns a new logger with default context |
| 5853 |
* merged from the current logger and the passed array |
| 5854 |
* |
| 5855 |
* @param array $context |
| 5856 |
* @return Logger |
| 5857 |
*/ |
| 5858 |
public function withContext(array $context) |
| 5859 |
{ |
| 5860 |
$logger = clone $this; |
| 5861 |
$logger->context = array_merge($this->context, $context); |
| 5862 |
return $logger; |
| 5863 |
} |
| 5864 |
/** |
| 5865 |
* System is unusable. |
| 5866 |
* |
| 5867 |
* @param string $message |
| 5868 |
* @param array $context |
| 5869 |
* |
| 5870 |
* @return void |
| 5871 |
*/ |
| 5872 |
public function emergency($message, array $context = array()) |
| 5873 |
{ |
| 5874 |
$this->log(\Kibo\Phast\Logging\LogLevel::EMERGENCY, $message, $context); |
| 5875 |
} |
| 5876 |
/** |
| 5877 |
* Action must be taken immediately. |
| 5878 |
* |
| 5879 |
* Example: Entire website down, database unavailable, etc. This should |
| 5880 |
* trigger the SMS alerts and wake you up. |
| 5881 |
* |
| 5882 |
* @param string $message |
| 5883 |
* @param array $context |
| 5884 |
* |
| 5885 |
* @return void |
| 5886 |
*/ |
| 5887 |
public function alert($message, array $context = array()) |
| 5888 |
{ |
| 5889 |
$this->log(\Kibo\Phast\Logging\LogLevel::ALERT, $message, $context); |
| 5890 |
} |
| 5891 |
/** |
| 5892 |
* Critical conditions. |
| 5893 |
* |
| 5894 |
* Example: Application component unavailable, unexpected exception. |
| 5895 |
* |
| 5896 |
* @param string $message |
| 5897 |
* @param array $context |
| 5898 |
* |
| 5899 |
* @return void |
| 5900 |
*/ |
| 5901 |
public function critical($message, array $context = array()) |
| 5902 |
{ |
| 5903 |
$this->log(\Kibo\Phast\Logging\LogLevel::CRITICAL, $message, $context); |
| 5904 |
} |
| 5905 |
/** |
| 5906 |
* Runtime errors that do not require immediate action but should typically |
| 5907 |
* be logged and monitored. |
| 5908 |
* |
| 5909 |
* @param string $message |
| 5910 |
* @param array $context |
| 5911 |
* |
| 5912 |
* @return void |
| 5913 |
*/ |
| 5914 |
public function error($message, array $context = array()) |
| 5915 |
{ |
| 5916 |
$this->log(\Kibo\Phast\Logging\LogLevel::ERROR, $message, $context); |
| 5917 |
} |
| 5918 |
/** |
| 5919 |
* Exceptional occurrences that are not errors. |
| 5920 |
* |
| 5921 |
* Example: Use of deprecated APIs, poor use of an API, undesirable things |
| 5922 |
* that are not necessarily wrong. |
| 5923 |
* |
| 5924 |
* @param string $message |
| 5925 |
* @param array $context |
| 5926 |
* |
| 5927 |
* @return void |
| 5928 |
*/ |
| 5929 |
public function warning($message, array $context = array()) |
| 5930 |
{ |
| 5931 |
$this->log(\Kibo\Phast\Logging\LogLevel::WARNING, $message, $context); |
| 5932 |
} |
| 5933 |
/** |
| 5934 |
* Normal but significant events. |
| 5935 |
* |
| 5936 |
* @param string $message |
| 5937 |
* @param array $context |
| 5938 |
* |
| 5939 |
* @return void |
| 5940 |
*/ |
| 5941 |
public function notice($message, array $context = array()) |
| 5942 |
{ |
| 5943 |
$this->log(\Kibo\Phast\Logging\LogLevel::NOTICE, $message, $context); |
| 5944 |
} |
| 5945 |
/** |
| 5946 |
* Interesting events. |
| 5947 |
* |
| 5948 |
* Example: User logs in, SQL logs. |
| 5949 |
* |
| 5950 |
* @param string $message |
| 5951 |
* @param array $context |
| 5952 |
* |
| 5953 |
* @return void |
| 5954 |
*/ |
| 5955 |
public function info($message, array $context = array()) |
| 5956 |
{ |
| 5957 |
$this->log(\Kibo\Phast\Logging\LogLevel::INFO, $message, $context); |
| 5958 |
} |
| 5959 |
/** |
| 5960 |
* Detailed debug information. |
| 5961 |
* |
| 5962 |
* @param string $message |
| 5963 |
* @param array $context |
| 5964 |
* |
| 5965 |
* @return void |
| 5966 |
*/ |
| 5967 |
public function debug($message, array $context = array()) |
| 5968 |
{ |
| 5969 |
$this->log(\Kibo\Phast\Logging\LogLevel::DEBUG, $message, $context); |
| 5970 |
} |
| 5971 |
protected function log($level, $message, array $context = array()) |
| 5972 |
{ |
| 5973 |
$context = array_merge(['timestamp' => $this->functions->microtime(true)], $context); |
| 5974 |
$this->writer->writeEntry(new \Kibo\Phast\Logging\LogEntry($level, $message, array_merge($this->context, $context))); |
| 5975 |
} |
| 5976 |
} |
| 5977 |
namespace Kibo\Phast\Logging; |
| 5978 |
|
| 5979 |
interface LogReader |
| 5980 |
{ |
| 5981 |
/** |
| 5982 |
* Reads LogMessage objects |
| 5983 |
* |
| 5984 |
* @return \Generator |
| 5985 |
*/ |
| 5986 |
public function readEntries(); |
| 5987 |
} |
| 5988 |
namespace Kibo\Phast\Logging; |
| 5989 |
|
| 5990 |
trait LoggingTrait |
| 5991 |
{ |
| 5992 |
protected function logger($method = null, $line = null) |
| 5993 |
{ |
| 5994 |
$context = ['class' => get_class($this)]; |
| 5995 |
if (!is_null($method)) { |
| 5996 |
$context['method'] = $method; |
| 5997 |
} |
| 5998 |
if (!is_null($line)) { |
| 5999 |
$context['line'] = $line; |
| 6000 |
} |
| 6001 |
return \Kibo\Phast\Logging\Log::context($context); |
| 6002 |
} |
| 6003 |
} |
| 6004 |
namespace Kibo\Phast\Logging\LogReaders\JSONLFile; |
| 6005 |
|
| 6006 |
class Reader implements \Kibo\Phast\Logging\LogReader |
| 6007 |
{ |
| 6008 |
use \Kibo\Phast\Logging\Common\JSONLFileLogTrait; |
| 6009 |
public function readEntries() |
| 6010 |
{ |
| 6011 |
$fp = @fopen($this->filename, 'r'); |
| 6012 |
while ($fp && ($row = @fgets($fp))) { |
| 6013 |
$decoded = @json_decode($row, true); |
| 6014 |
if (!$decoded) { |
| 6015 |
continue; |
| 6016 |
} |
| 6017 |
(yield new \Kibo\Phast\Logging\LogEntry(@$decoded['level'], @$decoded['message'], @$decoded['context'])); |
| 6018 |
} |
| 6019 |
@fclose($fp); |
| 6020 |
@unlink($this->filename); |
| 6021 |
} |
| 6022 |
public function __destruct() |
| 6023 |
{ |
| 6024 |
if (!($dir = @opendir($this->dir))) { |
| 6025 |
return; |
| 6026 |
} |
| 6027 |
$tenMinutesAgo = time() - 600; |
| 6028 |
while ($file = @readdir($dir)) { |
| 6029 |
$filename = $this->dir . "/{$file}"; |
| 6030 |
if (preg_match('/\\.jsonl$/', $file) && @filectime($filename) < $tenMinutesAgo) { |
| 6031 |
@unlink($filename); |
| 6032 |
} |
| 6033 |
} |
| 6034 |
} |
| 6035 |
} |
| 6036 |
namespace Kibo\Phast\Logging; |
| 6037 |
|
| 6038 |
interface LogWriter |
| 6039 |
{ |
| 6040 |
/** |
| 6041 |
* Set a bit-mask to filter entries that are actually written |
| 6042 |
* |
| 6043 |
* @param int $mask |
| 6044 |
* @return void |
| 6045 |
*/ |
| 6046 |
public function setLevelMask($mask); |
| 6047 |
/** |
| 6048 |
* Write an entry to the log |
| 6049 |
* |
| 6050 |
* @param LogEntry $entry |
| 6051 |
* @return void |
| 6052 |
*/ |
| 6053 |
public function writeEntry(\Kibo\Phast\Logging\LogEntry $entry); |
| 6054 |
} |
| 6055 |
namespace Kibo\Phast\Services; |
| 6056 |
|
| 6057 |
interface ServiceFilter |
| 6058 |
{ |
| 6059 |
/** |
| 6060 |
* @param Resource $resource |
| 6061 |
* @param array $request |
| 6062 |
* @return Resource |
| 6063 |
*/ |
| 6064 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request); |
| 6065 |
} |
| 6066 |
namespace Kibo\Phast\Services; |
| 6067 |
|
| 6068 |
class Factory |
| 6069 |
{ |
| 6070 |
/** |
| 6071 |
* @param string $service |
| 6072 |
* @param array $config |
| 6073 |
* @return BaseService |
| 6074 |
* @throws ItemNotFoundException |
| 6075 |
*/ |
| 6076 |
public function make($service, array $config) |
| 6077 |
{ |
| 6078 |
if (!preg_match('/^[a-z]+$/', $service)) { |
| 6079 |
throw new \Kibo\Phast\Exceptions\ItemNotFoundException('Bad service'); |
| 6080 |
} |
| 6081 |
$class = __NAMESPACE__ . '\\' . ucfirst($service) . '\\Factory'; |
| 6082 |
if (class_exists($class)) { |
| 6083 |
return (new $class())->make($config); |
| 6084 |
} |
| 6085 |
throw new \Kibo\Phast\Exceptions\ItemNotFoundException('Unknown service'); |
| 6086 |
} |
| 6087 |
} |
| 6088 |
namespace Kibo\Phast\Services; |
| 6089 |
|
| 6090 |
trait ServiceFactoryTrait |
| 6091 |
{ |
| 6092 |
/** |
| 6093 |
* @param array $config |
| 6094 |
* @param $cacheNamespace |
| 6095 |
* @return UniversalRetriever |
| 6096 |
*/ |
| 6097 |
public function makeUniversalCachingRetriever(array $config, $cacheNamespace) |
| 6098 |
{ |
| 6099 |
$retriever = new \Kibo\Phast\Retrievers\UniversalRetriever(); |
| 6100 |
$retriever->addRetriever(new \Kibo\Phast\Retrievers\LocalRetriever($config['retrieverMap'])); |
| 6101 |
$retriever->addRetriever(new \Kibo\Phast\Retrievers\CachingRetriever(new \Kibo\Phast\Cache\File\Cache($config['cache'], $cacheNamespace), (new \Kibo\Phast\Retrievers\RemoteRetrieverFactory())->make($config))); |
| 6102 |
return $retriever; |
| 6103 |
} |
| 6104 |
public function makeCachingServiceFilter(array $config, \Kibo\Phast\Filters\Service\CompositeFilter $compositeFilter, $cacheNamespace) |
| 6105 |
{ |
| 6106 |
return new \Kibo\Phast\Filters\Service\CachingServiceFilter(new \Kibo\Phast\Cache\File\Cache($config['cache'], $cacheNamespace), $compositeFilter, new \Kibo\Phast\Retrievers\LocalRetriever($config['retrieverMap'])); |
| 6107 |
} |
| 6108 |
} |
| 6109 |
namespace Kibo\Phast\Services\Bundler; |
| 6110 |
|
| 6111 |
class Service |
| 6112 |
{ |
| 6113 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 6114 |
/** |
| 6115 |
* @var ServiceSignature |
| 6116 |
*/ |
| 6117 |
private $signature; |
| 6118 |
/** |
| 6119 |
* @var Retriever |
| 6120 |
*/ |
| 6121 |
private $cssRetriever; |
| 6122 |
/** |
| 6123 |
* @var ServiceFilter |
| 6124 |
*/ |
| 6125 |
private $cssFilter; |
| 6126 |
/** |
| 6127 |
* @var Retriever |
| 6128 |
*/ |
| 6129 |
private $jsRetriever; |
| 6130 |
/** |
| 6131 |
* @var ServiceFilter |
| 6132 |
*/ |
| 6133 |
private $jsFilter; |
| 6134 |
private $tokenRefMaker; |
| 6135 |
public function __construct(\Kibo\Phast\Security\ServiceSignature $signature, \Kibo\Phast\Retrievers\Retriever $cssRetriever, \Kibo\Phast\Services\ServiceFilter $cssFilter, \Kibo\Phast\Retrievers\Retriever $jsRetriever, \Kibo\Phast\Services\ServiceFilter $jsFilter, \Kibo\Phast\Services\Bundler\TokenRefMaker $tokenRefMaker) |
| 6136 |
{ |
| 6137 |
$this->signature = $signature; |
| 6138 |
$this->cssRetriever = $cssRetriever; |
| 6139 |
$this->cssFilter = $cssFilter; |
| 6140 |
$this->jsRetriever = $jsRetriever; |
| 6141 |
$this->jsFilter = $jsFilter; |
| 6142 |
$this->tokenRefMaker = $tokenRefMaker; |
| 6143 |
} |
| 6144 |
/** |
| 6145 |
* @param ServiceRequest $request |
| 6146 |
* @return Response |
| 6147 |
*/ |
| 6148 |
public function serve(\Kibo\Phast\Services\ServiceRequest $request) |
| 6149 |
{ |
| 6150 |
$response = new \Kibo\Phast\HTTP\Response(); |
| 6151 |
$response->setHeader('Content-Type', 'application/json'); |
| 6152 |
$response->setContent($this->streamResponse($request)); |
| 6153 |
return $response; |
| 6154 |
} |
| 6155 |
private function streamResponse(\Kibo\Phast\Services\ServiceRequest $request) |
| 6156 |
{ |
| 6157 |
(yield '['); |
| 6158 |
$firstRow = true; |
| 6159 |
foreach ($this->getParams($request) as $key => $params) { |
| 6160 |
if (isset($params['ref'])) { |
| 6161 |
$ref = $params['ref']; |
| 6162 |
$params = $this->tokenRefMaker->getParams($ref); |
| 6163 |
if (!$params) { |
| 6164 |
$this->logger()->error('Could not resolve ref {ref}', ['ref' => $ref]); |
| 6165 |
(yield $this->generateJSONRow(['status' => 404], $firstRow)); |
| 6166 |
continue; |
| 6167 |
} |
| 6168 |
} |
| 6169 |
if (!isset($params['src'])) { |
| 6170 |
$this->logger()->error('No src found for set {key}', ['key' => $key]); |
| 6171 |
(yield $this->generateJSONRow(['status' => 404], $firstRow)); |
| 6172 |
continue; |
| 6173 |
} |
| 6174 |
if (!$this->verifyParams($params)) { |
| 6175 |
$this->logger()->error('Params verification failed for set {key}', ['key' => $key]); |
| 6176 |
(yield $this->generateJSONRow(['status' => 401], $firstRow)); |
| 6177 |
continue; |
| 6178 |
} |
| 6179 |
list($retriever, $filter) = $this->getRetrieverAndFilter($params); |
| 6180 |
$resource = \Kibo\Phast\ValueObjects\Resource::makeWithRetriever(\Kibo\Phast\ValueObjects\URL::fromString($params['src']), $retriever); |
| 6181 |
try { |
| 6182 |
$this->logger()->info('Applying for set {key}', ['key' => $key]); |
| 6183 |
$filtered = $filter->apply($resource, $params); |
| 6184 |
(yield $this->generateJSONRow(['status' => 200, 'content' => $filtered->getContent()], $firstRow)); |
| 6185 |
} catch (\Kibo\Phast\Exceptions\ItemNotFoundException $e) { |
| 6186 |
$this->logger()->error('Could not find {url} for set {key}', ['url' => $params['src'], 'key' => $key]); |
| 6187 |
(yield $this->generateJSONRow(['status' => 404], $firstRow)); |
| 6188 |
} catch (\Exception $e) { |
| 6189 |
$this->logger()->critical('Unhandled exception for set {key}: {type} Message: {message} File: {file} Line: {line}', ['key' => $key, 'type' => get_class($e), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine()]); |
| 6190 |
(yield $this->generateJSONRow(['status' => 500], $firstRow)); |
| 6191 |
} |
| 6192 |
} |
| 6193 |
(yield ']'); |
| 6194 |
} |
| 6195 |
private function getParams(\Kibo\Phast\Services\ServiceRequest $request) |
| 6196 |
{ |
| 6197 |
$params = $request->getParams(); |
| 6198 |
if (isset($params['src_0'])) { |
| 6199 |
return (new \Kibo\Phast\Services\Bundler\BundlerParamsParser())->parse($request); |
| 6200 |
} |
| 6201 |
return (new \Kibo\Phast\Services\Bundler\ShortBundlerParamsParser())->parse($request); |
| 6202 |
} |
| 6203 |
private function verifyParams(array $params) |
| 6204 |
{ |
| 6205 |
return \Kibo\Phast\Services\Bundler\ServiceParams::fromArray($params)->verify($this->signature); |
| 6206 |
} |
| 6207 |
private function getRetrieverAndFilter(array $params) |
| 6208 |
{ |
| 6209 |
if (isset($params['isScript'])) { |
| 6210 |
return [$this->jsRetriever, $this->jsFilter]; |
| 6211 |
} |
| 6212 |
return [$this->cssRetriever, $this->cssFilter]; |
| 6213 |
} |
| 6214 |
private function generateJSONRow(array $content, &$firstRow) |
| 6215 |
{ |
| 6216 |
if (!$firstRow) { |
| 6217 |
$prepend = ','; |
| 6218 |
} else { |
| 6219 |
$prepend = ''; |
| 6220 |
$firstRow = false; |
| 6221 |
} |
| 6222 |
return $prepend . \Kibo\Phast\Common\JSON::encode($content); |
| 6223 |
} |
| 6224 |
} |
| 6225 |
namespace Kibo\Phast\Services\Bundler; |
| 6226 |
|
| 6227 |
class BundlerParamsParser |
| 6228 |
{ |
| 6229 |
public function parse(\Kibo\Phast\Services\ServiceRequest $request) |
| 6230 |
{ |
| 6231 |
$result = []; |
| 6232 |
foreach ($request->getParams() as $name => $value) { |
| 6233 |
if (strpos($name, '_') !== false) { |
| 6234 |
list($name, $key) = explode('_', $name, 2); |
| 6235 |
$result[$key][$name] = $value; |
| 6236 |
} |
| 6237 |
} |
| 6238 |
return $result; |
| 6239 |
} |
| 6240 |
} |
| 6241 |
namespace Kibo\Phast\Services\Bundler; |
| 6242 |
|
| 6243 |
class ShortBundlerParamsParser |
| 6244 |
{ |
| 6245 |
public static function getParamsMappings() |
| 6246 |
{ |
| 6247 |
return ['s' => 'src', 'i' => 'strip-imports', 'c' => 'cacheMarker', 't' => 'token', 'j' => 'isScript', 'r' => 'ref']; |
| 6248 |
} |
| 6249 |
public function parse(\Kibo\Phast\Services\ServiceRequest $request) |
| 6250 |
{ |
| 6251 |
$query_string = $request->getHTTPRequest()->getQueryString(); |
| 6252 |
if (preg_match('/(^|&)f=/', $query_string)) { |
| 6253 |
$query = \Kibo\Phast\ValueObjects\Query::fromString($this->unobfuscateQuery($query_string)); |
| 6254 |
} else { |
| 6255 |
$query = $request->getQuery(); |
| 6256 |
} |
| 6257 |
$query = $this->unshortenParams($query->getIterator()); |
| 6258 |
$query = $this->uncompressSrcs($query); |
| 6259 |
$result = []; |
| 6260 |
$current = null; |
| 6261 |
foreach ($query as $key => $value) { |
| 6262 |
if (in_array($key, ['src', 'ref'])) { |
| 6263 |
if ($current) { |
| 6264 |
$result[] = $current; |
| 6265 |
} |
| 6266 |
$current = []; |
| 6267 |
} |
| 6268 |
if ($current !== null) { |
| 6269 |
$current[$key] = $value; |
| 6270 |
} |
| 6271 |
} |
| 6272 |
if ($current) { |
| 6273 |
$result[] = $current; |
| 6274 |
} |
| 6275 |
return $result; |
| 6276 |
} |
| 6277 |
private function unobfuscateQuery($query) |
| 6278 |
{ |
| 6279 |
$query = str_rot13($query); |
| 6280 |
if (strpos($query, '%2S') !== false) { |
| 6281 |
$query = preg_replace_callback('/%../', function ($match) { |
| 6282 |
return str_rot13($match[0]); |
| 6283 |
}, $query); |
| 6284 |
} |
| 6285 |
return $query; |
| 6286 |
} |
| 6287 |
private function unshortenParams(\Generator $query) |
| 6288 |
{ |
| 6289 |
$mappings = self::getParamsMappings(); |
| 6290 |
foreach ($query as $key => $value) { |
| 6291 |
if (isset($mappings[$key])) { |
| 6292 |
(yield $mappings[$key] => $value === '' ? '1' : $value); |
| 6293 |
} else { |
| 6294 |
(yield $key => $value); |
| 6295 |
} |
| 6296 |
} |
| 6297 |
} |
| 6298 |
private function uncompressSrcs(\Generator $query) |
| 6299 |
{ |
| 6300 |
$lastUrl = ''; |
| 6301 |
foreach ($query as $key => $value) { |
| 6302 |
if ($key === 'src') { |
| 6303 |
$prefixLength = (int) base_convert(substr($value, 0, 2), 36, 10); |
| 6304 |
$suffix = substr($value, 2); |
| 6305 |
$value = substr($lastUrl, 0, $prefixLength) . $suffix; |
| 6306 |
$lastUrl = $value; |
| 6307 |
} |
| 6308 |
(yield $key => $value); |
| 6309 |
} |
| 6310 |
} |
| 6311 |
} |
| 6312 |
namespace Kibo\Phast\Services\Bundler; |
| 6313 |
|
| 6314 |
class TokenRefMaker |
| 6315 |
{ |
| 6316 |
private $cache; |
| 6317 |
public function __construct(\Kibo\Phast\Cache\Cache $cache) |
| 6318 |
{ |
| 6319 |
$this->cache = $cache; |
| 6320 |
} |
| 6321 |
public function getRef($token, array $params) |
| 6322 |
{ |
| 6323 |
$ref = \Kibo\Phast\Common\Base64url::shortHash(\Kibo\Phast\Common\JSON::encode($params)); |
| 6324 |
$cachedParams = $this->cache->get($ref); |
| 6325 |
if (!$cachedParams) { |
| 6326 |
$this->cache->set($ref, $params); |
| 6327 |
$cachedParams = $this->cache->get($ref); |
| 6328 |
} |
| 6329 |
if ($cachedParams === $params) { |
| 6330 |
return $ref; |
| 6331 |
} |
| 6332 |
} |
| 6333 |
public function getParams($ref) |
| 6334 |
{ |
| 6335 |
return $this->cache->get($ref); |
| 6336 |
} |
| 6337 |
} |
| 6338 |
namespace Kibo\Phast\Services\Bundler; |
| 6339 |
|
| 6340 |
class TokenRefMakerFactory |
| 6341 |
{ |
| 6342 |
public function make(array $config) |
| 6343 |
{ |
| 6344 |
$cache = new \Kibo\Phast\Cache\File\Cache($config['cache'], 'token-refs'); |
| 6345 |
return new \Kibo\Phast\Services\Bundler\TokenRefMaker($cache); |
| 6346 |
} |
| 6347 |
} |
| 6348 |
namespace Kibo\Phast\Services\Bundler; |
| 6349 |
|
| 6350 |
class ServiceParams |
| 6351 |
{ |
| 6352 |
/** |
| 6353 |
* @var string |
| 6354 |
*/ |
| 6355 |
private $token; |
| 6356 |
/** |
| 6357 |
* @var array |
| 6358 |
*/ |
| 6359 |
private $params; |
| 6360 |
private function __construct() |
| 6361 |
{ |
| 6362 |
} |
| 6363 |
/** |
| 6364 |
* @param array $params |
| 6365 |
* @return ServiceParams |
| 6366 |
*/ |
| 6367 |
public static function fromArray(array $params) |
| 6368 |
{ |
| 6369 |
$instance = new self(); |
| 6370 |
if (isset($params['token'])) { |
| 6371 |
$instance->token = $params['token']; |
| 6372 |
unset($params['token']); |
| 6373 |
} |
| 6374 |
$instance->params = $params; |
| 6375 |
return $instance; |
| 6376 |
} |
| 6377 |
/** |
| 6378 |
* @param ServiceSignature $signature |
| 6379 |
* @return ServiceParams |
| 6380 |
*/ |
| 6381 |
public function sign(\Kibo\Phast\Security\ServiceSignature $signature) |
| 6382 |
{ |
| 6383 |
$new = new self(); |
| 6384 |
$new->token = $this->makeToken($signature); |
| 6385 |
$new->params = $this->params; |
| 6386 |
return $new; |
| 6387 |
} |
| 6388 |
/** |
| 6389 |
* @param ServiceSignature $signature |
| 6390 |
* @return bool |
| 6391 |
*/ |
| 6392 |
public function verify(\Kibo\Phast\Security\ServiceSignature $signature) |
| 6393 |
{ |
| 6394 |
if (!isset($this->token)) { |
| 6395 |
return false; |
| 6396 |
} |
| 6397 |
return $this->token == $this->makeToken($signature); |
| 6398 |
} |
| 6399 |
/** |
| 6400 |
* @return mixed |
| 6401 |
*/ |
| 6402 |
public function toArray() |
| 6403 |
{ |
| 6404 |
$params = $this->params; |
| 6405 |
if ($this->token) { |
| 6406 |
$params['token'] = $this->token; |
| 6407 |
} |
| 6408 |
return $params; |
| 6409 |
} |
| 6410 |
public function serialize() |
| 6411 |
{ |
| 6412 |
return \Kibo\Phast\Common\JSON::encode($this->toArray()); |
| 6413 |
} |
| 6414 |
private function makeToken(\Kibo\Phast\Security\ServiceSignature $signature) |
| 6415 |
{ |
| 6416 |
$params = $this->params; |
| 6417 |
if (isset($params['cacheMarker'])) { |
| 6418 |
unset($params['cacheMarker']); |
| 6419 |
} |
| 6420 |
ksort($params); |
| 6421 |
array_walk($params, function (&$item) { |
| 6422 |
$item = (string) $item; |
| 6423 |
}); |
| 6424 |
return $signature->sign(json_encode($params)); |
| 6425 |
} |
| 6426 |
public function replaceByTokenRef(\Kibo\Phast\Services\Bundler\TokenRefMaker $maker) |
| 6427 |
{ |
| 6428 |
if (!isset($this->token)) { |
| 6429 |
return $this; |
| 6430 |
} |
| 6431 |
$ref = $maker->getRef($this->token, $this->toArray()); |
| 6432 |
return $ref ? \Kibo\Phast\Services\Bundler\ServiceParams::fromArray(['ref' => $ref]) : $this; |
| 6433 |
} |
| 6434 |
} |
| 6435 |
namespace Kibo\Phast\Services\Bundler; |
| 6436 |
|
| 6437 |
class Factory |
| 6438 |
{ |
| 6439 |
use \Kibo\Phast\Services\ServiceFactoryTrait; |
| 6440 |
public function make(array $config) |
| 6441 |
{ |
| 6442 |
$cssServiceFactory = new \Kibo\Phast\Services\Css\Factory(); |
| 6443 |
$jsServiceFactory = new \Kibo\Phast\Services\Scripts\Factory(); |
| 6444 |
$cssFilter = $this->makeCachingServiceFilter($config, $cssServiceFactory->makeFilter($config), 'bundler-css'); |
| 6445 |
$jsFilter = $this->makeCachingServiceFilter($config, $jsServiceFactory->makeFilter($config), 'bundler-js'); |
| 6446 |
return new \Kibo\Phast\Services\Bundler\Service((new \Kibo\Phast\Security\ServiceSignatureFactory())->make($config), $cssServiceFactory->makeRetriever($config), $cssFilter, $jsServiceFactory->makeRetriever($config), $jsFilter, (new \Kibo\Phast\Services\Bundler\TokenRefMakerFactory())->make($config)); |
| 6447 |
} |
| 6448 |
} |
| 6449 |
namespace Kibo\Phast\Services\Css; |
| 6450 |
|
| 6451 |
class Factory |
| 6452 |
{ |
| 6453 |
use \Kibo\Phast\Services\ServiceFactoryTrait; |
| 6454 |
public function make(array $config) |
| 6455 |
{ |
| 6456 |
$cssComposite = $this->makeFilter($config); |
| 6457 |
$composite = $this->makeCachingServiceFilter($config, $cssComposite, 'css-processing-2'); |
| 6458 |
return new \Kibo\Phast\Services\Css\Service((new \Kibo\Phast\Security\ServiceSignatureFactory())->make($config), [], $this->makeRetriever($config), $composite, $config); |
| 6459 |
} |
| 6460 |
public function makeRetriever(array $config) |
| 6461 |
{ |
| 6462 |
return $this->makeUniversalCachingRetriever($config, 'css'); |
| 6463 |
} |
| 6464 |
public function makeFilter(array $config) |
| 6465 |
{ |
| 6466 |
return (new \Kibo\Phast\Filters\CSS\Composite\Factory())->make($config); |
| 6467 |
} |
| 6468 |
} |
| 6469 |
namespace Kibo\Phast\Services\Diagnostics; |
| 6470 |
|
| 6471 |
class Factory |
| 6472 |
{ |
| 6473 |
public function make(array $config) |
| 6474 |
{ |
| 6475 |
$logRoot = null; |
| 6476 |
foreach ($config['logging']['logWriters'] as $writerConfig) { |
| 6477 |
if ($writerConfig['class'] == \Kibo\Phast\Logging\LogWriters\JSONLFile\Writer::class) { |
| 6478 |
$logRoot = $writerConfig['logRoot']; |
| 6479 |
break; |
| 6480 |
} |
| 6481 |
} |
| 6482 |
return new \Kibo\Phast\Services\Diagnostics\Service($logRoot); |
| 6483 |
} |
| 6484 |
} |
| 6485 |
namespace Kibo\Phast\Services\Diagnostics; |
| 6486 |
|
| 6487 |
class Service |
| 6488 |
{ |
| 6489 |
private $logRoot; |
| 6490 |
public function __construct($logRoot) |
| 6491 |
{ |
| 6492 |
$this->logRoot = $logRoot; |
| 6493 |
} |
| 6494 |
public function serve(\Kibo\Phast\Services\ServiceRequest $request) |
| 6495 |
{ |
| 6496 |
$params = $request->getParams(); |
| 6497 |
if (isset($params['documentRequestId'])) { |
| 6498 |
$items = $this->getRequestLog($params['documentRequestId']); |
| 6499 |
} else { |
| 6500 |
$items = $this->getSystemDiagnostics(); |
| 6501 |
} |
| 6502 |
$response = new \Kibo\Phast\HTTP\Response(); |
| 6503 |
$response->setContent(\Kibo\Phast\Common\JSON::prettyEncode($items)); |
| 6504 |
$response->setHeader('Content-Type', 'application/json'); |
| 6505 |
return $response; |
| 6506 |
} |
| 6507 |
private function getRequestLog($requestId) |
| 6508 |
{ |
| 6509 |
return iterator_to_array((new \Kibo\Phast\Logging\LogReaders\JSONLFile\Reader($this->logRoot, $requestId))->readEntries()); |
| 6510 |
} |
| 6511 |
private function getSystemDiagnostics() |
| 6512 |
{ |
| 6513 |
return (new \Kibo\Phast\Diagnostics\SystemDiagnostics())->run(require PHAST_CONFIG_FILE); |
| 6514 |
} |
| 6515 |
} |
| 6516 |
namespace Kibo\Phast\Services\Scripts; |
| 6517 |
|
| 6518 |
class Factory |
| 6519 |
{ |
| 6520 |
use \Kibo\Phast\Services\ServiceFactoryTrait; |
| 6521 |
public function make(array $config) |
| 6522 |
{ |
| 6523 |
$cachedComposite = $this->makeFilter($config); |
| 6524 |
return new \Kibo\Phast\Services\Scripts\Service((new \Kibo\Phast\Security\ServiceSignatureFactory())->make($config), $config['scripts']['whitelist'], $this->makeRetriever($config), $this->makeCachingServiceFilter($config, $cachedComposite, 'scripts-minified'), $config); |
| 6525 |
} |
| 6526 |
public function makeRetriever(array $config) |
| 6527 |
{ |
| 6528 |
return $this->makeUniversalCachingRetriever($config, 'scripts'); |
| 6529 |
} |
| 6530 |
public function makeFilter(array $config) |
| 6531 |
{ |
| 6532 |
$filter = new \Kibo\Phast\Filters\Service\CompositeFilter(); |
| 6533 |
$filter->addFilter(new \Kibo\Phast\Filters\Text\Decode\Filter()); |
| 6534 |
$filter->addFilter(new \Kibo\Phast\Filters\JavaScript\Minification\JSMinifierFilter(@$config['scripts']['removeLicenseHeaders'])); |
| 6535 |
return $filter; |
| 6536 |
} |
| 6537 |
} |
| 6538 |
namespace Kibo\Phast\Services\Images; |
| 6539 |
|
| 6540 |
class Factory |
| 6541 |
{ |
| 6542 |
public function make(array $config) |
| 6543 |
{ |
| 6544 |
if ($config['images']['api-mode']) { |
| 6545 |
$retriever = new \Kibo\Phast\Retrievers\PostDataRetriever(); |
| 6546 |
} else { |
| 6547 |
$retriever = new \Kibo\Phast\Retrievers\UniversalRetriever(); |
| 6548 |
$retriever->addRetriever(new \Kibo\Phast\Retrievers\LocalRetriever($config['retrieverMap'])); |
| 6549 |
$retriever->addRetriever((new \Kibo\Phast\Retrievers\RemoteRetrieverFactory())->make($config)); |
| 6550 |
} |
| 6551 |
return new \Kibo\Phast\Services\Images\Service((new \Kibo\Phast\Security\ServiceSignatureFactory())->make($config), $config['images']['whitelist'], $retriever, (new \Kibo\Phast\Filters\Image\Composite\Factory($config))->make(), $config); |
| 6552 |
} |
| 6553 |
} |
| 6554 |
namespace Kibo\Phast\Services; |
| 6555 |
|
| 6556 |
abstract class BaseService |
| 6557 |
{ |
| 6558 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 6559 |
/** |
| 6560 |
* @var ServiceSignature |
| 6561 |
*/ |
| 6562 |
protected $signature; |
| 6563 |
/** |
| 6564 |
* @var string[] |
| 6565 |
*/ |
| 6566 |
protected $whitelist = array(); |
| 6567 |
/** |
| 6568 |
* @var Retriever |
| 6569 |
*/ |
| 6570 |
protected $retriever; |
| 6571 |
/** |
| 6572 |
* @var ServiceFilter |
| 6573 |
*/ |
| 6574 |
protected $filter; |
| 6575 |
/** |
| 6576 |
* @var array |
| 6577 |
*/ |
| 6578 |
protected $config; |
| 6579 |
/** |
| 6580 |
* BaseService constructor. |
| 6581 |
* @param ServiceSignature $signature |
| 6582 |
* @param array $whitelist |
| 6583 |
* @param Retriever $retriever |
| 6584 |
* @param ServiceFilter $filter |
| 6585 |
* @param array $config |
| 6586 |
*/ |
| 6587 |
public function __construct(\Kibo\Phast\Security\ServiceSignature $signature, array $whitelist, \Kibo\Phast\Retrievers\Retriever $retriever, \Kibo\Phast\Services\ServiceFilter $filter, array $config) |
| 6588 |
{ |
| 6589 |
$this->signature = $signature; |
| 6590 |
$this->whitelist = $whitelist; |
| 6591 |
$this->retriever = $retriever; |
| 6592 |
$this->filter = $filter; |
| 6593 |
$this->config = $config; |
| 6594 |
} |
| 6595 |
/** |
| 6596 |
* @param ServiceRequest $request |
| 6597 |
* @return Response |
| 6598 |
*/ |
| 6599 |
public function serve(\Kibo\Phast\Services\ServiceRequest $request) |
| 6600 |
{ |
| 6601 |
$this->validateRequest($request); |
| 6602 |
$request = $this->getParams($request); |
| 6603 |
$resource = \Kibo\Phast\ValueObjects\Resource::makeWithRetriever(\Kibo\Phast\ValueObjects\URL::fromString(isset($request['src']) ? $request['src'] : ''), $this->retriever); |
| 6604 |
$filtered = $this->filter->apply($resource, $request); |
| 6605 |
return $this->makeResponse($filtered, $request); |
| 6606 |
} |
| 6607 |
/** |
| 6608 |
* @param ServiceRequest $request |
| 6609 |
* @return array |
| 6610 |
*/ |
| 6611 |
protected function getParams(\Kibo\Phast\Services\ServiceRequest $request) |
| 6612 |
{ |
| 6613 |
return $request->getParams(); |
| 6614 |
} |
| 6615 |
/** |
| 6616 |
* @param Resource $resource |
| 6617 |
* @param array $request |
| 6618 |
* @return Response |
| 6619 |
*/ |
| 6620 |
protected function makeResponse(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 6621 |
{ |
| 6622 |
$response = new \Kibo\Phast\HTTP\Response(); |
| 6623 |
$response->setContent($resource->getContent()); |
| 6624 |
return $response; |
| 6625 |
} |
| 6626 |
protected function validateRequest(\Kibo\Phast\Services\ServiceRequest $request) |
| 6627 |
{ |
| 6628 |
$this->validateIntegrity($request); |
| 6629 |
try { |
| 6630 |
$this->validateToken($request); |
| 6631 |
} catch (\Kibo\Phast\Exceptions\UnauthorizedException $e) { |
| 6632 |
$this->validateWhitelisted($request); |
| 6633 |
} |
| 6634 |
} |
| 6635 |
protected function validateIntegrity(\Kibo\Phast\Services\ServiceRequest $request) |
| 6636 |
{ |
| 6637 |
$params = $request->getParams(); |
| 6638 |
if (!isset($params['src'])) { |
| 6639 |
throw new \Kibo\Phast\Exceptions\ItemNotFoundException('No source is set!'); |
| 6640 |
} |
| 6641 |
} |
| 6642 |
protected function validateToken(\Kibo\Phast\Services\ServiceRequest $request) |
| 6643 |
{ |
| 6644 |
if (!$request->verify($this->signature)) { |
| 6645 |
throw new \Kibo\Phast\Exceptions\UnauthorizedException('Invalid token in request: ' . $request->serialize(\Kibo\Phast\Services\ServiceRequest::FORMAT_QUERY)); |
| 6646 |
} |
| 6647 |
} |
| 6648 |
protected function validateWhitelisted(\Kibo\Phast\Services\ServiceRequest $request) |
| 6649 |
{ |
| 6650 |
$params = $request->getParams(); |
| 6651 |
foreach ($this->whitelist as $pattern) { |
| 6652 |
if (preg_match($pattern, $params['src'])) { |
| 6653 |
return; |
| 6654 |
} |
| 6655 |
} |
| 6656 |
throw new \Kibo\Phast\Exceptions\UnauthorizedException('Not allowed url: ' . $params['src']); |
| 6657 |
} |
| 6658 |
} |
| 6659 |
namespace Kibo\Phast\Services; |
| 6660 |
|
| 6661 |
class ServiceRequest |
| 6662 |
{ |
| 6663 |
const FORMAT_QUERY = 1; |
| 6664 |
const FORMAT_PATH = 2; |
| 6665 |
private static $defaultSerializationMode = self::FORMAT_PATH; |
| 6666 |
/** |
| 6667 |
* @var string |
| 6668 |
*/ |
| 6669 |
private static $propagatedSwitches = ''; |
| 6670 |
/** |
| 6671 |
* @var Switches |
| 6672 |
*/ |
| 6673 |
private static $switches; |
| 6674 |
/** |
| 6675 |
* @var string |
| 6676 |
*/ |
| 6677 |
private static $documentRequestId; |
| 6678 |
/** |
| 6679 |
* @var Request |
| 6680 |
*/ |
| 6681 |
private $httpRequest; |
| 6682 |
/** |
| 6683 |
* @var URL |
| 6684 |
*/ |
| 6685 |
private $url; |
| 6686 |
/** |
| 6687 |
* @var Query |
| 6688 |
*/ |
| 6689 |
private $query; |
| 6690 |
/** |
| 6691 |
* @var string |
| 6692 |
*/ |
| 6693 |
private $token; |
| 6694 |
/** |
| 6695 |
* @var bool |
| 6696 |
*/ |
| 6697 |
private $trusted = false; |
| 6698 |
public function __construct() |
| 6699 |
{ |
| 6700 |
if (!isset(self::$switches)) { |
| 6701 |
self::$switches = new \Kibo\Phast\Environment\Switches(); |
| 6702 |
} |
| 6703 |
$this->query = new \Kibo\Phast\ValueObjects\Query(); |
| 6704 |
} |
| 6705 |
public static function resetRequestState() |
| 6706 |
{ |
| 6707 |
self::$defaultSerializationMode = self::FORMAT_PATH; |
| 6708 |
self::$propagatedSwitches = ''; |
| 6709 |
self::$switches = null; |
| 6710 |
self::$documentRequestId = null; |
| 6711 |
} |
| 6712 |
public static function setDefaultSerializationMode($mode) |
| 6713 |
{ |
| 6714 |
self::$defaultSerializationMode = $mode; |
| 6715 |
} |
| 6716 |
public static function getDefaultSerializationMode() |
| 6717 |
{ |
| 6718 |
return self::$defaultSerializationMode; |
| 6719 |
} |
| 6720 |
public static function fromHTTPRequest(\Kibo\Phast\HTTP\Request $request) |
| 6721 |
{ |
| 6722 |
$instance = new self(); |
| 6723 |
self::$switches = new \Kibo\Phast\Environment\Switches(); |
| 6724 |
$instance->httpRequest = $request; |
| 6725 |
if ($request->getCookie('phast')) { |
| 6726 |
self::$switches = \Kibo\Phast\Environment\Switches::fromString($request->getCookie('phast')); |
| 6727 |
} |
| 6728 |
if ($service = self::getRewrittenService($request)) { |
| 6729 |
$instance->query = \Kibo\Phast\ValueObjects\Query::fromAssoc(['service' => $service, 'src' => $request->getAbsoluteURI()]); |
| 6730 |
$instance->trusted = true; |
| 6731 |
} else { |
| 6732 |
$query = $request->getQuery(); |
| 6733 |
if ($query->get('src')) { |
| 6734 |
$query->set('src', preg_replace('~^hxxp(?=s?://)~', 'http', $query->get('src'))); |
| 6735 |
} |
| 6736 |
$pathInfo = $request->getPathInfo(); |
| 6737 |
if ($pathParams = self::parseBase64PathInfo($pathInfo)) { |
| 6738 |
$query->update($pathParams); |
| 6739 |
} elseif ($pathInfo) { |
| 6740 |
$query->update(self::parsePathInfo($pathInfo)); |
| 6741 |
} |
| 6742 |
if ($token = $query->pop('token')) { |
| 6743 |
$instance->token = $token; |
| 6744 |
} |
| 6745 |
$instance->query = $query; |
| 6746 |
if ($query->get('phast')) { |
| 6747 |
self::$propagatedSwitches = $query->get('phast'); |
| 6748 |
$paramsSwitches = \Kibo\Phast\Environment\Switches::fromString($query->get('phast')); |
| 6749 |
self::$switches = self::$switches->merge($paramsSwitches); |
| 6750 |
} |
| 6751 |
if ($query->get('documentRequestId')) { |
| 6752 |
self::$documentRequestId = $query->get('documentRequestId'); |
| 6753 |
} else { |
| 6754 |
self::$documentRequestId = (string) mt_rand(0, 999999999); |
| 6755 |
} |
| 6756 |
} |
| 6757 |
return $instance; |
| 6758 |
} |
| 6759 |
public static function getRewrittenService(\Kibo\Phast\HTTP\Request $request) |
| 6760 |
{ |
| 6761 |
if ($service = $request->getEnvValue('REDIRECT_PHAST_SERVICE')) { |
| 6762 |
return $service; |
| 6763 |
} |
| 6764 |
if ($service = $request->getEnvValue('PHAST_SERVICE')) { |
| 6765 |
return $service; |
| 6766 |
} |
| 6767 |
return null; |
| 6768 |
} |
| 6769 |
public function hasRequestSwitchesSet() |
| 6770 |
{ |
| 6771 |
return !empty(self::$propagatedSwitches); |
| 6772 |
} |
| 6773 |
/** |
| 6774 |
* @return Switches |
| 6775 |
*/ |
| 6776 |
public function getSwitches() |
| 6777 |
{ |
| 6778 |
return self::$switches; |
| 6779 |
} |
| 6780 |
/** |
| 6781 |
* @return array |
| 6782 |
*/ |
| 6783 |
public function getParams() |
| 6784 |
{ |
| 6785 |
return $this->query->toAssoc(); |
| 6786 |
} |
| 6787 |
/** |
| 6788 |
* @return Query |
| 6789 |
*/ |
| 6790 |
public function getQuery() |
| 6791 |
{ |
| 6792 |
return $this->query; |
| 6793 |
} |
| 6794 |
/** |
| 6795 |
* @return Request |
| 6796 |
*/ |
| 6797 |
public function getHTTPRequest() |
| 6798 |
{ |
| 6799 |
return $this->httpRequest; |
| 6800 |
} |
| 6801 |
/** |
| 6802 |
* @return string |
| 6803 |
*/ |
| 6804 |
public function getDocumentRequestId() |
| 6805 |
{ |
| 6806 |
return self::$documentRequestId; |
| 6807 |
} |
| 6808 |
/** |
| 6809 |
* @param array $params |
| 6810 |
* @return ServiceRequest |
| 6811 |
*/ |
| 6812 |
public function withParams(array $params) |
| 6813 |
{ |
| 6814 |
$result = clone $this; |
| 6815 |
$result->query = \Kibo\Phast\ValueObjects\Query::fromAssoc($params); |
| 6816 |
return $result; |
| 6817 |
} |
| 6818 |
/** |
| 6819 |
* @param URL $url |
| 6820 |
* @return ServiceRequest |
| 6821 |
*/ |
| 6822 |
public function withUrl(\Kibo\Phast\ValueObjects\URL $url) |
| 6823 |
{ |
| 6824 |
$result = clone $this; |
| 6825 |
$result->url = $url; |
| 6826 |
return $result; |
| 6827 |
} |
| 6828 |
/** |
| 6829 |
* @param ServiceSignature $signature |
| 6830 |
* @return ServiceRequest |
| 6831 |
*/ |
| 6832 |
public function sign(\Kibo\Phast\Security\ServiceSignature $signature) |
| 6833 |
{ |
| 6834 |
$token = $signature->sign($this->getVerificationString()); |
| 6835 |
$result = clone $this; |
| 6836 |
$result->token = $token; |
| 6837 |
return $result; |
| 6838 |
} |
| 6839 |
/** |
| 6840 |
* @param ServiceSignature $signature |
| 6841 |
* @return bool |
| 6842 |
*/ |
| 6843 |
public function verify(\Kibo\Phast\Security\ServiceSignature $signature) |
| 6844 |
{ |
| 6845 |
return $this->trusted || $signature->verify($this->token, $this->getVerificationString()) || $signature->verify($this->token, $this->getVerificationStringWithoutStemSuffix()); |
| 6846 |
} |
| 6847 |
private static function parsePathInfo($string) |
| 6848 |
{ |
| 6849 |
$values = new \Kibo\Phast\ValueObjects\Query(); |
| 6850 |
$parts = explode('/', $string); |
| 6851 |
foreach ($parts as $part) { |
| 6852 |
if ($part === '') { |
| 6853 |
continue; |
| 6854 |
} |
| 6855 |
$pair = explode('=', $part); |
| 6856 |
if (isset($pair[1])) { |
| 6857 |
$values->set($pair[0], self::decodeSingleValue($pair[1])); |
| 6858 |
} elseif (preg_match('/^__p__(@[1-9][0-9]*x)?\\./', $pair[0], $match)) { |
| 6859 |
if (!empty($match[1]) && $values->has('src')) { |
| 6860 |
$values->set('src', self::appendStemSuffix($values->get('src'), $match[1])); |
| 6861 |
} |
| 6862 |
break; |
| 6863 |
} else { |
| 6864 |
$values->set('src', self::decodeSingleValue($pair[0])); |
| 6865 |
} |
| 6866 |
} |
| 6867 |
return $values; |
| 6868 |
} |
| 6869 |
private static function decodeSingleValue($value) |
| 6870 |
{ |
| 6871 |
return urldecode(str_replace('-', '%', $value)); |
| 6872 |
} |
| 6873 |
private static function appendStemSuffix($src, $suffix) |
| 6874 |
{ |
| 6875 |
$url = \Kibo\Phast\ValueObjects\URL::fromString($src); |
| 6876 |
$path = preg_replace_callback('/\\.\\w+$/', function ($match) use($suffix) { |
| 6877 |
return $suffix . $match[0]; |
| 6878 |
}, $url->getPath()); |
| 6879 |
return $url->withPath($path)->toString(); |
| 6880 |
} |
| 6881 |
private static function parseBase64PathInfo($string) |
| 6882 |
{ |
| 6883 |
if (!preg_match('~^((/[a-z0-9_-]+)+)\\.q\\.js$~i', $string, $match)) { |
| 6884 |
return null; |
| 6885 |
} |
| 6886 |
$data = str_replace('/', '', $match[1]); |
| 6887 |
return \Kibo\Phast\ValueObjects\Query::fromString(\Kibo\Phast\Common\Base64url::decode($data)); |
| 6888 |
} |
| 6889 |
/** |
| 6890 |
* @param callable $paramsFilter |
| 6891 |
* @return string |
| 6892 |
*/ |
| 6893 |
private function getVerificationString($paramsFilter = null) |
| 6894 |
{ |
| 6895 |
$params = $this->getAllParams(); |
| 6896 |
if ($paramsFilter) { |
| 6897 |
$params = $paramsFilter($params); |
| 6898 |
} |
| 6899 |
ksort($params); |
| 6900 |
return http_build_query($params); |
| 6901 |
} |
| 6902 |
private function getVerificationStringWithoutStemSuffix() |
| 6903 |
{ |
| 6904 |
return $this->getVerificationString(function ($params) { |
| 6905 |
if (isset($params['src'])) { |
| 6906 |
$params['src'] = $this->stripStemSuffix($params['src']); |
| 6907 |
} |
| 6908 |
return $params; |
| 6909 |
}); |
| 6910 |
} |
| 6911 |
private function stripStemSuffix($src) |
| 6912 |
{ |
| 6913 |
$url = \Kibo\Phast\ValueObjects\URL::fromString($src); |
| 6914 |
$path = preg_replace('/@[1-9][0-9]*x(?=\\.\\w+$)/', '', $url->getPath()); |
| 6915 |
return $url->withPath($path)->toString(); |
| 6916 |
} |
| 6917 |
public function serialize($format = null) |
| 6918 |
{ |
| 6919 |
$params = $this->getAllParams(); |
| 6920 |
if ($this->token) { |
| 6921 |
$params['token'] = $this->token; |
| 6922 |
} |
| 6923 |
if (is_null($format)) { |
| 6924 |
$format = self::$defaultSerializationMode; |
| 6925 |
} |
| 6926 |
if ($format == self::FORMAT_PATH) { |
| 6927 |
return $this->serializeToPathFormat($params); |
| 6928 |
} |
| 6929 |
return $this->serializeToQueryFormat($params); |
| 6930 |
} |
| 6931 |
private function getAllParams() |
| 6932 |
{ |
| 6933 |
$urlParams = []; |
| 6934 |
if ($this->url) { |
| 6935 |
parse_str($this->url->getQuery(), $urlParams); |
| 6936 |
} |
| 6937 |
$params = array_merge($urlParams, $this->query->toAssoc()); |
| 6938 |
if (!empty(self::$propagatedSwitches)) { |
| 6939 |
$params['phast'] = self::$propagatedSwitches; |
| 6940 |
} |
| 6941 |
if (self::$switches->isOn(\Kibo\Phast\Environment\Switches::SWITCH_DIAGNOSTICS)) { |
| 6942 |
$params['documentRequestId'] = self::$documentRequestId; |
| 6943 |
} |
| 6944 |
return $params; |
| 6945 |
} |
| 6946 |
private function serializeToQueryFormat(array $params) |
| 6947 |
{ |
| 6948 |
$encoded = http_build_query($params); |
| 6949 |
if (!isset($this->url)) { |
| 6950 |
return $encoded; |
| 6951 |
} |
| 6952 |
$serialized = preg_replace('~\\?.*~', '', (string) $this->url); |
| 6953 |
if (self::$defaultSerializationMode === self::FORMAT_PATH && !preg_match('~/$~', $serialized)) { |
| 6954 |
$serialized .= '/' . $this->getDummyFilename($params); |
| 6955 |
} |
| 6956 |
return $serialized . '?' . $encoded; |
| 6957 |
} |
| 6958 |
/** @return string */ |
| 6959 |
private function serializeToPathFormat(array $params) |
| 6960 |
{ |
| 6961 |
$encodedSrc = null; |
| 6962 |
$values = []; |
| 6963 |
foreach (explode('&', http_build_query($params)) as $element) { |
| 6964 |
list($key, $value) = explode('=', $element, 2); |
| 6965 |
$encodedValue = str_replace(['-', '%'], ['%2D', '-'], $value); |
| 6966 |
if ($key == 'src') { |
| 6967 |
$encodedSrc = $encodedValue; |
| 6968 |
} else { |
| 6969 |
$values[] = $key . '=' . $encodedValue; |
| 6970 |
} |
| 6971 |
} |
| 6972 |
if ($encodedSrc) { |
| 6973 |
array_unshift($values, $encodedSrc); |
| 6974 |
} |
| 6975 |
$params = '/' . join('/', $values) . '/' . $this->getDummyFilename($params); |
| 6976 |
if (isset($this->url)) { |
| 6977 |
return preg_replace(['~\\?.*~', '~/$~'], '', $this->url) . $params; |
| 6978 |
} |
| 6979 |
return $params; |
| 6980 |
} |
| 6981 |
private function getDummyFilename(array $params) |
| 6982 |
{ |
| 6983 |
return '__p__.' . $this->getDummyExtension($params); |
| 6984 |
} |
| 6985 |
private function getDummyExtension(array $params) |
| 6986 |
{ |
| 6987 |
$default = 'js'; |
| 6988 |
if (empty($params['src'])) { |
| 6989 |
return $default; |
| 6990 |
} |
| 6991 |
$url = \Kibo\Phast\ValueObjects\URL::fromString($params['src']); |
| 6992 |
$ext = strtolower($url->getExtension()); |
| 6993 |
if (preg_match('/^(jpe?g|gif|png|js|css)$/', $ext)) { |
| 6994 |
return $ext; |
| 6995 |
} |
| 6996 |
return $default; |
| 6997 |
} |
| 6998 |
} |
| 6999 |
namespace Kibo\Phast\Security; |
| 7000 |
|
| 7001 |
class ServiceSignature |
| 7002 |
{ |
| 7003 |
const AUTO_TOKEN_SIZE = 128; |
| 7004 |
const SIGNATURE_LENGTH = 16; |
| 7005 |
/** |
| 7006 |
* @var Cache |
| 7007 |
*/ |
| 7008 |
private $cache; |
| 7009 |
/** |
| 7010 |
* @var array |
| 7011 |
*/ |
| 7012 |
private $identities; |
| 7013 |
/** |
| 7014 |
* ServiceSignature constructor. |
| 7015 |
* |
| 7016 |
* @param Cache $cache |
| 7017 |
*/ |
| 7018 |
public function __construct(\Kibo\Phast\Cache\Cache $cache) |
| 7019 |
{ |
| 7020 |
$this->cache = $cache; |
| 7021 |
} |
| 7022 |
/** |
| 7023 |
* @param string|array $identities |
| 7024 |
*/ |
| 7025 |
public function setIdentities($identities) |
| 7026 |
{ |
| 7027 |
if (is_string($identities)) { |
| 7028 |
$this->identities = ['' => $identities]; |
| 7029 |
} else { |
| 7030 |
$this->identities = $identities; |
| 7031 |
} |
| 7032 |
} |
| 7033 |
/** |
| 7034 |
* @return string |
| 7035 |
*/ |
| 7036 |
public function getCacheSalt() |
| 7037 |
{ |
| 7038 |
$identities = $this->getIdentities(); |
| 7039 |
return md5(join('=>', array_merge(array_keys($identities), array_values($identities)))); |
| 7040 |
} |
| 7041 |
public function sign($value) |
| 7042 |
{ |
| 7043 |
$identities = $this->getIdentities(); |
| 7044 |
$users = array_keys($identities); |
| 7045 |
list($user, $token) = [array_shift($users), array_shift($identities)]; |
| 7046 |
return $user . substr(md5($token . $value), 0, self::SIGNATURE_LENGTH); |
| 7047 |
} |
| 7048 |
public function verify($signature, $value) |
| 7049 |
{ |
| 7050 |
$user = substr($signature, 0, -self::SIGNATURE_LENGTH); |
| 7051 |
$identities = $this->getIdentities(); |
| 7052 |
if (!isset($identities[$user])) { |
| 7053 |
return false; |
| 7054 |
} |
| 7055 |
$token = $identities[$user]; |
| 7056 |
$signer = new self($this->cache); |
| 7057 |
$signer->setIdentities([$user => $token]); |
| 7058 |
return $signature === $signer->sign($value); |
| 7059 |
} |
| 7060 |
public static function generateToken() |
| 7061 |
{ |
| 7062 |
$token = ''; |
| 7063 |
for ($i = 0; $i < self::AUTO_TOKEN_SIZE; $i++) { |
| 7064 |
$token .= chr(mt_rand(33, 126)); |
| 7065 |
} |
| 7066 |
return $token; |
| 7067 |
} |
| 7068 |
private function getIdentities() |
| 7069 |
{ |
| 7070 |
if (!isset($this->identities)) { |
| 7071 |
$token = $this->cache->get('security-token', function () { |
| 7072 |
return self::generateToken(); |
| 7073 |
}); |
| 7074 |
$this->identities = ['' => $token]; |
| 7075 |
} |
| 7076 |
return $this->identities; |
| 7077 |
} |
| 7078 |
} |
| 7079 |
namespace Kibo\Phast\Security; |
| 7080 |
|
| 7081 |
class ServiceSignatureFactory |
| 7082 |
{ |
| 7083 |
public function make(array $config) |
| 7084 |
{ |
| 7085 |
$cache = new \Kibo\Phast\Cache\File\Cache($config['cache'], 'signature'); |
| 7086 |
$signature = new \Kibo\Phast\Security\ServiceSignature($cache); |
| 7087 |
if (isset($config['securityToken'])) { |
| 7088 |
$signature->setIdentities($config['securityToken']); |
| 7089 |
} |
| 7090 |
return $signature; |
| 7091 |
} |
| 7092 |
} |
| 7093 |
namespace Kibo\Phast\Exceptions; |
| 7094 |
|
| 7095 |
class LogicException extends \LogicException |
| 7096 |
{ |
| 7097 |
} |
| 7098 |
namespace Kibo\Phast\Exceptions; |
| 7099 |
|
| 7100 |
class RuntimeException extends \RuntimeException |
| 7101 |
{ |
| 7102 |
} |
| 7103 |
namespace Kibo\Phast\Exceptions; |
| 7104 |
|
| 7105 |
class CachedExceptionException extends \Exception |
| 7106 |
{ |
| 7107 |
} |
| 7108 |
namespace Kibo\Phast\Exceptions; |
| 7109 |
|
| 7110 |
class ItemNotFoundException extends \Exception |
| 7111 |
{ |
| 7112 |
/** |
| 7113 |
* @var URL |
| 7114 |
*/ |
| 7115 |
private $url; |
| 7116 |
public function __construct($message = '', $code = 0, \Throwable $previous = null, \Kibo\Phast\ValueObjects\URL $failed = null) |
| 7117 |
{ |
| 7118 |
parent::__construct($message, $code, $previous); |
| 7119 |
$this->url = $failed; |
| 7120 |
} |
| 7121 |
/** |
| 7122 |
* @return URL |
| 7123 |
*/ |
| 7124 |
public function getUrl() |
| 7125 |
{ |
| 7126 |
return $this->url; |
| 7127 |
} |
| 7128 |
} |
| 7129 |
namespace Kibo\Phast\Exceptions; |
| 7130 |
|
| 7131 |
class UnauthorizedException extends \Exception |
| 7132 |
{ |
| 7133 |
} |
| 7134 |
namespace Kibo\Phast\Exceptions; |
| 7135 |
|
| 7136 |
class UndefinedObjectifiedFunction extends \RuntimeException |
| 7137 |
{ |
| 7138 |
} |
| 7139 |
namespace Kibo\PhastPlugins\SDK; |
| 7140 |
|
| 7141 |
/** |
| 7142 |
* Provides commonly needed URLs |
| 7143 |
* |
| 7144 |
* Interface HostURLs |
| 7145 |
* @see URL |
| 7146 |
*/ |
| 7147 |
interface HostURLs |
| 7148 |
{ |
| 7149 |
/** |
| 7150 |
* The URL at which static resource (JS, CSS, IMG) |
| 7151 |
* optimizations reside |
| 7152 |
* |
| 7153 |
* @return URL |
| 7154 |
*/ |
| 7155 |
public function getServicesURL(); |
| 7156 |
/** |
| 7157 |
* The full URL of the root of the current site |
| 7158 |
* |
| 7159 |
* @return URL |
| 7160 |
*/ |
| 7161 |
public function getSiteURL(); |
| 7162 |
/** |
| 7163 |
* The CDN equivalent of a specified URL |
| 7164 |
* |
| 7165 |
* @return URL |
| 7166 |
*/ |
| 7167 |
public function getCDNURL(\Kibo\Phast\ValueObjects\URL $url); |
| 7168 |
/** |
| 7169 |
* URL of the admin page at which |
| 7170 |
* the plugin's settings are located |
| 7171 |
* |
| 7172 |
* @return URL |
| 7173 |
*/ |
| 7174 |
public function getSettingsURL(); |
| 7175 |
/** |
| 7176 |
* URL for admin panel AJAX communication |
| 7177 |
* |
| 7178 |
* @return URL |
| 7179 |
*/ |
| 7180 |
public function getAJAXEndPoint(); |
| 7181 |
/** |
| 7182 |
* A URL to a publicly available image. |
| 7183 |
* |
| 7184 |
* @return URL |
| 7185 |
*/ |
| 7186 |
public function getTestImageURL(); |
| 7187 |
} |
| 7188 |
namespace Kibo\PhastPlugins\SDK; |
| 7189 |
|
| 7190 |
/** |
| 7191 |
* Services container for the Phast Plugins Services SDK |
| 7192 |
* |
| 7193 |
* Class SDK |
| 7194 |
*/ |
| 7195 |
class ServiceSDK |
| 7196 |
{ |
| 7197 |
/** |
| 7198 |
* @var ServiceHost |
| 7199 |
*/ |
| 7200 |
protected $host; |
| 7201 |
/** |
| 7202 |
* @var EnvironmentIdentifier |
| 7203 |
*/ |
| 7204 |
private $environmentIdentifier; |
| 7205 |
public function __construct(\Kibo\PhastPlugins\SDK\ServiceHost $host) |
| 7206 |
{ |
| 7207 |
$this->host = $host; |
| 7208 |
$this->environmentIdentifier = new \Kibo\PhastPlugins\SDK\Configuration\EnvironmentIdentifier(); |
| 7209 |
} |
| 7210 |
public function getServiceAPI() |
| 7211 |
{ |
| 7212 |
return new \Kibo\PhastPlugins\SDK\APIs\Service($this->getServiceConfiguration()); |
| 7213 |
} |
| 7214 |
public function getServiceConfiguration() |
| 7215 |
{ |
| 7216 |
return new \Kibo\PhastPlugins\SDK\Configuration\ServiceConfiguration($this->getPHPFilesServiceConfigurationRepository(), $this->getEnvironmentIdentifier(), $this->getCacheRootManager(), [$this->host, 'onServiceConfigurationLoad']); |
| 7217 |
} |
| 7218 |
public function getCacheRootManager() |
| 7219 |
{ |
| 7220 |
return new \Kibo\PhastPlugins\SDK\Caching\CacheRootManager($this->host->getCacheRootCandidatesProvider()); |
| 7221 |
} |
| 7222 |
/** |
| 7223 |
* Returns a default implementation of the |
| 7224 |
* ServiceConfigurationRepository interface |
| 7225 |
* |
| 7226 |
* @see ServiceConfigurationRepository |
| 7227 |
* @return PHPFilesServiceConfigurationRepository |
| 7228 |
*/ |
| 7229 |
public function getPHPFilesServiceConfigurationRepository() |
| 7230 |
{ |
| 7231 |
return new \Kibo\PhastPlugins\SDK\Configuration\PHPFilesServiceConfigurationRepository($this->getCacheRootManager()); |
| 7232 |
} |
| 7233 |
public function getEnvironmentIdentifier() |
| 7234 |
{ |
| 7235 |
return $this->environmentIdentifier; |
| 7236 |
} |
| 7237 |
} |
| 7238 |
namespace Kibo\PhastPlugins\SDK; |
| 7239 |
|
| 7240 |
/** |
| 7241 |
* Services container for the Phast Plugins SDK |
| 7242 |
* |
| 7243 |
* Class SDK |
| 7244 |
*/ |
| 7245 |
class SDK extends \Kibo\PhastPlugins\SDK\ServiceSDK |
| 7246 |
{ |
| 7247 |
/** |
| 7248 |
* @var PluginHost |
| 7249 |
*/ |
| 7250 |
protected $host; |
| 7251 |
/** |
| 7252 |
* SDK constructor. |
| 7253 |
* @param PluginHost $host |
| 7254 |
*/ |
| 7255 |
public function __construct(\Kibo\PhastPlugins\SDK\PluginHost $host) |
| 7256 |
{ |
| 7257 |
parent::__construct($host); |
| 7258 |
} |
| 7259 |
/** |
| 7260 |
* The current SDK version |
| 7261 |
* |
| 7262 |
* @return string |
| 7263 |
*/ |
| 7264 |
public function getSDKVersion() |
| 7265 |
{ |
| 7266 |
return '8'; |
| 7267 |
} |
| 7268 |
/** |
| 7269 |
* The current plugin version. |
| 7270 |
* Composed from the host plugin name, |
| 7271 |
* the host plugin version |
| 7272 |
* and the SDK version |
| 7273 |
* |
| 7274 |
* @return string |
| 7275 |
*/ |
| 7276 |
public function getPluginVersion() |
| 7277 |
{ |
| 7278 |
return join('-', [$this->host->getPluginHostName(), $this->host->getPluginHostVersion(), $this->getSDKVersion()]); |
| 7279 |
} |
| 7280 |
/** |
| 7281 |
* @return Phast |
| 7282 |
*/ |
| 7283 |
public function getPhastAPI() |
| 7284 |
{ |
| 7285 |
return new \Kibo\PhastPlugins\SDK\APIs\Phast($this->getPhastConfiguration()); |
| 7286 |
} |
| 7287 |
public function getAdminPanel() |
| 7288 |
{ |
| 7289 |
return new \Kibo\PhastPlugins\SDK\AdminPanel\AdminPanel($this->host->getPhastUser(), $this->host->getHostURLs()->getAJAXEndPoint(), $this->getAdminPanelData(), $this->getTranslationsManager(), $this->host->isDev()); |
| 7290 |
} |
| 7291 |
public function getAJAXRequestsDispatcher() |
| 7292 |
{ |
| 7293 |
return new \Kibo\PhastPlugins\SDK\AJAX\RequestsDispatcher($this->host->getPhastUser(), $this); |
| 7294 |
} |
| 7295 |
public function getAdminPanelData() |
| 7296 |
{ |
| 7297 |
return new \Kibo\PhastPlugins\SDK\AdminPanel\AdminPanelData($this->getPluginConfiguration(), $this->getServiceConfigurationGenerator(), $this->getPhastConfiguration(), $this->getCacheRootManager(), $this->host); |
| 7298 |
} |
| 7299 |
public function getInstallNotice() |
| 7300 |
{ |
| 7301 |
return new \Kibo\PhastPlugins\SDK\AdminPanel\InstallNotice($this->getPluginConfiguration(), $this->host->getInstallNoticeRenderer(), $this->getTranslationsManager(), $this->host->getHostURLs()->getSettingsURL(), $this->host->getHostURLs()->getAJAXEndPoint()); |
| 7302 |
} |
| 7303 |
public function getPluginConfiguration() |
| 7304 |
{ |
| 7305 |
return new \Kibo\PhastPlugins\SDK\Configuration\PluginConfiguration($this->getPluginConfigurationRepository(), $this->getServiceConfigurationGenerator(), $this->getCacheRootManager(), $this->host->getPhastUser(), $this->host->getNonceChecker()); |
| 7306 |
} |
| 7307 |
public function getPhastConfiguration() |
| 7308 |
{ |
| 7309 |
return new \Kibo\PhastPlugins\SDK\Configuration\PhastConfiguration($this->getServiceConfigurationGenerator(), $this->getServiceConfiguration(), $this->getPluginConfiguration(), [$this->host, 'onPhastConfigurationLoad']); |
| 7310 |
} |
| 7311 |
public function getAutoConfiguration() |
| 7312 |
{ |
| 7313 |
return new \Kibo\PhastPlugins\SDK\Configuration\AutoConfiguration($this->getPluginConfiguration(), $this->getPhastConfiguration(), $this->host->getHostURLs()->getServicesURL(), $this->host->getHostURLs()->getTestImageURL(), $this->host->getNonce(), $this->host->getHostURLs()->getAJAXEndPoint()); |
| 7314 |
} |
| 7315 |
public function getTranslationsManager() |
| 7316 |
{ |
| 7317 |
return new \Kibo\PhastPlugins\SDK\AdminPanel\TranslationsManager($this->host->getLocale(), $this->host->getPluginName()); |
| 7318 |
} |
| 7319 |
private function getServiceConfigurationGenerator() |
| 7320 |
{ |
| 7321 |
return new \Kibo\PhastPlugins\SDK\Configuration\ServiceConfigurationGenerator($this->getPHPFilesServiceConfigurationRepository(), $this->getEnvironmentIdentifier(), $this->getPluginVersion(), $this->host->getHostURLs()); |
| 7322 |
} |
| 7323 |
public function updatePreviewCookie($enable = true) |
| 7324 |
{ |
| 7325 |
if (headers_sent()) { |
| 7326 |
return false; |
| 7327 |
} |
| 7328 |
$enabled = isset($_COOKIE['PHAST_PREVIEW']) && (bool) $_COOKIE['PHAST_PREVIEW']; |
| 7329 |
if (!$enabled && $enable) { |
| 7330 |
return setcookie('PHAST_PREVIEW', '1', 0, '/'); |
| 7331 |
} |
| 7332 |
if ($enabled && !$enable) { |
| 7333 |
return setcookie('PHAST_PREVIEW', '0', 0, '/'); |
| 7334 |
} |
| 7335 |
return true; |
| 7336 |
} |
| 7337 |
private function getPluginConfigurationRepository() |
| 7338 |
{ |
| 7339 |
return new \Kibo\PhastPlugins\SDK\Configuration\PluginConfigurationRepository($this->host->getKeyValueStore()); |
| 7340 |
} |
| 7341 |
} |
| 7342 |
namespace Kibo\PhastPlugins\SDK\AdminPanel; |
| 7343 |
|
| 7344 |
/** |
| 7345 |
* Represents the data that needs to be send |
| 7346 |
* to the plugin's admin panel |
| 7347 |
* |
| 7348 |
* Class AdminPanelData |
| 7349 |
*/ |
| 7350 |
class AdminPanelData |
| 7351 |
{ |
| 7352 |
/** |
| 7353 |
* @var PluginConfiguration |
| 7354 |
*/ |
| 7355 |
private $pluginConfig; |
| 7356 |
/** |
| 7357 |
* @var ServiceConfigurationGenerator |
| 7358 |
*/ |
| 7359 |
private $serviceConfigGenerator; |
| 7360 |
/** |
| 7361 |
* @var PhastConfiguration |
| 7362 |
*/ |
| 7363 |
private $phastConfig; |
| 7364 |
/** |
| 7365 |
* @var CacheRootManager |
| 7366 |
*/ |
| 7367 |
private $cacheRootManager; |
| 7368 |
/** |
| 7369 |
* @var PluginHost |
| 7370 |
*/ |
| 7371 |
private $host; |
| 7372 |
public function __construct(\Kibo\PhastPlugins\SDK\Configuration\PluginConfiguration $pluginConfig, \Kibo\PhastPlugins\SDK\Configuration\ServiceConfigurationGenerator $serviceConfigGenerator, \Kibo\PhastPlugins\SDK\Configuration\PhastConfiguration $phastConfig, \Kibo\PhastPlugins\SDK\Caching\CacheRootManager $cacheRootManager, \Kibo\PhastPlugins\SDK\PluginHost $host) |
| 7373 |
{ |
| 7374 |
$this->pluginConfig = $pluginConfig; |
| 7375 |
$this->serviceConfigGenerator = $serviceConfigGenerator; |
| 7376 |
$this->phastConfig = $phastConfig; |
| 7377 |
$this->cacheRootManager = $cacheRootManager; |
| 7378 |
$this->host = $host; |
| 7379 |
} |
| 7380 |
public function get() |
| 7381 |
{ |
| 7382 |
$siteUrl = $this->host->getHostURLs()->getSiteURL(); |
| 7383 |
$urlWithPhast = $this->addQueryParam($siteUrl, 'phast', 'phast'); |
| 7384 |
$urlWithoutPhast = $this->addQueryParam($siteUrl, 'phast', '-phast'); |
| 7385 |
$pageSpeedToolUrl = 'https://developers.google.com/speed/pagespeed/insights/?url='; |
| 7386 |
$errors = []; |
| 7387 |
if (!$this->cacheRootManager->hasCacheRoot()) { |
| 7388 |
$errors[] = ['type' => 'no-cache-root', 'params' => $this->cacheRootManager->getCacheRootCandidates()]; |
| 7389 |
} |
| 7390 |
if (!$this->serviceConfigGenerator->generateIfNotExists($this->pluginConfig)) { |
| 7391 |
$errors[] = ['type' => 'no-service-config', 'params' => $this->cacheRootManager->getCacheRootCandidates()]; |
| 7392 |
} |
| 7393 |
$warnings = []; |
| 7394 |
$api_client_warning = []; |
| 7395 |
$phast_config = $this->phastConfig->get(); |
| 7396 |
$diagnostics = new \Kibo\Phast\Diagnostics\SystemDiagnostics(); |
| 7397 |
foreach ($diagnostics->run($phast_config) as $status) { |
| 7398 |
if ($status->isAvailable()) { |
| 7399 |
continue; |
| 7400 |
} |
| 7401 |
$package = $status->getPackage(); |
| 7402 |
$type = $package->getType(); |
| 7403 |
if ($type == 'Cache') { |
| 7404 |
$errors[] = ['type' => 'cache', 'params' => [$status->getReason()]]; |
| 7405 |
} elseif ($type == 'ImageFilter') { |
| 7406 |
$name = substr($package->getNamespace(), strrpos($package->getNamespace(), '\\') + 1); |
| 7407 |
if ($name === 'ImageAPIClient') { |
| 7408 |
$api_client_warning[] = 'Image optimization API error: ' . $status->getReason(); |
| 7409 |
} else { |
| 7410 |
$warnings[] = $status->getReason(); |
| 7411 |
} |
| 7412 |
} |
| 7413 |
} |
| 7414 |
$phastpress_config = $this->pluginConfig->get(); |
| 7415 |
if ($phastpress_config['img-optimization-api']) { |
| 7416 |
$warnings = $api_client_warning; |
| 7417 |
} |
| 7418 |
$nonce = $this->host->getNonce(); |
| 7419 |
return ['config' => $phastpress_config, 'settingsStrings' => ['urlWithPhast' => $pageSpeedToolUrl . rawurlencode($urlWithPhast), 'urlWithoutPhast' => $pageSpeedToolUrl . rawurlencode($urlWithoutPhast), 'maxImageWidth' => 1920 * 2, 'maxImageHeight' => 1080 * 2], 'errors' => $errors, 'warnings' => $warnings, 'nonce' => $nonce->getValue(), 'nonceName' => $nonce->getFieldName(), 'pluginName' => $this->host->getPluginName(), 'pluginVersion' => $this->host->getPluginHostVersion()]; |
| 7420 |
} |
| 7421 |
private function addQueryParam(\Kibo\Phast\ValueObjects\URL $url, $key, $value) |
| 7422 |
{ |
| 7423 |
// TODO: Move this functionality to URL class |
| 7424 |
$urlStr = (string) $url; |
| 7425 |
$glue = strpos($urlStr, '?') === false ? '?' : '&'; |
| 7426 |
return $urlStr . $glue . $key . '=' . $value; |
| 7427 |
} |
| 7428 |
} |
| 7429 |
namespace Kibo\PhastPlugins\SDK\AdminPanel; |
| 7430 |
|
| 7431 |
/** |
| 7432 |
* Represents an installation notice |
| 7433 |
* displayed everywhere in the host system's admin panel |
| 7434 |
* upon plugin activation. |
| 7435 |
* |
| 7436 |
* Class InstallNotice |
| 7437 |
*/ |
| 7438 |
class InstallNotice |
| 7439 |
{ |
| 7440 |
/** |
| 7441 |
* @var PluginConfiguration |
| 7442 |
*/ |
| 7443 |
private $config; |
| 7444 |
/** |
| 7445 |
* @var InstallNoticeRenderer |
| 7446 |
*/ |
| 7447 |
private $renderer; |
| 7448 |
/** |
| 7449 |
* @var TranslationsManager |
| 7450 |
*/ |
| 7451 |
private $translations; |
| 7452 |
/** |
| 7453 |
* @var URL |
| 7454 |
*/ |
| 7455 |
private $settingsUrl; |
| 7456 |
/** |
| 7457 |
* @var URL |
| 7458 |
*/ |
| 7459 |
private $ajaxEntryPoint; |
| 7460 |
/** |
| 7461 |
* InstallNotice constructor. |
| 7462 |
* @param PluginConfiguration $config |
| 7463 |
* @param InstallNoticeRenderer $renderer |
| 7464 |
* @param TranslationsManager $translations |
| 7465 |
* @param URL $settingsUrl |
| 7466 |
* @param URL $ajaxEndPoint |
| 7467 |
*/ |
| 7468 |
public function __construct(\Kibo\PhastPlugins\SDK\Configuration\PluginConfiguration $config, \Kibo\PhastPlugins\SDK\AdminPanel\InstallNoticeRenderer $renderer, \Kibo\PhastPlugins\SDK\AdminPanel\TranslationsManager $translations, \Kibo\Phast\ValueObjects\URL $settingsUrl, \Kibo\Phast\ValueObjects\URL $ajaxEndPoint) |
| 7469 |
{ |
| 7470 |
$this->config = $config; |
| 7471 |
$this->renderer = $renderer; |
| 7472 |
$this->translations = $translations; |
| 7473 |
$this->settingsUrl = $settingsUrl; |
| 7474 |
$this->ajaxEntryPoint = $ajaxEndPoint; |
| 7475 |
} |
| 7476 |
/** |
| 7477 |
* @return string The HTML to render the notice |
| 7478 |
*/ |
| 7479 |
public function render() |
| 7480 |
{ |
| 7481 |
$display_message = $this->config->shouldShowActivationNotification(); |
| 7482 |
if (!$display_message) { |
| 7483 |
return ''; |
| 7484 |
} |
| 7485 |
$config = $this->config->get(); |
| 7486 |
if ($config['enabled'] && $config['admin-only']) { |
| 7487 |
$status = 'Backend.status.admin'; |
| 7488 |
} elseif ($config['enabled']) { |
| 7489 |
$status = 'Backend.status.on'; |
| 7490 |
} else { |
| 7491 |
$status = 'Backend.status.off'; |
| 7492 |
} |
| 7493 |
$message = $this->translations->get('Backend.install-notice', ['pluginState' => $this->translations->get($status), 'settingsUrl' => (string) $this->settingsUrl]); |
| 7494 |
$onCloseFunction = "\n function () {\n var data = new FormData();\n data.append('phast-plugins-action', 'dismiss-notice')\n var xhr = new XMLHttpRequest();\n xhr.open('POST', '{$this->ajaxEntryPoint}')\n xhr.send(data)\n }\n "; |
| 7495 |
return $this->renderer->render($message, $onCloseFunction); |
| 7496 |
} |
| 7497 |
} |
| 7498 |
namespace Kibo\PhastPlugins\SDK\AdminPanel; |
| 7499 |
|
| 7500 |
interface InstallNoticeRenderer |
| 7501 |
{ |
| 7502 |
/** |
| 7503 |
* Renders a system notice. |
| 7504 |
* |
| 7505 |
* @param string $notice The message to show in the notice |
| 7506 |
* @param string $onCloseJSFunction JavaScript function to call on the client when |
| 7507 |
* an event that closes the notice occurs. |
| 7508 |
* @return string HTML for the notice |
| 7509 |
* @see InstallNotice |
| 7510 |
*/ |
| 7511 |
public function render($notice, $onCloseJSFunction); |
| 7512 |
} |
| 7513 |
namespace Kibo\PhastPlugins\SDK\AdminPanel; |
| 7514 |
|
| 7515 |
/** |
| 7516 |
* Represents a nonce form field used XSS defense |
| 7517 |
* |
| 7518 |
* Class Nonce |
| 7519 |
*/ |
| 7520 |
class Nonce implements \JsonSerializable |
| 7521 |
{ |
| 7522 |
/** |
| 7523 |
* @var string |
| 7524 |
*/ |
| 7525 |
private $fieldName; |
| 7526 |
/** |
| 7527 |
* @var string |
| 7528 |
*/ |
| 7529 |
private $value; |
| 7530 |
private function __construct() |
| 7531 |
{ |
| 7532 |
} |
| 7533 |
/** |
| 7534 |
* @param string $fieldName The name of the field in the form |
| 7535 |
* @param string $value The value of the field |
| 7536 |
* @return Nonce |
| 7537 |
*/ |
| 7538 |
public static function make($fieldName, $value) |
| 7539 |
{ |
| 7540 |
$instance = new self(); |
| 7541 |
$instance->fieldName = $fieldName; |
| 7542 |
$instance->value = $value; |
| 7543 |
return $instance; |
| 7544 |
} |
| 7545 |
/** |
| 7546 |
* @return string |
| 7547 |
*/ |
| 7548 |
public function getFieldName() |
| 7549 |
{ |
| 7550 |
return $this->fieldName; |
| 7551 |
} |
| 7552 |
/** |
| 7553 |
* @return string |
| 7554 |
*/ |
| 7555 |
public function getValue() |
| 7556 |
{ |
| 7557 |
return $this->value; |
| 7558 |
} |
| 7559 |
public function jsonSerialize() |
| 7560 |
{ |
| 7561 |
return ['fieldName' => $this->fieldName, 'value' => $this->value]; |
| 7562 |
} |
| 7563 |
} |
| 7564 |
namespace Kibo\PhastPlugins\SDK\AdminPanel; |
| 7565 |
|
| 7566 |
/** |
| 7567 |
* Represents the admin panel used for plugin configuration. |
| 7568 |
* Use this class for rendering the admin panel of the plugin. |
| 7569 |
* |
| 7570 |
* Class AdminPanel |
| 7571 |
*/ |
| 7572 |
class AdminPanel |
| 7573 |
{ |
| 7574 |
/** |
| 7575 |
* @var PhastUser |
| 7576 |
*/ |
| 7577 |
private $user; |
| 7578 |
/** |
| 7579 |
* @var URL |
| 7580 |
*/ |
| 7581 |
private $ajaxEndPoint; |
| 7582 |
/** |
| 7583 |
* @var AdminPanelData |
| 7584 |
*/ |
| 7585 |
private $data; |
| 7586 |
/** |
| 7587 |
* @var TranslationsManager |
| 7588 |
*/ |
| 7589 |
private $translations; |
| 7590 |
private $isDev = 'prod'; |
| 7591 |
private $styles = array('prod' => array('app.css'), 'dev' => array()); |
| 7592 |
private $scripts = array('prod' => array('manifest.js', 'vendor.js', 'babel-polyfill.js', 'app.js'), 'dev' => array('http://localhost:25903/babel-polyfill.js', 'http://localhost:25903/app.js')); |
| 7593 |
/** |
| 7594 |
* AdminPanel constructor. |
| 7595 |
* @param PhastUser $user |
| 7596 |
* @param URL $ajaxEndPoint |
| 7597 |
* @param AdminPanelData $data |
| 7598 |
* @param TranslationsManager $translations |
| 7599 |
* @param bool $isDev |
| 7600 |
*/ |
| 7601 |
public function __construct(\Kibo\PhastPlugins\SDK\Security\PhastUser $user, \Kibo\Phast\ValueObjects\URL $ajaxEndPoint, \Kibo\PhastPlugins\SDK\AdminPanel\AdminPanelData $data, \Kibo\PhastPlugins\SDK\AdminPanel\TranslationsManager $translations, $isDev) |
| 7602 |
{ |
| 7603 |
$this->user = $user; |
| 7604 |
$this->ajaxEndPoint = $ajaxEndPoint; |
| 7605 |
$this->data = $data; |
| 7606 |
$this->translations = $translations; |
| 7607 |
$this->isDev = (bool) $isDev; |
| 7608 |
} |
| 7609 |
/** |
| 7610 |
* Returns the HTML needed to display the admin panel |
| 7611 |
* |
| 7612 |
* @return string |
| 7613 |
*/ |
| 7614 |
public function render() |
| 7615 |
{ |
| 7616 |
if (!$this->user->mayModifySettings()) { |
| 7617 |
return ''; |
| 7618 |
} |
| 7619 |
$id = 'phast-plugins-sdk-admin-panel'; |
| 7620 |
$template = $this->getResourcesString(); |
| 7621 |
$template .= sprintf(' |
| 7622 |
<div id="%1$s"></div> |
| 7623 |
<script> |
| 7624 |
try { |
| 7625 |
window.PHAST_PLUGINS_SDK_ADMIN_PANEL.apply(window, %2$s) |
| 7626 |
} catch (e) { |
| 7627 |
document.getElementById("%1$s").innerText = "Error: " + e.message |
| 7628 |
throw e |
| 7629 |
} |
| 7630 |
</script> |
| 7631 |
', $id, json_encode([$id, $this->ajaxEndPoint->toString(), $this->data->get(), $this->translations->getAll()])); |
| 7632 |
return $template; |
| 7633 |
} |
| 7634 |
private function getResourcesString() |
| 7635 |
{ |
| 7636 |
return $this->isDev ? $this->getDevResourcesString() : $this->getProdResources(); |
| 7637 |
} |
| 7638 |
private function getProdResources() |
| 7639 |
{ |
| 7640 |
$resources = ''; |
| 7641 |
$base = __DIR__ . '/static/'; |
| 7642 |
$cssBase = $base . 'css/'; |
| 7643 |
foreach ($this->styles['prod'] as $style) { |
| 7644 |
$resources .= '<style>' . file_get_contents($cssBase . $style) . '</style>'; |
| 7645 |
} |
| 7646 |
$jsBase = $base . 'js/'; |
| 7647 |
foreach ($this->scripts['prod'] as $script) { |
| 7648 |
$resources .= '<script>' . file_get_contents($jsBase . $script) . '</script>'; |
| 7649 |
} |
| 7650 |
return $resources; |
| 7651 |
} |
| 7652 |
private function getDevResourcesString() |
| 7653 |
{ |
| 7654 |
$resources = ''; |
| 7655 |
foreach ($this->styles['dev'] as $href) { |
| 7656 |
$resources .= "<link rel=\"stylesheet\" href=\"{$href}\">"; |
| 7657 |
} |
| 7658 |
foreach ($this->scripts['dev'] as $src) { |
| 7659 |
$resources .= "<script src=\"{$src}\"></script>"; |
| 7660 |
} |
| 7661 |
return $resources; |
| 7662 |
} |
| 7663 |
} |
| 7664 |
namespace Kibo\PhastPlugins\SDK\AdminPanel; |
| 7665 |
|
| 7666 |
class TranslationsManager |
| 7667 |
{ |
| 7668 |
const DEFAULT_LOCALE = 'en'; |
| 7669 |
/** |
| 7670 |
* @var string |
| 7671 |
*/ |
| 7672 |
private $locale; |
| 7673 |
/** |
| 7674 |
* @var string |
| 7675 |
*/ |
| 7676 |
private $pluginName; |
| 7677 |
/** |
| 7678 |
* @var string |
| 7679 |
*/ |
| 7680 |
private $languagesDir; |
| 7681 |
/** |
| 7682 |
* @var array |
| 7683 |
*/ |
| 7684 |
private $modules = array(); |
| 7685 |
public function __construct($locale, $pluginName) |
| 7686 |
{ |
| 7687 |
$data = \Kibo\PhastPlugins\SDK\Generated\Translations::DATA; |
| 7688 |
if (isset($data[$this->locale])) { |
| 7689 |
$this->locale = $locale; |
| 7690 |
} else { |
| 7691 |
$this->locale = self::DEFAULT_LOCALE; |
| 7692 |
} |
| 7693 |
$this->pluginName = $pluginName; |
| 7694 |
} |
| 7695 |
public function getAll() |
| 7696 |
{ |
| 7697 |
return array_merge(['plugin-name' => $this->pluginName], \Kibo\PhastPlugins\SDK\Generated\Translations::DATA[$this->locale]); |
| 7698 |
} |
| 7699 |
public function get($key, $interpolationArguments = array()) |
| 7700 |
{ |
| 7701 |
$keyParts = explode('.', $key); |
| 7702 |
$transArr = \Kibo\PhastPlugins\SDK\Generated\Translations::DATA[$this->locale]; |
| 7703 |
while (count($keyParts) > 0) { |
| 7704 |
$part = array_shift($keyParts); |
| 7705 |
if (!isset($transArr[$part])) { |
| 7706 |
return $key; |
| 7707 |
} |
| 7708 |
$transArr = $transArr[$part]; |
| 7709 |
} |
| 7710 |
if (is_string($transArr)) { |
| 7711 |
return $this->interpolate($transArr, $interpolationArguments); |
| 7712 |
} |
| 7713 |
return $key; |
| 7714 |
} |
| 7715 |
private function interpolate($string, $arguments) |
| 7716 |
{ |
| 7717 |
$keys = array_map(function ($str) { |
| 7718 |
return '{' . $str . '}'; |
| 7719 |
}, array_keys($arguments)); |
| 7720 |
$params = array_combine($keys, array_values($arguments)); |
| 7721 |
$params['@:plugin-name'] = $this->pluginName; |
| 7722 |
return strtr($string, $params); |
| 7723 |
} |
| 7724 |
} |
| 7725 |
namespace Kibo\PhastPlugins\SDK\Configuration; |
| 7726 |
|
| 7727 |
/** |
| 7728 |
* Represents the javascript used |
| 7729 |
* for auto-configuration done |
| 7730 |
* immediately after activation of the plugin. |
| 7731 |
* |
| 7732 |
* Class AutoConfiguration |
| 7733 |
*/ |
| 7734 |
class AutoConfiguration |
| 7735 |
{ |
| 7736 |
/** |
| 7737 |
* @var PluginConfiguration |
| 7738 |
*/ |
| 7739 |
private $pluginConfig; |
| 7740 |
/** |
| 7741 |
* @var PhastConfiguration |
| 7742 |
*/ |
| 7743 |
private $phastConfig; |
| 7744 |
/** |
| 7745 |
* @var URL |
| 7746 |
*/ |
| 7747 |
private $servicesUrl; |
| 7748 |
/** |
| 7749 |
* @var URL |
| 7750 |
*/ |
| 7751 |
private $testImageUrl; |
| 7752 |
/** |
| 7753 |
* @var Nonce |
| 7754 |
*/ |
| 7755 |
private $nonce; |
| 7756 |
/** |
| 7757 |
* @var URL |
| 7758 |
*/ |
| 7759 |
private $ajaxEndPoint; |
| 7760 |
/** |
| 7761 |
* AutoConfiguration constructor. |
| 7762 |
* @param PluginConfiguration $pluginConfig |
| 7763 |
* @param PhastConfiguration $phastConfig |
| 7764 |
* @param URL $servicesUrl |
| 7765 |
* @param URL $testImageUrl |
| 7766 |
* @param Nonce $nonce |
| 7767 |
* @param URL $ajaxEndPoint |
| 7768 |
*/ |
| 7769 |
public function __construct(\Kibo\PhastPlugins\SDK\Configuration\PluginConfiguration $pluginConfig, \Kibo\PhastPlugins\SDK\Configuration\PhastConfiguration $phastConfig, \Kibo\Phast\ValueObjects\URL $servicesUrl, \Kibo\Phast\ValueObjects\URL $testImageUrl, \Kibo\PhastPlugins\SDK\AdminPanel\Nonce $nonce, \Kibo\Phast\ValueObjects\URL $ajaxEndPoint) |
| 7770 |
{ |
| 7771 |
$this->pluginConfig = $pluginConfig; |
| 7772 |
$this->phastConfig = $phastConfig; |
| 7773 |
$this->servicesUrl = $servicesUrl; |
| 7774 |
$this->testImageUrl = $testImageUrl; |
| 7775 |
$this->nonce = $nonce; |
| 7776 |
$this->ajaxEndPoint = $ajaxEndPoint; |
| 7777 |
} |
| 7778 |
/** |
| 7779 |
* Returns the script that needs to rendered |
| 7780 |
* in order for the script to get executed. |
| 7781 |
* |
| 7782 |
* @return string |
| 7783 |
*/ |
| 7784 |
public function renderScript() |
| 7785 |
{ |
| 7786 |
if (!$this->pluginConfig->shouldAutoConfigure()) { |
| 7787 |
return ''; |
| 7788 |
} |
| 7789 |
$config = \Kibo\Phast\Environment\Configuration::fromDefaults()->withUserConfiguration(new \Kibo\Phast\Environment\Configuration($this->phastConfig->get()))->getRuntimeConfig()->toArray(); |
| 7790 |
$signature = (new \Kibo\Phast\Security\ServiceSignatureFactory())->make($config); |
| 7791 |
$service_image_url = (new \Kibo\Phast\Services\ServiceRequest())->withUrl($this->servicesUrl)->withParams(['service' => 'images', 'src' => (string) $this->testImageUrl])->sign($signature)->serialize(\Kibo\Phast\Services\ServiceRequest::FORMAT_PATH); |
| 7792 |
$nonce = json_encode($this->nonce); |
| 7793 |
return '<script>(function (imageUrl, nonce, ajaxEndPoint) {' . "var logPrefix=\"[Phast autoconfiguration]\";var imageRequest=new XMLHttpRequest;imageRequest.open(\"GET\",imageUrl);imageRequest.onload=function(){var a=imageRequest.status>=200&&imageRequest.status<300;console.log(logPrefix,\"Got status\",imageRequest.status,\"which is\",a?\"successful\":\"unsuccessful\");configureRequestsFormat(a)};imageRequest.onerror=function(){console.log(logPrefix,\"Got error\");configureRequestsFormat(false)};imageRequest.ontimeout=function(){console.log(logPrefix,\"Request timed out\");configureRequestsFormat(false)};console.log(logPrefix,\"Requesting testing image through Phast service\");console.log(logPrefix,\"URL:\",imageUrl);imageRequest.send();function configureRequestsFormat(b){console.log(logPrefix,\"Configuring Phast with path info\",b?\"on\":\"off\");var c=new FormData;c.append(\"phast-plugins-action\",\"save-settings\");c.append(\"phastpress-pathinfo-query-format\",b?\"on\":\"off\");c.append(nonce.fieldName,nonce.value);var d=new XMLHttpRequest;d.open(\"POST\",ajaxEndPoint);d.responseType=\"json\";d.addEventListener(\"load\",function(){var e=d.response;if(typeof e===\"object\"&&e[\"phast-success\"]===true){console.log(logPrefix,\"Successfully autoconfigured! Dispatching event!\");var f=new CustomEvent(\"phast-auto-config\",{detail:e[\"phast-data\"]});window.dispatchEvent(f)}});d.send(c)}\n" . "})('{$service_image_url}', {$nonce}, '{$this->ajaxEndPoint}')</script>"; |
| 7794 |
} |
| 7795 |
} |
| 7796 |
namespace Kibo\PhastPlugins\SDK\Configuration; |
| 7797 |
|
| 7798 |
class ServiceConfigurationGenerator |
| 7799 |
{ |
| 7800 |
/** |
| 7801 |
* @var ServiceConfigurationRepository |
| 7802 |
*/ |
| 7803 |
private $repository; |
| 7804 |
/** |
| 7805 |
* @var EnvironmentIdentifier |
| 7806 |
*/ |
| 7807 |
private $environmentIdentifier; |
| 7808 |
/** |
| 7809 |
* @var URL |
| 7810 |
*/ |
| 7811 |
private $servicesUrl; |
| 7812 |
/** |
| 7813 |
* @var URL |
| 7814 |
*/ |
| 7815 |
private $cdnServicesUrl; |
| 7816 |
/** |
| 7817 |
* @var string |
| 7818 |
*/ |
| 7819 |
private $pluginVersion; |
| 7820 |
/** |
| 7821 |
* @var string |
| 7822 |
*/ |
| 7823 |
private $cdnHost; |
| 7824 |
public function __construct(\Kibo\PhastPlugins\SDK\Configuration\ServiceConfigurationRepository $repository, \Kibo\PhastPlugins\SDK\Configuration\EnvironmentIdentifier $environmentIdentifier, $pluginVersion, \Kibo\PhastPlugins\SDK\HostURLs $hostUrls) |
| 7825 |
{ |
| 7826 |
$this->repository = $repository; |
| 7827 |
$this->environmentIdentifier = $environmentIdentifier; |
| 7828 |
$this->pluginVersion = $pluginVersion; |
| 7829 |
$this->servicesUrl = $hostUrls->getServicesURL(); |
| 7830 |
$this->cdnServicesUrl = $hostUrls->getCDNURL($hostUrls->getServicesURL()); |
| 7831 |
$this->cdnHost = $hostUrls->getCDNURL($hostUrls->getSiteURL())->getHost(); |
| 7832 |
} |
| 7833 |
public function generateIfNotExists(\Kibo\PhastPlugins\SDK\Configuration\PluginConfiguration $pluginConfig) |
| 7834 |
{ |
| 7835 |
if (!$this->repository->has()) { |
| 7836 |
return $this->generate($pluginConfig); |
| 7837 |
} |
| 7838 |
$config = $this->repository->get(); |
| 7839 |
$envId = $this->environmentIdentifier->getValue(); |
| 7840 |
if (empty($config['plugin_version']) || $config['plugin_version'] != $this->pluginVersion || empty($config['alternativeServicesUrls'][$envId]) || $config['alternativeServicesUrls'][$envId] != $this->getServicesURLString($pluginConfig)) { |
| 7841 |
return $this->generate($pluginConfig); |
| 7842 |
} |
| 7843 |
return true; |
| 7844 |
} |
| 7845 |
public function generate(\Kibo\PhastPlugins\SDK\Configuration\PluginConfiguration $pluginConfig) |
| 7846 |
{ |
| 7847 |
$previousConfig = $this->repository->get(); |
| 7848 |
$plugin_config = $pluginConfig->get(); |
| 7849 |
$plugin_version = $this->pluginVersion; |
| 7850 |
$config = ['plugin_version' => $plugin_version, 'servicesUrl' => $this->getServicesURLString($pluginConfig), 'securityToken' => empty($previousConfig['securityToken']) ? \Kibo\Phast\Security\ServiceSignature::generateToken() : $previousConfig['securityToken'], 'images' => ['filters' => [\Kibo\Phast\Filters\Image\ImageAPIClient\Filter::class => ['enabled' => $plugin_config['img-optimization-api'], 'plugin-version' => $plugin_version]]], 'styles' => ['filters' => [\Kibo\Phast\Filters\CSS\ImageURLRewriter\Filter::class => ['enabled' => $plugin_config['img-optimization-css']]]], 'serviceRequestFormat' => $plugin_config['pathinfo-query-format'] ? \Kibo\Phast\Services\ServiceRequest::FORMAT_PATH : \Kibo\Phast\Services\ServiceRequest::FORMAT_QUERY, 'compressServiceResponse' => isset($plugin_config['compress-service-response']) ? !!$plugin_config['compress-service-response'] : true, 'cdnHost' => $this->cdnHost]; |
| 7851 |
if (isset($previousConfig['alternativeServicesUrls'])) { |
| 7852 |
$config['alternativeServicesUrls'] = $previousConfig['alternativeServicesUrls']; |
| 7853 |
} else { |
| 7854 |
$config['alternativeServicesUrls'] = []; |
| 7855 |
} |
| 7856 |
$id = $this->environmentIdentifier->getValue(); |
| 7857 |
unset($config['alternativeServicesUrls'][$id]); |
| 7858 |
$config['alternativeServicesUrls'][$id] = $this->getServicesURLString($pluginConfig); |
| 7859 |
$config['alternativeServicesUrls'] = array_slice($config['alternativeServicesUrls'], -1000); |
| 7860 |
return $this->repository->store($config); |
| 7861 |
} |
| 7862 |
private function getServicesURLString(\Kibo\PhastPlugins\SDK\Configuration\PluginConfiguration $pluginConfig) |
| 7863 |
{ |
| 7864 |
$plugin_config = $pluginConfig->get(); |
| 7865 |
if ($plugin_config['pathinfo-query-format']) { |
| 7866 |
return (string) $this->cdnServicesUrl; |
| 7867 |
} |
| 7868 |
return (string) $this->servicesUrl; |
| 7869 |
} |
| 7870 |
} |
| 7871 |
namespace Kibo\PhastPlugins\SDK\Configuration; |
| 7872 |
|
| 7873 |
/** |
| 7874 |
* Represents the configuration |
| 7875 |
* that needs to be passed to be returned |
| 7876 |
* by the callback passed to |
| 7877 |
* \Kibo\Phast\PhastServices::serve() |
| 7878 |
* |
| 7879 |
* @see \Kibo\Phast\PhastServices::serve() |
| 7880 |
* Class ServiceConfiguration |
| 7881 |
*/ |
| 7882 |
class ServiceConfiguration |
| 7883 |
{ |
| 7884 |
/** |
| 7885 |
* @var ServiceConfigurationRepository |
| 7886 |
*/ |
| 7887 |
private $repository; |
| 7888 |
/** |
| 7889 |
* @var EnvironmentIdentifier |
| 7890 |
*/ |
| 7891 |
private $environmentIdentifier; |
| 7892 |
/** |
| 7893 |
* @var CacheRootManager |
| 7894 |
*/ |
| 7895 |
private $cacheRootManager; |
| 7896 |
/** |
| 7897 |
* @var callable |
| 7898 |
*/ |
| 7899 |
private $onLoadCb; |
| 7900 |
/** |
| 7901 |
* ServiceConfiguration constructor. |
| 7902 |
* @param ServiceConfigurationRepository $repository |
| 7903 |
* @param CacheRootManager $cacheRootManager |
| 7904 |
* @param callable $onLoadCb |
| 7905 |
*/ |
| 7906 |
public function __construct(\Kibo\PhastPlugins\SDK\Configuration\ServiceConfigurationRepository $repository, \Kibo\PhastPlugins\SDK\Configuration\EnvironmentIdentifier $environmentIdentifier, \Kibo\PhastPlugins\SDK\Caching\CacheRootManager $cacheRootManager, callable $onLoadCb) |
| 7907 |
{ |
| 7908 |
$this->repository = $repository; |
| 7909 |
$this->environmentIdentifier = $environmentIdentifier; |
| 7910 |
$this->cacheRootManager = $cacheRootManager; |
| 7911 |
$this->onLoadCb = $onLoadCb; |
| 7912 |
} |
| 7913 |
/** |
| 7914 |
* Returns the configuration as config |
| 7915 |
* |
| 7916 |
* @return array|bool|mixed |
| 7917 |
*/ |
| 7918 |
public function get() |
| 7919 |
{ |
| 7920 |
$config = $this->repository->get(); |
| 7921 |
$envId = $this->environmentIdentifier->getValue(); |
| 7922 |
if (isset($config['alternativeServicesUrls'][$envId])) { |
| 7923 |
$config['servicesUrl'] = $config['alternativeServicesUrls'][$envId]; |
| 7924 |
} |
| 7925 |
if (!empty($config['cdnHost'])) { |
| 7926 |
$config['retrieverMap'][$config['cdnHost']] = \Kibo\Phast\HTTP\Request::fromGlobals()->getDocumentRoot(); |
| 7927 |
} |
| 7928 |
$config['cache'] = ['cacheRoot' => $this->cacheRootManager->getCacheRoot()]; |
| 7929 |
$apiFilterName = \Kibo\Phast\Filters\Image\ImageAPIClient\Filter::class; |
| 7930 |
$api_enabled = $config['images']['filters'][$apiFilterName]['enabled']; |
| 7931 |
if (!$api_enabled) { |
| 7932 |
unset($config['images']['filters'][$apiFilterName]); |
| 7933 |
return call_user_func($this->onLoadCb, $config); |
| 7934 |
} |
| 7935 |
$config['images']['filters'][$apiFilterName]['host-name'] = $_SERVER['HTTP_HOST']; |
| 7936 |
$config['images']['filters'][$apiFilterName]['request-uri'] = $_SERVER['REQUEST_URI']; |
| 7937 |
$config['images']['filters'][$apiFilterName]['api-url'] = 'https://optimize.phast.io/?service=images'; |
| 7938 |
return call_user_func($this->onLoadCb, $config); |
| 7939 |
} |
| 7940 |
} |
| 7941 |
namespace Kibo\PhastPlugins\SDK\Configuration; |
| 7942 |
|
| 7943 |
class EnvironmentIdentifier |
| 7944 |
{ |
| 7945 |
private $value; |
| 7946 |
public function __construct() |
| 7947 |
{ |
| 7948 |
$this->value = sprintf('%s://%s%s', empty($_SERVER['HTTPS']) ? 'http' : 'https', empty($_SERVER['HTTP_HOST']) ? '' : $_SERVER['HTTP_HOST'], empty($_SERVER['SERVER_PORT']) ? '' : ":{$_SERVER['SERVER_PORT']}"); |
| 7949 |
} |
| 7950 |
public function getValue() |
| 7951 |
{ |
| 7952 |
return $this->value; |
| 7953 |
} |
| 7954 |
} |
| 7955 |
namespace Kibo\PhastPlugins\SDK\Configuration; |
| 7956 |
|
| 7957 |
/** |
| 7958 |
* Represents the configuration |
| 7959 |
* that needs to be passed to |
| 7960 |
* \Kibo\Phast\PhastDocumentFilters::deploy() |
| 7961 |
* and |
| 7962 |
* \Kibo\Phast\PhastDocumentFilters::apply() |
| 7963 |
* |
| 7964 |
* @see \Kibo\Phast\PhastDocumentFilters |
| 7965 |
* Class PhastConfiguration |
| 7966 |
*/ |
| 7967 |
class PhastConfiguration |
| 7968 |
{ |
| 7969 |
const SETTINGS_2_FILTERS = array('img-optimization-tags' => array(\Kibo\Phast\Filters\HTML\ImagesOptimizationService\Tags\Filter::class), 'img-optimization-css' => array(\Kibo\Phast\Filters\HTML\ImagesOptimizationService\CSS\Filter::class, \Kibo\Phast\Filters\CSS\ImageURLRewriter\Filter::class), 'img-lazy' => array(\Kibo\Phast\Filters\HTML\LazyImageLoading\Filter::class), 'css-optimization' => array(\Kibo\Phast\Filters\HTML\CSSInlining\Filter::class), 'scripts-defer' => array(\Kibo\Phast\Filters\HTML\ScriptsDeferring\Filter::class), 'scripts-proxy' => array(\Kibo\Phast\Filters\HTML\ScriptsProxyService\Filter::class), 'iframe-defer' => array(\Kibo\Phast\Filters\HTML\DelayedIFrameLoading\Filter::class), 'minify-html' => array(\Kibo\Phast\Filters\HTML\Minify\Filter::class), 'minify-inline-scripts' => array(\Kibo\Phast\Filters\HTML\MinifyScripts\Filter::class)); |
| 7970 |
/** |
| 7971 |
* @var ServiceConfigurationGenerator |
| 7972 |
*/ |
| 7973 |
private $serviceConfigGenerator; |
| 7974 |
/** |
| 7975 |
* @var ServiceConfiguration |
| 7976 |
*/ |
| 7977 |
private $serviceConfig; |
| 7978 |
/** |
| 7979 |
* @var PluginConfiguration |
| 7980 |
*/ |
| 7981 |
private $pluginConfig; |
| 7982 |
/** |
| 7983 |
* @var callable |
| 7984 |
*/ |
| 7985 |
private $onLoadCb; |
| 7986 |
/** |
| 7987 |
* PhastConfiguration constructor. |
| 7988 |
* @param ServiceConfigurationGenerator $serviceConfigGenerator |
| 7989 |
* @param ServiceConfiguration $serviceConfig |
| 7990 |
* @param PluginConfiguration $pluginConfig |
| 7991 |
* @param callable $onLoadCb |
| 7992 |
*/ |
| 7993 |
public function __construct(\Kibo\PhastPlugins\SDK\Configuration\ServiceConfigurationGenerator $serviceConfigGenerator, \Kibo\PhastPlugins\SDK\Configuration\ServiceConfiguration $serviceConfig, \Kibo\PhastPlugins\SDK\Configuration\PluginConfiguration $pluginConfig, callable $onLoadCb) |
| 7994 |
{ |
| 7995 |
$this->serviceConfigGenerator = $serviceConfigGenerator; |
| 7996 |
$this->serviceConfig = $serviceConfig; |
| 7997 |
$this->pluginConfig = $pluginConfig; |
| 7998 |
$this->onLoadCb = $onLoadCb; |
| 7999 |
} |
| 8000 |
/** |
| 8001 |
* Returns the configuration to use on full html documents as an array |
| 8002 |
* |
| 8003 |
* @return array|bool|mixed |
| 8004 |
*/ |
| 8005 |
public function getForDocuments() |
| 8006 |
{ |
| 8007 |
list($phastConfig, $pluginConfig) = $this->getPhastAndPluginConfigs(); |
| 8008 |
foreach (array_keys(self::SETTINGS_2_FILTERS) as $setting) { |
| 8009 |
$this->setSettingInPhastConfig($setting, $pluginConfig, $phastConfig); |
| 8010 |
} |
| 8011 |
return call_user_func($this->onLoadCb, $phastConfig); |
| 8012 |
} |
| 8013 |
public function getForHTMLSnippets() |
| 8014 |
{ |
| 8015 |
list($phastConfig, $pluginConfig) = $this->getPhastAndPluginConfigs(); |
| 8016 |
$defaultConfig = \Kibo\Phast\Environment\Configuration::fromDefaults()->toArray(); |
| 8017 |
$allFilters = array_keys($defaultConfig['documents']['filters']); |
| 8018 |
foreach ($allFilters as $filter) { |
| 8019 |
$phastConfig['documents']['filters'][$filter]['enabled'] = false; |
| 8020 |
} |
| 8021 |
$this->setSettingInPhastConfig('img-optimization-tags', $pluginConfig, $phastConfig); |
| 8022 |
$this->setSettingInPhastConfig('img-optimization-css', $pluginConfig, $phastConfig); |
| 8023 |
$this->setSettingInPhastConfig('img-lazy', $pluginConfig, $phastConfig); |
| 8024 |
$phastConfig['optimizeHTMLDocumentsOnly'] = false; |
| 8025 |
$phastConfig['outputServerSideStats'] = false; |
| 8026 |
return call_user_func($this->onLoadCb, $phastConfig); |
| 8027 |
} |
| 8028 |
private function getPhastAndPluginConfigs() |
| 8029 |
{ |
| 8030 |
// TODO: Optimize so we do not read from the service config file a bunch of times |
| 8031 |
$this->serviceConfigGenerator->generateIfNotExists($this->pluginConfig); |
| 8032 |
$pluginConfig = $this->pluginConfig->get(); |
| 8033 |
$phastConfig = $this->serviceConfig->get(); |
| 8034 |
$phastConfig['documents']['filters'] = []; |
| 8035 |
$phastConfig['switches']['phast'] = $phastConfig && $this->pluginConfig->shouldDeployFilters(); |
| 8036 |
return [$phastConfig, $pluginConfig]; |
| 8037 |
} |
| 8038 |
private function setSettingInPhastConfig($settingName, $pluginConfig, &$phastConfig) |
| 8039 |
{ |
| 8040 |
foreach (self::SETTINGS_2_FILTERS[$settingName] as $filterClass) { |
| 8041 |
if (!class_exists($filterClass)) { |
| 8042 |
throw new \LogicException("No such filter: {$filterClass}"); |
| 8043 |
} |
| 8044 |
if (strpos($filterClass, \Kibo\Phast\Filters\HTML::class . '\\') === 0) { |
| 8045 |
$object = 'documents'; |
| 8046 |
} elseif (strpos($filterClass, \Kibo\Phast\Filters\CSS::class . '\\') === 0) { |
| 8047 |
$object = 'styles'; |
| 8048 |
} else { |
| 8049 |
throw new \LogicException("Invalid filter namespace: {$filterClass}"); |
| 8050 |
} |
| 8051 |
$phastConfig[$object]['filters'][$filterClass] = ['enabled' => $settingName]; |
| 8052 |
$phastConfig['switches'][$settingName] = $pluginConfig[$settingName]; |
| 8053 |
} |
| 8054 |
} |
| 8055 |
/** |
| 8056 |
* Returns the configuration as an array |
| 8057 |
* |
| 8058 |
* @return array|bool|mixed |
| 8059 |
* @deprecated use PhastConfiguration::getForDocuments() |
| 8060 |
*/ |
| 8061 |
public function get() |
| 8062 |
{ |
| 8063 |
return $this->getForDocuments(); |
| 8064 |
} |
| 8065 |
} |
| 8066 |
namespace Kibo\PhastPlugins\SDK\Configuration; |
| 8067 |
|
| 8068 |
/** |
| 8069 |
* Manages serialization of the phast service configuration. |
| 8070 |
* Must be as fast as possible, having as little |
| 8071 |
* dependency on the host system as possible (ideally - none). |
| 8072 |
* |
| 8073 |
* Interface ServiceConfigurationRepository |
| 8074 |
*/ |
| 8075 |
interface ServiceConfigurationRepository |
| 8076 |
{ |
| 8077 |
/** |
| 8078 |
* Store the given config |
| 8079 |
* |
| 8080 |
* @param array $config |
| 8081 |
* @return bool TRUE on success, FALSE on failure |
| 8082 |
*/ |
| 8083 |
public function store(array $config); |
| 8084 |
/** |
| 8085 |
* Returns the previously stored config |
| 8086 |
* |
| 8087 |
* @return array|bool - The config on success or |
| 8088 |
* FALSE on failure or if no config has been stored |
| 8089 |
*/ |
| 8090 |
public function get(); |
| 8091 |
/** |
| 8092 |
* Tells whether a config has been previously stored |
| 8093 |
* |
| 8094 |
* @return bool |
| 8095 |
*/ |
| 8096 |
public function has(); |
| 8097 |
} |
| 8098 |
namespace Kibo\PhastPlugins\SDK\Configuration; |
| 8099 |
|
| 8100 |
class PluginConfigurationRepository |
| 8101 |
{ |
| 8102 |
/** |
| 8103 |
* @var KeyValueStore |
| 8104 |
*/ |
| 8105 |
private $store; |
| 8106 |
/** |
| 8107 |
* JSONKeyValueStore constructor. |
| 8108 |
* @param KeyValueStore $store |
| 8109 |
*/ |
| 8110 |
public function __construct(\Kibo\PhastPlugins\SDK\Configuration\KeyValueStore $store) |
| 8111 |
{ |
| 8112 |
$this->store = $store; |
| 8113 |
} |
| 8114 |
/** |
| 8115 |
* @param mixed $key |
| 8116 |
* @param null $default |
| 8117 |
* @return mixed|null |
| 8118 |
*/ |
| 8119 |
public function get($key, $default = null) |
| 8120 |
{ |
| 8121 |
$value = $this->store->get($key); |
| 8122 |
if (!is_string($value) || $value === 'null') { |
| 8123 |
return $default; |
| 8124 |
} |
| 8125 |
$deserialised = @json_decode($value, true); |
| 8126 |
if (is_null($deserialised)) { |
| 8127 |
return $default; |
| 8128 |
} |
| 8129 |
return $deserialised; |
| 8130 |
} |
| 8131 |
/** |
| 8132 |
* @param mixed $key |
| 8133 |
* @param mixed $value |
| 8134 |
*/ |
| 8135 |
public function set($key, $value) |
| 8136 |
{ |
| 8137 |
$this->store->set($key, json_encode($value)); |
| 8138 |
} |
| 8139 |
} |
| 8140 |
namespace Kibo\PhastPlugins\SDK\Configuration; |
| 8141 |
|
| 8142 |
/** |
| 8143 |
* A key-value store for use within the plugin's admin panel |
| 8144 |
* |
| 8145 |
* Interface KeyValueStore |
| 8146 |
*/ |
| 8147 |
interface KeyValueStore |
| 8148 |
{ |
| 8149 |
/** |
| 8150 |
* @param string $key |
| 8151 |
* @return string|null The previously stored value or |
| 8152 |
* null if there was no value stored for this key |
| 8153 |
*/ |
| 8154 |
public function get($key); |
| 8155 |
/** |
| 8156 |
* @param string $key |
| 8157 |
* @param string $value |
| 8158 |
* @return void |
| 8159 |
*/ |
| 8160 |
public function set($key, $value); |
| 8161 |
} |
| 8162 |
namespace Kibo\PhastPlugins\SDK\Configuration; |
| 8163 |
|
| 8164 |
/** |
| 8165 |
* Represents the configuration of the plugin |
| 8166 |
* |
| 8167 |
* Class PluginConfiguration |
| 8168 |
*/ |
| 8169 |
class PluginConfiguration |
| 8170 |
{ |
| 8171 |
const KEY_SETTINGS = 'settings'; |
| 8172 |
const KEY_ACTIVATION_NOTIFICATION = 'activation-notification'; |
| 8173 |
/** |
| 8174 |
* @var PluginConfigurationRepository |
| 8175 |
*/ |
| 8176 |
private $repo; |
| 8177 |
/** |
| 8178 |
* @var ServiceConfigurationGenerator |
| 8179 |
*/ |
| 8180 |
private $serviceConfigGenerator; |
| 8181 |
/** |
| 8182 |
* @var CacheRootManager |
| 8183 |
*/ |
| 8184 |
private $cacheRootManager; |
| 8185 |
/** |
| 8186 |
* @var PhastUser |
| 8187 |
*/ |
| 8188 |
private $user; |
| 8189 |
/** |
| 8190 |
* @var NonceChecker |
| 8191 |
*/ |
| 8192 |
private $nonceChecker; |
| 8193 |
/** |
| 8194 |
* PluginConfiguration constructor. |
| 8195 |
* @param PluginConfigurationRepository $repo |
| 8196 |
* @param ServiceConfigurationGenerator $serviceConfigGenerator |
| 8197 |
* @param CacheRootManager $cacheRootManager |
| 8198 |
* @param PhastUser $user |
| 8199 |
* @param NonceChecker $nonceChecker |
| 8200 |
*/ |
| 8201 |
public function __construct(\Kibo\PhastPlugins\SDK\Configuration\PluginConfigurationRepository $repo, \Kibo\PhastPlugins\SDK\Configuration\ServiceConfigurationGenerator $serviceConfigGenerator, \Kibo\PhastPlugins\SDK\Caching\CacheRootManager $cacheRootManager, \Kibo\PhastPlugins\SDK\Security\PhastUser $user, \Kibo\PhastPlugins\SDK\Security\NonceChecker $nonceChecker) |
| 8202 |
{ |
| 8203 |
$this->repo = $repo; |
| 8204 |
$this->serviceConfigGenerator = $serviceConfigGenerator; |
| 8205 |
$this->cacheRootManager = $cacheRootManager; |
| 8206 |
$this->user = $user; |
| 8207 |
$this->nonceChecker = $nonceChecker; |
| 8208 |
} |
| 8209 |
public function get() |
| 8210 |
{ |
| 8211 |
$userSettings = $this->repo->get(self::KEY_SETTINGS, []); |
| 8212 |
return array_merge($this->getDefaultAdminPanelSettings(), $userSettings); |
| 8213 |
} |
| 8214 |
public function save(array $newConfig) |
| 8215 |
{ |
| 8216 |
if (!$this->nonceChecker->checkNonce($newConfig)) { |
| 8217 |
return; |
| 8218 |
} |
| 8219 |
$keys = array_keys($this->getDefaultAdminPanelSettings()); |
| 8220 |
$settings = []; |
| 8221 |
foreach ($keys as $key) { |
| 8222 |
$newConfigKey = "phastpress-{$key}"; |
| 8223 |
if (!isset($newConfig[$newConfigKey])) { |
| 8224 |
continue; |
| 8225 |
} |
| 8226 |
if ($newConfig[$newConfigKey] == 'on') { |
| 8227 |
$settings[$key] = true; |
| 8228 |
} elseif ($newConfig[$newConfigKey] == 'off') { |
| 8229 |
$settings[$key] = false; |
| 8230 |
} |
| 8231 |
} |
| 8232 |
$this->update($settings); |
| 8233 |
} |
| 8234 |
public function update(array $settings) |
| 8235 |
{ |
| 8236 |
$this->repo->set(self::KEY_SETTINGS, array_merge($this->get(), $settings)); |
| 8237 |
$this->serviceConfigGenerator->generate($this); |
| 8238 |
} |
| 8239 |
public function shouldShowActivationNotification() |
| 8240 |
{ |
| 8241 |
return $this->repo->get(self::KEY_ACTIVATION_NOTIFICATION, true); |
| 8242 |
} |
| 8243 |
public function hideActivationNotification() |
| 8244 |
{ |
| 8245 |
$this->repo->set(self::KEY_ACTIVATION_NOTIFICATION, false); |
| 8246 |
} |
| 8247 |
public function shouldAutoConfigure() |
| 8248 |
{ |
| 8249 |
return !$this->repo->get(self::KEY_SETTINGS); |
| 8250 |
} |
| 8251 |
public function shouldDeployFilters() |
| 8252 |
{ |
| 8253 |
$plugin_config = $this->get(); |
| 8254 |
if (!$plugin_config['enabled']) { |
| 8255 |
return false; |
| 8256 |
} |
| 8257 |
if (!$plugin_config['admin-only']) { |
| 8258 |
return true; |
| 8259 |
} |
| 8260 |
return $this->user->seesPreviewMode(); |
| 8261 |
} |
| 8262 |
public function shouldDisplayFooter() |
| 8263 |
{ |
| 8264 |
return $this->get()['footer-link'] && $this->shouldDeployFilters(); |
| 8265 |
} |
| 8266 |
private function getDefaultAdminPanelSettings() |
| 8267 |
{ |
| 8268 |
return ['enabled' => true, 'admin-only' => false, 'pathinfo-query-format' => false, 'footer-link' => false, 'compress-service-response' => true, 'img-optimization-tags' => true, 'img-optimization-css' => true, 'img-optimization-api' => true, 'img-lazy' => true, 'css-optimization' => true, 'scripts-rearrangement' => false, 'scripts-defer' => true, 'scripts-proxy' => true, 'iframe-defer' => true, 'minify-html' => true, 'minify-inline-scripts' => true]; |
| 8269 |
} |
| 8270 |
} |
| 8271 |
namespace Kibo\PhastPlugins\SDK\Configuration; |
| 8272 |
|
| 8273 |
/** |
| 8274 |
* A default implementation of the ServiceConfigurationRepository interface |
| 8275 |
* |
| 8276 |
* Class PHPFilesServiceConfigurationRepository |
| 8277 |
*/ |
| 8278 |
class PHPFilesServiceConfigurationRepository implements \Kibo\PhastPlugins\SDK\Configuration\ServiceConfigurationRepository |
| 8279 |
{ |
| 8280 |
/** |
| 8281 |
* @var CacheRootManager |
| 8282 |
*/ |
| 8283 |
private $cacheRootManager; |
| 8284 |
/** |
| 8285 |
* PHPFilesServiceConfigurationRepository constructor. |
| 8286 |
* @param CacheRootManager $cacheRootManager |
| 8287 |
*/ |
| 8288 |
public function __construct(\Kibo\PhastPlugins\SDK\Caching\CacheRootManager $cacheRootManager) |
| 8289 |
{ |
| 8290 |
$this->cacheRootManager = $cacheRootManager; |
| 8291 |
} |
| 8292 |
public function store(array $config) |
| 8293 |
{ |
| 8294 |
return $this->storeInPHPFile($this->getServiceConfigurationFilePath(), $config) !== false; |
| 8295 |
} |
| 8296 |
public function get() |
| 8297 |
{ |
| 8298 |
$json = $this->readFromPHPFile($this->getServiceConfigurationFilePath()); |
| 8299 |
if (!$json) { |
| 8300 |
return false; |
| 8301 |
} |
| 8302 |
if (strpos($json, 'a:') === 0) { |
| 8303 |
$config = unserialize($json); |
| 8304 |
} else { |
| 8305 |
$config = json_decode($json, true); |
| 8306 |
} |
| 8307 |
if ($config === null) { |
| 8308 |
return false; |
| 8309 |
} |
| 8310 |
return $config; |
| 8311 |
} |
| 8312 |
public function has() |
| 8313 |
{ |
| 8314 |
return !!$this->get(); |
| 8315 |
} |
| 8316 |
private function getServiceConfigurationFilePath() |
| 8317 |
{ |
| 8318 |
return $this->getCacheStoredFilePath('service-config'); |
| 8319 |
} |
| 8320 |
private function getCacheStoredFilePath($filename) |
| 8321 |
{ |
| 8322 |
$dir = $this->cacheRootManager->getCacheRoot(); |
| 8323 |
if (!$dir) { |
| 8324 |
return false; |
| 8325 |
} |
| 8326 |
$legacyName = "{$dir}/{$filename}.php"; |
| 8327 |
if (@file_exists($legacyName)) { |
| 8328 |
return $legacyName; |
| 8329 |
} |
| 8330 |
foreach (@scandir($dir) as $file) { |
| 8331 |
if (!preg_match('~^' . preg_quote($filename, '~') . '-[a-zA-Z0-9]{16}$~', $file)) { |
| 8332 |
continue; |
| 8333 |
} |
| 8334 |
$path = "{$dir}/{$file}"; |
| 8335 |
if (@is_file($path)) { |
| 8336 |
return $path; |
| 8337 |
} |
| 8338 |
} |
| 8339 |
return "{$dir}/service-config-{$this->generateRandomName()}"; |
| 8340 |
} |
| 8341 |
private function generateRandomName() |
| 8342 |
{ |
| 8343 |
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
| 8344 |
$o = ''; |
| 8345 |
for ($i = 0; $i < 16; $i++) { |
| 8346 |
$o .= $chars[mt_rand(0, strlen($chars) - 1)]; |
| 8347 |
} |
| 8348 |
return $o; |
| 8349 |
} |
| 8350 |
private function readFromPHPFile($filename) |
| 8351 |
{ |
| 8352 |
$content = @file_get_contents($filename); |
| 8353 |
if (!$content) { |
| 8354 |
return false; |
| 8355 |
} |
| 8356 |
if (!preg_match('/^[^>]*>\\n([a-f0-9]{40})\\n(.*)$/s', $content, $match)) { |
| 8357 |
return false; |
| 8358 |
} |
| 8359 |
if (sha1($match[2]) != $match[1]) { |
| 8360 |
return false; |
| 8361 |
} |
| 8362 |
return $match[2]; |
| 8363 |
} |
| 8364 |
private function storeInPHPFile($filename, $value) |
| 8365 |
{ |
| 8366 |
$value = json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_UNESCAPED_SLASHES) . "\n"; |
| 8367 |
$content = "<?php exit; ?>\n" . sha1($value) . "\n" . $value; |
| 8368 |
return @file_put_contents($filename, $content, LOCK_EX); |
| 8369 |
} |
| 8370 |
} |
| 8371 |
namespace Kibo\PhastPlugins\SDK\APIs; |
| 8372 |
|
| 8373 |
/** |
| 8374 |
* Presents convenient methods for common tasks. |
| 8375 |
* |
| 8376 |
* Class Service |
| 8377 |
*/ |
| 8378 |
class Service |
| 8379 |
{ |
| 8380 |
/** |
| 8381 |
* @var ServiceConfiguration |
| 8382 |
*/ |
| 8383 |
private $config; |
| 8384 |
/** |
| 8385 |
* Service constructor. |
| 8386 |
* @param ServiceConfiguration $config |
| 8387 |
*/ |
| 8388 |
public function __construct(\Kibo\PhastPlugins\SDK\Configuration\ServiceConfiguration $config) |
| 8389 |
{ |
| 8390 |
$this->config = $config; |
| 8391 |
} |
| 8392 |
/** |
| 8393 |
* Configures the services and serves the request |
| 8394 |
*/ |
| 8395 |
public function serve() |
| 8396 |
{ |
| 8397 |
\Kibo\Phast\PhastServices::serve(function () { |
| 8398 |
return $this->config->get(); |
| 8399 |
}); |
| 8400 |
} |
| 8401 |
} |
| 8402 |
namespace Kibo\PhastPlugins\SDK\APIs; |
| 8403 |
|
| 8404 |
/** |
| 8405 |
* Presents convenient methods for common tasks. |
| 8406 |
* |
| 8407 |
* Class Phast |
| 8408 |
*/ |
| 8409 |
class Phast |
| 8410 |
{ |
| 8411 |
/** |
| 8412 |
* @var PhastConfiguration |
| 8413 |
*/ |
| 8414 |
private $config; |
| 8415 |
/** |
| 8416 |
* PhastAPI constructor. |
| 8417 |
* @param PhastConfiguration $config |
| 8418 |
*/ |
| 8419 |
public function __construct(\Kibo\PhastPlugins\SDK\Configuration\PhastConfiguration $config) |
| 8420 |
{ |
| 8421 |
$this->config = $config; |
| 8422 |
} |
| 8423 |
/** |
| 8424 |
* Applies phast filters to $html |
| 8425 |
* with a configuration suited for full documents |
| 8426 |
* |
| 8427 |
* @param $html |
| 8428 |
* @return string |
| 8429 |
*/ |
| 8430 |
public function applyFiltersForDocument($html) |
| 8431 |
{ |
| 8432 |
return \Kibo\Phast\PhastDocumentFilters::apply($html, $this->config->getForDocuments()); |
| 8433 |
} |
| 8434 |
/** |
| 8435 |
* Applies phast filters to $html |
| 8436 |
* with a configuration suited for html snippets |
| 8437 |
* |
| 8438 |
* @param $html |
| 8439 |
* @return string |
| 8440 |
*/ |
| 8441 |
public function applyFiltersForSnippets($html) |
| 8442 |
{ |
| 8443 |
return \Kibo\Phast\PhastDocumentFilters::apply($html, $this->config->getForHTMLSnippets()); |
| 8444 |
} |
| 8445 |
/** |
| 8446 |
* Deploys phast output buffer filters |
| 8447 |
* with a configuration suited for full documents |
| 8448 |
*/ |
| 8449 |
public function deployOutputBufferForDocument() |
| 8450 |
{ |
| 8451 |
return \Kibo\Phast\PhastDocumentFilters::deploy($this->config->getForDocuments()); |
| 8452 |
} |
| 8453 |
/** |
| 8454 |
* Deploys phast output buffer filters |
| 8455 |
* with a configuration suited for html snippets |
| 8456 |
*/ |
| 8457 |
public function deployOutputBufferForSnippets() |
| 8458 |
{ |
| 8459 |
return \Kibo\Phast\PhastDocumentFilters::deploy($this->config->getForHTMLSnippets()); |
| 8460 |
} |
| 8461 |
} |
| 8462 |
namespace Kibo\PhastPlugins\SDK\Caching; |
| 8463 |
|
| 8464 |
interface CacheRootCandidatesProvider |
| 8465 |
{ |
| 8466 |
/** |
| 8467 |
* Return a list of folders that will potentially be used |
| 8468 |
* for storing cache and service configuration files. |
| 8469 |
* The directories will be checked for write access |
| 8470 |
* in the order they were provided. The first one writable |
| 8471 |
* will be used. |
| 8472 |
* |
| 8473 |
* @return string[] |
| 8474 |
*/ |
| 8475 |
public function getCacheRootCandidates(); |
| 8476 |
} |
| 8477 |
namespace Kibo\PhastPlugins\SDK\Caching; |
| 8478 |
|
| 8479 |
class CacheRootManager |
| 8480 |
{ |
| 8481 |
/** |
| 8482 |
* @var CacheRootCandidatesProvider |
| 8483 |
*/ |
| 8484 |
private $rootsProvider; |
| 8485 |
/** |
| 8486 |
* CacheRootManager constructor. |
| 8487 |
* @param CacheRootCandidatesProvider $rootsProvider |
| 8488 |
*/ |
| 8489 |
public function __construct(\Kibo\PhastPlugins\SDK\Caching\CacheRootCandidatesProvider $rootsProvider) |
| 8490 |
{ |
| 8491 |
$this->rootsProvider = $rootsProvider; |
| 8492 |
} |
| 8493 |
public function getCacheRootCandidates() |
| 8494 |
{ |
| 8495 |
return $this->rootsProvider->getCacheRootCandidates(); |
| 8496 |
} |
| 8497 |
public function getCacheRoot() |
| 8498 |
{ |
| 8499 |
$key = $this->getKey(); |
| 8500 |
$candidates = $this->getCacheRootCandidates(); |
| 8501 |
if ($result = $this->findExistingCacheRoot($key, $candidates)) { |
| 8502 |
return $result; |
| 8503 |
} |
| 8504 |
if ($this->createNewCacheRoot($key, $candidates)) { |
| 8505 |
return $this->findExistingCacheRoot($key, $candidates); |
| 8506 |
} |
| 8507 |
return false; |
| 8508 |
} |
| 8509 |
public function hasCacheRoot() |
| 8510 |
{ |
| 8511 |
return (bool) $this->getCacheRoot(); |
| 8512 |
} |
| 8513 |
public function getAllCacheRoots() |
| 8514 |
{ |
| 8515 |
return $this->findAllExistingCacheRoots($this->getKey(), $this->getCacheRootCandidates()); |
| 8516 |
} |
| 8517 |
private function getKey() |
| 8518 |
{ |
| 8519 |
return md5(@$_SERVER['DOCUMENT_ROOT']) . '.' . (new \Kibo\Phast\Common\System())->getUserId(); |
| 8520 |
} |
| 8521 |
private function findExistingCacheRoot($key, $candidates) |
| 8522 |
{ |
| 8523 |
foreach ($this->findAllExistingCacheRoots($key, $candidates) as $checkDir) { |
| 8524 |
if (!is_writable($checkDir)) { |
| 8525 |
continue; |
| 8526 |
} |
| 8527 |
if (function_exists('posix_geteuid') && fileowner($checkDir) !== posix_geteuid()) { |
| 8528 |
continue; |
| 8529 |
} |
| 8530 |
$this->createIndexFile($checkDir); |
| 8531 |
return $checkDir; |
| 8532 |
} |
| 8533 |
return false; |
| 8534 |
} |
| 8535 |
private function findAllExistingCacheRoots($key, $candidates) |
| 8536 |
{ |
| 8537 |
foreach ($this->getCacheRootCandidates() as $dir) { |
| 8538 |
$checkDirs = ["{$dir}/{$key}", "{$dir}/phastpress.{$key}", "{$dir}/phast.{$key}"]; |
| 8539 |
foreach ($checkDirs as $checkDir) { |
| 8540 |
if (!is_dir($checkDir)) { |
| 8541 |
continue; |
| 8542 |
} |
| 8543 |
(yield $checkDir); |
| 8544 |
} |
| 8545 |
} |
| 8546 |
} |
| 8547 |
private function createNewCacheRoot($key, $candidates) |
| 8548 |
{ |
| 8549 |
foreach ($this->getCacheRootCandidates() as $dir) { |
| 8550 |
if (@mkdir("{$dir}/phast.{$key}", 0777, true)) { |
| 8551 |
return true; |
| 8552 |
} |
| 8553 |
} |
| 8554 |
return false; |
| 8555 |
} |
| 8556 |
private function createIndexFile($dir) |
| 8557 |
{ |
| 8558 |
$path = "{$dir}/index.html"; |
| 8559 |
if (!@file_exists($path)) { |
| 8560 |
@touch($path); |
| 8561 |
} |
| 8562 |
} |
| 8563 |
} |
| 8564 |
namespace Kibo\PhastPlugins\SDK; |
| 8565 |
|
| 8566 |
class Autoloader |
| 8567 |
{ |
| 8568 |
private static $instance; |
| 8569 |
private $psr4 = array(); |
| 8570 |
public static function getInstance() |
| 8571 |
{ |
| 8572 |
if (!isset(self::$instance)) { |
| 8573 |
self::$instance = new self(); |
| 8574 |
self::$instance->install(); |
| 8575 |
} |
| 8576 |
return self::$instance; |
| 8577 |
} |
| 8578 |
public function install() |
| 8579 |
{ |
| 8580 |
spl_autoload_register(function ($class) { |
| 8581 |
$this->autoload($class); |
| 8582 |
}); |
| 8583 |
} |
| 8584 |
public function addPSR4($namespace, $dir) |
| 8585 |
{ |
| 8586 |
$this->psr4[] = [$namespace, $dir]; |
| 8587 |
return $this; |
| 8588 |
} |
| 8589 |
private function autoload($class) |
| 8590 |
{ |
| 8591 |
foreach ($this->psr4 as $psr4) { |
| 8592 |
list($namespace, $dir) = $psr4; |
| 8593 |
if (strcasecmp($namespace . '\\', substr($class, 0, strlen($namespace) + 1))) { |
| 8594 |
continue; |
| 8595 |
} |
| 8596 |
$relativeName = substr($class, strlen($namespace) + 1); |
| 8597 |
$relativePath = str_replace('\\', '/', $relativeName) . '.php'; |
| 8598 |
$fullPath = $dir . '/' . $relativePath; |
| 8599 |
if (file_exists($fullPath)) { |
| 8600 |
include $fullPath; |
| 8601 |
return; |
| 8602 |
} |
| 8603 |
} |
| 8604 |
} |
| 8605 |
} |
| 8606 |
namespace Kibo\PhastPlugins\SDK; |
| 8607 |
|
| 8608 |
interface ServiceHost |
| 8609 |
{ |
| 8610 |
/** |
| 8611 |
* @return CacheRootCandidatesProvider |
| 8612 |
*/ |
| 8613 |
public function getCacheRootCandidatesProvider(); |
| 8614 |
/** |
| 8615 |
* Called right after the service configuration |
| 8616 |
* has been loaded. Use it to modify the config |
| 8617 |
* and take any other needed action before |
| 8618 |
* the service is started. |
| 8619 |
* |
| 8620 |
* @param array $config - The configuration that has been loaded |
| 8621 |
* @return array - The configuration to use for the services |
| 8622 |
*/ |
| 8623 |
public function onServiceConfigurationLoad(array $config); |
| 8624 |
} |
| 8625 |
namespace Kibo\PhastPlugins\SDK\Generated; |
| 8626 |
|
| 8627 |
class Translations |
| 8628 |
{ |
| 8629 |
const DATA = array('en' => array('AdminPanel' => array('errors' => array('no-cache-root' => '@:plugin-name can not write to any cache directory! Please, make one of the following directories writable: {params}', 'no-service-config' => '@:plugin-name failed to create a service configuration in any of the following directories: {params}', 'network-error' => 'Failed to connect to plugin server! Please, try again later! {params}', 'cache' => '{params}'), 'warnings' => array('disabled' => '@:plugin-name optimizations are off!', 'admin-only' => '@:plugin-name optimizations will be applied only for logged-in users with the "Administrator" privilege. This is for previewing purposes. Select the "On" setting for "@:plugin-name optimizations" below to activate for all users! |
| 8630 |
')), 'Backend' => array('install-notice' => 'Thank you for using <b>@:plugin-name</b>. Optimizations are <b>{pluginState}</b>. Go to <b><a href="{settingsUrl}">Settings</a></b> to configure <b>@:plugin-name</b>. |
| 8631 |
', 'status' => array('on' => 'on', 'off' => 'off', 'admin' => 'on for administrators')), 'Information' => array('additional' => 'Additional information'), 'Notification' => array('error' => 'error', 'warning' => 'warning', 'information' => 'information', 'success' => 'success'), 'OnOffSwitch' => array('on' => 'On', 'off' => 'Off'), 'SavingStatus' => array('saving' => 'Saving', 'saved' => 'Saved'), 'Settings' => array('common' => array('tip' => 'Tip:', 'on' => 'On:', 'off' => 'Off:'), 'sections' => array('plugin' => array('title' => 'Plugin', 'enabled' => array('name' => '@:plugin-name optimizations', 'description' => array('main' => 'Test your site {without} and {with}', 'without' => 'without @:plugin-name', 'with' => 'with @:plugin-name')), 'admin-only' => array('name' => 'Only optimize for administrators', 'description' => array('on' => 'Only privileged users will be served with optimized version', 'off' => 'All users will be served with optimized version', 'tip' => 'Use this to preview your site before launching the optimizations')), 'pathinfo' => array('name' => 'Remove query string from processed resources', 'description' => array('start' => 'Make sure that processed resources don\'t have query strings, for a higher score in GTmetrix.', 'on' => 'Use the path for requests for processed resources. This requires a server that supports "PATH_INFO".', 'off' => 'Use the GET parameters for requests for processed resources.')), 'footer-link' => array('name' => 'Let the world know about @:plugin-name', 'description' => 'Add a "Optimized by @:plugin-name" notice to the footer of your site and help spread the word.'), 'compress-service-response' => array('name' => 'Enable gzip compression on processed resources', 'description' => 'This compresses the optimized and bundled JavaScript and CSS generated by PhastPress. Disable this if your server already compresses PhastPress responses.')), 'images' => array('title' => 'Images', 'tags' => array('name' => 'Optimize images in tags', 'description' => 'Compress images with optimal settings. {newline} Resize images to fit {width}x{height} pixels or to the appropriate size for {imgTag} tags with {widthAttr} or {heightAttr}. {newline} Reload changed images while still leveraging browser caching.'), 'css' => array('name' => 'Optimize images in CSS', 'description' => array(0 => 'Compress images in stylesheets with optional settings and resizes the to fit {width}x{height} pixels.', 1 => 'Reload changed images while still leveraging browser caching.')), 'api' => array('name' => 'Use the Phast Image Optimization API', 'description' => array(0 => 'Optimize your images on our servers free of charge.', 1 => 'This will give you the best possible results without installing any software and will reduce the load on your hosting.')), 'lazy' => array('name' => 'Lazy load images', 'description' => array(0 => 'This adds the loading=lazy attribute to img tags so that images are only load once they are visible on the page.', 1 => 'This helps pass the "Defer offscreen images" audit in PageSpeed Insights.'))), 'html-filters' => array('title' => 'HTML, CSS & JS', 'css' => array('name' => 'Optimize CSS', 'description' => array(0 => 'Incline critical styles first and prevent unused styles from blocking the page load.', 1 => 'Minify stylesheets and leverage browser caching.', 2 => 'Inline Google Fonts CSS to speed up font loading.')), 'async-js' => array('name' => 'Load scripts asynchronously', 'description' => 'Allow the page to finish loading before all scripts have been executed.'), 'minify-js' => array('name' => 'Minify scripts and improve caching', 'description' => array(0 => 'Minify scripts and set long cache durations.', 1 => 'Reload changed scripts while still leveraging browser caching.')), 'iframe' => array('name' => 'Defer IFrame loading', 'description' => 'Start loading IFrames after the page has finished loading.'), 'minify-html' => array('name' => 'Minify HTML', 'description' => 'Remove unnecessary whitespace from the HTML code of the page.'), 'minify-inline-scripts' => array('name' => 'Minify inline scripts and JSON', 'description' => 'Remove unnecessary whitespace from inline scripts and JSON data.')))))); |
| 8632 |
} |
| 8633 |
namespace Kibo\PhastPlugins\SDK\AJAX; |
| 8634 |
|
| 8635 |
/** |
| 8636 |
* Handles AJAX requests to the plugin admin. |
| 8637 |
* Use this class to handle requests to the plugin's admin AJAX end point |
| 8638 |
* |
| 8639 |
* Class RequestsDispatcher |
| 8640 |
*/ |
| 8641 |
class RequestsDispatcher |
| 8642 |
{ |
| 8643 |
const KEY_ACTION = 'phast-plugins-action'; |
| 8644 |
/** |
| 8645 |
* @var PhastUser |
| 8646 |
*/ |
| 8647 |
private $user; |
| 8648 |
/** |
| 8649 |
* @var SDK |
| 8650 |
*/ |
| 8651 |
private $sdk; |
| 8652 |
/** |
| 8653 |
* RequestsDispatcher constructor. |
| 8654 |
* @param PhastUser $user |
| 8655 |
* @param SDK $sdk |
| 8656 |
*/ |
| 8657 |
public function __construct(\Kibo\PhastPlugins\SDK\Security\PhastUser $user, \Kibo\PhastPlugins\SDK\SDK $sdk) |
| 8658 |
{ |
| 8659 |
$this->user = $user; |
| 8660 |
$this->sdk = $sdk; |
| 8661 |
} |
| 8662 |
/** |
| 8663 |
* Handles ajax requests to plugin's admin AJAX end point |
| 8664 |
* |
| 8665 |
* @param array $request The $_POST data to the request |
| 8666 |
* @return mixed Must be json encoded and returned to the client |
| 8667 |
*/ |
| 8668 |
public function dispatch(array $request) |
| 8669 |
{ |
| 8670 |
if (!$this->user->mayModifySettings()) { |
| 8671 |
return false; |
| 8672 |
} |
| 8673 |
$action = isset($request[self::KEY_ACTION]) ? $request[self::KEY_ACTION] : ''; |
| 8674 |
if ($action == 'save-settings') { |
| 8675 |
$this->sdk->getPluginConfiguration()->save($request); |
| 8676 |
return $this->makeResponse(true, $this->sdk->getAdminPanelData()->get()); |
| 8677 |
} |
| 8678 |
if ($action == 'dismiss-notice') { |
| 8679 |
$this->sdk->getPluginConfiguration()->hideActivationNotification(); |
| 8680 |
return $this->makeResponse(true); |
| 8681 |
} |
| 8682 |
return $this->makeResponse(false); |
| 8683 |
} |
| 8684 |
private function makeResponse($success, $data = null) |
| 8685 |
{ |
| 8686 |
return ['phast-success' => $success, 'phast-data' => $data]; |
| 8687 |
} |
| 8688 |
} |
| 8689 |
namespace Kibo\PhastPlugins\SDK\Security; |
| 8690 |
|
| 8691 |
/** |
| 8692 |
* Represents the user currently viewing the website (either backend or frontend) |
| 8693 |
* |
| 8694 |
* Interface PhastUser |
| 8695 |
*/ |
| 8696 |
interface PhastUser |
| 8697 |
{ |
| 8698 |
/** |
| 8699 |
* Tells whether the user can access and manipulate |
| 8700 |
* the plugin's settings |
| 8701 |
* |
| 8702 |
* @return bool |
| 8703 |
*/ |
| 8704 |
public function mayModifySettings(); |
| 8705 |
/** |
| 8706 |
* Tells whether the user can access the website with Phast enabled in preview mode |
| 8707 |
* |
| 8708 |
* @return bool |
| 8709 |
*/ |
| 8710 |
public function seesPreviewMode(); |
| 8711 |
} |
| 8712 |
namespace Kibo\PhastPlugins\SDK\Security; |
| 8713 |
|
| 8714 |
/** |
| 8715 |
* Checks whether posted data to the server |
| 8716 |
* contains the expected nonce. |
| 8717 |
* |
| 8718 |
* Interface NonceChecker |
| 8719 |
*/ |
| 8720 |
interface NonceChecker |
| 8721 |
{ |
| 8722 |
/** |
| 8723 |
* Performs the check |
| 8724 |
* |
| 8725 |
* @param array $data The data posted to the server |
| 8726 |
* @return bool TRUE if all is well, FALSE otherwise |
| 8727 |
*/ |
| 8728 |
public function checkNonce(array $data); |
| 8729 |
} |
| 8730 |
namespace Kibo\PhastPlugins\SDK\Common; |
| 8731 |
|
| 8732 |
/** |
| 8733 |
* Contains common implementations for methods |
| 8734 |
* of the PluginHost interface |
| 8735 |
* |
| 8736 |
* @see PluginHost |
| 8737 |
* Trait PluginHostTrait |
| 8738 |
*/ |
| 8739 |
trait PluginHostTrait |
| 8740 |
{ |
| 8741 |
public function getPluginName() |
| 8742 |
{ |
| 8743 |
return 'Phast'; |
| 8744 |
} |
| 8745 |
public function isDev() |
| 8746 |
{ |
| 8747 |
return $this->getPluginHostVersion() === '$VER' . 'SION$'; |
| 8748 |
} |
| 8749 |
public function onPhastConfigurationLoad(array $config) |
| 8750 |
{ |
| 8751 |
return $config; |
| 8752 |
} |
| 8753 |
public function getLocale() |
| 8754 |
{ |
| 8755 |
return 'en'; |
| 8756 |
} |
| 8757 |
public function getInstallNoticeRenderer() |
| 8758 |
{ |
| 8759 |
return new \Kibo\PhastPlugins\SDK\AdminPanel\DefaultInstallNoticeRenderer(); |
| 8760 |
} |
| 8761 |
} |
| 8762 |
namespace Kibo\PhastPlugins\SDK\Common; |
| 8763 |
|
| 8764 |
trait ServiceHostTrait |
| 8765 |
{ |
| 8766 |
public function onServiceConfigurationLoad(array $config) |
| 8767 |
{ |
| 8768 |
return $config; |
| 8769 |
} |
| 8770 |
} |
| 8771 |
namespace Kibo\PhastPlugins\SDK\Common; |
| 8772 |
|
| 8773 |
trait PreviewCookieTrait |
| 8774 |
{ |
| 8775 |
public function seesPreviewMode() |
| 8776 |
{ |
| 8777 |
return isset($_COOKIE['PHAST_PREVIEW']) && (bool) $_COOKIE['PHAST_PREVIEW']; |
| 8778 |
} |
| 8779 |
} |
| 8780 |
namespace Kibo\Phast\Environment\Exceptions; |
| 8781 |
|
| 8782 |
class PackageHasNoDiagnosticsException extends \Kibo\Phast\Exceptions\LogicException |
| 8783 |
{ |
| 8784 |
} |
| 8785 |
namespace Kibo\Phast\Environment\Exceptions; |
| 8786 |
|
| 8787 |
class PackageHasNoFactoryException extends \Kibo\Phast\Exceptions\LogicException |
| 8788 |
{ |
| 8789 |
} |
| 8790 |
namespace Kibo\Phast\Cache\File; |
| 8791 |
|
| 8792 |
class DiagnosticsLogWriter implements \Kibo\Phast\Logging\LogWriter |
| 8793 |
{ |
| 8794 |
public function setLevelMask($mask) |
| 8795 |
{ |
| 8796 |
} |
| 8797 |
public function writeEntry(\Kibo\Phast\Logging\LogEntry $entry) |
| 8798 |
{ |
| 8799 |
if ($entry->getLevel() > 2) { |
| 8800 |
$needles = array_map(function ($key) { |
| 8801 |
return '{' . $key . '}'; |
| 8802 |
}, array_keys($entry->getContext())); |
| 8803 |
$message = str_replace($needles, $entry->getContext(), $entry->getMessage()); |
| 8804 |
throw new \Kibo\Phast\Exceptions\RuntimeException("Error: Level: {$entry->getLevel()}, Msg: {$message}"); |
| 8805 |
} |
| 8806 |
} |
| 8807 |
} |
| 8808 |
namespace Kibo\Phast\Cache\File; |
| 8809 |
|
| 8810 |
class Diagnostics implements \Kibo\Phast\Diagnostics\Diagnostics |
| 8811 |
{ |
| 8812 |
public function diagnose(array $config) |
| 8813 |
{ |
| 8814 |
\Kibo\Phast\Logging\Log::setLogger(new \Kibo\Phast\Logging\Logger(new \Kibo\Phast\Cache\File\DiagnosticsLogWriter())); |
| 8815 |
$cache = new \Kibo\Phast\Cache\File\Cache($config['cache'], 'cache-self-diagnosis'); |
| 8816 |
$v1 = $cache->get('test-key', function () { |
| 8817 |
return 1; |
| 8818 |
}, 2); |
| 8819 |
$v2 = $cache->get('test-key', function () { |
| 8820 |
return 2; |
| 8821 |
}, 2); |
| 8822 |
if ($v1 != $v2) { |
| 8823 |
throw new \Kibo\Phast\Exceptions\RuntimeException('Cache failed, but no error was reported!'); |
| 8824 |
} |
| 8825 |
} |
| 8826 |
} |
| 8827 |
namespace Kibo\Phast\Filters\HTML\CommentsRemoval; |
| 8828 |
|
| 8829 |
class Filter implements \Kibo\Phast\Filters\HTML\HTMLStreamFilter |
| 8830 |
{ |
| 8831 |
public function transformElements(\Traversable $elements, \Kibo\Phast\Filters\HTML\HTMLPageContext $context) |
| 8832 |
{ |
| 8833 |
foreach ($elements as $element) { |
| 8834 |
if (!$element instanceof \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Comment || $element->isIEConditional()) { |
| 8835 |
(yield $element); |
| 8836 |
} |
| 8837 |
} |
| 8838 |
} |
| 8839 |
} |
| 8840 |
namespace Kibo\Phast\Filters\HTML\ScriptsDeferring; |
| 8841 |
|
| 8842 |
class Filter extends \Kibo\Phast\Filters\HTML\BaseHTMLStreamFilter |
| 8843 |
{ |
| 8844 |
use \Kibo\Phast\Filters\HTML\Helpers\JSDetectorTrait; |
| 8845 |
protected function isTagOfInterest(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag) |
| 8846 |
{ |
| 8847 |
return $tag->getTagName() == 'script'; |
| 8848 |
} |
| 8849 |
protected function handleTag(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $script) |
| 8850 |
{ |
| 8851 |
if ($this->isJSElement($script) && !$this->isDeferralDisabled($script)) { |
| 8852 |
$this->rewrite($script); |
| 8853 |
} |
| 8854 |
(yield $script); |
| 8855 |
} |
| 8856 |
protected function afterLoop() |
| 8857 |
{ |
| 8858 |
$this->context->addPhastJavaScript(\Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/ScriptsDeferring/scripts-loader.js', "var Promise=phast.ES6Promise;phast.ScriptsLoader={};phast.ScriptsLoader.getScriptsInExecutionOrder=function(a,b){var c=a.querySelectorAll('script[type=\"text/phast\"]');var d=[],e=[];for(var f=0;f<c.length;f++){if(getSrc(c[f])!==undefined&&c[f].hasAttribute(\"defer\")){e.push(c[f])}else{d.push(c[f])}}return d.concat(e).map(function(g){return b.makeScriptFromElement(g)})};phast.ScriptsLoader.executeScripts=function(h){var i=h.map(function(k){return k.init()});var j=Promise.resolve();h.forEach(function(l){j=phast.ScriptsLoader.chainScript(j,l)});return j.then(function(){return Promise.all(i).catch(function(){})})};phast.ScriptsLoader.chainScript=function(m,n){var o;try{if(n.describe){o=n.describe()}else{o=\"unknown script\"}}catch(p){o=\"script.describe() failed\"}return m.then(function(){var q=n.execute();q.then(function(){console.debug(\"\342\234\223\",o)});return q}).catch(function(r){console.error(\"\342\234\230\",o);if(r){console.log(r)}})};var insertBefore=window.Element.prototype.insertBefore;phast.ScriptsLoader.Utilities=function(s){this._document=s;var t=0;function u(C){return new Promise(function(D){var E=\"PhastCompleteScript\"+ ++t;var F=s.createElement(\"script\");F.textContent=C;var G=s.createElement(\"script\");G.textContent=E+\"()\";window[E]=H;s.body.appendChild(F);s.body.appendChild(G);function H(){D();s.body.removeChild(F);s.body.removeChild(G);delete window[E]}})}function v(I){var J=s.createElement(I.nodeName);Array.prototype.forEach.call(I.attributes,function(K){J.setAttribute(K.nodeName,K.nodeValue)});return J}function w(L){L.removeAttribute(\"data-phast-params\");var M={};Array.prototype.map.call(L.attributes,function(N){return N.nodeName}).map(function(O){var P=O.match(/^data-phast-original-(.*)/i);if(P){M[P[1].toLowerCase()]=L.getAttribute(O);L.removeAttribute(O)}});Object.keys(M).sort().map(function(Q){L.setAttribute(Q,M[Q])});if(!(\"type\"in M)){L.removeAttribute(\"type\")}}function x(R,S){return new Promise(function(T,U){var V=S.getAttribute(\"src\");S.addEventListener(\"load\",T);S.addEventListener(\"error\",U);S.removeAttribute(\"src\");insertBefore.call(R.parentNode,S,R);R.parentNode.removeChild(R);if(V){S.setAttribute(\"src\",V)}})}function y(W,X){return A(W,function(){return u(X)})}function z(Y,Z){return A(Z,function(){return x(Y,Z)})}function A(\$,_){var aa=\$.nextElementSibling;var ba=Promise.resolve();var ca;if(isAsync(\$)){ca=\"async\"}else if(isDefer(\$)){ca=\"defer\"}s.write=function(ga){if(ca){console.warn(\"document.write call from \"+ca+\" script ignored\");return}da(ga)};s.writeln=function(ha){if(ca){console.warn(\"document.writeln call from \"+ca+\" script ignored\");return}da(ha+\"\\n\")};function da(ia){var ja=s.createElement(\"div\");ja.innerHTML=ia;var ka=ea(ja);if(aa&&aa.parentNode!==\$.parentNode){aa=\$.nextElementSibling}while(ja.firstChild){\$.parentNode.insertBefore(ja.firstChild,aa)}ka.map(fa)}function ea(la){return Array.prototype.slice.call(la.getElementsByTagName(\"script\")).filter(function(ma){var na=ma.getAttribute(\"type\");return!na||/^(text|application)\\/javascript(;|\$)/i.test(na)})}function fa(oa){var pa=new phast.ScriptsLoader.Scripts.Factory(s);var qa=pa.makeScriptFromElement(oa);ba=phast.ScriptsLoader.chainScript(ba,qa)}return _().then(function(){return ba}).finally(function(){delete s.write;delete s.writeln})}function B(ra){var sa=s.createElement(\"link\");sa.setAttribute(\"rel\",\"preload\");sa.setAttribute(\"as\",\"script\");sa.setAttribute(\"href\",ra);s.head.appendChild(sa)}this.executeString=u;this.copyElement=v;this.restoreOriginals=w;this.replaceElement=x;this.writeProtectAndExecuteString=y;this.writeProtectAndReplaceElement=z;this.addPreload=B};phast.ScriptsLoader.Scripts={};phast.ScriptsLoader.Scripts.InlineScript=function(ta,ua){this._utils=ta;this._element=ua;this.init=function(){return Promise.resolve()};this.execute=function(){var va=ua.textContent.replace(/^\\s*<!--.*\\n/i,\"\");ta.restoreOriginals(ua);return ta.writeProtectAndExecuteString(ua,va)};this.describe=function(){return\"inline script\"}};phast.ScriptsLoader.Scripts.AsyncBrowserScript=function(wa,xa){var ya;this._utils=wa;this._element=xa;this.init=function(){wa.addPreload(getSrc(xa));return new Promise(function(za){ya=za})};this.execute=function(){var Aa=wa.copyElement(xa);wa.restoreOriginals(Aa);wa.replaceElement(xa,Aa).then(ya).catch(ya);return Promise.resolve()};this.describe=function(){return\"async script at \"+getSrc(xa)}};phast.ScriptsLoader.Scripts.SyncBrowserScript=function(Ba,Ca){this._utils=Ba;this._element=Ca;this.init=function(){Ba.addPreload(getSrc(Ca));return Promise.resolve()};this.execute=function(){var Da=Ba.copyElement(Ca);Ba.restoreOriginals(Da);return Ba.writeProtectAndReplaceElement(Ca,Da)};this.describe=function(){return\"sync script at \"+getSrc(Ca)}};phast.ScriptsLoader.Scripts.AsyncAJAXScript=function(Ea,Fa,Ga,Ha){this._utils=Ea;this._element=Fa;this._fetch=Ga;this._fallback=Ha;var Ia;var Ja;this.init=function(){Ia=Ga(Fa);return new Promise(function(Ka){Ja=Ka})};this.execute=function(){Ia.then(function(La){Ea.restoreOriginals(Fa);return Ea.writeProtectAndExecuteString(Fa,La).then(Ja)}).catch(function(){Ha.init();return Ha.execute().then(Ja)});return Promise.resolve()};this.describe=function(){return\"bundled async script at \"+Fa.getAttribute(\"data-phast-original-src\")}};phast.ScriptsLoader.Scripts.SyncAJAXScript=function(Ma,Na,Oa,Pa){this._utils=Ma;this._element=Na;this._fetch=Oa;this._fallback=Pa;var Qa;this.init=function(){Qa=Oa(Na);return Qa};this.execute=function(){return Qa.then(function(Ra){Ma.restoreOriginals(Na);return Ma.writeProtectAndExecuteString(Na,Ra)}).catch(function(){Pa.init();return Pa.execute()})};this.describe=function(){return\"bundled sync script at \"+Na.getAttribute(\"data-phast-original-src\")}};phast.ScriptsLoader.Scripts.Factory=function(Sa,Ta){var Ua=phast.ScriptsLoader.Scripts;var Va=new phast.ScriptsLoader.Utilities(Sa);this.makeScriptFromElement=function(Ya){var Za;if(Wa(Ya)){if(isAsync(Ya)){Za=new Ua.AsyncBrowserScript(Va,Ya);return Ta?new Ua.AsyncAJAXScript(Va,Ya,Ta,Za):Za}Za=new Ua.SyncBrowserScript(Va,Ya);return Ta?new Ua.SyncAJAXScript(Va,Ya,Ta,Za):Za}if(Xa(Ya)){return new Ua.InlineScript(Va,Ya)}if(isAsync(Ya)){return new Ua.AsyncBrowserScript(Va,Ya)}return new Ua.SyncBrowserScript(Va,Ya)};function Wa(\$a){return \$a.hasAttribute(\"data-phast-params\")}function Xa(_a){return!_a.hasAttribute(\"src\")}};function getSrc(ab){if(ab.hasAttribute(\"data-phast-original-src\")){return ab.getAttribute(\"data-phast-original-src\")}else if(ab.hasAttribute(\"src\")){return ab.getAttribute(\"src\")}}function isAsync(bb){return bb.hasAttribute(\"async\")}function isDefer(cb){return cb.hasAttribute(\"defer\")}\n")); |
| 8859 |
$this->context->addPhastJavaScript(\Kibo\Phast\ValueObjects\PhastJavaScript::fromString('/home/albert/code/phast/src/Build/../../src/Filters/HTML/ScriptsDeferring/rewrite.js', "var Promise=phast.ES6Promise;var go=phast.once(loadScripts);phast.on(document,\"DOMContentLoaded\").then(function(){if(phast.stylesLoading){phast.onStylesLoaded=go;setTimeout(go,4e3)}else{Promise.resolve().then(go)}});var loadFiltered=false;window.addEventListener(\"load\",function(a){if(!loadFiltered){a.stopImmediatePropagation()}loadFiltered=true});function loadScripts(){var b=new phast.ScriptsLoader.Scripts.Factory(document,fetchScript);var c=phast.ScriptsLoader.getScriptsInExecutionOrder(document,b);if(c.length===0){return}try{Object.defineProperty(document,\"readyState\",{configurable:true,get:function(){return\"loading\"}})}catch(d){console.error(\"[Phast] Unable to override document.readyState on this browser: \",d)}phast.ScriptsLoader.executeScripts(c).then(restoreReadyState)}function restoreReadyState(){window.requestAnimationFrame(function(){delete document[\"readyState\"];triggerEvent(document,\"readystatechange\");triggerEvent(document,\"DOMContentLoaded\");window.requestAnimationFrame(function(){if(loadFiltered){triggerEvent(window,\"load\")}else{loadFiltered=true}})})}function triggerEvent(e,f){var g=document.createEvent(\"Event\");g.initEvent(f,true,true);e.dispatchEvent(g)}function fetchScript(h){return phast.ResourceLoader.instance.get(phast.ResourceLoader.RequestParams.fromString(h.getAttribute(\"data-phast-params\")))}\n")); |
| 8860 |
} |
| 8861 |
private function rewrite(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $script) |
| 8862 |
{ |
| 8863 |
if ($script->hasAttribute('type')) { |
| 8864 |
$script->setAttribute('data-phast-original-type', $script->getAttribute('type')); |
| 8865 |
} |
| 8866 |
$script->setAttribute('type', 'text/phast'); |
| 8867 |
if ($script->hasAttribute('data-phast-params')) { |
| 8868 |
$script->removeAttribute('src'); |
| 8869 |
} |
| 8870 |
} |
| 8871 |
private function isDeferralDisabled(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $script) |
| 8872 |
{ |
| 8873 |
return $script->hasAttribute('data-phast-no-defer') || $script->hasAttribute('data-pagespeed-no-defer') || $script->getAttribute('data-cfasync') === 'false' || preg_match('~^\\s*(?<q>[\'"])phast-no-defer\\k<q>~', $script->textContent); |
| 8874 |
} |
| 8875 |
} |
| 8876 |
namespace Kibo\Phast\Filters\HTML\ImagesOptimizationService\Tags; |
| 8877 |
|
| 8878 |
class Factory implements \Kibo\Phast\Filters\HTML\HTMLFilterFactory |
| 8879 |
{ |
| 8880 |
public function make(array $config) |
| 8881 |
{ |
| 8882 |
return new \Kibo\Phast\Filters\HTML\ImagesOptimizationService\Tags\Filter((new \Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageURLRewriterFactory())->make($config, \Kibo\Phast\Filters\HTML\ImagesOptimizationService\Tags\Filter::class)); |
| 8883 |
} |
| 8884 |
} |
| 8885 |
namespace Kibo\Phast\Filters\HTML\ImagesOptimizationService\CSS; |
| 8886 |
|
| 8887 |
class Factory implements \Kibo\Phast\Filters\HTML\HTMLFilterFactory |
| 8888 |
{ |
| 8889 |
public function make(array $config) |
| 8890 |
{ |
| 8891 |
return new \Kibo\Phast\Filters\HTML\ImagesOptimizationService\CSS\Filter((new \Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageURLRewriterFactory())->make($config, \Kibo\Phast\Filters\HTML\ImagesOptimizationService\CSS\Filter::class)); |
| 8892 |
} |
| 8893 |
} |
| 8894 |
namespace Kibo\Phast\Filters\HTML\ImagesOptimizationService\CSS; |
| 8895 |
|
| 8896 |
class Filter extends \Kibo\Phast\Filters\HTML\BaseHTMLStreamFilter |
| 8897 |
{ |
| 8898 |
/** |
| 8899 |
* @var ImageURLRewriter |
| 8900 |
*/ |
| 8901 |
protected $rewriter; |
| 8902 |
/** |
| 8903 |
* Filter constructor. |
| 8904 |
* @param ImageURLRewriter $rewriter |
| 8905 |
*/ |
| 8906 |
public function __construct(\Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageURLRewriter $rewriter) |
| 8907 |
{ |
| 8908 |
$this->rewriter = $rewriter; |
| 8909 |
} |
| 8910 |
protected function handleTag(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Tag $tag) |
| 8911 |
{ |
| 8912 |
if ($tag->hasAttribute('style')) { |
| 8913 |
$tag->setAttribute('style', $this->rewriter->rewriteStyle($tag->getAttribute('style'))); |
| 8914 |
} |
| 8915 |
(yield $tag); |
| 8916 |
} |
| 8917 |
} |
| 8918 |
namespace Kibo\Phast\Filters\HTML\PhastScriptsCompiler; |
| 8919 |
|
| 8920 |
class Factory implements \Kibo\Phast\Filters\HTML\HTMLFilterFactory |
| 8921 |
{ |
| 8922 |
public function make(array $config) |
| 8923 |
{ |
| 8924 |
$cache = new \Kibo\Phast\Cache\File\Cache($config['cache'], 'phast-scripts'); |
| 8925 |
$compiler = new \Kibo\Phast\Filters\HTML\PhastScriptsCompiler\PhastJavaScriptCompiler($cache, $config['servicesUrl'], $config['serviceRequestFormat']); |
| 8926 |
return new \Kibo\Phast\Filters\HTML\PhastScriptsCompiler\Filter($compiler); |
| 8927 |
} |
| 8928 |
} |
| 8929 |
namespace Kibo\Phast\Filters\HTML\ScriptsProxyService; |
| 8930 |
|
| 8931 |
class Factory implements \Kibo\Phast\Filters\HTML\HTMLFilterFactory |
| 8932 |
{ |
| 8933 |
public function make(array $config) |
| 8934 |
{ |
| 8935 |
if (!isset($config['documents']['filters'][\Kibo\Phast\Filters\HTML\ScriptsProxyService\Filter::class]['serviceUrl'])) { |
| 8936 |
$config['documents']['filters'][\Kibo\Phast\Filters\HTML\ScriptsProxyService\Filter::class]['serviceUrl'] = $config['servicesUrl']; |
| 8937 |
} |
| 8938 |
$filterConfig = $config['documents']['filters'][\Kibo\Phast\Filters\HTML\ScriptsProxyService\Filter::class]; |
| 8939 |
$filterConfig['match'] = $config['scripts']['whitelist']; |
| 8940 |
return new \Kibo\Phast\Filters\HTML\ScriptsProxyService\Filter($filterConfig, (new \Kibo\Phast\Security\ServiceSignatureFactory())->make($config), new \Kibo\Phast\Retrievers\LocalRetriever($config['retrieverMap']), (new \Kibo\Phast\Services\Bundler\TokenRefMakerFactory())->make($config)); |
| 8941 |
} |
| 8942 |
} |
| 8943 |
namespace Kibo\Phast\Filters\HTML\CSSInlining; |
| 8944 |
|
| 8945 |
class Factory implements \Kibo\Phast\Filters\HTML\HTMLFilterFactory |
| 8946 |
{ |
| 8947 |
public function make(array $config) |
| 8948 |
{ |
| 8949 |
$localRetriever = new \Kibo\Phast\Retrievers\LocalRetriever($config['retrieverMap']); |
| 8950 |
$retriever = new \Kibo\Phast\Retrievers\UniversalRetriever(); |
| 8951 |
$retriever->addRetriever($localRetriever); |
| 8952 |
$retriever->addRetriever(new \Kibo\Phast\Retrievers\CachingRetriever(new \Kibo\Phast\Cache\File\Cache($config['cache'], 'css'))); |
| 8953 |
if (!isset($config['documents']['filters'][\Kibo\Phast\Filters\HTML\CSSInlining\Filter::class]['serviceUrl'])) { |
| 8954 |
$config['documents']['filters'][\Kibo\Phast\Filters\HTML\CSSInlining\Filter::class]['serviceUrl'] = $config['servicesUrl']; |
| 8955 |
} |
| 8956 |
return new \Kibo\Phast\Filters\HTML\CSSInlining\Filter((new \Kibo\Phast\Security\ServiceSignatureFactory())->make($config), \Kibo\Phast\ValueObjects\URL::fromString($config['documents']['baseUrl']), $config['documents']['filters'][\Kibo\Phast\Filters\HTML\CSSInlining\Filter::class], $localRetriever, $retriever, new \Kibo\Phast\Filters\HTML\CSSInlining\OptimizerFactory($config), (new \Kibo\Phast\Filters\CSS\Composite\Factory())->make($config), (new \Kibo\Phast\Services\Bundler\TokenRefMakerFactory())->make($config)); |
| 8957 |
} |
| 8958 |
} |
| 8959 |
namespace Kibo\Phast\Filters\HTML\Diagnostics; |
| 8960 |
|
| 8961 |
class Factory implements \Kibo\Phast\Filters\HTML\HTMLFilterFactory |
| 8962 |
{ |
| 8963 |
public function make(array $config) |
| 8964 |
{ |
| 8965 |
$url = isset($config['documents']['filters'][\Kibo\Phast\Filters\HTML\Diagnostics\Filter::class]['serviceUrl']) ? $config['documents']['filters'][\Kibo\Phast\Filters\HTML\Diagnostics\Filter::class]['serviceUrl'] : $config['servicesUrl'] . '?service=diagnostics'; |
| 8966 |
return new \Kibo\Phast\Filters\HTML\Diagnostics\Filter($url); |
| 8967 |
} |
| 8968 |
} |
| 8969 |
namespace Kibo\Phast\Filters\Image\Exceptions; |
| 8970 |
|
| 8971 |
class ImageProcessingException extends \Kibo\Phast\Exceptions\RuntimeException |
| 8972 |
{ |
| 8973 |
} |
| 8974 |
namespace Kibo\Phast\Filters\Image\ImageImplementations; |
| 8975 |
|
| 8976 |
class DummyImage extends \Kibo\Phast\Filters\Image\ImageImplementations\BaseImage implements \Kibo\Phast\Filters\Image\Image |
| 8977 |
{ |
| 8978 |
/** |
| 8979 |
* @var string |
| 8980 |
*/ |
| 8981 |
private $imageString; |
| 8982 |
private $transformationString; |
| 8983 |
/** |
| 8984 |
* DummyImage constructor. |
| 8985 |
* |
| 8986 |
* @param int $width |
| 8987 |
* @param int $height |
| 8988 |
*/ |
| 8989 |
public function __construct($width = null, $height = null) |
| 8990 |
{ |
| 8991 |
$this->width = $width; |
| 8992 |
$this->height = $height; |
| 8993 |
} |
| 8994 |
/** |
| 8995 |
* @return int |
| 8996 |
*/ |
| 8997 |
public function getWidth() |
| 8998 |
{ |
| 8999 |
return $this->width; |
| 9000 |
} |
| 9001 |
/** |
| 9002 |
* @return int |
| 9003 |
*/ |
| 9004 |
public function getHeight() |
| 9005 |
{ |
| 9006 |
return $this->height; |
| 9007 |
} |
| 9008 |
/** |
| 9009 |
* @return string |
| 9010 |
*/ |
| 9011 |
public function getType() |
| 9012 |
{ |
| 9013 |
return $this->type; |
| 9014 |
} |
| 9015 |
/** |
| 9016 |
* @param string $type |
| 9017 |
*/ |
| 9018 |
public function setType($type) |
| 9019 |
{ |
| 9020 |
$this->type = $type; |
| 9021 |
} |
| 9022 |
/** |
| 9023 |
* @return int |
| 9024 |
*/ |
| 9025 |
public function getCompression() |
| 9026 |
{ |
| 9027 |
return $this->compression; |
| 9028 |
} |
| 9029 |
/** |
| 9030 |
* @return string |
| 9031 |
*/ |
| 9032 |
public function getAsString() |
| 9033 |
{ |
| 9034 |
return $this->imageString; |
| 9035 |
} |
| 9036 |
/** |
| 9037 |
* @param string $imageString |
| 9038 |
*/ |
| 9039 |
public function setImageString($imageString) |
| 9040 |
{ |
| 9041 |
$this->imageString = $imageString; |
| 9042 |
} |
| 9043 |
/** |
| 9044 |
* @param mixed $transformationString |
| 9045 |
*/ |
| 9046 |
public function setTransformationString($transformationString) |
| 9047 |
{ |
| 9048 |
$this->transformationString = $transformationString; |
| 9049 |
} |
| 9050 |
protected function __clone() |
| 9051 |
{ |
| 9052 |
$this->imageString = $this->transformationString; |
| 9053 |
} |
| 9054 |
} |
| 9055 |
namespace Kibo\Phast\Filters\Service; |
| 9056 |
|
| 9057 |
class CachingServiceFilter implements \Kibo\Phast\Services\ServiceFilter |
| 9058 |
{ |
| 9059 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 9060 |
/** |
| 9061 |
* @var Cache |
| 9062 |
*/ |
| 9063 |
private $cache; |
| 9064 |
/** |
| 9065 |
* @var CachedResultServiceFilter |
| 9066 |
*/ |
| 9067 |
private $cachedFilter; |
| 9068 |
/** |
| 9069 |
* @var Retriever |
| 9070 |
*/ |
| 9071 |
private $retriever; |
| 9072 |
/** |
| 9073 |
* CachingServiceFilter constructor. |
| 9074 |
* @param Cache $cache |
| 9075 |
* @param CachedResultServiceFilter $cachedFilter |
| 9076 |
* @param Retriever $retriever |
| 9077 |
*/ |
| 9078 |
public function __construct(\Kibo\Phast\Cache\Cache $cache, \Kibo\Phast\Filters\Service\CachedResultServiceFilter $cachedFilter, \Kibo\Phast\Retrievers\Retriever $retriever) |
| 9079 |
{ |
| 9080 |
$this->cache = $cache; |
| 9081 |
$this->cachedFilter = $cachedFilter; |
| 9082 |
$this->retriever = $retriever; |
| 9083 |
} |
| 9084 |
/** |
| 9085 |
* @param Resource $resource |
| 9086 |
* @param array $request |
| 9087 |
* @return Resource |
| 9088 |
* @throws CachedExceptionException |
| 9089 |
*/ |
| 9090 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9091 |
{ |
| 9092 |
$key = $this->cachedFilter->getCacheSalt($resource, $request); |
| 9093 |
$this->logger()->info('Trying to get {url} from cache', ['url' => (string) $resource->getUrl()]); |
| 9094 |
$result = $this->cache->get($key); |
| 9095 |
if (isset($result['encoding']) && $result['encoding'] != 'identity') { |
| 9096 |
$result = null; |
| 9097 |
} |
| 9098 |
if ($result && $this->checkDependencies($result)) { |
| 9099 |
return $this->deserializeCachedData($result); |
| 9100 |
} |
| 9101 |
try { |
| 9102 |
$result = $this->cachedFilter->apply($resource, $request); |
| 9103 |
$this->cache->set($key, $this->serializeResource($result)); |
| 9104 |
return $result; |
| 9105 |
} catch (\Exception $e) { |
| 9106 |
$cachingException = $this->serializeException($e); |
| 9107 |
$this->cache->set($key, $cachingException); |
| 9108 |
throw $this->deserializeException($cachingException); |
| 9109 |
} |
| 9110 |
} |
| 9111 |
private function checkDependencies(array $data) |
| 9112 |
{ |
| 9113 |
foreach ((array) @$data['dependencies'] as $dep) { |
| 9114 |
$url = \Kibo\Phast\ValueObjects\URL::fromString($dep['url']); |
| 9115 |
if ($this->retriever->getCacheSalt($url) >= $dep['cacheMarker']) { |
| 9116 |
return false; |
| 9117 |
} |
| 9118 |
} |
| 9119 |
return true; |
| 9120 |
} |
| 9121 |
private function deserializeCachedData(array $data) |
| 9122 |
{ |
| 9123 |
if ($data['dataType'] == 'exception') { |
| 9124 |
throw $this->deserializeException($data); |
| 9125 |
} |
| 9126 |
return $this->deserializeResource($data); |
| 9127 |
} |
| 9128 |
private function serializeResource(\Kibo\Phast\ValueObjects\Resource $resource) |
| 9129 |
{ |
| 9130 |
return ['dataType' => 'resource', 'url' => $resource->getUrl()->toString(), 'mimeType' => $resource->getMimeType(), 'blob' => base64_encode($resource->getContent()), 'dependencies' => $this->serializeDependencies($resource)]; |
| 9131 |
} |
| 9132 |
private function serializeDependencies(\Kibo\Phast\ValueObjects\Resource $resource) |
| 9133 |
{ |
| 9134 |
return array_map(function (\Kibo\Phast\ValueObjects\Resource $dep) { |
| 9135 |
return ['url' => $dep->getUrl()->toString(), 'cacheMarker' => $dep->getCacheSalt()]; |
| 9136 |
}, $resource->getDependencies()); |
| 9137 |
} |
| 9138 |
private function deserializeResource(array $data) |
| 9139 |
{ |
| 9140 |
$params = [\Kibo\Phast\ValueObjects\URL::fromString($data['url']), base64_decode($data['blob']), $data['mimeType']]; |
| 9141 |
return \Kibo\Phast\ValueObjects\Resource::makeWithContent(...$params); |
| 9142 |
} |
| 9143 |
private function serializeException(\Exception $e) |
| 9144 |
{ |
| 9145 |
return ['dataType' => 'exception', 'class' => get_class($e), 'msg' => $e->getMessage(), 'code' => $e->getCode()]; |
| 9146 |
} |
| 9147 |
private function deserializeException(array $data) |
| 9148 |
{ |
| 9149 |
return new \Kibo\Phast\Exceptions\CachedExceptionException(sprintf('Phast: %s: Type: %s, Msg: %s, Code: %s', static::class, $data['class'], $data['msg'], $data['code'])); |
| 9150 |
} |
| 9151 |
} |
| 9152 |
namespace Kibo\Phast\Filters\Service; |
| 9153 |
|
| 9154 |
interface CachedResultServiceFilter extends \Kibo\Phast\Services\ServiceFilter |
| 9155 |
{ |
| 9156 |
/** |
| 9157 |
* @param Resource $resource |
| 9158 |
* @param array $request |
| 9159 |
* @return string |
| 9160 |
*/ |
| 9161 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\Resource $resource, array $request); |
| 9162 |
} |
| 9163 |
namespace Kibo\Phast\Filters\Service; |
| 9164 |
|
| 9165 |
class CompositeFilter implements \Kibo\Phast\Filters\Service\CachedResultServiceFilter |
| 9166 |
{ |
| 9167 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 9168 |
/** |
| 9169 |
* @var ServiceFilter[] |
| 9170 |
*/ |
| 9171 |
private $filters = array(); |
| 9172 |
public function addFilter(\Kibo\Phast\Services\ServiceFilter $filter) |
| 9173 |
{ |
| 9174 |
$this->filters[] = $filter; |
| 9175 |
} |
| 9176 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9177 |
{ |
| 9178 |
$classes = array_map('get_class', $this->filters); |
| 9179 |
$cached = array_filter($this->filters, function (\Kibo\Phast\Services\ServiceFilter $filter) { |
| 9180 |
return $filter instanceof \Kibo\Phast\Filters\Service\CachedResultServiceFilter; |
| 9181 |
}); |
| 9182 |
$salts = array_map(function (\Kibo\Phast\Filters\Service\CachedResultServiceFilter $filter) use($resource, $request) { |
| 9183 |
return $filter->getCacheSalt($resource, $request); |
| 9184 |
}, $cached); |
| 9185 |
return join("\n", array_merge($classes, $salts, [$resource->getUrl(), $resource->getCacheSalt()])); |
| 9186 |
} |
| 9187 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9188 |
{ |
| 9189 |
$this->logger()->info('Starting filtering for resource {url}', ['url' => $resource->getUrl()]); |
| 9190 |
$result = array_reduce($this->filters, function (\Kibo\Phast\ValueObjects\Resource $resource, \Kibo\Phast\Services\ServiceFilter $filter) use($request) { |
| 9191 |
$this->logger()->info('Starting {filter}', ['filter' => get_class($filter)]); |
| 9192 |
try { |
| 9193 |
return $filter->apply($resource, $request); |
| 9194 |
} catch (\Kibo\Phast\Exceptions\RuntimeException $e) { |
| 9195 |
$message = 'Phast RuntimeException: Filter: {filter} Exception: {exceptionClass} Msg: {message} Code: {code} File: {file} Line: {line}'; |
| 9196 |
$this->logger()->critical($message, ['filter' => get_class($filter), 'exceptionClass' => get_class($e), 'message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile(), 'line' => $e->getLine()]); |
| 9197 |
return $resource; |
| 9198 |
} |
| 9199 |
}, $resource); |
| 9200 |
$this->logger()->info('Done filtering for resource {url}', ['url' => $resource->getUrl()]); |
| 9201 |
return $result; |
| 9202 |
} |
| 9203 |
} |
| 9204 |
namespace Kibo\Phast\Filters\CSS\CSSMinifier; |
| 9205 |
|
| 9206 |
class Filter implements \Kibo\Phast\Services\ServiceFilter |
| 9207 |
{ |
| 9208 |
/** |
| 9209 |
* @param Resource $resource |
| 9210 |
* @param array $request |
| 9211 |
* @return Resource |
| 9212 |
*/ |
| 9213 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9214 |
{ |
| 9215 |
$content = $resource->getContent(); |
| 9216 |
// Normalize whitespace |
| 9217 |
$content = preg_replace('~\\s+~', ' ', $content); |
| 9218 |
// Remove whitespace before and after operators |
| 9219 |
$chars = [',', '{', '}', ';']; |
| 9220 |
foreach ($chars as $char) { |
| 9221 |
$content = str_replace("{$char} ", $char, $content); |
| 9222 |
$content = str_replace(" {$char}", $char, $content); |
| 9223 |
} |
| 9224 |
// Remove whitespace after colons |
| 9225 |
$content = str_replace(': ', ':', $content); |
| 9226 |
return $resource->withContent(trim($content)); |
| 9227 |
} |
| 9228 |
} |
| 9229 |
namespace Kibo\Phast\Filters\CSS\CSSURLRewriter; |
| 9230 |
|
| 9231 |
class Filter implements \Kibo\Phast\Services\ServiceFilter |
| 9232 |
{ |
| 9233 |
/** |
| 9234 |
* @param Resource $resource |
| 9235 |
* @param array $request |
| 9236 |
* @return Resource |
| 9237 |
*/ |
| 9238 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9239 |
{ |
| 9240 |
$baseUrl = $resource->getUrl(); |
| 9241 |
$callback = function ($match) use($baseUrl) { |
| 9242 |
if (preg_match('~^[a-z]+:|^#~i', $match[3])) { |
| 9243 |
return $match[0]; |
| 9244 |
} |
| 9245 |
return $match[1] . \Kibo\Phast\ValueObjects\URL::fromString($match[3])->withBase($baseUrl) . $match[4]; |
| 9246 |
}; |
| 9247 |
$cssContent = preg_replace_callback('~ |
| 9248 |
\\b |
| 9249 |
( url\\( ([\'"]?) ) |
| 9250 |
([A-Za-z0-9_/.:?&=+%,#@-]+) |
| 9251 |
( \\2 \\) ) |
| 9252 |
~x', $callback, $resource->getContent()); |
| 9253 |
$cssContent = preg_replace_callback('~ |
| 9254 |
( @import \\s+ ([\'"]) ) |
| 9255 |
([A-Za-z0-9_/.:?&=+%,#@-]+) |
| 9256 |
( \\2 ) |
| 9257 |
~x', $callback, $cssContent); |
| 9258 |
return $resource->withContent($cssContent); |
| 9259 |
} |
| 9260 |
} |
| 9261 |
namespace Kibo\Phast\Filters\CSS\ImageURLRewriter; |
| 9262 |
|
| 9263 |
class Filter implements \Kibo\Phast\Filters\Service\CachedResultServiceFilter |
| 9264 |
{ |
| 9265 |
/** |
| 9266 |
* @var ImageURLRewriter |
| 9267 |
*/ |
| 9268 |
private $rewriter; |
| 9269 |
/** |
| 9270 |
* Filter constructor. |
| 9271 |
* @param ImageURLRewriter $rewriter |
| 9272 |
*/ |
| 9273 |
public function __construct(\Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageURLRewriter $rewriter) |
| 9274 |
{ |
| 9275 |
$this->rewriter = $rewriter; |
| 9276 |
} |
| 9277 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9278 |
{ |
| 9279 |
return $this->rewriter->getCacheSalt(); |
| 9280 |
} |
| 9281 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9282 |
{ |
| 9283 |
$content = $this->rewriter->rewriteStyle($resource->getContent()); |
| 9284 |
$dependencies = $this->rewriter->getInlinedResources(); |
| 9285 |
return $resource->withContent($content)->withDependencies($dependencies); |
| 9286 |
} |
| 9287 |
} |
| 9288 |
namespace Kibo\Phast\Filters\CSS\Composite; |
| 9289 |
|
| 9290 |
class Filter extends \Kibo\Phast\Filters\Service\CompositeFilter implements \Kibo\Phast\Filters\Service\CachedResultServiceFilter |
| 9291 |
{ |
| 9292 |
public function __construct() |
| 9293 |
{ |
| 9294 |
$this->addFilter(new \Kibo\Phast\Filters\CSS\CommentsRemoval\Filter()); |
| 9295 |
} |
| 9296 |
} |
| 9297 |
namespace Kibo\Phast\Filters\CSS\FontSwap; |
| 9298 |
|
| 9299 |
class Filter implements \Kibo\Phast\Services\ServiceFilter |
| 9300 |
{ |
| 9301 |
const FONT_FACE_REGEXP = '/(@font-face\\s*\\{)([^}]*)/i'; |
| 9302 |
const ICON_FONT_FAMILIES = array('Font Awesome', 'GeneratePress', 'Dashicons', 'Ionicons'); |
| 9303 |
private $fontDisplayBlockPattern; |
| 9304 |
public function __construct() |
| 9305 |
{ |
| 9306 |
$this->fontDisplayBlockPattern = $this->getFontDisplayBlockPattern(); |
| 9307 |
} |
| 9308 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9309 |
{ |
| 9310 |
$css = $resource->getContent(); |
| 9311 |
$filtered = preg_replace_callback(self::FONT_FACE_REGEXP, function ($match) { |
| 9312 |
list($block, $start, $contents) = $match; |
| 9313 |
$mode = preg_match($this->fontDisplayBlockPattern, $contents) ? 'block' : 'swap'; |
| 9314 |
return $start . 'font-display:' . $mode . ';' . $contents; |
| 9315 |
}, $css); |
| 9316 |
return $resource->withContent($filtered); |
| 9317 |
} |
| 9318 |
private function getFontDisplayBlockPattern() |
| 9319 |
{ |
| 9320 |
$patterns = []; |
| 9321 |
foreach (self::ICON_FONT_FAMILIES as $family) { |
| 9322 |
$chars = str_split($family); |
| 9323 |
$chars = array_map(function ($char) { |
| 9324 |
return preg_quote($char, '~'); |
| 9325 |
}, $chars); |
| 9326 |
$patterns[] = implode('\\s*', $chars); |
| 9327 |
} |
| 9328 |
return '~' . implode('|', $patterns) . '~i'; |
| 9329 |
} |
| 9330 |
} |
| 9331 |
namespace Kibo\Phast\Filters\CSS\ImportsStripper; |
| 9332 |
|
| 9333 |
class Filter implements \Kibo\Phast\Filters\Service\CachedResultServiceFilter |
| 9334 |
{ |
| 9335 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 9336 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9337 |
{ |
| 9338 |
return $this->shouldStripImports($request) ? 'strip-imports' : 'no-strip-imports'; |
| 9339 |
} |
| 9340 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9341 |
{ |
| 9342 |
if (!$this->shouldStripImports($request)) { |
| 9343 |
$this->logger()->info('No import stripping requested! Skipping!'); |
| 9344 |
return $resource; |
| 9345 |
} |
| 9346 |
$css = $resource->getContent(); |
| 9347 |
$stripped = preg_replace(\Kibo\Phast\Filters\HTML\CSSInlining\Filter::CSS_IMPORTS_REGEXP, '', $css); |
| 9348 |
return $resource->withContent($stripped); |
| 9349 |
} |
| 9350 |
private function shouldStripImports(array $request) |
| 9351 |
{ |
| 9352 |
return isset($request['strip-imports']); |
| 9353 |
} |
| 9354 |
} |
| 9355 |
namespace Kibo\Phast\Filters\CSS\CommentsRemoval; |
| 9356 |
|
| 9357 |
class Filter implements \Kibo\Phast\Services\ServiceFilter |
| 9358 |
{ |
| 9359 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9360 |
{ |
| 9361 |
$content = preg_replace('~/\\*[^*]*\\*+([^/*][^*]*\\*+)*/~', '', $resource->getContent()); |
| 9362 |
return $resource->withContent($content); |
| 9363 |
} |
| 9364 |
} |
| 9365 |
namespace Kibo\Phast\Filters\Text\Decode; |
| 9366 |
|
| 9367 |
class Filter implements \Kibo\Phast\Services\ServiceFilter |
| 9368 |
{ |
| 9369 |
const UTF8_BOM = "\357\273\277"; |
| 9370 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request = array()) |
| 9371 |
{ |
| 9372 |
$content = $resource->getContent(); |
| 9373 |
if (substr($content, 0, strlen(self::UTF8_BOM)) == self::UTF8_BOM) { |
| 9374 |
$content = substr($content, strlen(self::UTF8_BOM)); |
| 9375 |
} |
| 9376 |
return $resource->withContent($content); |
| 9377 |
} |
| 9378 |
} |
| 9379 |
namespace Kibo\Phast\Parsing\HTML\HTMLStreamElements; |
| 9380 |
|
| 9381 |
class ClosingTag extends \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Element |
| 9382 |
{ |
| 9383 |
/** |
| 9384 |
* @var string |
| 9385 |
*/ |
| 9386 |
private $tagName; |
| 9387 |
/** |
| 9388 |
* ClosingTag constructor. |
| 9389 |
* @param string $tagName |
| 9390 |
*/ |
| 9391 |
public function __construct($tagName) |
| 9392 |
{ |
| 9393 |
$this->tagName = strtolower($tagName); |
| 9394 |
} |
| 9395 |
/** |
| 9396 |
* @return string |
| 9397 |
*/ |
| 9398 |
public function getTagName() |
| 9399 |
{ |
| 9400 |
return $this->tagName; |
| 9401 |
} |
| 9402 |
public function appendChild(\Kibo\Phast\Parsing\HTML\HTMLStreamElements\Element $element) |
| 9403 |
{ |
| 9404 |
$this->stream->insertBeforeElement($this, $element); |
| 9405 |
} |
| 9406 |
public function dumpValue() |
| 9407 |
{ |
| 9408 |
return $this->tagName; |
| 9409 |
} |
| 9410 |
} |
| 9411 |
namespace Kibo\Phast\Parsing\HTML\HTMLStreamElements; |
| 9412 |
|
| 9413 |
class Tag extends \Kibo\Phast\Parsing\HTML\HTMLStreamElements\Element |
| 9414 |
{ |
| 9415 |
/** |
| 9416 |
* @var string |
| 9417 |
*/ |
| 9418 |
private $tagName; |
| 9419 |
/** |
| 9420 |
* @var array |
| 9421 |
*/ |
| 9422 |
private $attributes = array(); |
| 9423 |
/** |
| 9424 |
* @var array |
| 9425 |
*/ |
| 9426 |
private $newAttributes = array(); |
| 9427 |
/** |
| 9428 |
* @var \Iterator |
| 9429 |
*/ |
| 9430 |
private $attributeReader; |
| 9431 |
/** |
| 9432 |
* @var string |
| 9433 |
*/ |
| 9434 |
private $textContent = ''; |
| 9435 |
/** |
| 9436 |
* @var string |
| 9437 |
*/ |
| 9438 |
private $closingTag = ''; |
| 9439 |
private $dirty = false; |
| 9440 |
/** |
| 9441 |
* Tag constructor. |
| 9442 |
* @param $tagName |
| 9443 |
* @param array|\Traversable $attributes |
| 9444 |
*/ |
| 9445 |
public function __construct($tagName, $attributes = array()) |
| 9446 |
{ |
| 9447 |
$this->tagName = strtolower($tagName); |
| 9448 |
if ($attributes instanceof \Iterator) { |
| 9449 |
$this->attributeReader = $attributes; |
| 9450 |
} elseif (is_array($attributes)) { |
| 9451 |
$this->attributeReader = new \ArrayIterator($attributes); |
| 9452 |
} else { |
| 9453 |
throw new \InvalidArgumentException('Attributes must be array or Iterator'); |
| 9454 |
} |
| 9455 |
} |
| 9456 |
/** |
| 9457 |
* @return string |
| 9458 |
*/ |
| 9459 |
public function getTagName() |
| 9460 |
{ |
| 9461 |
return $this->tagName; |
| 9462 |
} |
| 9463 |
/** |
| 9464 |
* @param string $attrName |
| 9465 |
* @return bool |
| 9466 |
*/ |
| 9467 |
public function hasAttribute($attrName) |
| 9468 |
{ |
| 9469 |
return $this->getAttribute($attrName) !== null; |
| 9470 |
} |
| 9471 |
/** |
| 9472 |
* @param string $attrName |
| 9473 |
* @return mixed|null |
| 9474 |
*/ |
| 9475 |
public function getAttribute($attrName) |
| 9476 |
{ |
| 9477 |
if (array_key_exists($attrName, $this->newAttributes)) { |
| 9478 |
return $this->newAttributes[$attrName]; |
| 9479 |
} |
| 9480 |
if (!array_key_exists($attrName, $this->attributes)) { |
| 9481 |
$this->readUntilAttribute($attrName); |
| 9482 |
} |
| 9483 |
if (isset($this->attributes[$attrName])) { |
| 9484 |
return $this->attributes[$attrName]; |
| 9485 |
} |
| 9486 |
} |
| 9487 |
/** @return string[] */ |
| 9488 |
public function getAttributes() |
| 9489 |
{ |
| 9490 |
$this->readUntilAttribute(null); |
| 9491 |
return array_filter($this->newAttributes + $this->attributes, function ($value) { |
| 9492 |
return $value !== null; |
| 9493 |
}); |
| 9494 |
} |
| 9495 |
private function readUntilAttribute($attrName) |
| 9496 |
{ |
| 9497 |
if (!$this->attributeReader) { |
| 9498 |
return; |
| 9499 |
} |
| 9500 |
while ($this->attributeReader->valid()) { |
| 9501 |
$name = strtolower($this->attributeReader->key()); |
| 9502 |
$value = $this->attributeReader->current(); |
| 9503 |
$this->attributeReader->next(); |
| 9504 |
if (!isset($this->attributes[$name])) { |
| 9505 |
$this->attributes[$name] = $value; |
| 9506 |
} |
| 9507 |
if ($name == $attrName) { |
| 9508 |
return; |
| 9509 |
} |
| 9510 |
} |
| 9511 |
$this->attributeReader = null; |
| 9512 |
} |
| 9513 |
/** |
| 9514 |
* @param string $attrName |
| 9515 |
* @param string $value |
| 9516 |
*/ |
| 9517 |
public function setAttribute($attrName, $value) |
| 9518 |
{ |
| 9519 |
if ($this->getAttribute($attrName) === $value) { |
| 9520 |
return; |
| 9521 |
} |
| 9522 |
$this->dirty = true; |
| 9523 |
$this->newAttributes[$attrName] = $value; |
| 9524 |
} |
| 9525 |
/** |
| 9526 |
* @param string $attrName |
| 9527 |
*/ |
| 9528 |
public function removeAttribute($attrName) |
| 9529 |
{ |
| 9530 |
$this->dirty = true; |
| 9531 |
$this->newAttributes[$attrName] = null; |
| 9532 |
} |
| 9533 |
/** |
| 9534 |
* @return string |
| 9535 |
*/ |
| 9536 |
public function getTextContent() |
| 9537 |
{ |
| 9538 |
return $this->textContent; |
| 9539 |
} |
| 9540 |
/** |
| 9541 |
* @param string $textContent |
| 9542 |
*/ |
| 9543 |
public function setTextContent($textContent) |
| 9544 |
{ |
| 9545 |
$this->textContent = $textContent; |
| 9546 |
} |
| 9547 |
/** |
| 9548 |
* @param $closingTag |
| 9549 |
* @return Tag |
| 9550 |
*/ |
| 9551 |
public function withClosingTag($closingTag) |
| 9552 |
{ |
| 9553 |
$new = clone $this; |
| 9554 |
$new->closingTag = $closingTag; |
| 9555 |
return $new; |
| 9556 |
} |
| 9557 |
/** |
| 9558 |
* @return string |
| 9559 |
*/ |
| 9560 |
public function getClosingTag() |
| 9561 |
{ |
| 9562 |
return $this->closingTag; |
| 9563 |
} |
| 9564 |
public function __toString() |
| 9565 |
{ |
| 9566 |
return $this->getOpening() . $this->textContent . $this->getClosing(); |
| 9567 |
} |
| 9568 |
private function getOpening() |
| 9569 |
{ |
| 9570 |
if ($this->dirty || !isset($this->originalString)) { |
| 9571 |
return $this->generateOpeningTag(); |
| 9572 |
} |
| 9573 |
return parent::__toString(); |
| 9574 |
} |
| 9575 |
private function getClosing() |
| 9576 |
{ |
| 9577 |
if ($this->closingTag) { |
| 9578 |
return $this->closingTag; |
| 9579 |
} |
| 9580 |
if ($this->mustHaveClosing() && !$this->isFromParser()) { |
| 9581 |
return '</' . $this->tagName . '>'; |
| 9582 |
} |
| 9583 |
return ''; |
| 9584 |
} |
| 9585 |
private function generateOpeningTag() |
| 9586 |
{ |
| 9587 |
$parts = ['<' . $this->tagName]; |
| 9588 |
foreach ($this->getAttributes() as $name => $value) { |
| 9589 |
$parts[] = $this->generateAttribute($name, $value); |
| 9590 |
} |
| 9591 |
return join(' ', $parts) . '>'; |
| 9592 |
} |
| 9593 |
private function generateAttribute($name, $value) |
| 9594 |
{ |
| 9595 |
$result = $name; |
| 9596 |
if ($value != '') { |
| 9597 |
$result .= '=' . $this->quoteAttributeValue($value); |
| 9598 |
} |
| 9599 |
return $result; |
| 9600 |
} |
| 9601 |
private function quoteAttributeValue($value) |
| 9602 |
{ |
| 9603 |
if (strpos($value, '"') === false) { |
| 9604 |
return '"' . htmlspecialchars($value) . '"'; |
| 9605 |
} |
| 9606 |
return "'" . str_replace(['&', "'"], ['&', '''], $value) . "'"; |
| 9607 |
} |
| 9608 |
private function mustHaveClosing() |
| 9609 |
{ |
| 9610 |
return !\Kibo\Phast\Parsing\HTML\HTMLInfo::isA($this->tagName, \Kibo\Phast\Parsing\HTML\HTMLInfo::VOID_TAG); |
| 9611 |
} |
| 9612 |
private function isFromParser() |
| 9613 |
{ |
| 9614 |
return isset($this->originalString); |
| 9615 |
} |
| 9616 |
public function dumpValue() |
| 9617 |
{ |
| 9618 |
$o = $this->tagName; |
| 9619 |
foreach ($this->attributes as $name => $_) { |
| 9620 |
$o .= " {$name}=\"" . $this->getAttribute($name) . '"'; |
| 9621 |
} |
| 9622 |
if ($this->textContent) { |
| 9623 |
$o .= " content=[{$this->textContent}]"; |
| 9624 |
} |
| 9625 |
return $o; |
| 9626 |
} |
| 9627 |
} |
| 9628 |
namespace Kibo\Phast\Logging\LogWriters\Dummy; |
| 9629 |
|
| 9630 |
class Writer implements \Kibo\Phast\Logging\LogWriter |
| 9631 |
{ |
| 9632 |
public function setLevelMask($mask) |
| 9633 |
{ |
| 9634 |
} |
| 9635 |
public function writeEntry(\Kibo\Phast\Logging\LogEntry $entry) |
| 9636 |
{ |
| 9637 |
} |
| 9638 |
} |
| 9639 |
namespace Kibo\Phast\Logging\LogWriters; |
| 9640 |
|
| 9641 |
abstract class BaseLogWriter implements \Kibo\Phast\Logging\LogWriter |
| 9642 |
{ |
| 9643 |
protected $levelMask = ~0; |
| 9644 |
protected abstract function doWriteEntry(\Kibo\Phast\Logging\LogEntry $entry); |
| 9645 |
public function setLevelMask($mask) |
| 9646 |
{ |
| 9647 |
$this->levelMask = $mask; |
| 9648 |
} |
| 9649 |
public function writeEntry(\Kibo\Phast\Logging\LogEntry $entry) |
| 9650 |
{ |
| 9651 |
if ($this->levelMask & $entry->getLevel()) { |
| 9652 |
$this->doWriteEntry($entry); |
| 9653 |
} |
| 9654 |
} |
| 9655 |
} |
| 9656 |
namespace Kibo\Phast\Services\Css; |
| 9657 |
|
| 9658 |
class Service extends \Kibo\Phast\Services\BaseService |
| 9659 |
{ |
| 9660 |
protected function makeResponse(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9661 |
{ |
| 9662 |
$response = parent::makeResponse($resource, $request); |
| 9663 |
$response->setHeader('Content-Type', 'text/css'); |
| 9664 |
return $response; |
| 9665 |
} |
| 9666 |
} |
| 9667 |
namespace Kibo\Phast\Services\Scripts; |
| 9668 |
|
| 9669 |
class Service extends \Kibo\Phast\Services\BaseService |
| 9670 |
{ |
| 9671 |
protected function makeResponse(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9672 |
{ |
| 9673 |
$response = parent::makeResponse($resource, $request); |
| 9674 |
$response->setHeader('Content-Type', 'application/javascript'); |
| 9675 |
return $response; |
| 9676 |
} |
| 9677 |
} |
| 9678 |
namespace Kibo\Phast\Services\Images; |
| 9679 |
|
| 9680 |
class Service extends \Kibo\Phast\Services\BaseService |
| 9681 |
{ |
| 9682 |
protected function getParams(\Kibo\Phast\Services\ServiceRequest $request) |
| 9683 |
{ |
| 9684 |
$params = parent::getParams($request); |
| 9685 |
if ($this->proxySupportsAccept($request->getHTTPRequest())) { |
| 9686 |
$params['varyAccept'] = true; |
| 9687 |
if ($this->browserSupportsWebp($request->getHTTPRequest())) { |
| 9688 |
$params['preferredType'] = \Kibo\Phast\Filters\Image\Image::TYPE_WEBP; |
| 9689 |
\Kibo\Phast\Logging\Log::info('WebP will be served if possible!'); |
| 9690 |
} |
| 9691 |
} |
| 9692 |
return $params; |
| 9693 |
} |
| 9694 |
protected function makeResponse(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9695 |
{ |
| 9696 |
$response = parent::makeResponse($resource, $request); |
| 9697 |
$srcUrl = $resource->getUrl(); |
| 9698 |
$response->setHeader('Link', "<{$srcUrl}>; rel=\"canonical\""); |
| 9699 |
$response->setHeader('Content-Type', $resource->getMimeType()); |
| 9700 |
if ($resource->getMimeType() != \Kibo\Phast\Filters\Image\Image::TYPE_PNG && @$request['varyAccept']) { |
| 9701 |
$response->setHeader('Vary', 'Accept'); |
| 9702 |
} |
| 9703 |
return $response; |
| 9704 |
} |
| 9705 |
protected function validateIntegrity(\Kibo\Phast\Services\ServiceRequest $request) |
| 9706 |
{ |
| 9707 |
if (!$this->config['images']['api-mode']) { |
| 9708 |
parent::validateIntegrity($request); |
| 9709 |
} |
| 9710 |
} |
| 9711 |
protected function validateWhitelisted(\Kibo\Phast\Services\ServiceRequest $request) |
| 9712 |
{ |
| 9713 |
if (!$this->config['images']['api-mode']) { |
| 9714 |
parent::validateWhitelisted($request); |
| 9715 |
} |
| 9716 |
} |
| 9717 |
private function browserSupportsWebp(\Kibo\Phast\HTTP\Request $request) |
| 9718 |
{ |
| 9719 |
return strpos($request->getHeader('accept'), 'image/webp') !== false; |
| 9720 |
} |
| 9721 |
private function proxySupportsAccept(\Kibo\Phast\HTTP\Request $request) |
| 9722 |
{ |
| 9723 |
return !$request->isCloudflare(); |
| 9724 |
} |
| 9725 |
} |
| 9726 |
namespace Kibo\PhastPlugins\SDK\AdminPanel; |
| 9727 |
|
| 9728 |
class DefaultInstallNoticeRenderer implements \Kibo\PhastPlugins\SDK\AdminPanel\InstallNoticeRenderer |
| 9729 |
{ |
| 9730 |
public function render($notice, $onCloseJSFunction) |
| 9731 |
{ |
| 9732 |
return $notice; |
| 9733 |
} |
| 9734 |
} |
| 9735 |
namespace Kibo\PhastPlugins\SDK; |
| 9736 |
|
| 9737 |
interface PluginHost extends \Kibo\PhastPlugins\SDK\ServiceHost |
| 9738 |
{ |
| 9739 |
/** |
| 9740 |
* The name of the plugin used for displaying to the users |
| 9741 |
* |
| 9742 |
* @return string |
| 9743 |
*/ |
| 9744 |
public function getPluginName(); |
| 9745 |
/** |
| 9746 |
* The name of the host system |
| 9747 |
* |
| 9748 |
* @return string |
| 9749 |
*/ |
| 9750 |
public function getPluginHostName(); |
| 9751 |
/** |
| 9752 |
* The version of the plugin |
| 9753 |
* |
| 9754 |
* @return string |
| 9755 |
*/ |
| 9756 |
public function getPluginHostVersion(); |
| 9757 |
/** |
| 9758 |
* Tells whether we are in production or development mode. |
| 9759 |
* In development mode static files will be loaded from a dev server. |
| 9760 |
* In production mode static files will be loaded from a prebuilt source. |
| 9761 |
* |
| 9762 |
* @return bool - TRUE for development, FALSE for production |
| 9763 |
*/ |
| 9764 |
public function isDev(); |
| 9765 |
/** |
| 9766 |
* @return KeyValueStore |
| 9767 |
*/ |
| 9768 |
public function getKeyValueStore(); |
| 9769 |
/** |
| 9770 |
* @return InstallNoticeRenderer |
| 9771 |
*/ |
| 9772 |
public function getInstallNoticeRenderer(); |
| 9773 |
/** |
| 9774 |
* @return HostURLs |
| 9775 |
*/ |
| 9776 |
public function getHostURLs(); |
| 9777 |
/** |
| 9778 |
* @return Nonce |
| 9779 |
*/ |
| 9780 |
public function getNonce(); |
| 9781 |
/** |
| 9782 |
* @return NonceChecker |
| 9783 |
*/ |
| 9784 |
public function getNonceChecker(); |
| 9785 |
/** |
| 9786 |
* @return PhastUser |
| 9787 |
*/ |
| 9788 |
public function getPhastUser(); |
| 9789 |
/** |
| 9790 |
* Called right after phast's configuration |
| 9791 |
* has been loaded. Use it to modify the config |
| 9792 |
* and take any other needed action before |
| 9793 |
* the filters are applied. |
| 9794 |
* |
| 9795 |
* @param array $config - The configuration that has been loaded |
| 9796 |
* @return array - The configuration to use for phast |
| 9797 |
*/ |
| 9798 |
public function onPhastConfigurationLoad(array $config); |
| 9799 |
/** |
| 9800 |
* Returns the current system locale |
| 9801 |
* |
| 9802 |
* @return string |
| 9803 |
*/ |
| 9804 |
public function getLocale(); |
| 9805 |
} |
| 9806 |
namespace Kibo\Phast\Filters\JavaScript\Minification; |
| 9807 |
|
| 9808 |
class JSMinifierFilter implements \Kibo\Phast\Filters\Service\CachedResultServiceFilter |
| 9809 |
{ |
| 9810 |
const VERSION = 2; |
| 9811 |
private $removeLicenseHeaders = true; |
| 9812 |
/** |
| 9813 |
* JSMinifierFilter constructor. |
| 9814 |
* @param bool $removeLicenseHeaders |
| 9815 |
*/ |
| 9816 |
public function __construct($removeLicenseHeaders) |
| 9817 |
{ |
| 9818 |
$this->removeLicenseHeaders = (bool) $removeLicenseHeaders; |
| 9819 |
} |
| 9820 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9821 |
{ |
| 9822 |
return http_build_query(['v' => self::VERSION, 'removeLicenseHeaders' => $this->removeLicenseHeaders]); |
| 9823 |
} |
| 9824 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9825 |
{ |
| 9826 |
$minified = (new \Kibo\Phast\Common\JSMinifier($resource->getContent(), $this->removeLicenseHeaders))->min(); |
| 9827 |
return $resource->withContent($minified); |
| 9828 |
} |
| 9829 |
} |
| 9830 |
namespace Kibo\Phast\Filters\Image\Composite; |
| 9831 |
|
| 9832 |
class Filter implements \Kibo\Phast\Filters\Service\CachedResultServiceFilter |
| 9833 |
{ |
| 9834 |
use \Kibo\Phast\Logging\LoggingTrait; |
| 9835 |
/** |
| 9836 |
* @var ImageFactory |
| 9837 |
*/ |
| 9838 |
private $imageFactory; |
| 9839 |
/** |
| 9840 |
* @var ImageInliningManager |
| 9841 |
*/ |
| 9842 |
private $inliningManager; |
| 9843 |
/** |
| 9844 |
* @var ImageFilter[] |
| 9845 |
*/ |
| 9846 |
private $filters = array(); |
| 9847 |
/** |
| 9848 |
* Filter constructor. |
| 9849 |
* @param ImageFactory $imageFactory |
| 9850 |
* @param ImageInliningManager $inliningManager |
| 9851 |
*/ |
| 9852 |
public function __construct(\Kibo\Phast\Filters\Image\ImageFactory $imageFactory, \Kibo\Phast\Filters\HTML\ImagesOptimizationService\ImageInliningManager $inliningManager) |
| 9853 |
{ |
| 9854 |
$this->imageFactory = $imageFactory; |
| 9855 |
$this->inliningManager = $inliningManager; |
| 9856 |
} |
| 9857 |
public function addImageFilter(\Kibo\Phast\Filters\Image\ImageFilter $filter) |
| 9858 |
{ |
| 9859 |
$this->filters[] = $filter; |
| 9860 |
} |
| 9861 |
public function getCacheSalt(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9862 |
{ |
| 9863 |
$filters = array_map('get_class', $this->filters); |
| 9864 |
$salts = array_map(function (\Kibo\Phast\Filters\Image\ImageFilter $filter) use($request) { |
| 9865 |
return $filter->getCacheSalt($request); |
| 9866 |
}, $this->filters); |
| 9867 |
return implode("\n", array_merge($filters, $salts, [$this->inliningManager->getMaxImageInliningSize(), $resource->getUrl(), $resource->getCacheSalt()])); |
| 9868 |
} |
| 9869 |
/** |
| 9870 |
* @param Resource $resource |
| 9871 |
* @param array $request |
| 9872 |
* @return Resource |
| 9873 |
*/ |
| 9874 |
public function apply(\Kibo\Phast\ValueObjects\Resource $resource, array $request) |
| 9875 |
{ |
| 9876 |
$image = $this->imageFactory->getForResource($resource); |
| 9877 |
$filteredImage = $image; |
| 9878 |
foreach ($this->filters as $filter) { |
| 9879 |
$this->logger()->info('Applying {filter}', ['filter' => get_class($filter)]); |
| 9880 |
try { |
| 9881 |
$filteredImage = $filter->transformImage($filteredImage, $request); |
| 9882 |
} catch (\Kibo\Phast\Filters\Image\Exceptions\ImageProcessingException $e) { |
| 9883 |
$message = 'Image filter exception: Filter: {filter} Exception: {exceptionClass} Msg: {message} Code: {code} File: {file} Line: {line}'; |
| 9884 |
$this->logger()->critical($message, ['filter' => get_class($filter), 'exceptionClass' => get_class($e), 'message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile(), 'line' => $e->getLine()]); |
| 9885 |
} |
| 9886 |
} |
| 9887 |
$sizeBefore = $filteredImage->getSizeAsString(); |
| 9888 |
$sizeAfter = $image->getSizeAsString(); |
| 9889 |
$sizeDifference = $sizeBefore - $sizeAfter; |
| 9890 |
$this->logger()->info('Image processed. Size before/after: {sizeBefore}/{sizeAfter} ({sizeDifference})', ['sizeBefore' => $sizeBefore, 'sizeAfter' => $sizeAfter, 'sizeDifference' => $sizeDifference < 0 ? $sizeDifference : "+{$sizeDifference}"]); |
| 9891 |
if ($sizeDifference < 0) { |
| 9892 |
$this->logger()->info('Return filtered image and save {sizeDifference} bytes', ['sizeDifference' => -$sizeDifference]); |
| 9893 |
$image = $filteredImage; |
| 9894 |
} else { |
| 9895 |
$this->logger()->info('Return original image'); |
| 9896 |
} |
| 9897 |
$processedResource = $resource->withContent($image->getAsString(), $image->getType()); |
| 9898 |
$this->inliningManager->maybeStoreForInlining($processedResource); |
| 9899 |
return $processedResource; |
| 9900 |
} |
| 9901 |
} |
| 9902 |
namespace Kibo\Phast\Logging\LogWriters\JSONLFile; |
| 9903 |
|
| 9904 |
class Writer extends \Kibo\Phast\Logging\LogWriters\BaseLogWriter |
| 9905 |
{ |
| 9906 |
use \Kibo\Phast\Logging\Common\JSONLFileLogTrait; |
| 9907 |
/** |
| 9908 |
* @param LogEntry $entry |
| 9909 |
*/ |
| 9910 |
protected function doWriteEntry(\Kibo\Phast\Logging\LogEntry $entry) |
| 9911 |
{ |
| 9912 |
$encoded = @\Kibo\Phast\Common\JSON::encode($entry->toArray()); |
| 9913 |
if ($encoded) { |
| 9914 |
$this->makeDirIfNotExists(); |
| 9915 |
@file_put_contents($this->filename, $encoded . "\n", FILE_APPEND | LOCK_EX); |
| 9916 |
} |
| 9917 |
} |
| 9918 |
private function makeDirIfNotExists() |
| 9919 |
{ |
| 9920 |
if (!@file_exists($this->dir)) { |
| 9921 |
@mkdir($this->dir, 0777, true); |
| 9922 |
} |
| 9923 |
} |
| 9924 |
} |
| 9925 |
namespace Kibo\Phast\Logging\LogWriters\Composite; |
| 9926 |
|
| 9927 |
class Writer extends \Kibo\Phast\Logging\LogWriters\BaseLogWriter |
| 9928 |
{ |
| 9929 |
/** |
| 9930 |
* @var Writer[] |
| 9931 |
*/ |
| 9932 |
private $writers = array(); |
| 9933 |
public function addWriter(\Kibo\Phast\Logging\LogWriter $writer) |
| 9934 |
{ |
| 9935 |
$this->writers[] = $writer; |
| 9936 |
} |
| 9937 |
protected function doWriteEntry(\Kibo\Phast\Logging\LogEntry $entry) |
| 9938 |
{ |
| 9939 |
foreach ($this->writers as $writer) { |
| 9940 |
$writer->writeEntry($entry); |
| 9941 |
} |
| 9942 |
} |
| 9943 |
} |
| 9944 |
namespace Kibo\Phast\Logging\LogWriters\RotatingTextFile; |
| 9945 |
|
| 9946 |
class Writer extends \Kibo\Phast\Logging\LogWriters\BaseLogWriter |
| 9947 |
{ |
| 9948 |
/** @var string */ |
| 9949 |
private $path = 'phast.log'; |
| 9950 |
/** @var int */ |
| 9951 |
private $maxFiles = 2; |
| 9952 |
/** @var int */ |
| 9953 |
private $maxSize = 10 * 1024 * 1024; |
| 9954 |
/** @var ObjectifiedFunctions */ |
| 9955 |
private $funcs; |
| 9956 |
/** |
| 9957 |
* @param array $config |
| 9958 |
* @param ?ObjectifiedFunctions $funcs |
| 9959 |
*/ |
| 9960 |
public function __construct(array $config, \Kibo\Phast\Common\ObjectifiedFunctions $funcs = null) |
| 9961 |
{ |
| 9962 |
if (isset($config['path'])) { |
| 9963 |
$this->path = (string) $config['path']; |
| 9964 |
} |
| 9965 |
if (isset($config['maxFiles'])) { |
| 9966 |
$this->maxFiles = (int) $config['maxFiles']; |
| 9967 |
} |
| 9968 |
if (isset($config['maxSize'])) { |
| 9969 |
$this->maxSize = (int) $config['maxSize']; |
| 9970 |
} |
| 9971 |
$this->funcs = is_null($funcs) ? new \Kibo\Phast\Common\ObjectifiedFunctions() : $funcs; |
| 9972 |
} |
| 9973 |
protected function doWriteEntry(\Kibo\Phast\Logging\LogEntry $entry) |
| 9974 |
{ |
| 9975 |
if (!($this->levelMask & $entry->getLevel())) { |
| 9976 |
return; |
| 9977 |
} |
| 9978 |
$message = $this->interpolate($entry->getMessage(), $entry->getContext()); |
| 9979 |
$line = sprintf("%s %s %s\n", gmdate('Y-m-d\\TH:i:s\\Z', $this->funcs->time()), \Kibo\Phast\Logging\LogLevel::toString($entry->getLevel()), $message); |
| 9980 |
clearstatcache(true, $this->path); |
| 9981 |
$this->rotate(strlen($line)); |
| 9982 |
file_put_contents($this->path, $line, FILE_APPEND); |
| 9983 |
} |
| 9984 |
private function interpolate($message, $context) |
| 9985 |
{ |
| 9986 |
$prefix = ''; |
| 9987 |
$prefixKeys = ['requestId', 'service', 'class', 'method', 'line']; |
| 9988 |
foreach ($prefixKeys as $key) { |
| 9989 |
if (isset($context[$key])) { |
| 9990 |
$prefix .= '{' . $key . "}\t"; |
| 9991 |
} |
| 9992 |
} |
| 9993 |
return preg_replace_callback('/{(.+?)}/', function ($match) use($context) { |
| 9994 |
return array_key_exists($match[1], $context) ? $context[$match[1]] : $match[0]; |
| 9995 |
}, $prefix . $message); |
| 9996 |
} |
| 9997 |
private function rotate($bufferSize) |
| 9998 |
{ |
| 9999 |
if (!$this->shouldRotate($bufferSize)) { |
| 10000 |
return; |
| 10001 |
} |
| 10002 |
if (!($fp = fopen($this->path, 'r+'))) { |
| 10003 |
return; |
| 10004 |
} |
| 10005 |
try { |
| 10006 |
if (!flock($fp, LOCK_EX | LOCK_NB)) { |
| 10007 |
return; |
| 10008 |
} |
| 10009 |
if (!$this->shouldRotate($bufferSize)) { |
| 10010 |
return; |
| 10011 |
} |
| 10012 |
for ($i = $this->maxFiles - 1; $i > 0; $i--) { |
| 10013 |
@rename($this->getName($i - 1), $this->getName($i)); |
| 10014 |
} |
| 10015 |
} finally { |
| 10016 |
fclose($fp); |
| 10017 |
} |
| 10018 |
} |
| 10019 |
private function getName($index) |
| 10020 |
{ |
| 10021 |
if ($index <= 0) { |
| 10022 |
return $this->path; |
| 10023 |
} |
| 10024 |
return $this->path . '.' . $index; |
| 10025 |
} |
| 10026 |
private function shouldRotate($bufferSize) |
| 10027 |
{ |
| 10028 |
$currentSize = @filesize($this->path); |
| 10029 |
if (!$currentSize) { |
| 10030 |
return false; |
| 10031 |
} |
| 10032 |
$newSize = $currentSize + $bufferSize; |
| 10033 |
return $newSize > $this->maxSize; |
| 10034 |
} |
| 10035 |
} |
| 10036 |
namespace Kibo\Phast\Logging\LogWriters\PHPError; |
| 10037 |
|
| 10038 |
class Writer extends \Kibo\Phast\Logging\LogWriters\BaseLogWriter |
| 10039 |
{ |
| 10040 |
private $messageType = 0; |
| 10041 |
private $destination = null; |
| 10042 |
private $extraHeaders = null; |
| 10043 |
/** |
| 10044 |
* @var ObjectifiedFunctions |
| 10045 |
*/ |
| 10046 |
private $funcs; |
| 10047 |
/** |
| 10048 |
* PHPErrorLogWriter constructor. |
| 10049 |
* @param array $config |
| 10050 |
* @param ObjectifiedFunctions $funcs |
| 10051 |
*/ |
| 10052 |
public function __construct(array $config, \Kibo\Phast\Common\ObjectifiedFunctions $funcs = null) |
| 10053 |
{ |
| 10054 |
foreach (['messageType', 'destination', 'extraHeaders'] as $field) { |
| 10055 |
if (isset($config[$field])) { |
| 10056 |
$this->{$field} = $config[$field]; |
| 10057 |
} |
| 10058 |
} |
| 10059 |
$this->funcs = is_null($funcs) ? new \Kibo\Phast\Common\ObjectifiedFunctions() : $funcs; |
| 10060 |
} |
| 10061 |
protected function doWriteEntry(\Kibo\Phast\Logging\LogEntry $entry) |
| 10062 |
{ |
| 10063 |
if ($this->levelMask & $entry->getLevel()) { |
| 10064 |
$this->funcs->error_log($this->interpolate($entry->getMessage(), $entry->getContext()), $this->messageType, $this->destination, $this->extraHeaders); |
| 10065 |
} |
| 10066 |
} |
| 10067 |
private function interpolate($message, $context) |
| 10068 |
{ |
| 10069 |
$prefix = ''; |
| 10070 |
$prefixKeys = ['requestId', 'service', 'class', 'method', 'line']; |
| 10071 |
foreach ($prefixKeys as $key) { |
| 10072 |
if (isset($context[$key])) { |
| 10073 |
$prefix .= '{' . $key . "}\t"; |
| 10074 |
} |
| 10075 |
} |
| 10076 |
return preg_replace_callback('/{(.+?)}/', function ($match) use($context) { |
| 10077 |
return array_key_exists($match[1], $context) ? $context[$match[1]] : $match[0]; |
| 10078 |
}, $prefix . $message); |
| 10079 |
} |
| 10080 |
} |