Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hooks
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 7
240
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getInstance
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 onEditFilterMergedContent
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 onPageSaveComplete
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
30
 onTranslateInitGroupLoaders
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onCodeEditorGetPageLanguage
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 onTranslateInitGroupLoadersImpl
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageBundleTranslation;
5
6use Content;
7use IContextSource;
8use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroupWANCache;
9use MediaWiki\Extension\Translate\Utilities\Utilities;
10use MediaWiki\Hook\EditFilterMergedContentHook;
11use MediaWiki\Logger\LoggerFactory;
12use MediaWiki\MediaWikiServices;
13use MediaWiki\Revision\SlotRecord;
14use MediaWiki\Storage\Hook\PageSaveCompleteHook;
15use MediaWiki\Title\Title;
16use Psr\Log\LoggerInterface;
17use Status;
18use User;
19use WANObjectCache;
20
21/**
22 * @author Niklas Laxström
23 * @license GPL-2.0-or-later
24 * @since 2021.05
25 */
26class Hooks implements EditFilterMergedContentHook, PageSaveCompleteHook {
27    public const CONSTRUCTOR_OPTIONS = [
28        'TranslateEnableMessageBundleIntegration',
29    ];
30
31    private static self $instance;
32    private LoggerInterface $logger;
33    private MessageBundleStore $messageBundleStore;
34    private WANObjectCache $WANObjectCache;
35    private bool $enableIntegration;
36
37    public function __construct(
38        LoggerInterface $logger,
39        WANObjectCache $WANObjectCache,
40        MessageBundleStore $messageBundleStore,
41        bool $enableIntegration
42    ) {
43        $this->logger = $logger;
44        $this->WANObjectCache = $WANObjectCache;
45        $this->messageBundleStore = $messageBundleStore;
46        $this->enableIntegration = $enableIntegration;
47    }
48
49    public static function getInstance(): self {
50        $services = MediaWikiServices::getInstance();
51        self::$instance ??= new self(
52            LoggerFactory::getInstance( 'Translate.MessageBundle' ),
53            $services->getMainWANObjectCache(),
54            $services->get( 'Translate:MessageBundleStore' ),
55            $services->getMainConfig()->get( 'TranslateEnableMessageBundleIntegration' )
56        );
57        return self::$instance;
58    }
59
60    /** @inheritDoc */
61    public function onEditFilterMergedContent(
62        IContextSource $context,
63        Content $content,
64        Status $status,
65        $summary,
66        User $user,
67        $minoredit
68    ): void {
69        if ( $content instanceof MessageBundleContent ) {
70            try {
71                // Validation is performed in the store because injecting services into the
72                // Content class is not straightforward
73                $this->messageBundleStore->validate( $context->getTitle(), $content );
74            } catch ( MalformedBundle $e ) {
75                // MalformedBundle implements MessageSpecifier, but for unknown reason it gets
76                // cast to a string if we don't convert it to a proper message.
77                $status->fatal( 'translate-messagebundle-validation-error', $context->msg( $e ) );
78            }
79        }
80    }
81
82    /** @inheritDoc */
83    public function onPageSaveComplete(
84        $wikiPage,
85        $user,
86        $summary,
87        $flags,
88        $revisionRecord,
89        $editResult
90    ): void {
91        if ( !$this->enableIntegration ) {
92            return;
93        }
94
95        $method = __METHOD__;
96        $content = $revisionRecord->getContent( SlotRecord::MAIN );
97        $pageTitle = $wikiPage->getTitle();
98
99        if ( $content === null ) {
100            $this->logger->debug( "Unable to access content of page {pageName} in $method", [
101                'pageName' => $pageTitle->getPrefixedText()
102            ] );
103            return;
104        }
105
106        if ( !$content instanceof MessageBundleContent ) {
107            return;
108        }
109
110        try {
111            $this->messageBundleStore->save( $pageTitle, $revisionRecord, $content );
112        } catch ( MalformedBundle $e ) {
113            // This should not happen, as it should not be possible to save a page with invalid content
114            $this->logger->warning( "Page {pageName} is not a valid message bundle in $method", [
115                'pageName' => $pageTitle->getPrefixedText(),
116                'exception' => $e,
117            ] );
118            return;
119        }
120    }
121
122    /** Hook: TranslateInitGroupLoaders */
123    public static function onTranslateInitGroupLoaders( array &$groupLoader ): void {
124        self::getInstance()->onTranslateInitGroupLoadersImpl( $groupLoader );
125    }
126
127    /** Hook: CodeEditorGetPageLanguage */
128    public static function onCodeEditorGetPageLanguage( Title $title, ?string &$lang, string $model ) {
129        if ( $model === MessageBundleContent::CONTENT_MODEL_ID ) {
130            $lang = 'json';
131        }
132    }
133
134    public function onTranslateInitGroupLoadersImpl( array &$groupLoader ): void {
135        if ( !$this->enableIntegration ) {
136            return;
137        }
138
139        $groupLoader[] = new MessageBundleMessageGroupLoader(
140            Utilities::getSafeReadDB(),
141            new MessageGroupWANCache( $this->WANObjectCache )
142        );
143    }
144}