xref: /dokuwiki/inc/Feed/FeedParserFile.php (revision 1bec819783079825ddbd786114a650fe9e324a81)
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