Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiBackend
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 6
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setLogger
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 retrieveThreadData
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
20
 retrievePageDataById
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 retrieveTopRevisionByTitle
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 retrievePageData
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
42
 apiCall
n/a
0 / 0
n/a
0 / 0
0
 getKey
n/a
0 / 0
n/a
0 / 0
0
1<?php
2
3namespace Flow\Import\LiquidThreadsApi;
4
5use Flow\Import\ImportException;
6use InvalidArgumentException;
7use MediaWiki\Api\ApiBase;
8use Psr\Log\LoggerAwareInterface;
9use Psr\Log\LoggerInterface;
10use Psr\Log\NullLogger;
11
12abstract class ApiBackend implements LoggerAwareInterface {
13
14    /**
15     * @var LoggerInterface
16     */
17    protected $logger;
18
19    public function __construct() {
20        $this->logger = new NullLogger;
21    }
22
23    public function setLogger( LoggerInterface $logger ): void {
24        $this->logger = $logger;
25    }
26
27    /**
28     * Retrieves LiquidThreads data from the API
29     *
30     * @param array $conditions The parameters to pass to select the threads.
31     *  Usually used in two ways: with thstartid/thpage, or with ththreadid
32     * @return array Data as returned under query.threads by the API
33     * @throws ApiNotFoundException Thrown when the remote api reports that the provided conditions
34     *  have no matching records.
35     * @throws ImportException When an error is received from the remote api.  This is often either
36     *  a bad request or lqt threw an exception trying to respond to a valid request.
37     */
38    public function retrieveThreadData( array $conditions ) {
39        $params = [
40            'action' => 'query',
41            'list' => 'threads',
42            'thprop' => 'id|subject|page|parent|ancestor|created|modified|author|summaryid' . '|type|rootid|replies|signature',
43            'rawcontinue' => 1,
44            // We're doing continuation a different way, but this avoids a warning.
45            'format' => 'json',
46            'limit' => ApiBase::LIMIT_BIG1,
47        ];
48        $data = $this->apiCall( $params + $conditions );
49
50        if ( !isset( $data['query']['threads'] ) ) {
51            $this->logger->error(
52                __METHOD__ . ': Failed API call against ' . $this->getKey() .
53                ' with conditions : ' . json_encode( $conditions )
54            );
55            throw new ImportException(
56                "Null response from API module:" . json_encode( $data )
57            );
58        }
59
60        $threads = $data['query']['threads'];
61        if ( !$threads ) {
62            $message = "Did not find thread with conditions: " . json_encode( $conditions );
63            $this->logger->debug( __METHOD__ . "$message" );
64            throw new ApiNotFoundException( $message );
65        }
66        $firstThread = reset( $threads );
67        if ( !isset( $firstThread['replies'] ) ) {
68            throw new ImportException(
69                "Foreign API does not support reply exporting:" . json_encode( $data )
70            );
71        }
72
73        return $threads;
74    }
75
76    /**
77     * Retrieves data about a set of pages from the API
78     *
79     * @param int[] $pageIds Page IDs to return data for. There must be at least one element.
80     * @phan-param non-empty-list<int> $pageIds
81     * @return array The query.pages part of the API response.
82     */
83    public function retrievePageDataById( array $pageIds ) {
84        if ( !$pageIds ) {
85            throw new InvalidArgumentException( 'At least one page id must be provided' );
86        }
87
88        return $this->retrievePageData(
89            [
90                'pageids' => implode( '|', $pageIds ),
91            ]
92        );
93    }
94
95    /**
96     * Retrieves data about the latest revision of the titles
97     * from the API
98     *
99     * @param string[] $titles Titles to return data for. There must be at least one element.
100     * @phan-param non-empty-list<string> $titles
101     * @return array The query.pages part of the API response.
102     * @throws ImportException
103     */
104    public function retrieveTopRevisionByTitle( array $titles ) {
105        if ( !$titles ) {
106            throw new InvalidArgumentException( 'At least one title must be provided' );
107        }
108
109        return $this->retrievePageData(
110            [
111                'titles' => implode( '|', $titles ),
112                'rvlimit' => 1,
113                'rvdir' => 'older',
114            ],
115            true
116        );
117    }
118
119    /**
120     * Retrieves data about a set of pages from the API
121     *
122     * @param array $conditions Conditions to retrieve pages by; to be sent to the API.
123     * @param bool $expectContinue Pass true here when caller expects more revisions to exist than
124     *  they are requesting information about.
125     * @return array The query.pages part of the API response.
126     * @throws ApiNotFoundException Thrown when the remote api reports that the provided conditions
127     *  have no matching records.
128     * @throws ImportException When an error is received from the remote api.  This is often either
129     *  a bad request or lqt threw an exception trying to respond to a valid request.
130     * @throws ImportException When more revisions are available than can be returned in a single
131     *  query and the calling code does not set $expectContinue to true.
132     */
133    public function retrievePageData( array $conditions, $expectContinue = false ) {
134        $conditions += [
135            'action' => 'query',
136            'prop' => 'revisions',
137            'rvprop' => 'timestamp|user|content|ids',
138            'format' => 'json',
139            'rvlimit' => 5000,
140            'rvdir' => 'newer',
141            'continue' => '',
142        ];
143        $data = $this->apiCall( $conditions );
144
145        if ( !isset( $data['query'] ) ) {
146            $this->logger->error(
147                __METHOD__ . ': Failed API call against ' . $this->getKey() .
148                ' with conditions : ' . json_encode( $conditions )
149            );
150            throw new ImportException(
151                "Null response from API module: " . json_encode( $data )
152            );
153        } elseif ( !$expectContinue && isset( $data['continue'] ) ) {
154            throw new ImportException(
155                "More revisions than can be retrieved for conditions, import would" . " be incomplete: " . json_encode(
156                    $conditions
157                )
158            );
159        }
160        if ( !$data['query']['pages'] ) {
161            $message = "Did not find pages: " . json_encode( $conditions );
162            $this->logger->debug( __METHOD__ . "$message" );
163            throw new ApiNotFoundException( $message );
164        }
165        $dataProcessed = [];
166        // Reprocess to a formatversion=1-esque format for compatibility
167        // with the CachedData structure
168        foreach ( $data['query']['pages'] as $page ) {
169            $dataProcessed[(int)$page['pageid']] = $page;
170        }
171        return $dataProcessed;
172    }
173
174    /**
175     * Calls the remote API
176     *
177     * @param array $params The API request to send
178     * @param int $retry Retry the request on failure this many times
179     * @return array API return value, decoded from JSON into an array.
180     */
181    abstract public function apiCall( array $params, $retry = 1 );
182
183    /**
184     * @return string A unique identifier for this backend.
185     */
186    abstract public function getKey();
187}