Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.49% covered (success)
91.49%
43 / 47
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
MessageBuilder
91.49% covered (success)
91.49%
43 / 47
87.50% covered (warning)
87.50%
7 / 8
19.22
0.00% covered (danger)
0.00%
0 / 1
 stripTildes
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 buildMessage
63.64% covered (warning)
63.64%
7 / 11
0.00% covered (danger)
0.00%
0 / 1
4.77
 buildSubject
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 buildPlaintextSubject
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 sanitizeSubject
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 wrapBasedOnLanguage
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 needsWrapping
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 wrapContentWithLanguageAttributes
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\MassMessage;
5
6use Language;
7use MediaWiki\Html\Html;
8
9/**
10 * Contains logic to build the message and subject to be posted
11 * @author Abijeet Patro
12 * @since 2022.01
13 * @license GPL-2.0-or-later
14 */
15class MessageBuilder {
16    private const USE_INLINE = true;
17
18    /**
19     * Strip tildes at the end of the message
20     *
21     * @param string $customMessage
22     * @return string
23     */
24    public function stripTildes( string $customMessage ): string {
25        $strippedText = rtrim( $customMessage );
26
27        if ( $strippedText
28            && substr( $strippedText, -4 ) === '~~~~'
29            && substr( $strippedText, -5 ) !== '~~~~~'
30        ) {
31            $strippedText = substr( $strippedText, 0, -4 );
32        }
33
34        return $strippedText;
35    }
36
37    /**
38     * Merge page message passed as message, wrap it in necessary HTML tags / attributes and
39     * adds language tagging if necessary. Includes a comment about who is the sender.
40     *
41     * @param string $customMessageText
42     * @param LanguageAwareText|null $pageContent
43     * @param Language|null $targetLanguage
44     * @param array $commentParams
45     * @return string
46     */
47    public function buildMessage(
48        string $customMessageText,
49        ?LanguageAwareText $pageContent,
50        ?Language $targetLanguage,
51        array $commentParams
52    ): string {
53        $trimmedText = rtrim( $customMessageText );
54        $fullMessageText = '';
55
56        if ( $pageContent ) {
57            $fullMessageText = $this->wrapBasedOnLanguage( $pageContent, $targetLanguage, !self::USE_INLINE );
58        }
59
60        // If either is empty, the extra new lines will be trimmed
61        $fullMessageText = trim( $fullMessageText . "\n\n" . $trimmedText );
62
63        // $commentParams will always be present unless we are runnning tests.
64        if ( $commentParams ) {
65            $commentMessage = wfMessage( 'massmessage-hidden-comment' )->params( $commentParams );
66            if ( $targetLanguage ) {
67                $commentMessage = $commentMessage->inLanguage( $targetLanguage );
68            }
69            $fullMessageText .= "\n" . $commentMessage->text();
70        }
71
72        return $fullMessageText;
73    }
74
75    /**
76     * Compose the subject depending on page subject or subject and target language.
77     *
78     * @param string $customSubject
79     * @param LanguageAwareText|null $pageSubject
80     * @param Language|null $targetPageLanguage
81     * @return string
82     */
83    public function buildSubject(
84        string $customSubject,
85        ?LanguageAwareText $pageSubject,
86        ?Language $targetPageLanguage
87    ): string {
88        if ( $pageSubject ) {
89            $strippedPageSubject = new LanguageAwareText(
90                $this->sanitizeSubject( $pageSubject->getWikitext() ),
91                $pageSubject->getLanguageCode(),
92                $pageSubject->getLanguageDirection()
93            );
94
95            return $this->wrapBasedOnLanguage( $strippedPageSubject, $targetPageLanguage, self::USE_INLINE );
96        }
97
98        return $this->sanitizeSubject( $customSubject );
99    }
100
101    /**
102     * Compose the page subject without any HTML wrapping
103     *
104     * @param string $customSubject
105     * @param LanguageAwareText|null $pageSubject
106     * @return string
107     */
108    public function buildPlaintextSubject( string $customSubject, ?LanguageAwareText $pageSubject ): string {
109        if ( $pageSubject ) {
110            return $this->sanitizeSubject( $pageSubject->getWikitext() );
111        }
112
113        return $this->sanitizeSubject( $customSubject );
114    }
115
116    /**
117     * Remove all newlines in-between content and remove tags
118     *
119     * @param string $subject
120     * @return string
121     */
122    private function sanitizeSubject( string $subject ): string {
123        return rtrim( strip_tags( str_replace( "\n", '', $subject ) ) );
124    }
125
126    /**
127     * Wraps the page content based on the page content and the target page language
128     *
129     * @param LanguageAwareText $pageContent
130     * @param Language|null $targetLanguage
131     * @param bool $useInline
132     * @return string
133     */
134    private function wrapBasedOnLanguage(
135        LanguageAwareText $pageContent,
136        ?Language $targetLanguage,
137        bool $useInline
138    ): string {
139        if ( $this->needsWrapping( $targetLanguage, $pageContent ) ) {
140            // Wrap page contents if it differs from target page's language. Ideally the
141            // message contents would be wrapped too, but we do not know its language.
142            return $this->wrapContentWithLanguageAttributes( $pageContent, $useInline );
143        } else {
144            return $pageContent->getWikitext();
145        }
146    }
147
148    /**
149     * Check if the page contents need to be wrapped
150     * @param Language|null $targetLanguage
151     * @param LanguageAwareText $pageContent
152     * @return bool
153     */
154    private function needsWrapping( ?Language $targetLanguage, LanguageAwareText $pageContent ): bool {
155        return !$targetLanguage || $targetLanguage->getCode() !== $pageContent->getLanguageCode();
156    }
157
158    /**
159     * Wrap contents with language attributes using inline or block elements
160     * @param LanguageAwareText $pageContent
161     * @param bool $useInline
162     * @return string
163     */
164    private function wrapContentWithLanguageAttributes( LanguageAwareText $pageContent, bool $useInline ): string {
165        $elementToUse = 'div';
166        $content = "\n" . $pageContent->getWikitext() . "\n";
167
168        if ( $useInline == self::USE_INLINE ) {
169            $elementToUse = 'span';
170            $content = $pageContent->getWikitext();
171        }
172
173        return Html::rawElement(
174            $elementToUse,
175            [
176                'lang' => $pageContent->getLanguageCode(),
177                'dir' => $pageContent->getLanguageDirection(),
178                // This class is needed for proper rendering of list items (and maybe more)
179                'class' => 'mw-content-' . $pageContent->getLanguageDirection()
180            ],
181            $content
182        );
183    }
184}