Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.64% covered (warning)
63.64%
35 / 55
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ActionApiImageRecommendationApiHandler
63.64% covered (warning)
63.64%
35 / 55
33.33% covered (danger)
33.33%
1 / 3
21.13
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getApiRequest
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 getSuggestionDataFromApiResponse
82.05% covered (warning)
82.05%
32 / 39
0.00% covered (danger)
0.00%
0 / 1
11.70
1<?php
2
3namespace GrowthExperiments\NewcomerTasks\AddImage;
4
5use GrowthExperiments\NewcomerTasks\TaskType\ImageRecommendationTaskTypeHandler;
6use GrowthExperiments\NewcomerTasks\TaskType\SectionImageRecommendationTaskTypeHandler;
7use GrowthExperiments\NewcomerTasks\TaskType\TaskType;
8use MediaWiki\Api\ApiRawMessage;
9use MediaWiki\Http\HttpRequestFactory;
10use MediaWiki\Language\RawMessage;
11use MediaWiki\Logger\LoggerFactory;
12use MediaWiki\Status\Status;
13use MediaWiki\Title\Title;
14use RuntimeException;
15use StatusValue;
16
17/**
18 * Handler for the action=query&prop=growthimagesuggestiondata API.
19 * Uses a remote installation of this extension to proxy from ProductionApiImageRecommendationApiHandler.
20 * Documentation: https://en.wikipedia.org/wiki/Special:ApiHelp/query+growthimagesuggestiondata
21 * Configuration of constructor parameters:
22 * - $apiUrl: GEImageRecommendationServiceUrl (should point to api.php)
23 * - $accessToken: GEImageRecommendationServiceAccessToken
24 */
25class ActionApiImageRecommendationApiHandler implements ImageRecommendationApiHandler {
26
27    private HttpRequestFactory $httpRequestFactory;
28    /** @var string api.php URL */
29    private string $apiUrl;
30    /** @var string MediaWiki OAuth 2 access token for upstream wiki */
31    private string $accessToken;
32
33    /**
34     * @param HttpRequestFactory $httpRequestFactory
35     * @param string $apiUrl api.php URL
36     * @param string $accessToken
37     */
38    public function __construct(
39        HttpRequestFactory $httpRequestFactory,
40        string $apiUrl,
41        string $accessToken
42    ) {
43        $this->httpRequestFactory = $httpRequestFactory;
44        $this->apiUrl = $apiUrl;
45        $this->accessToken = $accessToken;
46    }
47
48    /** @inheritDoc */
49    public function getApiRequest( Title $title, TaskType $taskType ) {
50        // Ideally we'd use Util::getApiUrl() + maybe handle continuation, but the
51        // RecommendationProvider/ApiHandler split prevents that.
52        $url = wfAppendQuery( $this->apiUrl, [
53            'action' => 'query',
54            'prop' => 'growthimagesuggestiondata',
55            'titles' => $title->getPrefixedText(),
56            'gisdtasktype' => $taskType->getId(),
57            'format' => 'json',
58            'formatversion' => '2',
59            'errorformat' => 'wikitext',
60            'errorlang' => 'en',
61        ] );
62        $request = $this->httpRequestFactory->create( $url, [], __METHOD__ );
63        $request->setHeader( 'Authorization', 'Bearer ' . $this->accessToken );
64        return $request;
65    }
66
67    /** @inheritDoc */
68    public function getSuggestionDataFromApiResponse( array $apiResponse, TaskType $taskType ) {
69        // based on Util::getApiUrl()
70        if ( isset( $apiResponse['errors'] ) ) {
71            $errorStatus = StatusValue::newGood();
72            foreach ( $apiResponse['errors'] as $error ) {
73                $errorStatus->fatal( new ApiRawMessage( $error['text'], $error['code'] ) );
74            }
75            return $errorStatus;
76        }
77        if ( isset( $apiResponse['warnings'] ) ) {
78            $warningStatus = StatusValue::newGood();
79            foreach ( $apiResponse['warnings'] as $warning ) {
80                $warningStatus->warning( new RawMessage( $warning['module'] . ': ' . $warning['text'] ) );
81            }
82            LoggerFactory::getInstance( 'GrowthExperiments' )->warning(
83                Status::wrap( $warningStatus )->getWikiText( false, false, 'en' ),
84                [ 'exception' => new RuntimeException ]
85            );
86        }
87
88        if ( isset( $apiResponse['query']['pages'][0]['growthimagesuggestiondataerrors'] ) ) {
89            $errorStatus = StatusValue::newGood();
90            foreach ( $apiResponse['query']['pages'][0]['growthimagesuggestiondataerrors'] as $error ) {
91                $errorStatus->fatal( new ApiRawMessage( $error['text'], $error['code'] ) );
92            }
93            return $errorStatus;
94        }
95        if ( !isset( $apiResponse['query']['pages'][0]['growthimagesuggestiondata'] ) ) {
96            // page not found or has no suggestions
97            return [];
98        }
99        $imageRecommendationArray = $apiResponse['query']['pages'][0]['growthimagesuggestiondata'][0];
100        // $imageRecommendationArray is a serialized ImageRecommendation; the nice thing to do here
101        // would be to just deserialize it, but ServiceImageRecommendationProvider / ApiHandler are
102        // not structured right for that.
103
104        $imageData = [];
105        foreach ( $imageRecommendationArray['images'] as $imageDataArray ) {
106            $imageDataArrayTaskTypeId = isset( $imageDataArray['sectionTitle'] ) ?
107                SectionImageRecommendationTaskTypeHandler::TASK_TYPE_ID :
108                ImageRecommendationTaskTypeHandler::TASK_TYPE_ID;
109            if ( $imageDataArrayTaskTypeId !== $taskType->getId() ) {
110                continue;
111            }
112
113            $source = ImageRecommendationImage::SOURCE_ALIASES[ $imageDataArray['source'] ]
114                ?? $imageDataArray['source'];
115            $imageData[] = new ImageRecommendationData(
116                $imageDataArray['image'],
117                $source,
118                implode( ',', $imageDataArray['projects'] ?? [] ),
119                $imageRecommendationArray['datasetId'],
120                // the fallbacks are only needed until d78543cb reaches production
121                $imageDataArray['sectionNumber'] ?? null,
122                $imageDataArray['sectionTitle'] ?? null
123            );
124        }
125        return $imageData;
126    }
127
128}