Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
EditFilterMergedContentHookConstraint
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
5 / 5
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 checkConstraint
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
7
 getLegacyStatus
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHookError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatStatusErrors
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
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 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\EditPage\Constraint;
22
23use MediaWiki\Content\Content;
24use MediaWiki\Context\IContextSource;
25use MediaWiki\HookContainer\HookContainer;
26use MediaWiki\HookContainer\HookRunner;
27use MediaWiki\Html\Html;
28use MediaWiki\Language\Language;
29use MediaWiki\Message\Message;
30use MediaWiki\Status\Status;
31use MediaWiki\User\User;
32use StatusValue;
33
34/**
35 * Verify `EditFilterMergedContent` hook
36 *
37 * @since 1.36
38 * @author DannyS712
39 * @internal
40 */
41class EditFilterMergedContentHookConstraint implements IEditConstraint {
42
43    private HookRunner $hookRunner;
44    private Content $content;
45    private IContextSource $hookContext;
46    private string $summary;
47    private bool $minorEdit;
48    private Language $language;
49    private User $hookUser;
50    private Status $status;
51    private string $hookError = '';
52
53    /**
54     * @param HookContainer $hookContainer
55     * @param Content $content
56     * @param IContextSource $hookContext NOTE: This should only be passed to the hook.
57     * @param string $summary
58     * @param bool $minorEdit
59     * @param Language $language
60     * @param User $hookUser NOTE: This should only be passed to the hook.
61     */
62    public function __construct(
63        HookContainer $hookContainer,
64        Content $content,
65        IContextSource $hookContext,
66        string $summary,
67        bool $minorEdit,
68        Language $language,
69        User $hookUser
70    ) {
71        $this->hookRunner = new HookRunner( $hookContainer );
72        $this->content = $content;
73        $this->hookContext = $hookContext;
74        $this->summary = $summary;
75        $this->minorEdit = $minorEdit;
76        $this->language = $language;
77        $this->hookUser = $hookUser;
78        $this->status = Status::newGood();
79    }
80
81    public function checkConstraint(): string {
82        $hookResult = $this->hookRunner->onEditFilterMergedContent(
83            $this->hookContext,
84            $this->content,
85            $this->status,
86            $this->summary,
87            $this->hookUser,
88            $this->minorEdit
89        );
90        if ( !$hookResult ) {
91            // Error messages etc. could be handled within the hook...
92            if ( $this->status->isGood() ) {
93                $this->status->fatal( 'hookaborted' );
94                // Not setting $this->hookError here is a hack to allow the hook
95                // to cause a return to the edit page without $this->hookError
96                // being set. This is used by ConfirmEdit to display a captcha
97                // without any error message cruft.
98            } else {
99                if ( !$this->status->getMessages() ) {
100                    // Provide a fallback error message if none was set
101                    $this->status->fatal( 'hookaborted' );
102                }
103                $this->hookError = $this->formatStatusErrors( $this->status );
104            }
105            // Use the existing $status->value if the hook set it
106            if ( !$this->status->value ) {
107                // T273354: Should be AS_HOOK_ERROR_EXPECTED to display error message
108                $this->status->value = self::AS_HOOK_ERROR_EXPECTED;
109            }
110            return self::CONSTRAINT_FAILED;
111        }
112
113        if ( !$this->status->isOK() ) {
114            // ...or the hook could be expecting us to produce an error
115            // FIXME this sucks, we should just use the Status object throughout
116            if ( !$this->status->getMessages() ) {
117                // Provide a fallback error message if none was set
118                $this->status->fatal( 'hookaborted' );
119            }
120            $this->hookError = $this->formatStatusErrors( $this->status );
121            $this->status->value = self::AS_HOOK_ERROR_EXPECTED;
122            return self::CONSTRAINT_FAILED;
123        }
124
125        return self::CONSTRAINT_PASSED;
126    }
127
128    public function getLegacyStatus(): StatusValue {
129        // This returns a Status instead of a StatusValue since a Status object is
130        // used in the hook
131        return $this->status;
132    }
133
134    /**
135     * TODO this is really ugly. The constraint shouldn't know that the status
136     * will be used as wikitext, which is what the hookError represents, rather
137     * than just the error code. This needs a big refactor to remove the hook
138     * error string and just rely on the status object entirely.
139     *
140     * @internal
141     * @return string
142     */
143    public function getHookError(): string {
144        return $this->hookError;
145    }
146
147    /**
148     * Wrap status errors in error boxes for increased visibility.
149     * @param Status $status
150     * @return string
151     */
152    private function formatStatusErrors( Status $status ): string {
153        $ret = '';
154        foreach ( $status->getMessages() as $msg ) {
155            $msg = Message::newFromSpecifier( $msg );
156            $ret .= Html::errorBox( "\n" . $msg->inLanguage( $this->language )->plain() . "\n" );
157        }
158        return $ret;
159    }
160
161}