Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
EchoHtmlDigestEmailFormatter
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 9
156
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 formatModels
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
2
 renderBody
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 getCategoryTitle
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 groupByCategory
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 applyStyleToCategory
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 applyStyleToEvent
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 renderDigestList
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 renderAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Notifications\Formatters;
4
5use Language;
6use MediaWiki\Html\Html;
7use MediaWiki\Parser\Sanitizer;
8use MediaWiki\SpecialPage\SpecialPage;
9use MediaWiki\User\User;
10
11class EchoHtmlDigestEmailFormatter extends EchoEventDigestFormatter {
12
13    /**
14     * @var string 'daily' or 'weekly'
15     */
16    protected $digestMode;
17
18    public function __construct( User $user, Language $language, $digestMode ) {
19        parent::__construct( $user, $language );
20        $this->digestMode = $digestMode;
21    }
22
23    /**
24     * @param EchoEventPresentationModel[] $models
25     * @return string[] Array of the following format:
26     *               [ 'body'    => formatted email body,
27     *                 'subject' => formatted email subject ]
28     */
29    protected function formatModels( array $models ) {
30        // echo-email-batch-body-intro-daily
31        // echo-email-batch-body-intro-weekly
32        $intro = $this->msg( 'echo-email-batch-body-intro-' . $this->digestMode )
33            ->params( $this->user->getName() )
34            ->parse();
35        $intro = nl2br( $intro );
36
37        $eventsByCategory = $this->groupByCategory( $models );
38        ksort( $eventsByCategory );
39        $digestList = $this->renderDigestList( $eventsByCategory );
40
41        $htmlFormatter = new EchoHtmlEmailFormatter( $this->user, $this->language );
42
43        $body = $this->renderBody(
44            $this->language,
45            $intro,
46            $digestList,
47            $this->renderAction(),
48            $htmlFormatter->getFooter()
49        );
50
51        // echo-email-batch-subject-daily
52        // echo-email-batch-subject-weekly
53        $subject = $this->msg( 'echo-email-batch-subject-' . $this->digestMode )
54            ->numParams( count( $models ), count( $models ) )
55            ->text();
56
57        return [
58            'subject' => $subject,
59            'body' => $body,
60        ];
61    }
62
63    private function renderBody( Language $language, $intro, $digestList, $action, $footer ) {
64        $alignStart = $language->alignStart();
65        $langCode = $language->getHtmlCode();
66        $langDir = $language->getDir();
67
68        // phpcs:disable Generic.Files.LineLength
69        return <<< EOF
70<html><head>
71    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
72    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
73    <style>
74        @media only screen and (max-width: 480px){
75            table[id="email-container"]{max-width:600px !important; width:100% !important;}
76        }
77    </style>
78</head><body>
79<table cellspacing="0" cellpadding="0" border="0" width="100%" align="center" lang="$langCode" dir="$langDir">
80<tr>
81    <td bgcolor="#EAECF0"><center>
82        <br /><br />
83        <table cellspacing="0" cellpadding="0" border="0" width="600" id="email-container">
84            <tr>
85                <td bgcolor="#FFFFFF" width="5%">&nbsp;</td>
86                <td bgcolor="#FFFFFF" width="6%">&nbsp;</td>
87                <td bgcolor="#FFFFFF" width="79%" style="line-height:40px;">&nbsp;</td>
88                <td bgcolor="#FFFFFF" width="10%">&nbsp;</td>
89            </tr>
90            <tr>
91                <td bgcolor="#FFFFFF" rowspan="2">&nbsp;</td>
92                <td bgcolor="#FFFFFF" rowspan="2">&nbsp;</td>
93                <td bgcolor="#FFFFFF" align="center" style="font-family: Arial, Helvetica, sans-serif; font-size:13px; line-height:20px; color:#72777D; text-align: center;">$intro</td>
94                <td bgcolor="#FFFFFF" rowspan="2">&nbsp;</td>
95            </tr>
96            <tr>
97                <td bgcolor="#FFFFFF" align="$alignStart" style="font-family: Arial, Helvetica, sans-serif; line-height: 20px; font-weight: 600;">
98                    <table cellspacing="0" cellpadding="0" border="0" width="100%">
99                        <tr>
100                            <td bgcolor="#FFFFFF" align="$alignStart" style="font-family: Arial, Helvetica, sans-serif; font-size:13px; color: #54595D; padding-top: 25px;">
101                                $digestList
102                            </td>
103                        </tr>
104                    </table>
105                    <br /><br />
106                </td>
107            </tr>
108            <tr>
109                <td bgcolor="#FFFFFF">&nbsp;</td>
110                <td bgcolor="#FFFFFF">&nbsp;</td>
111                <td bgcolor="#FFFFFF" style="line-height:60px;" align="center">$action</td>
112                <td bgcolor="#FFFFFF">&nbsp;</td>
113            </tr>
114            <tr>
115                <td bgcolor="#FFFFFF">&nbsp;</td>
116                <td bgcolor="#FFFFFF">&nbsp;</td>
117                <td bgcolor="#FFFFFF" style="line-height:40px;">&nbsp;</td>
118                <td bgcolor="#FFFFFF">&nbsp;</td>
119            </tr>
120            <tr>
121                <td>&nbsp;</td>
122                <td>&nbsp;</td>
123                <td align="$alignStart" style="font-family: Arial, Helvetica, sans-serif; font-size:10px; line-height:13px; color:#72777D; padding: 10px 20px;"><br />
124                    $footer
125                    <br /><br />
126                </td>
127                <td>&nbsp;</td>
128            </tr>
129            <tr>
130                <td colspan="4">&nbsp;</td>
131            </tr>
132        </table>
133        <br><br></center>
134    </td>
135</tr>
136</table>
137</body></html>
138EOF;
139        // phpcs:enable Generic.Files.LineLength
140    }
141
142    /**
143     * @param string $type Notification type
144     * @param int $count Number of notifications in this type's section
145     * @return string Formatted category section title
146     */
147    private function getCategoryTitle( $type, $count ) {
148        return $this->msg( "echo-category-title-$type" )
149            ->numParams( $count )
150            ->parse();
151    }
152
153    /**
154     * @param EchoEventPresentationModel[] $models
155     * @return array [ 'category name' => EchoEventPresentationModel[] ]
156     */
157    private function groupByCategory( array $models ) {
158        $eventsByCategory = [];
159        foreach ( $models as $model ) {
160            $eventsByCategory[$model->getCategory()][] = $model;
161        }
162        return $eventsByCategory;
163    }
164
165    /**
166     * Apply style to notification category header
167     * @param string $category Can contain HTML. Is included as-is in HTML template, is not escaped.
168     * @return string
169     */
170    protected function applyStyleToCategory( $category ) {
171        return <<< EOF
172<tr>
173    <td colspan="2" style="color: #72777D; font-weight: normal; font-size: 13px; padding-top: 15px;">
174        $category <br />
175        <hr style="background-color:#FFFFFF; color:#FFFFFF; border: 1px solid #EAECF0;" />
176    </td>
177</tr>
178EOF;
179    }
180
181    /**
182     * Apply style to individual notification event
183     * @param EchoEventPresentationModel $model
184     * @return string
185     */
186    protected function applyStyleToEvent( EchoEventPresentationModel $model ) {
187        $iconUrl = wfExpandUrl(
188            EchoIcon::getRasterizedUrl( $model->getIconType(), $this->language->getCode() ),
189            PROTO_CANONICAL
190        );
191
192        $imgSrc = Sanitizer::encodeAttribute( $iconUrl );
193
194        // notification text
195        $text = $model->getHeaderMessage()->parse();
196
197        return <<< EOF
198<tr>
199    <td width="30">
200        <img src="$imgSrc" width="30" height="30" style="vertical-align:middle;">
201    </td>
202    <td style="font-family: Arial, Helvetica, sans-serif; font-size:13px; color: #54595D;">
203        $text
204    </td>
205</tr>
206EOF;
207    }
208
209    private function renderDigestList( $eventsByCategory ) {
210        $result = [];
211        // build the html section for each category
212        foreach ( $eventsByCategory as $category => $models ) {
213            $output = $this->applyStyleToCategory(
214                $this->getCategoryTitle( $category, count( $models ) )
215            );
216            foreach ( $models as $model ) {
217                $output .= "\n" . $this->applyStyleToEvent( $model );
218            }
219            $result[] = '<table border="0" width="100%">' . $output . '</table>';
220        }
221
222        return trim( implode( "\n", $result ) );
223    }
224
225    private function renderAction() {
226        return Html::element(
227            'a',
228            [
229                'href' => SpecialPage::getTitleFor( 'Notifications' )
230                    ->getFullURL( '', false, PROTO_CANONICAL ),
231                'style' => EchoHtmlEmailFormatter::PRIMARY_LINK_STYLE,
232            ],
233            $this->msg( 'echo-email-batch-link-text-view-all-notifications' )->text()
234        );
235    }
236
237}