Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.00% covered (warning)
84.00%
21 / 25
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SlotDiffRenderer
84.00% covered (warning)
84.00%
21 / 25
20.00% covered (danger)
20.00%
1 / 5
18.18
0.00% covered (danger)
0.00%
0 / 1
 getDiff
n/a
0 / 0
n/a
0 / 0
0
 localizeDiff
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTablePrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addModules
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExtraCacheKeys
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 normalizeContents
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
13
1<?php
2/**
3 * Renders a diff for a single slot.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup DifferenceEngine
22 */
23
24use MediaWiki\Context\IContextSource;
25use MediaWiki\Output\OutputPage;
26use MediaWiki\Title\Title;
27use Wikimedia\Assert\Assert;
28
29/**
30 * Renders a diff for a single slot (that is, a diff between two content objects).
31 *
32 * Callers should obtain instances of this class by invoking ContentHandler::getSlotDiffRenderer
33 * on the content handler of the new content object (ie. the one shown on the right side
34 * of the diff), or of the old one if the new one does not exist.
35 *
36 * The default implementation just does a text diff on the native text representation.
37 * Content handler extensions can subclass this to provide a more appropriate diff method by
38 * overriding ContentHandler::getSlotDiffRendererInternal. Other extensions that want to interfere
39 * with diff generation in some way can use the GetSlotDiffRenderer hook.
40 *
41 * @stable to extend
42 * @ingroup DifferenceEngine
43 */
44abstract class SlotDiffRenderer {
45
46    /**
47     * Get a diff between two content objects. One of them might be null (meaning a slot was
48     * created or removed), but both cannot be. $newContent (or if it's null then $oldContent)
49     * must have the same content model that was used to obtain this diff renderer.
50     * @param Content|null $oldContent
51     * @param Content|null $newContent
52     * @return string HTML. One or more <tr> tags, or an empty string if the inputs are identical.
53     * @throws IncompatibleDiffTypesException
54     */
55    abstract public function getDiff( Content $oldContent = null, Content $newContent = null );
56
57    /**
58     * Localize language-independent text returned by getDiff(), making it
59     * suitable for display. Subclasses overriding this should arrange for
60     * injection of a MessageLocalizer.
61     *
62     * @param string $diff
63     * @param array $options Associative array of options:
64     *   - reducedLineNumbers: If true, remove "line 1" but allow other line numbers
65     * @return string
66     */
67    public function localizeDiff( string $diff, array $options = [] ) {
68        return $diff;
69    }
70
71    /**
72     * Get the content to add above the main diff table.
73     *
74     * @since 1.41
75     * @param IContextSource $context
76     * @param Title $newTitle
77     * @return (string|null)[] An array of HTML fragments to assemble into the prefix
78     *   area. They will be deduplicated and sorted by key.
79     */
80    public function getTablePrefix( IContextSource $context, Title $newTitle ): array {
81        return [];
82    }
83
84    /**
85     * Add modules needed for correct styling/behavior of the diff.
86     * @stable to override
87     * @param OutputPage $output
88     */
89    public function addModules( OutputPage $output ) {
90    }
91
92    /**
93     * Return any extra keys to split the diff cache by.
94     * @stable to override
95     * @return string[]
96     */
97    public function getExtraCacheKeys() {
98        return [];
99    }
100
101    /**
102     * Helper method to normalize the input of getDiff().
103     * Verifies that at least one of $oldContent and $newContent is not null, verifies that
104     * they are instances of one of the allowed classes (if provided), and replaces null with
105     * empty content.
106     * @param Content|null &$oldContent
107     * @param Content|null &$newContent
108     * @param string|array|null $allowedClasses
109     * @throws IncompatibleDiffTypesException
110     */
111    protected function normalizeContents(
112        Content &$oldContent = null, Content &$newContent = null, $allowedClasses = null
113    ) {
114        if ( !$oldContent && !$newContent ) {
115            throw new InvalidArgumentException( '$oldContent and $newContent cannot both be null' );
116        }
117
118        if ( $allowedClasses ) {
119            if ( is_string( $allowedClasses ) ) {
120                $allowedClasses = explode( '|', $allowedClasses );
121            }
122            $allowedClassesOrNull = array_merge( $allowedClasses, [ 'null' ] );
123
124            // The new content (or the old one if the new one is null) must always match the renderer
125            // since the ContentHandler of that model should be used to create it
126            Assert::parameterType( $allowedClassesOrNull, $newContent, '$newContent' );
127            if ( !$newContent ) {
128                Assert::parameterType( $allowedClassesOrNull, $oldContent, '$oldContent' );
129            }
130
131            // If there are two content objects, the old one can be arbitrary as it is possible
132            // to generate a diff between any two revisions; so an incompatible model should be
133            // handled as a user error, not a logic error.
134            if ( $oldContent && $newContent ) {
135                $allowed = false;
136                foreach ( $allowedClasses as $class ) {
137                    if ( $oldContent instanceof $class ) {
138                        $allowed = true;
139                        break;
140                    }
141                }
142                if ( !$allowed ) {
143                    throw new IncompatibleDiffTypesException( $oldContent->getModel(), $newContent->getModel() );
144                }
145            }
146        }
147
148        if ( !$oldContent ) {
149            $oldContent = $newContent->getContentHandler()->makeEmptyContent();
150        } elseif ( !$newContent ) {
151            $newContent = $oldContent->getContentHandler()->makeEmptyContent();
152        }
153    }
154
155}