Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 143
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
CentralNoticePageLogPager
0.00% covered (danger)
0.00%
0 / 143
0.00% covered (danger)
0.00%
0 / 6
272
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
 getIndexField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
6
 formatRow
0.00% covered (danger)
0.00%
0 / 86
0.00% covered (danger)
0.00%
0 / 1
90
 getStartBody
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
6
 getEndBody
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Html\Html;
4use MediaWiki\MediaWikiServices;
5use MediaWiki\Pager\ReverseChronologicalPager;
6use MediaWiki\RecentChanges\RecentChange;
7use MediaWiki\SpecialPage\SpecialPage;
8use MediaWiki\Title\Title;
9use MediaWiki\User\User;
10use Wikimedia\Rdbms\IExpression;
11use Wikimedia\Rdbms\LikeValue;
12
13/**
14 * This class generates a paginated log of recent changes to banner messages (the parts that get
15 * translated). We use the rencentchanges table since it is lightweight, however, this means that
16 * the log only goes back 30 days.
17 */
18class CentralNoticePageLogPager extends ReverseChronologicalPager {
19    /** @var Title */
20    public $viewPage;
21
22    /**
23     * @param SpecialPage $special
24     * @param string $logType type of log - 'bannercontent' or 'bannermessages' (optional)
25     */
26    public function __construct(
27        private readonly SpecialPage $special,
28        private readonly string $logType = 'bannercontent',
29    ) {
30        parent::__construct();
31
32        $this->viewPage = SpecialPage::getTitleFor( 'NoticeTemplate', 'view' );
33    }
34
35    /**
36     * Sort the log list by timestamp
37     * @return string
38     */
39    public function getIndexField() {
40        return 'rc_timestamp';
41    }
42
43    /**
44     * Pull log entries from the database
45     * @return array[]
46     */
47    public function getQueryInfo() {
48        $conds = [
49            // include bot edits (all edits made by CentralNotice are bot edits)
50            'rc_bot' => 1,
51            // only MediaWiki pages
52            'rc_namespace' => NS_MEDIAWIKI,
53        ];
54        $db = CNDatabase::getReplicaDb();
55        if ( $this->logType == 'bannercontent' ) {
56            // Add query conditions for banner content log
57            $conds += [
58                // get banner content
59                $db->expr( 'rc_title', IExpression::LIKE,
60                    new LikeValue( 'Centralnotice-template-', $db->anyString() ) ),
61            ];
62        } else {
63            // Add query conditions for banner messages log
64            $conds += [
65                // get banner messages
66                $db->expr( 'rc_title', IExpression::LIKE, new LikeValue( 'Centralnotice-', $db->anyString() ) ),
67                // exclude normal banner content
68                $db->expr( 'rc_title', IExpression::NOT_LIKE,
69                    new LikeValue( 'Centralnotice-template-', $db->anyString() ) ),
70            ];
71        }
72
73        $rcQuery = RecentChange::getQueryInfo();
74        return [
75            'tables' => $rcQuery['tables'],
76            'fields' => $rcQuery['fields'],
77            'conds' => $conds,
78            'join_conds' => $rcQuery['joins'],
79        ];
80    }
81
82    /**
83     * Generate the content of each table row (1 row = 1 log entry)
84     * @param stdClass $row
85     * @return string HTML
86     */
87    public function formatRow( $row ) {
88        // Create a user object so we can pull the name, user page, etc.
89        $loggedUser = User::newFromId( $row->rc_user );
90        // Create the user page link
91        $userLink = $this->special->getLinkRenderer()->makeKnownLink(
92            $loggedUser->getUserPage(),
93            $loggedUser->getName()
94        );
95        $userTalkLink = $this->special->getLinkRenderer()->makeKnownLink(
96            $loggedUser->getTalkPage(),
97            $this->msg( 'centralnotice-talk-link' )->text()
98        );
99
100        // English is the default for CentralNotice messages
101        $language = 'en';
102
103        if ( $this->logType == 'bannercontent' ) {
104            // Extract the banner name from the title
105            $pattern = '/Centralnotice-template-(.*)/';
106            preg_match( $pattern, $row->rc_title, $matches );
107            $banner = $matches[1];
108        } elseif ( $this->logType == 'bannermessages' ) {
109            // Split the title into banner, message, and language
110            $titlePieces = explode( "/", $row->rc_title, 2 );
111            $titleBase = $titlePieces[0];
112            if ( array_key_exists( 1, $titlePieces ) ) {
113                $language = $titlePieces[1];
114            }
115            $pattern = '/Centralnotice-([^-]*)-(.*)/';
116            preg_match( $pattern, $titleBase, $matches );
117            $banner = $matches[1];
118            $message = $matches[2];
119        } else {
120            throw new LogicException( "Unknown type {$this->logType}" );
121        }
122
123        // Create banner link
124        $bannerLink = $this->special->getLinkRenderer()->makeKnownLink(
125            $this->viewPage,
126            $banner,
127            [],
128            [ 'template' => $banner ]
129        );
130
131        // Create title object
132        $title = Title::newFromText( "MediaWiki:{$row->rc_title}" );
133
134        if ( $this->logType == 'bannercontent' ) {
135            // If the banner was just created, show a link to the banner. If the banner was
136            // edited, show a link to the banner and a link to the diff.
137            if ( $row->rc_source === RecentChange::SRC_NEW ) {
138                $bannerCell = $bannerLink;
139            } else {
140                $querydiff = [
141                    'curid' => $row->rc_cur_id,
142                    'diff' => $row->rc_this_oldid,
143                    'oldid' => $row->rc_last_oldid
144                ];
145                $diffUrl = htmlspecialchars( $title->getLinkUrl( $querydiff ) );
146                // Should "diff" be localised? It appears not to be elsewhere in the interface.
147                // See ChangesList->preCacheMessages() for example.
148                $bannerCell = $bannerLink . "&nbsp;(<a href=\"$diffUrl\">diff</a>)";
149            }
150        } elseif ( $this->logType == 'bannermessages' ) {
151            $bannerCell = $bannerLink;
152
153            // Create the message link
154            // @phan-suppress-next-line PhanPossiblyUndeclaredVariable
155            $messageLink = $this->special->getLinkRenderer()->makeKnownLink( $title, $message );
156
157            // If the message was just created, show a link to the message. If the message was
158            // edited, show a link to the message and a link to the diff.
159            if ( $row->rc_source === RecentChange::SRC_NEW ) {
160                $messageCell = $messageLink;
161            } else {
162                $querydiff = [
163                    'curid' => $row->rc_cur_id,
164                    'diff' => $row->rc_this_oldid,
165                    'oldid' => $row->rc_last_oldid
166                ];
167                $diffUrl = htmlspecialchars( $title->getLinkUrl( $querydiff ) );
168                // Should "diff" be localised? It appears not to be elsewhere in the interface.
169                // See ChangesList->preCacheMessages() for example.
170                $messageCell = $messageLink . "&nbsp;(<a href=\"$diffUrl\">diff</a>)";
171            }
172        } else {
173            throw new LogicException( "Unknown type {$this->logType}" );
174        }
175
176        // Begin log entry primary row
177        $lang = $this->getLanguage();
178        $htmlOut = Html::openElement( 'tr' );
179
180        $htmlOut .= Html::element( 'td', [ 'valign' => 'top' ] );
181        $htmlOut .= Html::element( 'td', [ 'valign' => 'top', 'class' => 'primary' ],
182            $lang->date( $row->rc_timestamp ) . ' ' . $lang->time( $row->rc_timestamp )
183        );
184        $htmlOut .= Html::rawElement( 'td', [ 'valign' => 'top', 'class' => 'primary' ],
185            $this->msg( 'centralnotice-user-links' )
186                ->rawParams( $userLink, $userTalkLink )
187                ->escaped()
188        );
189        $htmlOut .= Html::rawElement( 'td', [ 'valign' => 'top', 'class' => 'primary' ],
190            $bannerCell
191        );
192        if ( $this->logType == 'bannermessages' ) {
193            // @phan-suppress-next-next-line PhanPossiblyUndeclaredVariable
194            $htmlOut .= Html::rawElement( 'td', [ 'valign' => 'top', 'class' => 'primary' ],
195                $messageCell
196            );
197            $htmlOut .= Html::rawElement( 'td', [ 'valign' => 'top', 'class' => 'primary' ],
198                $language
199            );
200        }
201
202        $htmlOut .= Html::rawElement( 'td',
203            [ 'valign' => 'top', 'class' => 'primary-summary' ],
204            htmlspecialchars(
205                MediaWikiServices::getInstance()->getCommentStore()->getComment( 'rc_comment', $row )->text
206            )
207        );
208        $htmlOut .= Html::rawElement( 'td', [],
209            '&nbsp;'
210        );
211
212        // End log entry primary row
213        $htmlOut .= Html::closeElement( 'tr' );
214
215        return $htmlOut;
216    }
217
218    /**
219     * @return string
220     */
221    public function getStartBody() {
222        $htmlOut = '';
223        $htmlOut .= Html::openElement( 'table', [ 'id' => 'cn-campaign-logs', 'cellpadding' => 3 ] );
224        $htmlOut .= Html::openElement( 'tr' );
225        $htmlOut .= Html::element( 'th', [ 'style' => 'width: 20px;' ] );
226        $htmlOut .= Html::element( 'th', [ 'align' => 'left', 'style' => 'width: 130px;' ],
227            $this->msg( 'centralnotice-timestamp' )->text()
228        );
229        $htmlOut .= Html::element( 'th', [ 'align' => 'left', 'style' => 'width: 160px;' ],
230            $this->msg( 'centralnotice-user' )->text()
231        );
232        $htmlOut .= Html::element( 'th', [ 'align' => 'left', 'style' => 'width: 160px;' ],
233            $this->msg( 'centralnotice-banner' )->text()
234        );
235        if ( $this->logType == 'bannermessages' ) {
236            $htmlOut .= Html::element( 'th', [ 'align' => 'left', 'style' => 'width: 160px;' ],
237                $this->msg( 'centralnotice-message' )->text()
238            );
239            $htmlOut .= Html::element( 'th', [ 'align' => 'left', 'style' => 'width: 100px;' ],
240                $this->msg( 'centralnotice-language' )->text()
241            );
242
243            $commentWidth = '120px';
244
245        } else {
246            $commentWidth = '250px';
247        }
248
249        $htmlOut .= Html::element( 'th',
250            [ 'align' => 'left', 'style' => "width: {$commentWidth};" ],
251            $this->msg( 'centralnotice-change-summary-heading' )->text()
252        );
253
254        $htmlOut .= Html::rawElement( 'td', [],
255            '&nbsp;'
256        );
257        $htmlOut .= Html::closeElement( 'tr' );
258        return $htmlOut;
259    }
260
261    /**
262     * Close table
263     * @return string
264     */
265    public function getEndBody() {
266        return Html::closeElement( 'table' );
267    }
268}