Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
CampaignHooks
0.00% covered (danger)
0.00%
0 / 63
0.00% covered (danger)
0.00%
0 / 7
306
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onContentModelCanBeUsedOn
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 onPageSaveComplete
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
20
 onLinksUpdateComplete
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 onArticleDeleteComplete
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 onPageMoveComplete
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 onEditFilterMergedContent
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace MediaWiki\Extension\UploadWizard;
4
5use Content;
6use IContextSource;
7use JsonSchemaException;
8use ManualLogEntry;
9use MediaWiki\Content\Hook\ContentModelCanBeUsedOnHook;
10use MediaWiki\Deferred\LinksUpdate\LinksUpdate;
11use MediaWiki\EditPage\EditPage;
12use MediaWiki\Hook\EditFilterMergedContentHook;
13use MediaWiki\Hook\LinksUpdateCompleteHook;
14use MediaWiki\Hook\PageMoveCompleteHook;
15use MediaWiki\Linker\LinkTarget;
16use MediaWiki\Page\Hook\ArticleDeleteCompleteHook;
17use MediaWiki\Revision\RevisionRecord;
18use MediaWiki\Status\Status;
19use MediaWiki\Storage\EditResult;
20use MediaWiki\Storage\Hook\PageSaveCompleteHook;
21use MediaWiki\Title\Title;
22use MediaWiki\User\User;
23use MediaWiki\User\UserIdentity;
24use Wikimedia\Rdbms\IConnectionProvider;
25use WikiPage;
26
27/**
28 * Hooks for managing JSON Schema namespace and content model.
29 *
30 * @file
31 * @ingroup Extensions
32 * @ingroup UploadWizard
33 *
34 * @author Ori Livneh <ori@wikimedia.org>
35 */
36
37class CampaignHooks implements
38    ContentModelCanBeUsedOnHook,
39    EditFilterMergedContentHook,
40    PageSaveCompleteHook,
41    ArticleDeleteCompleteHook,
42    PageMoveCompleteHook,
43    LinksUpdateCompleteHook
44{
45
46    /** @var IConnectionProvider */
47    private $dbLoadBalancerFactory;
48
49    /**
50     * @param IConnectionProvider $dbLoadBalancerFactory
51     */
52    public function __construct( IConnectionProvider $dbLoadBalancerFactory ) {
53        $this->dbLoadBalancerFactory = $dbLoadBalancerFactory;
54    }
55
56    /**
57     * 'Campaign' content model must be used in, and only in, the 'Campaign' namespace.
58     *
59     * @param string $contentModel
60     * @param Title $title
61     * @param bool &$ok
62     * @return bool
63     */
64    public function onContentModelCanBeUsedOn( $contentModel, $title, &$ok ) {
65        $isCampaignModel = $contentModel === 'Campaign';
66        $isCampaignNamespace = $title->inNamespace( NS_CAMPAIGN );
67        if ( $isCampaignModel !== $isCampaignNamespace ) {
68            $ok = false;
69            return false;
70        }
71        return true;
72    }
73
74    /**
75     * FIXME: This should be done as a DataUpdate
76     *
77     * Sets up appropriate entries in the uc_campaigns table for each Campaign
78     * Acts everytime a page in the NS_CAMPAIGN namespace is saved
79     *
80     * @param WikiPage $wikiPage
81     * @param UserIdentity $userIdentity
82     * @param string $summary
83     * @param int $flags
84     * @param RevisionRecord $revisionRecord
85     * @param EditResult $editResult
86     */
87    public function onPageSaveComplete(
88        $wikiPage,
89        $userIdentity,
90        $summary,
91        $flags,
92        $revisionRecord,
93        $editResult
94    ) {
95        $content = $wikiPage->getContent();
96        if ( !$content instanceof CampaignContent ) {
97            return;
98        }
99
100        $dbw = $this->dbLoadBalancerFactory->getPrimaryDatabase();
101
102        $campaignData = $content->getJsonData();
103        $insertData = [
104            'campaign_enabled' => $campaignData !== null && $campaignData['enabled'] ? 1 : 0
105        ];
106        $dbw->newInsertQueryBuilder()
107            ->insertInto( 'uw_campaigns' )
108            ->row( array_merge(
109                [ 'campaign_name' => $wikiPage->getTitle()->getDBkey() ],
110                $insertData
111            ) )
112            ->onDuplicateKeyUpdate()
113            ->uniqueIndexFields( 'campaign_name' )
114            ->set( $insertData )
115            ->caller( __METHOD__ )
116            ->execute();
117
118        $campaign = new Campaign( $wikiPage->getTitle(), $content->getJsonData() );
119        $dbw->onTransactionPreCommitOrIdle( static function () use ( $campaign ) {
120            $campaign->invalidateCache();
121        }, __METHOD__ );
122    }
123
124    /**
125     * Invalidates the cache for a campaign when any of its dependents are edited. The 'dependents'
126     * are tracked by entries in the templatelinks table, which are inserted by using the
127     * PageContentSaveComplete hook.
128     *
129     * This is usually run via the Job Queue mechanism.
130     * @param LinksUpdate $linksupdate
131     * @param mixed $ticket
132     */
133    public function onLinksUpdateComplete( $linksupdate, $ticket ) {
134        if ( !$linksupdate->getTitle()->inNamespace( NS_CAMPAIGN ) ) {
135            return;
136        }
137
138        $campaign = new Campaign( $linksupdate->getTitle() );
139        $campaign->invalidateCache();
140    }
141
142    /**
143     * Deletes entries from uc_campaigns table when a Campaign is deleted
144     * @param WikiPage $article
145     * @param User $user
146     * @param string $reason
147     * @param int $id
148     * @param Content $content
149     * @param ManualLogEntry $logEntry
150     * @param int $archivedRevisionCount
151     */
152    public function onArticleDeleteComplete(
153        $article, $user, $reason, $id, $content, $logEntry, $archivedRevisionCount
154    ) {
155        if ( !$article->getTitle()->inNamespace( NS_CAMPAIGN ) ) {
156            return;
157        }
158
159        $fname = __METHOD__;
160        $dbw = $this->dbLoadBalancerFactory->getPrimaryDatabase();
161        $dbw->onTransactionPreCommitOrIdle( static function () use ( $dbw, $article, $fname ) {
162            $dbw->newDeleteQueryBuilder()
163                ->deleteFrom( 'uw_campaigns' )
164                ->where( [ 'campaign_name' => $article->getTitle()->getDBkey() ] )
165                ->caller( $fname )
166                ->execute();
167        }, $fname );
168    }
169
170    /**
171     * Update campaign names when the Campaign page moves
172     * @param LinkTarget $oldTitle
173     * @param LinkTarget $newTitle
174     * @param UserIdentity $user
175     * @param int $pageid
176     * @param int $redirid
177     * @param string $reason
178     * @param RevisionRecord $revisionRecord
179     */
180    public function onPageMoveComplete(
181        $oldTitle,
182        $newTitle,
183        $user,
184        $pageid,
185        $redirid,
186        $reason,
187        $revisionRecord
188    ): void {
189        if ( !$oldTitle->inNamespace( NS_CAMPAIGN ) ) {
190            return;
191        }
192
193        $dbw = $this->dbLoadBalancerFactory->getPrimaryDatabase();
194        $dbw->newUpdateQueryBuilder()
195            ->update( 'uw_campaigns' )
196            ->set( [ 'campaign_name' => $newTitle->getDBkey() ] )
197            ->where( [ 'campaign_name' => $oldTitle->getDBkey() ] )
198            ->caller( __METHOD__ )
199            ->execute();
200    }
201
202    /**
203     * Validates that the revised contents are valid JSON.
204     * If not valid, rejects edit with error message.
205     * @param IContextSource $context
206     * @param Content $content
207     * @param Status $status
208     * @param string $summary
209     * @param User $user
210     * @param bool $minoredit
211     * @return bool
212     */
213    public function onEditFilterMergedContent( IContextSource $context, Content $content, Status $status, $summary,
214        User $user, $minoredit
215    ) {
216        if ( !$context->getTitle()->inNamespace( NS_CAMPAIGN )
217            || !$content instanceof CampaignContent
218        ) {
219            return true;
220        }
221
222        try {
223            $content->validate();
224        } catch ( JsonSchemaException $e ) {
225            $status->fatal( $context->msg( $e->getCode(), $e->args ) );
226            // @todo Remove this line after this extension do not support mediawiki version 1.36 and before
227            $status->value = EditPage::AS_HOOK_ERROR_EXPECTED;
228            return false;
229        }
230
231        return true;
232    }
233}