Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.44% covered (success)
98.44%
63 / 64
96.30% covered (success)
96.30%
26 / 27
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImportPlan
98.44% covered (success)
98.44%
63 / 64
96.30% covered (success)
96.30%
26 / 27
33
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
 getRequest
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDetails
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOriginalTitle
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getTitle
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 getFileName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInterWikiPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileExtension
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileInfoText
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getInitialFileInfoText
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getCleanedLatestRevisionText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCleanedLatestRevisionText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNumberOfTemplateReplacements
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setNumberOfTemplateReplacements
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAutomateSourceWikiCleanUp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setAutomateSourceWikiCleanUp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAutomateSourceWikiDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setAutomateSourceWikiDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 wasFileInfoTextChanged
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addImportAnnotation
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 joinWikitextChunks
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 setActionIsPerformed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setActionStats
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getActionStats
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setValidationWarnings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addValidationWarning
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getValidationWarnings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace FileImporter\Data;
4
5use DateTime;
6use DateTimeZone;
7use MediaWiki\Config\Config;
8use MediaWiki\MediaWikiServices;
9use MediaWiki\Title\MalformedTitleException;
10use MediaWiki\Title\Title;
11use MessageLocalizer;
12
13/**
14 * Planned import.
15 * Data from the source site can be found in the ImportDetails object and the user requested changes
16 * can be found in the ImportRequest object.
17 *
18 * @license GPL-2.0-or-later
19 * @author Addshore
20 */
21class ImportPlan {
22
23    private ImportRequest $request;
24    private ImportDetails $details;
25    private Config $config;
26    private MessageLocalizer $messageLocalizer;
27    /** @var Title|null */
28    private $title = null;
29    /** @var Title|null */
30    private $originalTitle = null;
31    /** @var string */
32    private $interWikiPrefix;
33    /** @var string|null */
34    private $cleanedLatestRevisionText;
35    /** @var int */
36    private $numberOfTemplateReplacements = 0;
37    /** @var array<string,int> */
38    private $actionStats = [];
39    /** @var (int|string)[] */
40    private $validationWarnings = [];
41    /** @var bool */
42    private $automateSourceWikiCleanUp = false;
43    /** @var bool */
44    private $automateSourceWikiDelete = false;
45
46    /**
47     * ImportPlan constructor, should not be constructed directly in production code.
48     * Use an ImportPlanFactory instance.
49     */
50    public function __construct(
51        ImportRequest $request,
52        ImportDetails $details,
53        Config $config,
54        MessageLocalizer $messageLocalizer,
55        string $prefix
56    ) {
57        $this->request = $request;
58        $this->details = $details;
59        $this->config = $config;
60        $this->messageLocalizer = $messageLocalizer;
61        $this->interWikiPrefix = $prefix;
62    }
63
64    public function getRequest(): ImportRequest {
65        return $this->request;
66    }
67
68    public function getDetails(): ImportDetails {
69        return $this->details;
70    }
71
72    public function getOriginalTitle(): Title {
73        $this->originalTitle ??= Title::newFromLinkTarget( $this->details->getSourceLinkTarget() );
74        return $this->originalTitle;
75    }
76
77    /**
78     * @throws MalformedTitleException
79     */
80    public function getTitle(): Title {
81        if ( !$this->title ) {
82            $intendedFileName = $this->request->getIntendedName();
83            if ( $intendedFileName !== null ) {
84                $linkTarget = MediaWikiServices::getInstance()->getTitleParser()->parseTitle(
85                    // FIXME: will be incorrect for Codex UI
86                    $intendedFileName . '.' . $this->details->getSourceFileExtension(),
87                    NS_FILE
88                );
89            } else {
90                $linkTarget = $this->details->getSourceLinkTarget();
91            }
92            $this->title = Title::newFromLinkTarget( $linkTarget );
93        }
94
95        return $this->title;
96    }
97
98    /**
99     * @throws MalformedTitleException
100     */
101    public function getFileName(): string {
102        return pathinfo( $this->getTitle()->getText() )['filename'];
103    }
104
105    public function getInterWikiPrefix(): string {
106        return $this->interWikiPrefix;
107    }
108
109    public function getFileExtension(): string {
110        return $this->details->getSourceFileExtension();
111    }
112
113    public function getFileInfoText(): string {
114        $text = $this->request->getIntendedText();
115        if ( $text !== null ) {
116            return $text;
117        }
118
119        return $this->addImportAnnotation( $this->getCleanedLatestRevisionText() );
120    }
121
122    public function getInitialFileInfoText(): string {
123        $textRevision = $this->details->getTextRevisions()->getLatest();
124        return $textRevision ? $textRevision->getContent() : '';
125    }
126
127    public function getCleanedLatestRevisionText(): string {
128        return $this->cleanedLatestRevisionText ?? $this->getInitialFileInfoText();
129    }
130
131    /**
132     * @param string $text
133     */
134    public function setCleanedLatestRevisionText( $text ): void {
135        $this->cleanedLatestRevisionText = $text;
136    }
137
138    public function getNumberOfTemplateReplacements(): int {
139        return $this->numberOfTemplateReplacements;
140    }
141
142    public function setNumberOfTemplateReplacements( int $replacements ): void {
143        $this->numberOfTemplateReplacements = $replacements;
144    }
145
146    public function getAutomateSourceWikiCleanUp(): bool {
147        return $this->automateSourceWikiCleanUp;
148    }
149
150    public function setAutomateSourceWikiCleanUp( bool $bool ): void {
151        $this->automateSourceWikiCleanUp = $bool;
152    }
153
154    public function getAutomateSourceWikiDelete(): bool {
155        return $this->automateSourceWikiDelete;
156    }
157
158    public function setAutomateSourceWikiDelete( bool $bool ): void {
159        $this->automateSourceWikiDelete = $bool;
160    }
161
162    public function wasFileInfoTextChanged(): bool {
163        return $this->getFileInfoText() !== $this->getInitialFileInfoText();
164    }
165
166    private function addImportAnnotation( string $text ): string {
167        return $this->joinWikitextChunks(
168            wfMsgReplaceArgs(
169                $this->config->get( 'FileImporterTextForPostImportRevision' ),
170                [ $this->request->getUrl() ]
171            ),
172
173            $this->messageLocalizer->msg(
174                'fileimporter-post-import-revision-annotation',
175                $this->request->getUrl(),
176                ( new DateTime( 'now', new DateTimeZone( 'UTC' ) ) )->format( 'c' )
177            )->inContentLanguage()->plain(),
178
179            $text
180        );
181    }
182
183    /**
184     * Concatenate wikitext using newlines when appropriate
185     *
186     * Any empty chunks are discarded before joining.
187     *
188     * @param string|array ...$chunks Varargs of each wikitext chunk, or a
189     *  single parameter with the chunks as an array.
190     * @return string Result of concatenation.
191     */
192    private function joinWikitextChunks( ...$chunks ): string {
193        if ( is_array( reset( $chunks ) ) ) {
194            $chunks = $chunks[0];
195        }
196        $chunks = array_filter(
197            $chunks,
198            static function ( $wikitext ): bool {
199                return $wikitext !== null && $wikitext !== '';
200            }
201        );
202        return implode( "\n", $chunks );
203    }
204
205    public function setActionIsPerformed( string $actionKey ): void {
206        $this->actionStats[$actionKey] = 1;
207    }
208
209    /**
210     * @param array<string,int> $stats
211     */
212    public function setActionStats( array $stats ): void {
213        $this->actionStats = $stats;
214    }
215
216    /**
217     * @return array<string,int> Array mapping string keys to optional counts. The numbers default to 1 and are
218     *  typically not really of interest.
219     */
220    public function getActionStats(): array {
221        return $this->actionStats;
222    }
223
224    /**
225     * @param (int|string)[] $warnings
226     */
227    public function setValidationWarnings( array $warnings ): void {
228        $this->validationWarnings = $warnings;
229    }
230
231    /**
232     * @param int|string $warning
233     */
234    public function addValidationWarning( $warning ): void {
235        $this->validationWarnings[] = $warning;
236    }
237
238    /**
239     * @return (int|string)[]
240     */
241    public function getValidationWarnings(): array {
242        return $this->validationWarnings;
243    }
244
245}