Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 79 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
| ApiBackend | |
0.00% |
0 / 79 |
|
0.00% |
0 / 6 |
272 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| setLogger | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| retrieveThreadData | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
20 | |||
| retrievePageDataById | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
| retrieveTopRevisionByTitle | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
| retrievePageData | |
0.00% |
0 / 32 |
|
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 | |
| 3 | namespace Flow\Import\LiquidThreadsApi; |
| 4 | |
| 5 | use Flow\Import\ImportException; |
| 6 | use InvalidArgumentException; |
| 7 | use MediaWiki\Api\ApiBase; |
| 8 | use Psr\Log\LoggerAwareInterface; |
| 9 | use Psr\Log\LoggerInterface; |
| 10 | use Psr\Log\NullLogger; |
| 11 | |
| 12 | abstract 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 | } |