Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.14% covered (success)
97.14%
68 / 70
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiInvalidateImageRecommendation
97.14% covered (success)
97.14%
68 / 70
66.67% covered (warning)
66.67%
4 / 6
8
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 execute
96.97% covered (success)
96.97%
32 / 33
0.00% covered (danger)
0.00%
0 / 1
3
 needsToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 mustBePosted
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllowedParams
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
1
 isInternal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace GrowthExperiments\Api;
4
5use ApiBase;
6use ApiMain;
7use ApiUsageException;
8use GrowthExperiments\NewcomerTasks\AddImage\AddImageSubmissionHandler;
9use GrowthExperiments\NewcomerTasks\ConfigurationLoader\ConfigurationLoader;
10use GrowthExperiments\NewcomerTasks\NewcomerTasksUserOptionsLookup;
11use GrowthExperiments\NewcomerTasks\TaskSuggester\TaskSuggesterFactory;
12use GrowthExperiments\NewcomerTasks\TaskType\ImageRecommendationBaseTaskType;
13use GrowthExperiments\NewcomerTasks\TaskType\ImageRecommendationTaskTypeHandler;
14use GrowthExperiments\NewcomerTasks\TaskType\SectionImageRecommendationTaskTypeHandler;
15use MediaWiki\Logger\LoggerFactory;
16use MediaWiki\ParamValidator\TypeDef\TitleDef;
17use MediaWiki\Title\TitleFactory;
18use Psr\Log\LoggerAwareTrait;
19use Wikimedia\Assert\Assert;
20use Wikimedia\ParamValidator\ParamValidator;
21
22/**
23 * Endpoint for invalidating image recommendation.
24 *
25 * This is used when the recommendation is determined to be invalid upon display (for example,
26 * when the article already has an image). See mw.libs.ge.AddImageArticleTarget.
27 */
28class ApiInvalidateImageRecommendation extends ApiBase {
29    use LoggerAwareTrait;
30
31    private AddImageSubmissionHandler $imageSubmissionHandler;
32    private TaskSuggesterFactory $taskSuggesterFactory;
33    private NewcomerTasksUserOptionsLookup $newcomerTasksUserOptionsLookup;
34    private TitleFactory $titleFactory;
35    private ConfigurationLoader $configurationLoader;
36
37    /**
38     * @param ApiMain $mainModule
39     * @param string $moduleName
40     * @param ConfigurationLoader $configurationLoader
41     * @param AddImageSubmissionHandler $imageSubmissionHandler
42     * @param TaskSuggesterFactory $taskSuggesterFactory
43     * @param NewcomerTasksUserOptionsLookup $newcomerTasksUserOptionsLookup
44     * @param TitleFactory $titleFactory
45     */
46    public function __construct(
47        ApiMain $mainModule,
48        string $moduleName,
49        ConfigurationLoader $configurationLoader,
50        AddImageSubmissionHandler $imageSubmissionHandler,
51        TaskSuggesterFactory $taskSuggesterFactory,
52        NewcomerTasksUserOptionsLookup $newcomerTasksUserOptionsLookup,
53        TitleFactory $titleFactory
54    ) {
55        parent::__construct( $mainModule, $moduleName );
56        $this->configurationLoader = $configurationLoader;
57        $this->imageSubmissionHandler = $imageSubmissionHandler;
58        $this->taskSuggesterFactory = $taskSuggesterFactory;
59        $this->newcomerTasksUserOptionsLookup = $newcomerTasksUserOptionsLookup;
60        $this->titleFactory = $titleFactory;
61
62        $this->setLogger( LoggerFactory::getInstance( 'GrowthExperiments' ) );
63    }
64
65    /**
66     * @inheritDoc
67     * @throws ApiUsageException
68     */
69    public function execute() {
70        $params = $this->extractRequestParams();
71        // This API is used by external clients for their own structured task workflows so
72        // include disabled task types.
73        $allTaskTypes = $this->configurationLoader->getTaskTypes()
74            + $this->configurationLoader->getDisabledTaskTypes();
75        $taskType = $allTaskTypes[$params['tasktype']] ?? null;
76        if ( $taskType === null ) {
77            $this->logger->warning(
78                'Task type {tasktype} was not found in {configpage}',
79                [
80                    'tasktype' => $params['tasktype'],
81                    'configpage' => $this->getConfig()->get( 'GENewcomerTasksConfigTitle' ),
82                ]
83            );
84            $this->dieWithError(
85                [ 'growthexperiments-homepage-imagesuggestiondata-not-in-config', $params['tasktype'] ],
86                'not-in-config'
87            );
88        }
89
90        Assert::parameterType( ImageRecommendationBaseTaskType::class, $taskType, '$taskType' );
91        '@phan-var ImageRecommendationBaseTaskType $taskType';/** @var ImageRecommendationBaseTaskType $taskType */
92        $titleValue = $params['title'];
93
94        $page = $this->titleFactory->newFromLinkTarget( $titleValue )->toPageIdentity();
95        if ( $page->exists() ) {
96            $this->imageSubmissionHandler->invalidateRecommendation(
97                $taskType,
98                $page,
99                $this->getAuthority()->getUser()->getId(),
100                null,
101                $params['filename'],
102                $params['sectiontitle'],
103                $params['sectionnumber'],
104            );
105            $this->getResult()->addValue( null, $this->getModuleName(), [
106                'status' => 'ok'
107            ] );
108        } else {
109            $this->dieWithError( [ 'apierror-invalidtitle', $titleValue->getDBkey() ] );
110        }
111    }
112
113    /**
114     * @inheritDoc
115     */
116    public function needsToken() {
117        return 'csrf';
118    }
119
120    /**
121     * @inheritDoc
122     */
123    public function mustBePosted() {
124        return true;
125    }
126
127    /**
128     * @inheritDoc
129     */
130    public function getAllowedParams() {
131        return [
132            'tasktype' => [
133                ParamValidator::PARAM_TYPE => [
134                    // Do not filter out non-existing task-types: during API structure tests
135                    // none of the task types exist and an empty list would cause test failures.
136                    ImageRecommendationTaskTypeHandler::TASK_TYPE_ID,
137                    SectionImageRecommendationTaskTypeHandler::TASK_TYPE_ID,
138                ],
139                ParamValidator::PARAM_REQUIRED => false,
140                ParamValidator::PARAM_DEFAULT => ImageRecommendationTaskTypeHandler::TASK_TYPE_ID,
141            ],
142            'title' => [
143                ParamValidator::PARAM_REQUIRED => true,
144                ParamValidator::PARAM_TYPE => 'title',
145                TitleDef::PARAM_RETURN_OBJECT => true,
146            ],
147            'filename' => [
148                ParamValidator::PARAM_REQUIRED => true,
149                ParamValidator::PARAM_TYPE => 'string',
150            ],
151            'sectiontitle' => [
152                ParamValidator::PARAM_REQUIRED => false,
153                ParamValidator::PARAM_TYPE => 'string',
154            ],
155            'sectionnumber' => [
156                ParamValidator::PARAM_REQUIRED => false,
157                ParamValidator::PARAM_TYPE => 'integer',
158            ]
159        ];
160    }
161
162    /** @inheritDoc */
163    public function isInternal() {
164        return true;
165    }
166}