Completed
Pull Request — master (#28)
by Peter
02:09
created

SemanticTasksMailer   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 341
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 6
dl 0
loc 341
rs 9.52
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A mailAssigneesUpdatedTask() 0 13 4
A getAssignedUsersFromParserOutput() 0 41 4
B mailAssignees() 0 51 6
B mailNotification() 0 51 7
A setUserMailer() 0 3 1
A generateDiffBodyTxt() 0 33 5
B remindAssignees() 0 56 6
A printDebug() 0 12 3
1
<?php
2
3
namespace ST;
4
5
use Content;
6
use ContentHandler;
7
use Exception;
8
use IContextSource;
9
use Language;
10
use MediaWiki\Diff\ComplexityException;
11
use MWException;
12
use ParserOutput;
13
use SMW\ApplicationFactory;
14
use SMW\DIWikiPage;
15
use SMWDataItem;
16
use SMWPrintRequest;
17
use Title;
18
use User;
19
use WikiPage;
20
21
if ( !defined( 'MEDIAWIKI' ) ) {
22
	echo 'Not a valid entry point';
23
	exit( 1 );
24
}
25
26
if ( !defined( 'SMW_VERSION' ) ) {
27
	echo 'This extension requires Semantic MediaWiki to be installed.';
28
	exit( 1 );
29
}
30
31
// constants for message type
32
if ( !defined( 'ST_NEWTASK' ) ) {
33
	define( 'ST_NEWTASK', 0 );
34
	define( 'ST_UPDATE', 1 );
35
	define( 'ST_ASSIGNED', 2 );
36
	define( 'ST_CLOSED', 3 );
37
	define( 'ST_UNASSIGNED', 4 );
38
}
39
40
/**
41
 * This class handles the creation and sending of notification emails.
42
 */
43
class SemanticTasksMailer {
44
45
	private static $user_mailer;
46
47
	/**
48
	 * Mails the assignees when the task is modified
49
	 *
50
	 * @param Assignees $assignees
51
	 * @param WikiPage $article
52
	 * @param User $current_user
53
	 * @param Content $text
54
	 * @param string $summary Unused
55
	 * @param bool $minoredit
56
	 * @param null $watchthis Unused
57
	 * @param null $sectionanchor Unused
58
	 * @param $flags
59
	 * @return boolean
60
	 * @throws ComplexityException
61
	 * @throws MWException
62
	 */
63
	public static function mailAssigneesUpdatedTask( Assignees $assignees, WikiPage $article, User $current_user, $text,
64
			$summary, $minoredit, $watchthis, $sectionanchor, $flags, $revision ) {
0 ignored issues
show
Unused Code introduced by
The parameter $summary 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...
Unused Code introduced by
The parameter $watchthis 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...
Unused Code introduced by
The parameter $sectionanchor 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...
65
		if ( $minoredit ) {
66
			return true;
67
		}
68
		$status = ST_UPDATE;
69
		if ( ( $flags & EDIT_NEW ) && !$article->getTitle()->isTalkPage() ) {
70
			$status = ST_NEWTASK;
71
		}
72
		$assignedToParserOutput = self::getAssignedUsersFromParserOutput($article, $revision, $current_user);
73
74
		return self::mailAssignees( $article, $text, $current_user, $status, $assignees, $assignedToParserOutput );
75
	}
76
77
	// todo: this could replace Assignees->getAssignees(...).
78
	public static function getAssignedUsersFromParserOutput(WikiPage $article, $revision, User $current_user) {
79
		if ( $revision === null) {
80
			return [];
81
		}
82
		$smwFactory = ApplicationFactory::getInstance();
83
		$mwCollaboratorFactory = $smwFactory->newMwCollaboratorFactory();
84
		if ( version_compare( SMW_VERSION, '3.1', '<' ) ) {
85
			$editInfo = $mwCollaboratorFactory->newEditInfoProvider(
86
				$article,
87
				$revision,
88
				$current_user
89
			);
90
		} else {
91
			$editInfo = $mwCollaboratorFactory->newEditInfo(
92
				$article,
93
				$revision,
94
				$current_user
95
			);
96
		}
97
		$editInfo->fetchEditInfo();
98
		$parserOutput = $editInfo->getOutput();
99
100
		if ( !$parserOutput instanceof ParserOutput ) {
0 ignored issues
show
Bug introduced by
The class ParserOutput does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
101
			return [];
102
		}
103
104
		global $stgPropertyAssignedTo;
105
		$stgPropertyAssignedToString = str_replace(' ', '_', $stgPropertyAssignedTo);
106
		$property = new \SMW\DIProperty($stgPropertyAssignedToString, false);
107
108
		/** @var $semanticData \SMW\SemanticData */
109
		$semanticData = $parserOutput->getExtensionData('smwdata');
110
		$assigneesPropValues = $semanticData->getPropertyValues($property);
111
		// todo: maybe there should be a check if these pages are userpages.
112
		$assigneeList = array_map(function(DIWikiPage $assignePropVal) {
113
			return $assignePropVal->getTitle()->getText();
114
		}, $assigneesPropValues);
115
116
117
		return $assigneeList;
118
	}
119
120
	/**
121
	 *
122
	 * @param WikiPage $article
123
	 * @param Content $content
124
	 * @param User $user
125
	 * @param integer $status
126
	 * @param Assignees $assignees
127
	 * @return boolean
128
	 * @throws ComplexityException
129
	 * @throws MWException
130
	 * @global boolean $wgSemanticTasksNotifyIfUnassigned
131
	 */
132
	static function mailAssignees( WikiPage $article, Content $content, User $user, $status, Assignees $assignees,
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
133
								   $assignedToParserOutput ) {
134
		$text = ContentHandler::getContentText( $content );
135
		$title = $article->getTitle();
136
137
		// Notify those unassigned from this task
138
		global $wgSemanticTasksNotifyIfUnassigned;
139
		if ( $wgSemanticTasksNotifyIfUnassigned ) {
140
			$removedAssignees = $assignees->getRemovedAssignees( $article );
141
			$removedAssignees = Assignees::getAssigneeAddresses( $removedAssignees );
142
			self::mailNotification( $removedAssignees, $text, $title, $user, ST_UNASSIGNED );
143
		}
144
145
		// Send notification of an assigned task to assignees
146
		// Treat task as new
147
		$newAssignees = $assignees->getNewAssignees( $article );
148
		$newAssignees = Assignees::getAssigneeAddresses( $newAssignees );
149
		self::mailNotification( $newAssignees, $text, $title, $user, ST_ASSIGNED );
150
151
		$copies = $assignees->getCurrentCarbonCopy( $article );
152
		$currentStatus = $assignees->getCurrentStatus( $article );
153
		$oldStatus = $assignees->getSavedStatus();
154
		if ( $currentStatus === "Closed" && $oldStatus !== "Closed" ) {
155
			$close_mailto = Assignees::getAssigneeAddresses( $copies );
156
			self::mailNotification( $close_mailto, $text, $title, $user, ST_CLOSED );
157
		}
158
159
		$currentAssignees = $assignees->getCurrentAssignees( $article );
160
161
		// Only send group notifications on new tasks
162
		$groups = array();
163
		if ( $status === ST_NEWTASK ) {
164
			$groups = $assignees->getGroupAssignees( $article );
165
166
			// if this is a new task and there are no $currentAssignees but there are $assignedToParserOutput
167
			// then this is probably a new page and sending ST_ASSIGNED notifications didn't work and needs to be retried.
168
			if ( empty( $currentAssignees ) ) {
169
				$mails = Assignees::getAssigneeAddresses( $assignedToParserOutput );
170
				self::mailNotification( $mails, $text, $title, $user, ST_ASSIGNED );
171
			}
172
		}
173
174
		$mailto = array_merge( $currentAssignees, $copies, $groups );
175
		$mailto = array_unique( $mailto );
176
		$mailto = Assignees::getAssigneeAddresses( $mailto );
177
178
		// Send notifications to assignees, ccs, and groups
179
		self::mailNotification( $mailto, $text, $title, $user, $status );
180
181
		return true;
182
	}
183
184
	/**
185
	 * Sends mail notifications
186
	 *
187
	 * @param array $assignees
188
	 * @param string $text
189
	 * @param Title $title
190
	 * @param User $user
191
	 * @param integer $status
192
	 * @throws MWException
193
	 * @throws ComplexityException
194
	 * @global string $wgSitename
195
	 */
196
	static function mailNotification( array $assignees, $text, Title $title, User $user, $status ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
197
		global $wgSitename;
198
199
		if ( empty( $assignees ) ) {
200
			return;
201
		}
202
		$title_text = $title->getFullText();
203
		$from = new \MailAddress( $user->getEmail(), $user->getName() );
204
		$link = htmlspecialchars( $title->getFullURL() );
205
206
		/** @todo This should probably be refactored */
207
		if ( $status == ST_NEWTASK ) {
208
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-newtask' )->text() . ' ' .
209
				$title_text;
210
			$message = 'semantictasks-newtask-msg';
211
			$body = wfMessage( $message, $title_text )->text() . " " . $link;
212
			$body .= "\n \n" . wfMessage( 'semantictasks-text-message' )->text() . "\n" . $text;
213
		} elseif ( $status == ST_UPDATE ) {
214
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-taskupdated' )->text() . ' ' .
215
				$title_text;
216
			$message = 'semantictasks-updatedtoyou-msg2';
217
			$body = wfMessage( $message, $title_text )->text() . " " . $link;
218
			$body .= "\n \n" . wfMessage( 'semantictasks-diff-message' )->text() . "\n" .
219
				self::generateDiffBodyTxt( $title );
220
		} elseif ( $status == ST_CLOSED ) {
221
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-taskclosed' )->text() . ' ' .
222
				$title_text;
223
			$message = 'semantictasks-taskclosed-msg';
224
			$body = wfMessage( $message, $title_text )->text() . " " . $link;
225
			$body .= "\n \n" . wfMessage( 'semantictasks-text-message' )->text() . "\n" . $text;
226
		} elseif ( $status == ST_UNASSIGNED ) {
227
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-taskunassigned' )->text() . ' ' .
228
				$title_text;
229
			$message = 'semantictasks-unassignedtoyou-msg2';
230
			$body = wfMessage( $message, $title_text )->text() . " " . $link;
231
			$body .= "\n \n" . wfMessage( 'semantictasks-text-message' )->text() . "\n" . $text;
232
		} else {
233
			// status == ASSIGNED
234
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-taskassigned' )->text() . ' ' .
235
				$title_text;
236
			$message = 'semantictasks-assignedtoyou-msg2';
237
			$body = wfMessage( $message, $title_text )->text() . " " . $link;
238
			$body .= "\n \n" . wfMessage( 'semantictasks-text-message' )->text() . "\n" . $text;
239
		}
240
241
		if (!self::$user_mailer) {
242
			self::$user_mailer = new \ST\UserMailer(new \UserMailer());
243
		}
244
245
		self::$user_mailer->send( $assignees, $from, $subject, $body );
246
	}
247
248
	static function setUserMailer(\ST\UserMailer $user_mailer) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
249
		self::$user_mailer = $user_mailer;
250
	}
251
252
	/**
253
	 * Generates a diff txt
254
	 *
255
	 * Code is similar to DifferenceEngine::generateTextDiffBody
256
	 * @param Title $title
257
	 * @param IContextSource $context
258
	 * @return string
259
	 * @throws ComplexityException
260
	 * @throws MWException
261
	 */
262
	static function generateDiffBodyTxt( Title $title, IContextSource $context = null) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
263
		$revision = \Revision::newFromTitle( $title, 0 );
264
		if ($revision === null) {
265
			return '';
266
		}
267
		/** @todo The first parameter should be a Context. */
268
		$diff = new \DifferenceEngine( $context, $revision->getId(), 'prev' );
269
		// The DifferenceEngine::getDiffBody() method generates html,
270
		// so let's generate the txt diff manually:
271
		global $wgContLang;
272
		$diff->loadText();
273
		$otext = '';
274
		$ntext = '';
275
		if ( version_compare( MW_VERSION, '1.32', '<' ) ) {
276
			$otext = str_replace( "\r\n", "\n", \ContentHandler::getContentText( $diff->mOldContent ) );
277
			$ntext = str_replace( "\r\n", "\n", \ContentHandler::getContentText( $diff->mNewContent ) );
278
		} else {
279
			if ($diff->getOldRevision()) {
280
				$otext = str_replace( "\r\n", "\n", ContentHandler::getContentText( $diff->getOldRevision()->getContent( 'main' ) ) );
281
			}
282
			if ($diff->getNewRevision()) {
283
				$ntext = str_replace( "\r\n", "\n", ContentHandler::getContentText( $diff->getNewRevision()->getContent( 'main' ) ) );
284
			}
285
		}
286
		$ota = explode( "\n", $wgContLang->segmentForDiff( $otext ) );
287
		$nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) );
