Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
67.50% covered (warning)
67.50%
27 / 40
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
WikipediaAppCounter
67.50% covered (warning)
67.50%
27 / 40
37.50% covered (danger)
37.50%
3 / 8
29.12
0.00% covered (danger)
0.00%
0 / 1
 validateComment
n/a
0 / 0
n/a
0 / 0
0
 getLanguageFromComment
n/a
0 / 0
n/a
0 / 0
0
 onEditSuccess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onRevert
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 conditionallyIncrementEditCount
83.33% covered (warning)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
4.07
 conditionallyIncrementRevertCount
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 hasSuggestedEditsChangeTag
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getLanguageFromWikibaseComment
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
 isWikipediaAppRequest
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getMagicCommentPattern
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 *
17 * @file
18 */
19
20namespace MediaWiki\Extension\WikimediaEditorTasks;
21
22use MediaWiki\MediaWikiServices;
23use MediaWiki\Request\WebRequest;
24use MediaWiki\Revision\RevisionRecord;
25
26abstract class WikipediaAppCounter extends Counter {
27
28    /**
29     * @param string $comment Revision comment to be validated for whether to include this revision in the count.
30     * @return bool Whether this revision should be counted by this counter.
31     */
32    abstract protected function validateComment( string $comment ): bool;
33
34    /**
35     * @param string $comment Revision comment from which the language may be extracted.
36     * @return string|null Language parsed from the given comment, or a constant overridden language.
37     */
38    abstract protected function getLanguageFromComment( string $comment ): ?string;
39
40    /** @inheritDoc */
41    public function onEditSuccess( int $centralId, WebRequest $request, RevisionRecord $revision ): void {
42        $this->conditionallyIncrementEditCount( $centralId, $request, $revision );
43    }
44
45    /** @inheritDoc */
46    public function onRevert( int $centralId, int $revisionId, RevisionRecord $revision ): void {
47        if ( !$this->hasSuggestedEditsChangeTag( $revisionId ) ) {
48            return;
49        }
50        if ( $this->isRevertCountingEnabled() ) {
51            $this->conditionallyIncrementRevertCount( $centralId, $revision );
52        } else {
53            $this->reset( $centralId );
54        }
55    }
56
57    /**
58     * Increment the counter corresponding to the provided MW API action
59     * @param int $centralId central ID of the editing user
60     * @param WebRequest $request
61     * @param RevisionRecord $revision revision representing the successful edit
62     */
63    protected function conditionallyIncrementEditCount( int $centralId, WebRequest $request,
64        RevisionRecord $revision ): void {
65        if ( !$this->isWikipediaAppRequest( $request ) ) {
66            return;
67        }
68        $comment = $revision->getComment()->text;
69        if ( !$this->validateComment( $comment ) ) {
70            return;
71        }
72        $lang = $this->getLanguageFromComment( $comment );
73        if ( !$lang ) {
74            return;
75        }
76        $this->incrementEditCountForLang( $centralId, $lang );
77        $this->updateEditStreak( $centralId );
78
79        $changeTagsStore = MediaWikiServices::getInstance()->getChangeTagsStore();
80        $changeTagsStore->addTags( 'apps-suggested-edits', null, $revision->getId() );
81    }
82
83    /**
84     * Increment the revert counter
85     * @param int $centralId central ID of the editing user
86     * @param RevisionRecord $revision the RevisionRecord corresponding with $revisionId
87     */
88    protected function conditionallyIncrementRevertCount(
89        int $centralId,
90        RevisionRecord $revision
91    ): void {
92        $comment = $revision->getComment()->text;
93        if ( !$this->validateComment( $comment ) ) {
94            return;
95        }
96        $lang = $this->getLanguageFromComment( $comment );
97        if ( $lang ) {
98            $this->incrementRevertCountForLang( $centralId, $lang );
99        }
100    }
101
102    /**
103     * Return true if the suggested edits change tag is associated with the revision.
104     * @param int $revisionId
105     * @return bool
106     */
107    protected function hasSuggestedEditsChangeTag( int $revisionId ): bool {
108        $services = MediaWikiServices::getInstance();
109        $dbr = $services->getConnectionProvider()->getReplicaDatabase();
110        $tags = $services->getChangeTagsStore()->getTags( $dbr, null, $revisionId, null );
111        return in_array( 'apps-suggested-edits', $tags, true );
112    }
113
114    /**
115     * Get the language code from the semi-structured Wikibase edit summary text.
116     * Examples:
117     *  \/* wbsetdescription-add:1|en *\/ 19th century French painter
118     *  \/* wbsetlabel-add:1|en *\/ A chicken in the snow
119     * See docs at mediawiki-extensions-Wikibase/docs/summaries.md.
120     * TODO: Update to use structured comment data when that's implemented (T215422)
121     * @param string $action
122     * @param string $comment
123     * @return string|null language code, if found
124     */
125    protected function getLanguageFromWikibaseComment( string $action, string $comment ): ?string {
126        if ( !$comment ) {
127            return null;
128        }
129        $matches = [];
130        $result = preg_match( $this->getMagicCommentPattern( $action ), $comment, $matches );
131        if ( $result ) {
132            return $matches[1];
133        }
134        return null;
135    }
136
137    /**
138     * @param WebRequest $request
139     * @return bool
140     */
141    private function isWikipediaAppRequest( WebRequest $request ) {
142        $ua = $request->getHeader( 'User-agent' );
143        if ( $ua ) {
144            return strpos( $ua, 'WikipediaApp/' ) === 0;
145        }
146        return false;
147    }
148
149    /**
150     * @param string $action Wikibase action to which this pattern will apply.
151     * @return string pattern matching Wikibase magic comments associated with this counter.
152     */
153    protected function getMagicCommentPattern( string $action ): string {
154        return '/^\/\* ' . $action . '-[a-z]{3}:[0-9]\|([a-z-]+) /';
155    }
156
157}