Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 62 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
TranslatableBundleImporter | |
0.00% |
0 / 62 |
|
0.00% |
0 / 7 |
342 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getInstance | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
import | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
42 | |||
setPageImportCompleteCallback | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
logImport | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
addReadyTagForTranslatablePage | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
onAfterImportPage | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\MessageGroupProcessing; |
5 | |
6 | use Closure; |
7 | use Exception; |
8 | use ImportStreamSource; |
9 | use ManualLogEntry; |
10 | use MediaWiki\Extension\Translate\PageTranslation\TranslatablePage; |
11 | use MediaWiki\Extension\Translate\PageTranslation\TranslatablePageParser; |
12 | use MediaWiki\Extension\Translate\Services; |
13 | use MediaWiki\Hook\AfterImportPageHook; |
14 | use MediaWiki\Permissions\UltimateAuthority; |
15 | use MediaWiki\Revision\RevisionLookup; |
16 | use MediaWiki\Revision\SlotRecord; |
17 | use MediaWiki\Title\NamespaceInfo; |
18 | use MediaWiki\Title\Title; |
19 | use MediaWiki\Title\TitleFactory; |
20 | use MediaWiki\User\UserIdentity; |
21 | use TextContent; |
22 | use WikiImporterFactory; |
23 | |
24 | /** |
25 | * Service to import a translatable bundle from a file. Uses WikiImporter from MediaWiki core. |
26 | * @since 2023.05 |
27 | * @license GPL-2.0-or-later |
28 | * @author Abijeet Patro |
29 | */ |
30 | class TranslatableBundleImporter implements AfterImportPageHook { |
31 | private WikiImporterFactory $wikiImporterFactory; |
32 | private TranslatablePageParser $translatablePageParser; |
33 | private RevisionLookup $revisionLookup; |
34 | private ?Title $bundleTitle; |
35 | private ?Closure $pageImportCompleteCallback = null; |
36 | private NamespaceInfo $namespaceInfo; |
37 | private TitleFactory $titleFactory; |
38 | private bool $importInProgress = false; |
39 | |
40 | public function __construct( |
41 | WikiImporterFactory $wikiImporterFactory, |
42 | TranslatablePageParser $translatablePageParser, |
43 | RevisionLookup $revisionLookup, |
44 | NamespaceInfo $namespaceInfo, |
45 | TitleFactory $titleFactory |
46 | ) { |
47 | $this->wikiImporterFactory = $wikiImporterFactory; |
48 | $this->translatablePageParser = $translatablePageParser; |
49 | $this->revisionLookup = $revisionLookup; |
50 | $this->namespaceInfo = $namespaceInfo; |
51 | $this->titleFactory = $titleFactory; |
52 | } |
53 | |
54 | /** Factory method used to initialize this HookHandler */ |
55 | public static function getInstance(): self { |
56 | return Services::getInstance()->getTranslatableBundleImporter(); |
57 | } |
58 | |
59 | public function import( |
60 | string $importFilePath, |
61 | string $interwikiPrefix, |
62 | bool $assignKnownUsers, |
63 | UserIdentity $user, |
64 | ?Title $targetPage, |
65 | ?string $comment |
66 | ): Title { |
67 | $importSource = ImportStreamSource::newFromFile( $importFilePath ); |
68 | if ( !$importSource->isOK() ) { |
69 | throw new TranslatableBundleImportException( |
70 | "Error while reading import file '$importFilePath': " . $importSource->getMessage()->text() |
71 | ); |
72 | } |
73 | |
74 | $wikiImporter = $this->wikiImporterFactory |
75 | // This is used only in a maintenance script (importTranslatableBundle.php), |
76 | // so use UltimateAuthority to skip permission checks |
77 | ->getWikiImporter( $importSource->value, new UltimateAuthority( $user ) ); |
78 | $wikiImporter->setUsernamePrefix( $interwikiPrefix, $assignKnownUsers ); |
79 | |
80 | if ( $targetPage !== null ) { |
81 | $wikiImporter->setImportTitleFactory( |
82 | new TranslatableBundleImportTitleFactory( $this->namespaceInfo, $this->titleFactory, $targetPage ) |
83 | ); |
84 | } |
85 | |
86 | try { |
87 | $this->importInProgress = true; |
88 | // Reset the currently set title which might have been set during the previous import process |
89 | $this->bundleTitle = null; |
90 | $importResult = $wikiImporter->doImport(); |
91 | } catch ( Exception $e ) { |
92 | throw new TranslatableBundleImportException( |
93 | $e->getMessage(), |
94 | $e->getCode(), |
95 | $e |
96 | ); |
97 | } finally { |
98 | $this->importInProgress = false; |
99 | } |
100 | |
101 | if ( $importResult === false ) { |
102 | throw new TranslatableBundleImportException( 'Unknown error while importing translatable bundle.' ); |
103 | } |
104 | |
105 | if ( !$this->bundleTitle ) { |
106 | throw new TranslatableBundleImportException( 'Import done, but could not identify imported page.' ); |
107 | } |
108 | |
109 | // WikiImporter does not trigger hooks that run after a page is edited. Hence, manually add the ready |
110 | // tag to the imported page if it contains the markup |
111 | $this->addReadyTagForTranslatablePage( $this->bundleTitle ); |
112 | $this->logImport( $user, $this->bundleTitle, $comment ); |
113 | |
114 | return $this->bundleTitle; |
115 | } |
116 | |
117 | public function setPageImportCompleteCallback( callable $callable ): void { |
118 | $this->pageImportCompleteCallback = Closure::fromCallable( $callable ); |
119 | } |
120 | |
121 | private function logImport( UserIdentity $user, Title $bundle, ?string $comment ): void { |
122 | $entry = new ManualLogEntry( 'import', 'translatable-bundle' ); |
123 | $entry->setPerformer( $user ); |
124 | $entry->setTarget( $bundle ); |
125 | $logId = $entry->insert(); |
126 | if ( $comment ) { |
127 | $entry->setComment( $comment ); |
128 | } |
129 | $entry->publish( $logId ); |
130 | } |
131 | |
132 | /** Add ready tag in case the page imported has <translate> markup */ |
133 | private function addReadyTagForTranslatablePage( Title $translatablePageTitle ) { |
134 | $revisionRecord = $this->revisionLookup->getRevisionByTitle( $translatablePageTitle ); |
135 | if ( !$revisionRecord ) { |
136 | throw new TranslatableBundleImportException( |
137 | "Revision record could not be found for imported page: $translatablePageTitle" |
138 | ); |
139 | } |
140 | |
141 | $content = $revisionRecord->getContent( SlotRecord::MAIN ); |
142 | if ( !$content instanceof TextContent ) { |
143 | throw new TranslatableBundleImportException( |
144 | "Content in revision record for $translatablePageTitle is not of type TextContent" |
145 | ); |
146 | } |
147 | |
148 | if ( $this->translatablePageParser->containsMarkup( $content->getText() ) ) { |
149 | // Add the ready tag |
150 | $page = TranslatablePage::newFromTitle( Title::newFromLinkTarget( $translatablePageTitle ) ); |
151 | $page->addReadyTag( $revisionRecord->getId() ); |
152 | } |
153 | } |
154 | |
155 | public function onAfterImportPage( $title, $foreignTitle, $revCount, $sRevCount, $pageInfo ) { |
156 | if ( $this->importInProgress ) { |
157 | $this->bundleTitle ??= $title; |
158 | if ( $this->pageImportCompleteCallback ) { |
159 | call_user_func( $this->pageImportCompleteCallback, $title, $foreignTitle ); |
160 | } |
161 | } |
162 | } |
163 | } |