1<?php 2 3namespace dokuwiki\Feed; 4 5use dokuwiki\HTTP\DokuHTTPClient; 6use SimplePie\File; 7 8/** 9 * Fetch an URL using our own HTTPClient 10 * 11 * Replaces SimplePie's own File class. 12 */ 13class FeedParserFile extends File 14{ 15 /** @var DokuHTTPClient */ 16 protected $http; 17 18 /** @var string the requested URL */ 19 protected $requestUrl; 20 /** @var int the HTTP status code of the response */ 21 protected $responseStatus; 22 /** @var array<string, string[]> response headers in SimplePie's representation */ 23 protected $responseHeaders = []; 24 /** @var string the response body */ 25 protected $responseBody = ''; 26 27 /** @noinspection PhpMissingParentConstructorInspection */ 28 29 /** 30 * Fetches the given URL through DokuHTTPClient 31 * 32 * SimplePie creates this object through its registry and reads the response via the 33 * get_*() methods below, so the fetch has to happen here in the constructor. 34 * 35 * @inheritdoc 36 */ 37 public function __construct($url) 38 { 39 $this->http = $this->initHTTPClient(); 40 $this->success = $this->http->sendRequest($url); 41 42 $this->requestUrl = $url; 43 $this->responseStatus = (int)$this->http->status; 44 $this->responseBody = (string)$this->http->resp_body; 45 $this->responseHeaders = $this->normalizeHeaders($this->http->resp_headers); 46 // DokuHTTPClient uses an empty string for "no error", but SimplePie's FileClient 47 // treats any non-null error combined with a zero status code as a failed request 48 $this->error = $this->http->error ?: null; 49 } 50 51 /** 52 * Creates the HTTP client used to fetch the feed 53 * 54 * Separated out so tests can inject a client with a canned response 55 * 56 * @return DokuHTTPClient 57 */ 58 protected function initHTTPClient() 59 { 60 return new DokuHTTPClient(); 61 } 62 63 /** 64 * Converts DokuHTTPClient's "name => value" headers into SimplePie's 65 * "name => [values]" representation 66 * 67 * @param array<string, string> $headers 68 * @return array<string, string[]> 69 */ 70 protected function normalizeHeaders($headers) 71 { 72 return array_map(static function ($value) { 73 return array_map('trim', explode(',', (string)$value)); 74 }, $headers); 75 } 76 77 // the following methods implement SimplePie's Response interface and have to keep its naming 78 // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps 79 80 /** @inheritdoc */ 81 public function get_final_requested_uri(): string 82 { 83 return (string)$this->requestUrl; 84 } 85 86 /** @inheritdoc */ 87 public function get_permanent_uri(): string 88 { 89 return (string)$this->requestUrl; 90 } 91 92 /** @inheritdoc */ 93 public function get_status_code(): int 94 { 95 return $this->responseStatus; 96 } 97 98 /** @inheritdoc */ 99 public function get_headers(): array 100 { 101 return $this->responseHeaders; 102 } 103 104 /** @inheritdoc */ 105 public function has_header(string $name): bool 106 { 107 return isset($this->responseHeaders[strtolower($name)]); 108 } 109 110 /** @inheritdoc */ 111 public function get_header(string $name): array 112 { 113 return $this->responseHeaders[strtolower($name)] ?? []; 114 } 115 116 /** @inheritdoc */ 117 public function get_header_line(string $name): string 118 { 119 return implode(', ', $this->get_header($name)); 120 } 121 122 /** @inheritdoc */ 123 public function get_body_content(): string 124 { 125 return $this->responseBody; 126 } 127 128 // phpcs:enable PSR1.Methods.CamelCapsMethodName.NotCamelCaps 129} 130