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