Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
48.21% |
27 / 56 |
|
58.33% |
7 / 12 |
CRAP | |
0.00% |
0 / 1 |
ConversionStrategy | |
48.21% |
27 / 56 |
|
58.33% |
7 / 12 |
96.47 | |
0.00% |
0 / 1 |
__construct | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
2.00 | |||
getSourceStore | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMoveComment | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCleanupComment | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isConversionFinished | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
createImportSource | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
decideArchiveTitle | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getPostprocessor | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
createArchiveCleanupRevisionContent | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
meetsSubpageRequirements | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
hasNoConvertTemplate | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 | |||
shouldConvert | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | |
3 | namespace Flow\Import\Wikitext; |
4 | |
5 | use DateTime; |
6 | use DateTimeZone; |
7 | use Flow\Import\ArchiveNameHelper; |
8 | use Flow\Import\IConversionStrategy; |
9 | use Flow\Import\SourceStore\SourceStoreInterface; |
10 | use MediaWiki\Content\WikitextContent; |
11 | use MediaWiki\MediaWikiServices; |
12 | use MediaWiki\Parser\Parser; |
13 | use MediaWiki\Registration\ExtensionRegistry; |
14 | use MediaWiki\StubObject\StubObject; |
15 | use MediaWiki\Title\Title; |
16 | use MediaWiki\User\User; |
17 | use Psr\Log\LoggerInterface; |
18 | |
19 | /** |
20 | * Does not really convert. Archives wikitext pages out of the way and puts |
21 | * a new flow board in place. We take either the entire page, or the page up |
22 | * to the first section and put it into the header of the flow board. We |
23 | * additionally edit both the flow header and the archived page to include |
24 | * a localized template containing the reciprocal title and the conversion |
25 | * date in GMT. |
26 | * |
27 | * It is plausible something with the EchoDiscussionParser could be worked up |
28 | * to do an import of topics and posts. We know it wont work for everything, |
29 | * but we don't know if it works for 90%, 99%, or 99.99% of topics. We know |
30 | * for sure that it does not currently understand anything about editing an |
31 | * existing comment. |
32 | */ |
33 | class ConversionStrategy implements IConversionStrategy { |
34 | /** |
35 | * @var LoggerInterface |
36 | */ |
37 | protected $logger; |
38 | |
39 | /** |
40 | * @var SourceStoreInterface |
41 | */ |
42 | protected $sourceStore; |
43 | |
44 | /** |
45 | * @var Parser|StubObject |
46 | */ |
47 | protected $parser; |
48 | |
49 | /** |
50 | * @var array |
51 | */ |
52 | protected $archiveTitleSuggestions; |
53 | |
54 | /** |
55 | * @var string |
56 | */ |
57 | protected $headerSuffix; |
58 | |
59 | /** @var User User doing the conversion actions (e.g. initial description, wikitext |
60 | * archive edit). However, actions will be attributed to the original user when |
61 | * possible (e.g. the user who did the original LQT reply) |
62 | * |
63 | */ |
64 | protected $user; |
65 | |
66 | /** |
67 | * @var Title[] |
68 | */ |
69 | private $noConvertTemplates; |
70 | |
71 | /** |
72 | * @param Parser|StubObject $parser |
73 | * @param SourceStoreInterface $sourceStore |
74 | * @param LoggerInterface $logger |
75 | * @param User $user User to take conversion actions are (applicable for actions |
76 | * where if there is no 'original' user) |
77 | * @param Title[] $noConvertTemplates List of templates that flag pages that |
78 | * shouldn't be converted (optional) |
79 | * @param string|null $headerSuffix Wikitext to add to the end of the header (optional) |
80 | */ |
81 | public function __construct( |
82 | $parser, |
83 | SourceStoreInterface $sourceStore, |
84 | LoggerInterface $logger, |
85 | User $user, |
86 | array $noConvertTemplates = [], |
87 | $headerSuffix = null |
88 | ) { |
89 | $this->parser = $parser; |
90 | $this->sourceStore = $sourceStore; |
91 | $this->logger = $logger; |
92 | $this->user = $user; |
93 | $this->noConvertTemplates = $noConvertTemplates; |
94 | $this->headerSuffix = $headerSuffix; |
95 | |
96 | $archiveFormat = wfMessage( 'flow-conversion-archive-page-name-format' )->inContentLanguage()->plain(); |
97 | if ( strpos( $archiveFormat, "\n" ) === false ) { |
98 | $this->archiveTitleSuggestions = [ $archiveFormat ]; |
99 | } else { |
100 | $this->archiveTitleSuggestions = explode( "\n", $archiveFormat ); |
101 | } |
102 | } |
103 | |
104 | /** |
105 | * @inheritDoc |
106 | */ |
107 | public function getSourceStore() { |
108 | return $this->sourceStore; |
109 | } |
110 | |
111 | /** |
112 | * @inheritDoc |
113 | */ |
114 | public function getMoveComment( Title $from, Title $to ) { |
115 | return wfMessage( 'flow-talk-conversion-move-reason', $from->getPrefixedText() )->plain(); |
116 | } |
117 | |
118 | /** |
119 | * @inheritDoc |
120 | */ |
121 | public function getCleanupComment( Title $from, Title $to ) { |
122 | return wfMessage( 'flow-talk-conversion-archive-edit-reason' )->plain(); |
123 | } |
124 | |
125 | /** |
126 | * @inheritDoc |
127 | */ |
128 | public function isConversionFinished( Title $title, ?Title $movedFrom = null ) { |
129 | if ( $title->getContentModel() === CONTENT_MODEL_FLOW_BOARD ) { |
130 | // page is a flow board already |
131 | return true; |
132 | } elseif ( $movedFrom ) { |
133 | // page was moved out of the way by import - leave it alone |
134 | return true; |
135 | } else { |
136 | return false; |
137 | } |
138 | } |
139 | |
140 | /** |
141 | * @inheritDoc |
142 | */ |
143 | public function createImportSource( Title $title ) { |
144 | return new ImportSource( $title, $this->parser, $this->user, $this->headerSuffix ); |
145 | } |
146 | |
147 | /** |
148 | * @inheritDoc |
149 | */ |
150 | public function decideArchiveTitle( Title $source ) { |
151 | $archiveNameHelper = new ArchiveNameHelper(); |
152 | return $archiveNameHelper->decideArchiveTitle( $source, $this->archiveTitleSuggestions ); |
153 | } |
154 | |
155 | /** |
156 | * @inheritDoc |
157 | */ |
158 | public function getPostprocessor() { |
159 | return null; |
160 | } |
161 | |
162 | /** |
163 | * @inheritDoc |
164 | */ |
165 | public function createArchiveCleanupRevisionContent( WikitextContent $content, Title $title ) { |
166 | $now = new DateTime( "now", new DateTimeZone( "GMT" ) ); |
167 | $arguments = implode( '|', [ |
168 | 'from=' . $title->getPrefixedText(), |
169 | 'date=' . $now->format( 'Y-m-d' ), |
170 | ] ); |
171 | |
172 | $template = wfMessage( 'flow-importer-wt-converted-archive-template' )->inContentLanguage()->plain(); |
173 | $newWikitext = "{{{$template}|$arguments}}" . "\n\n" . $content->getText(); |
174 | |
175 | return new WikitextContent( $newWikitext ); |
176 | } |
177 | |
178 | // Public only for unit testing |
179 | |
180 | /** |
181 | * Checks whether it meets the applicable subpage rules. Meant to be overriden by |
182 | * subclasses that do not have the same requirements |
183 | * |
184 | * @param Title $sourceTitle Title to check |
185 | * @return bool Whether it meets the applicable subpage requirements |
186 | */ |
187 | public function meetsSubpageRequirements( Title $sourceTitle ) { |
188 | // Don't allow conversion of sub pages unless it is |
189 | // a talk page with matching subject page. For example |
190 | // we will convert User_talk:Foo/bar only if User:Foo/bar |
191 | // exists, and we will never convert User:Baz/bang. |
192 | if ( $sourceTitle->isSubpage() && |
193 | ( !$sourceTitle->isTalkPage() || !$sourceTitle->getSubjectPage()->exists() ) |
194 | ) { |
195 | return false; |
196 | } |
197 | |
198 | return true; |
199 | } |
200 | |
201 | /** |
202 | * Check whether the given title has one of the templates that should protect it from |
203 | * being converted. |
204 | * @param Title $sourceTitle Title to check |
205 | * @return bool Whether the title has such a template |
206 | */ |
207 | protected function hasNoConvertTemplate( Title $sourceTitle ) { |
208 | if ( count( $this->noConvertTemplates ) === 0 ) { |
209 | return false; |
210 | } |
211 | |
212 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
213 | $batch = MediaWikiServices::getInstance()->getLinkBatchFactory()->newLinkBatch( $this->noConvertTemplates ); |
214 | return (bool)$dbr->newSelectQueryBuilder() |
215 | ->select( 'tl_from' ) |
216 | ->from( 'templatelinks' ) |
217 | ->where( [ |
218 | 'tl_from' => $sourceTitle->getArticleID(), |
219 | $batch->constructSet( 'tl', $dbr ) |
220 | ] ) |
221 | ->caller( __METHOD__ ) |
222 | ->fetchRow(); |
223 | } |
224 | |
225 | /** |
226 | * @inheritDoc |
227 | */ |
228 | public function shouldConvert( Title $sourceTitle ) { |
229 | // If we have LiquidThreads filter out any pages with that enabled. They should |
230 | // be converted separately. |
231 | if ( ExtensionRegistry::getInstance()->isLoaded( 'Liquid Threads' ) ) { |
232 | if ( \LqtDispatch::isLqtPage( $sourceTitle ) ) { |
233 | $this->logger->info( "Skipping LQT enabled page, conversion must be done with " . |
234 | "convertLqtPagesWithProp.php or convertLqtPageOnLocalWiki.php: $sourceTitle" ); |
235 | return false; |
236 | } |
237 | } |
238 | |
239 | if ( !$this->meetsSubpageRequirements( $sourceTitle ) || |
240 | $this->hasNoConvertTemplate( $sourceTitle ) |
241 | ) { |
242 | return false; |
243 | } |
244 | |
245 | return true; |
246 | } |
247 | } |