288
		// We use here the php diff engine included in MediaWiki
289
		$diffs = new \Diff( $ota, $nta );
290
		// And we ask for a txt formatted diff
291
		$formatter = new \UnifiedDiffFormatter();
292
		$diff_text = $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) );
293
		return $diff_text;
294
	}
295
296
	/**
297
	 * Run by the maintenance script to remind the assignees
298
	 *
299
	 * @return boolean
300
	 * @throws Exception
301
	 * @global string $wgSitename
302
	 * @global Language $wgLang
303
	 */
304
	static function remindAssignees() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
305
		global $wgSitename;
306
		global $stgPropertyReminderAt;
307
		global $stgPropertyAssignedTo;
308
		global $stgPropertyTargetDate;
309
		global $stgPropertyStatus;
310
311
		# Make this equal to midnight. Rational is that if users set today as the Target date with
312
		# reminders set to "0" so that the reminder happens on the deadline, the reminders will go
313
		# out even though now it is after the beginning of today and technically past the
314
		# target date.
315
		$today = wfTimestamp( TS_ISO_8601, strtotime( 'today midnight' ) );
316
317
		# Get tasks where a reminder is called for, whose status is either new or in progress, and
318
		# whose target date is in the future.
319
		$query_string = "[[$stgPropertyReminderAt::+]][[$stgPropertyStatus::New||In Progress]][[$stgPropertyTargetDate::≥ $today]]";
