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