Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
LqtNotifications
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 7
132
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 overrideUsersToNotify
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 locateUsersWithPendingLqtNotifications
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 afterTopicImported
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 importAborted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 afterHeaderImported
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 afterPostImported
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Flow\Import\Postprocessor;
4
5use BatchRowIterator;
6use EchoCallbackIterator;
7use EchoEvent;
8use Flow\Import\IImportHeader;
9use Flow\Import\IImportPost;
10use Flow\Import\IImportTopic;
11use Flow\Import\ImportException;
12use Flow\Import\LiquidThreadsApi\ImportTopic;
13use Flow\Import\PageImportState;
14use Flow\Import\TopicImportState;
15use Flow\Model\PostRevision;
16use Flow\Notifications\Controller;
17use MediaWiki\User\User;
18use RecursiveIteratorIterator;
19use Wikimedia\Rdbms\IDatabase;
20
21/**
22 * Converts LQT unread notifications into Echo notifications after a topic is imported
23 */
24class LqtNotifications implements Postprocessor {
25
26    /**
27     * @var Controller
28     */
29    protected $controller;
30
31    /**
32     * @var IDatabase
33     */
34    protected $dbw;
35
36    /**
37     * @var PostRevision[] Array of imported replies
38     */
39    protected $postsImported = [];
40
41    public function __construct( Controller $controller, IDatabase $dbw ) {
42        $this->controller = $controller;
43        $this->dbw = $dbw;
44        $this->overrideUsersToNotify();
45    }
46
47    /**
48     * evil, but what should we do instead? This basically removes the default methods
49     * of determining users to notify so they can be replaced with this class during imports.
50     */
51    protected function overrideUsersToNotify() {
52        global $wgEchoNotifications;
53
54        // Remove the user-locators that choose on a per-notification basis who
55        // should be notified.
56        $notifications = require dirname( dirname( dirname( __DIR__ ) ) ) . '/Notifications.php';
57        foreach ( array_keys( $notifications ) as $type ) {
58            unset( $wgEchoNotifications[$type]['user-locators'] );
59
60            // The job queue causes our overrides to be lost since it
61            // has a separate execution context.
62            $wgEchoNotifications[$type]['immediate'] = true;
63        }
64
65        // Insert our own user locator to decide who should be notified.
66        // Note this has to be a closure rather than direct callback due to how
67        // echo considers an array to be extra parameters.
68        // Overrides existing user-locators, because we don't want unintended
69        // notifications to go out here.
70        $wgEchoNotifications['flow-post-reply']['user-locators'] = [
71            function ( EchoEvent $event ) {
72                return $this->locateUsersWithPendingLqtNotifications( $event );
73            }
74        ];
75    }
76
77    /**
78     * @param EchoEvent $event
79     * @param int $batchSize
80     * @throws ImportException
81     * @return EchoCallbackIterator
82     */
83    public function locateUsersWithPendingLqtNotifications( EchoEvent $event, $batchSize = 500 ) {
84        $activeThreadId = $event->getExtraParam( 'lqtThreadId' );
85        if ( $activeThreadId === null ) {
86            throw new ImportException( 'No active thread!' );
87        }
88
89        $it = new BatchRowIterator(
90            $this->dbw,
91            /* table = */ 'user_message_state',
92            /* primary keys */ [ 'ums_user' ],
93            $batchSize
94        );
95        $it->addConditions( [
96            'ums_conversation' => $activeThreadId,
97            'ums_read_timestamp' => null,
98        ] );
99        $it->setCaller( __METHOD__ );
100
101        // flatten result into a stream of rows
102        $it = new RecursiveIteratorIterator( $it );
103
104        // add callback to convert user id to user objects
105        $it = new EchoCallbackIterator( $it, static function ( $row ) {
106            return User::newFromId( $row->ums_user );
107        } );
108
109        return $it;
110    }
111
112    public function afterTopicImported( TopicImportState $state, IImportTopic $topic ) {
113        if ( !$topic instanceof ImportTopic ) {
114            return;
115        }
116        if ( !$this->postsImported ) {
117            // nothing was imported in this topic
118            return;
119        }
120
121        $this->controller->notifyPostChange( 'flow-post-reply', [
122            'revision' => $this->postsImported[0],
123            'topic-title' => $state->topicTitle,
124            'topic-workflow' => $state->topicWorkflow,
125            'title' => $state->topicWorkflow->getOwnerTitle(),
126            'reply-to' => $state->topicTitle,
127            'extra-data' => [
128                'lqtThreadId' => $topic->getLqtThreadId(),
129                'notifyAgent' => true,
130            ],
131            'timestamp' => $topic->getTimestamp(),
132        ] );
133
134        $this->postsImported = [];
135    }
136
137    public function importAborted() {
138        $this->postsImported = [];
139    }
140
141    public function afterHeaderImported( PageImportState $state, IImportHeader $header ) {
142        // not a thing to do, yet
143    }
144
145    public function afterPostImported( TopicImportState $state, IImportPost $post, PostRevision $newPost ) {
146        $this->postsImported[] = $newPost;
147    }
148}