320
		$properties_to_display = array( $stgPropertyReminderAt, $stgPropertyAssignedTo, $stgPropertyTargetDate );
321
322
		$results = Query::getQueryResults( $query_string, $properties_to_display, true );
323
		if ( empty( $results ) ) {
324
			return false;
325
		}
326
327
		while ( $row = $results->getNext() ) {
328
			$task_name = $row[0]->getNextObject()->getTitle();
329
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-reminder' )->text() . $task_name;
330
			// The following doesn't work, maybe because we use a cron job.
331
			// $link = $task_name->getFullURL();
332
			// So let's do it manually
333
			//$link = $wiki_url . $task_name->getPartialURL();
334
			// You know what? Let's try it again.
335
			$link = $task_name->getFullURL();
336
337
			$target_date = $row[3]->getNextObject();
338
			$tg_date = new DateTime( $target_date->getShortHTMLText() );
339
340
			while ( $reminder = $row[1]->getNextObject() ) {
341
				$remind_me_in = $reminder->getShortHTMLText();
342
				$date = new DateTime( 'today midnight' );
343
				$date->modify( "+$remind_me_in day" );
344
345
				if ( $tg_date === $date ) {
346
					global $wgLang;
347
					while ( $task_assignee = $row[2]->getNextObject() ) {
348
						$assignee_username = $task_assignee->getTitle()->getText();
349
						$assignee = User::newFromName( $assignee_username );
350
351
						$body = wfMessage( 'semantictasks-reminder-message2', $task_name,
352
							$wgLang->formatNum( $remind_me_in ), $link )->text();
353
						$assignee->sendMail( $subject, $body );
354
					}
355
				}
356
			}
357
		}
358
		return true;
359
	}
360
361
	/**
362
	 * Prints debugging information. $debugText is what you want to print, $debugVal
363
	 * is the level at which you want to print the information.
364
	 *
365
	 * @global boolean $wgSemanticTasksDebug
366
	 * @param string $debugText
367
	 * @param string $debugArr
368
	 * @access private
369
	 */
370
	static function printDebug( $debugText, $debugArr = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
371
		global $wgSemanticTasksDebug;
372
373
		if ( $wgSemanticTasksDebug ) {
374
			if ( isset( $debugArr ) ) {
375
				$text = $debugText . ' ' . implode( '::', $debugArr );
376
				wfDebugLog( 'semantic-tasks', $text, false );
377
			} else {
378
				wfDebugLog( 'semantic-tasks', $debugText, false );
379
			}
380
		}
381
	}
382
383
}
384