Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
DiscussionToolsEventTrait
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 3
182
0.00% covered (danger)
0.00%
0 / 1
 userCan
n/a
0 / 0
n/a
0 / 0
0
 isBundled
n/a
0 / 0
n/a
0 / 0
0
 getBundledEvents
n/a
0 / 0
n/a
0 / 0
0
 getBundledIds
n/a
0 / 0
n/a
0 / 0
0
 getCommentLink
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
 getContentSnippet
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 addMarkAsRead
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2/**
3 * Common code for all Echo event presentation models for DiscussionTools events.
4 *
5 * @file
6 * @ingroup Extensions
7 * @license MIT
8 */
9
10namespace MediaWiki\Extension\DiscussionTools\Notifications;
11
12use MediaWiki\Extension\Notifications\DiscussionParser;
13use MediaWiki\Extension\Notifications\Formatters\EchoPresentationModelSection;
14use MediaWiki\Extension\Notifications\Model\Event;
15use MediaWiki\Language\Language;
16use MediaWiki\Revision\RevisionRecord;
17use MediaWiki\WikiMap\WikiMap;
18
19/**
20 * This trait must be only used on EchoEventPresentationModel subclasses.
21 *
22 * @property Event $event
23 * @property Language $language
24 * @property EchoPresentationModelSection $section
25 */
26trait DiscussionToolsEventTrait {
27
28    /**
29     * @param int $type RevisionRecord::DELETED_* constant
30     * @return bool
31     */
32    abstract protected function userCan( $type );
33
34    /**
35     * @return bool
36     */
37    abstract protected function isBundled();
38
39    /**
40     * @return Event[]
41     */
42    abstract protected function getBundledEvents();
43
44    /**
45     * @return int[]|false
46     */
47    abstract protected function getBundledIds();
48
49    /**
50     * Get a link to the individual comment, if available.
51     *
52     * @return string|null Full URL linking to the comment, null if not available
53     */
54    protected function getCommentLink(): ?string {
55        if ( !$this->userCan( RevisionRecord::DELETED_TEXT ) ) {
56            return null;
57        }
58        if ( !$this->isBundled() ) {
59            // For a single-comment notification, make a pretty(ish) direct link to the comment.
60            // The browser scrolls and we highlight it client-side.
61            $commentId = $this->event->getExtraParam( 'comment-id' );
62            if ( !$commentId ) {
63                return null;
64            }
65            $title = $this->event->getTitle();
66            return $title->createFragmentTarget( $commentId )->getFullURL();
67        } else {
68            // For a multi-comment notification, we can't make a direct link, because we don't know
69            // which comment appears first on the page; the best we can do is a link to the section.
70            // We handle both scrolling and highlighting client-side, using the ugly parameter
71            // listing all comments.
72
73            // Bundling works differently for different notification types:
74            // * Subscribed topic notifications are bundled per-section.
75            // * User talk page notifications are bundled per-page (so basically, always bundled).
76            // * Mention notifications are *never* bundled.
77
78            // Just pass the oldest comment in the bundle. The client has access to the comment
79            // tree and so can work out all the other comments since this one.
80
81            // This does not include the newest comment, $this->event, but we are looking
82            // for the oldest comment.
83            $bundledEvents = $this->getBundledEvents();
84            $oldestEvent = end( $bundledEvents );
85            $params = [ 'dtnewcommentssince' => $oldestEvent->getExtraParam( 'comment-id' ) ];
86            if ( $this->event->getType() === 'dt-added-topic' ) {
87                // New topics notifications: Tell client to only highlight topics **started** since this one
88                $params[ 'dtsincethread' ] = 1;
89            } elseif ( $this->event->getExtraParam( 'subscribed-comment-name' ) ) {
90                // Topic notifications: Tell client to restrict highlights to this thread
91                $params[ 'dtinthread' ] = 1;
92            }
93            // This may or may not have a fragment identifier, depending on whether it was recorded for
94            // the first one of the bundled events. It's usually not needed because we handle scrolling
95            // client-side, but we can keep it for no-JS users, and to reduce the jump when scrolling.
96            $titleWithOptionalSection = $this->section->getTitleWithSection();
97            return $titleWithOptionalSection->getFullURL( $params );
98        }
99    }
100
101    /**
102     * Get a snippet of the individual comment, if available.
103     *
104     * @return string The snippet, as plain text (may be empty)
105     */
106    protected function getContentSnippet(): string {
107        if ( !$this->userCan( RevisionRecord::DELETED_TEXT ) ) {
108            return '';
109        }
110        // Note that we store plain text in the 'content' param.
111        // Echo also has a 'content' param (for mention notifications), but it contains wikitext.
112        $content = $this->event->getExtraParam( 'content' );
113        if ( !$content ) {
114            return '';
115        }
116        return $this->language->truncateForVisual( $content, DiscussionParser::DEFAULT_SNIPPET_LENGTH );
117    }
118
119    /**
120     * Add mark-as-read params to a link array
121     *
122     * Taken from EchoEventPresentationModel::getPrimaryLinkWithMarkAsRead
123     * TODO: Upstream to Echo?
124     *
125     * @param array $link Link
126     * @return array
127     */
128    protected function addMarkAsRead( $link ) {
129        global $wgEchoCrossWikiNotifications;
130        if ( $link ) {
131            $eventIds = [ $this->event->getId() ];
132            if ( $this->getBundledIds() ) {
133                $eventIds = array_merge( $eventIds, $this->getBundledIds() );
134            }
135
136            $queryParams = [ 'markasread' => implode( '|', $eventIds ) ];
137            if ( $wgEchoCrossWikiNotifications ) {
138                $queryParams['markasreadwiki'] = WikiMap::getCurrentWikiId();
139            }
140
141            $link['url'] = wfAppendQuery( $link['url'], $queryParams );
142        }
143        return $link;
144    }
145
146}