Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.51% covered (success)
98.51%
66 / 67
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemoteSourceFileEditDeleteAction
98.51% covered (success)
98.51%
66 / 67
83.33% covered (warning)
83.33%
5 / 6
12
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 manualTemplateFallback
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 addNowCommonsToSource
96.55% covered (success)
96.55%
28 / 29
0.00% covered (danger)
0.00%
0 / 1
3
 deleteSourceFile
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
2
 successMessage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace FileImporter\Remote\MediaWiki;
4
5use FileImporter\Data\ImportPlan;
6use FileImporter\Interfaces\PostImportHandler;
7use FileImporter\Services\WikidataTemplateLookup;
8use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
9use MediaWiki\User\User;
10use NullStatsdDataFactory;
11use Psr\Log\LoggerInterface;
12use Psr\Log\NullLogger;
13use StatusValue;
14
15/**
16 * Delete the source file, or edit to add the {{NowCommons}} template.
17 *
18 * @license GPL-2.0-or-later
19 */
20class RemoteSourceFileEditDeleteAction implements PostImportHandler {
21
22    private const STATSD_SOURCE_DELETE_FAIL = 'FileImporter.import.postImport.delete.failed';
23    private const STATSD_SOURCE_DELETE_SUCCESS = 'FileImporter.import.postImport.delete.successful';
24    private const STATSD_SOURCE_EDIT_FAIL = 'FileImporter.import.postImport.edit.failed';
25    private const STATSD_SOURCE_EDIT_SUCCESS = 'FileImporter.import.postImport.edit.successful';
26
27    private PostImportHandler $fallbackHandler;
28    private WikidataTemplateLookup $templateLookup;
29    private RemoteApiActionExecutor $remoteAction;
30    private LoggerInterface $logger;
31    private StatsdDataFactoryInterface $statsd;
32
33    public function __construct(
34        PostImportHandler $fallbackHandler,
35        WikidataTemplateLookup $templateLookup,
36        RemoteApiActionExecutor $remoteAction,
37        LoggerInterface $logger = null,
38        StatsdDataFactoryInterface $statsd = null
39    ) {
40        $this->fallbackHandler = $fallbackHandler;
41        $this->templateLookup = $templateLookup;
42        $this->remoteAction = $remoteAction;
43        $this->logger = $logger ?? new NullLogger();
44        $this->statsd = $statsd ?? new NullStatsdDataFactory();
45    }
46
47    /**
48     * @inheritDoc
49     */
50    public function execute( ImportPlan $importPlan, User $user ): StatusValue {
51        if ( $importPlan->getAutomateSourceWikiDelete() ) {
52            return $this->deleteSourceFile( $importPlan, $user );
53        } elseif ( $importPlan->getAutomateSourceWikiCleanUp() ) {
54            return $this->addNowCommonsToSource( $importPlan, $user );
55        } else {
56            // Note this may also be triggered if the above methods fail.
57            return $this->manualTemplateFallback( $importPlan, $user );
58        }
59    }
60
61    private function manualTemplateFallback(
62        ImportPlan $importPlan,
63        User $user,
64        string $warningMsg = null
65    ): StatusValue {
66        $status = $this->fallbackHandler->execute( $importPlan, $user );
67        if ( $warningMsg ) {
68            $status->warning( $warningMsg );
69        }
70        return $status;
71    }
72
73    private function addNowCommonsToSource( ImportPlan $importPlan, User $user ): StatusValue {
74        $templateName = $this->templateLookup->fetchNowCommonsLocalTitle(
75            $importPlan->getDetails()->getSourceUrl()
76        );
77        // This should be unreachable because the user can't click the checkbox in this case. But we
78        // know this from a POST request, which might be altered or simply outdated.
79        // Note: This intentionally doesn't allow a template with the name "0".
80        if ( !$templateName ) {
81            return $this->successMessage();
82        }
83
84        $sourceUrl = $importPlan->getDetails()->getSourceUrl();
85        $summary = wfMessage(
86            'fileimporter-cleanup-summary',
87            wfExpandIRI( $importPlan->getTitle()->getFullURL( '', false, PROTO_CANONICAL ) )
88        )->inLanguage( $importPlan->getDetails()->getPageLanguage() )->text();
89        $text = "\n{{" . wfEscapeWikiText( $templateName ) . '|' .
90            wfEscapeWikiText( $importPlan->getTitle()->getText() ) . '}}';
91
92        $status = $this->remoteAction->executeEditAction(
93            $sourceUrl,
94            $user,
95            $importPlan->getOriginalTitle()->getPrefixedText(),
96            [ 'appendtext' => $text ],
97            $summary
98        );
99
100        if ( $status->isGood() ) {
101            $this->statsd->increment( self::STATSD_SOURCE_EDIT_SUCCESS );
102            return $this->successMessage();
103        } else {
104            $this->logger->error( __METHOD__ . ' failed to do post import edit.' );
105            $this->statsd->increment( self::STATSD_SOURCE_EDIT_FAIL );
106
107            return $this->manualTemplateFallback(
108                $importPlan,
109                $user,
110                'fileimporter-cleanup-failed'
111            );
112        }
113    }
114
115    private function deleteSourceFile( ImportPlan $importPlan, User $user ): StatusValue {
116        $sourceUrl = $importPlan->getDetails()->getSourceUrl();
117        $summary = wfMessage(
118            'fileimporter-delete-summary',
119            wfExpandIRI( $importPlan->getTitle()->getFullURL( '', false, PROTO_CANONICAL ) )
120        )->inLanguage( $importPlan->getDetails()->getPageLanguage() )->text();
121
122        $status = $this->remoteAction->executeDeleteAction(
123            $sourceUrl,
124            $user,
125            $importPlan->getOriginalTitle()->getPrefixedText(),
126            $summary
127        );
128
129        if ( $status->isGood() ) {
130            $this->statsd->increment( self::STATSD_SOURCE_DELETE_SUCCESS );
131            return $this->successMessage();
132        } else {
133            $this->logger->error( __METHOD__ . ' failed to do post import delete.' );
134            $this->statsd->increment( self::STATSD_SOURCE_DELETE_FAIL );
135
136            $status = $this->successMessage();
137            $status->warning(
138                'fileimporter-delete-failed',
139                $sourceUrl->getHost(),
140                $sourceUrl->getUrl()
141            );
142            return $status;
143        }
144    }
145
146    private function successMessage(): StatusValue {
147        return StatusValue::newGood( 'fileimporter-imported-success-banner' );
148    }
149
150}