EchoNotificationsHandlers::onLocalUserCreated()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
namespace Wikibase\Client\Hooks;
4
5
use Diff\DiffOp\DiffOp;
6
use Diff\DiffOp\DiffOpAdd;
7
use Diff\DiffOp\DiffOpChange;
8
use EchoEvent;
9
use Title;
10
use User;
11
use Wikibase\Client\NamespaceChecker;
12
use Wikibase\Client\RepoLinker;
13
use Wikibase\Client\WikibaseClient;
14
use Wikibase\Lib\Changes\Change;
15
use Wikibase\Lib\Changes\ItemChange;
16
use WikiPage;
17
18
/**
19
 * Handlers for client Echo notifications
20
 *
21
 * @license GPL-2.0-or-later
22
 * @author Matěj Suchánek
23
 */
24
class EchoNotificationsHandlers {
25
26
	/**
27
	 * Type of notification
28
	 */
29
	const NOTIFICATION_TYPE = 'page-connection';
30
31
	/**
32
	 * @var RepoLinker
33
	 */
34
	private $repoLinker;
35
36
	/**
37
	 * @var NamespaceChecker
38
	 */
39
	private $namespaceChecker;
40
41
	/**
42
	 * @var string
43
	 */
44
	private $siteId;
45
46
	/**
47
	 * @var bool
48
	 */
49
	private $sendEchoNotification;
50
51
	/**
52
	 * @var string
53
	 */
54
	private $repoSiteName;
55
56
	/**
57
	 * @param RepoLinker $repoLinker
58
	 * @param NamespaceChecker $namespaceChecker
59
	 * @param string $siteId
60
	 * @param bool $sendEchoNotification
61
	 * @param string $repoSiteName
62
	 */
63
	public function __construct(
64
		RepoLinker $repoLinker,
65
		NamespaceChecker $namespaceChecker,
66
		$siteId,
67
		$sendEchoNotification,
68
		$repoSiteName
69
	) {
70
		$this->repoLinker = $repoLinker;
71
		$this->namespaceChecker = $namespaceChecker;
72
		$this->siteId = $siteId;
73
		$this->sendEchoNotification = $sendEchoNotification;
74
		$this->repoSiteName = $repoSiteName;
75
	}
76
77
	/**
78
	 * @return self
79
	 */
80
	public static function factory() {
81
		$wikibaseClient = WikibaseClient::getDefaultInstance();
82
		$settings = $wikibaseClient->getSettings();
83
84
		return new self(
85
			$wikibaseClient->newRepoLinker(),
86
			$wikibaseClient->getNamespaceChecker(),
87
			$settings->getSetting( 'siteGlobalID' ),
88
			$settings->getSetting( 'sendEchoNotification' ),
89
			$settings->getSetting( 'repoSiteName' )
90
		);
91
	}
92
93
	/**
94
	 * Handler for EchoGetBundleRules hook
95
	 * @see https://www.mediawiki.org/wiki/Notifications/Developer_guide#Bundled_notifications
96
	 *
97
	 * @param EchoEvent $event
98
	 * @param string &$bundleString
99
	 */
100
	public static function onEchoGetBundleRules( EchoEvent $event, &$bundleString ) {
101
		if ( $event->getType() === self::NOTIFICATION_TYPE ) {
102
			$bundleString = self::NOTIFICATION_TYPE;
103
		}
104
	}
105
106
	/**
107
	 * Handler for LocalUserCreated hook.
108
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/LocalUserCreated
109
	 * @param User $user User object that was created.
110
	 * @param bool $autocreated True when account was auto-created
111
	 */
112
	public static function onLocalUserCreated( User $user, $autocreated ) {
113
		$self = self::factory();
114
		$self->doLocalUserCreated( $user, $autocreated );
115
	}
116
117
	/**
118
	 * @param User $user
119
	 * @param bool $autocreated
120
	 */
121
	public function doLocalUserCreated( User $user, $autocreated ) {
0 ignored issues
show
Unused Code introduced by
The parameter $autocreated is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
122
		if ( $this->sendEchoNotification === true ) {
123
			$user->setOption( 'echo-subscriptions-web-wikibase-action', true );
124
			$user->saveSettings();
125
		}
126
	}
127
128
	/**
129
	 * Handler for WikibaseHandleChange hook
130
	 * @see doWikibaseHandleChange
131
	 *
132
	 * @param Change $change
133
	 */
134
	public static function onWikibaseHandleChange( Change $change ) {
135
		$self = self::factory();
136
		$self->doWikibaseHandleChange( $change );
137
	}
138
139
	/**
140
	 * @param Change $change
141
	 *
142
	 * @return bool
143
	 */
144
	public function doWikibaseHandleChange( Change $change ) {
145
		if ( $this->sendEchoNotification !== true ) {
146
			return false;
147
		}
148
149
		if ( !( $change instanceof ItemChange ) ) {
150
			return false;
151
		}
152
153
		$siteLinkDiff = $change->getSiteLinkDiff();
154
		if ( $siteLinkDiff->isEmpty() ) {
155
			return false;
156
		}
157
158
		$siteId = $this->siteId;
159
		if ( !isset( $siteLinkDiff[$siteId] ) || !isset( $siteLinkDiff[$siteId]['name'] ) ) {
160
			return false;
161
		}
162
163
		$siteLinkDiffOp = $siteLinkDiff[$siteId]['name'];
164
165
		$title = $this->getTitleForNotification( $siteLinkDiffOp );
166
		if ( $title !== false ) {
167
			$metadata = $change->getMetadata();
168
			$entityId = $change->getEntityId();
169
			$agent = User::newFromName( $metadata['user_text'], false );
170
			EchoEvent::create( [
171
				'agent' => $agent,
172
				'extra' => [
173
					// maybe also a diff link?
174
					'url' => $this->repoLinker->getEntityUrl( $entityId ),
0 ignored issues
show
Bug introduced by
It seems like $entityId defined by $change->getEntityId() on line 168 can be null; however, Wikibase\Client\RepoLinker::getEntityUrl() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
175
					'repoSiteName' => $this->repoSiteName,
176
					'entity' => $entityId->getSerialization(),
177
				],
178
				'title' => $title,
179
				'type' => self::NOTIFICATION_TYPE
180
			] );
181
182
			return true;
183
		}
184
185
		return false;
186
	}
187
188
	/**
189
	 * Determines whether the change was a real sitelink addition
190
	 * and returns either title, or false
191
	 *
192
	 * @param DiffOp $siteLinkDiffOp
193
	 *
194
	 * @return Title|false
195
	 */
196
	private function getTitleForNotification( DiffOp $siteLinkDiffOp ) {
197
		if ( $siteLinkDiffOp instanceof DiffOpAdd ) {
198
			$new = $siteLinkDiffOp->getNewValue();
199
			$newTitle = Title::newFromText( $new );
200
			return $this->canNotifyForTitle( $newTitle ) ? $newTitle : false;
201
		}
202
203
		// if it's a sitelink change, make sure it wasn't triggered by a page move
204
		if ( $siteLinkDiffOp instanceof DiffOpChange ) {
205
			$new = $siteLinkDiffOp->getNewValue();
206
			$newTitle = Title::newFromText( $new );
207
208
			if ( !$this->canNotifyForTitle( $newTitle ) ) {
209
				return false;
210
			}
211
212
			$old = $siteLinkDiffOp->getOldValue();
213
			$oldTitle = Title::newFromText( $old );
214
215
			// propably means that there was a page move
216
			// without keeping the old title as redirect
217
			if ( !$oldTitle->exists() ) {
218
				return false;
219
			}
220
221
			// even if the old page is a redirect, make sure it redirects to the new title
222
			if ( $oldTitle->isRedirect() ) {
223
				$page = WikiPage::factory( $oldTitle );
224
				$targetTitle = $page->getRedirectTarget();
225
				if ( $targetTitle && $targetTitle->equals( $newTitle ) ) {
226
					return false;
227
				}
228
			}
229
230
			return $newTitle;
231
		}
232
233
		return false;
234
	}
235
236
	/**
237
	 * Whether it's reasonable to send a notification for the title
238
	 *
239
	 * @param Title $title
240
	 *
241
	 * @return bool
242
	 */
243
	private function canNotifyForTitle( Title $title ) {
244
		return $title->exists() && !$title->isRedirect()
245
			&& $this->namespaceChecker->isWikibaseEnabled( $title->getNamespace() );
246
	}
247
248
}
249