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 fn($value) => array_map(trim(...), explode(',', (string)$value)), $headers); 73 } 74 75 // the following methods implement SimplePie's Response interface and have to keep its naming 76 // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps 77 78 /** @inheritdoc */ 79 public function get_final_requested_uri(): string 80 { 81 return (string)$this->requestUrl; 82 } 83 84 /** @inheritdoc */ 85 public function get_permanent_uri(): string 86 { 87 return (string)$this->requestUrl; 88 } 89 90 /** @inheritdoc */ 91 public function get_status_code(): int 92 { 93 return $this->responseStatus; 94 } 95 96 /** @inheritdoc */ 97 public function get_headers(): array 98 { 99 return $this->responseHeaders; 100 } 101 102 /** @inheritdoc */ 103 public function has_header(string $name): bool 104 { 105 return isset($this->responseHeaders[strtolower($name)]); 106 } 107 108 /** @inheritdoc */ 109 public function get_header(string $name): array 110 { 111 return $this->responseHeaders[strtolower($name)] ?? []; 112 } 113 114 /** @inheritdoc */ 115 public function get_header_line(string $name): string 116 { 117 return implode(', ', $this->get_header($name)); 118 } 119 120 /** @inheritdoc */ 121 public function get_body_content(): string 122 { 123 return $this->responseBody; 124 } 125 126 // phpcs:enable PSR1.Methods.CamelCapsMethodName.NotCamelCaps 127} 128