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

SemanticTasksMailer   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 333
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 6
dl 0
loc 333
rs 9.6
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A mailAssigneesUpdatedTask() 0 13 4
A getAssignedUsersFromParserOutput() 0 33 3
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
		$editInfo = $mwCollaboratorFactory->newEditInfo(
85
			$article,
86
			$revision,
87
			$current_user
88
		);
89
		$editInfo->fetchEditInfo();
90
		$parserOutput = $editInfo->getOutput();
91
92
		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...
93
			return [];
94
		}
95
96
		global $stgPropertyAssignedTo;
97
		$stgPropertyAssignedToString = str_replace(' ', '_', $stgPropertyAssignedTo);
98
		$property = new \SMW\DIProperty($stgPropertyAssignedToString, false);
99
100
		/** @var $semanticData \SMW\SemanticData */
101
		$semanticData = $parserOutput->getExtensionData('smwdata');
102
		$assigneesPropValues = $semanticData->getPropertyValues($property);
103
		// todo: maybe there should be a check if these pages are userpages.
104
		$assigneeList = array_map(function(DIWikiPage $assignePropVal) {
105
			return $assignePropVal->getTitle()->getText();
106
		}, $assigneesPropValues);
107
108
109
		return $assigneeList;
110
	}
111
112
	/**
113
	 *
114
	 * @param WikiPage $article
115
	 * @param Content $content
116
	 * @param User $user
117
	 * @param integer $status
118
	 * @param Assignees $assignees
119
	 * @return boolean
120
	 * @throws ComplexityException
121
	 * @throws MWException
122
	 * @global boolean $wgSemanticTasksNotifyIfUnassigned
123
	 */
124
	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...
125
								   $assignedToParserOutput ) {
126
		$text = ContentHandler::getContentText( $content );
127
		$title = $article->getTitle();
128
129
		// Notify those unassigned from this task
130
		global $wgSemanticTasksNotifyIfUnassigned;
131
		if ( $wgSemanticTasksNotifyIfUnassigned ) {
132
			$removedAssignees = $assignees->getRemovedAssignees( $article );
133
			$removedAssignees = Assignees::getAssigneeAddresses( $removedAssignees );
134
			self::mailNotification( $removedAssignees, $text, $title, $user, ST_UNASSIGNED );
135
		}
136
137
		// Send notification of an assigned task to assignees
138
		// Treat task as new
139
		$newAssignees = $assignees->getNewAssignees( $article );
140
		$newAssignees = Assignees::getAssigneeAddresses( $newAssignees );
141
		self::mailNotification( $newAssignees, $text, $title, $user, ST_ASSIGNED );
142
143
		$copies = $assignees->getCurrentCarbonCopy( $article );
144
		$currentStatus = $assignees->getCurrentStatus( $article );
145
		$oldStatus = $assignees->getSavedStatus();
146
		if ( $currentStatus === "Closed" && $oldStatus !== "Closed" ) {
147
			$close_mailto = Assignees::getAssigneeAddresses( $copies );
148
			self::mailNotification( $close_mailto, $text, $title, $user, ST_CLOSED );
149
		}
150
151
		$currentAssignees = $assignees->getCurrentAssignees( $article );
152
153
		// Only send group notifications on new tasks
154
		$groups = array();
155
		if ( $status === ST_NEWTASK ) {
156
			$groups = $assignees->getGroupAssignees( $article );
157
158
			// if this is a new task and there are no $currentAssignees but there are $assignedToParserOutput
159
			// then this is probably a new page and sending ST_ASSIGNED notifications didn't work and needs to be retried.
160
			if ( empty( $currentAssignees ) ) {
161
				$mails = Assignees::getAssigneeAddresses( $assignedToParserOutput );
162
				self::mailNotification( $mails, $text, $title, $user, ST_ASSIGNED );
163
			}
164
		}
165
166
		$mailto = array_merge( $currentAssignees, $copies, $groups );
167
		$mailto = array_unique( $mailto );
168
		$mailto = Assignees::getAssigneeAddresses( $mailto );
169
170
		// Send notifications to assignees, ccs, and groups
171
		self::mailNotification( $mailto, $text, $title, $user, $status );
172
173
		return true;
174
	}
175
176
	/**
177
	 * Sends mail notifications
178
	 *
179
	 * @param array $assignees
180
	 * @param string $text
181
	 * @param Title $title
182
	 * @param User $user
183
	 * @param integer $status
184
	 * @throws MWException
185
	 * @throws ComplexityException
186
	 * @global string $wgSitename
187
	 */
188
	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...
189
		global $wgSitename;
190
191
		if ( empty( $assignees ) ) {
192
			return;
193
		}
194
		$title_text = $title->getFullText();
195
		$from = new \MailAddress( $user->getEmail(), $user->getName() );
196
		$link = htmlspecialchars( $title->getFullURL() );
197
198
		/** @todo This should probably be refactored */
199
		if ( $status == ST_NEWTASK ) {
200
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-newtask' )->text() . ' ' .
201
				$title_text;
202
			$message = 'semantictasks-newtask-msg';
203
			$body = wfMessage( $message, $title_text )->text() . " " . $link;
204
			$body .= "\n \n" . wfMessage( 'semantictasks-text-message' )->text() . "\n" . $text;
205
		} elseif ( $status == ST_UPDATE ) {
206
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-taskupdated' )->text() . ' ' .
207
				$title_text;
208
			$message = 'semantictasks-updatedtoyou-msg2';
209
			$body = wfMessage( $message, $title_text )->text() . " " . $link;
210
			$body .= "\n \n" . wfMessage( 'semantictasks-diff-message' )->text() . "\n" .
211
				self::generateDiffBodyTxt( $title );
212
		} elseif ( $status == ST_CLOSED ) {
213
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-taskclosed' )->text() . ' ' .
214
				$title_text;
215
			$message = 'semantictasks-taskclosed-msg';
216
			$body = wfMessage( $message, $title_text )->text() . " " . $link;
217
			$body .= "\n \n" . wfMessage( 'semantictasks-text-message' )->text() . "\n" . $text;
218
		} elseif ( $status == ST_UNASSIGNED ) {
219
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-taskunassigned' )->text() . ' ' .
220
				$title_text;
221
			$message = 'semantictasks-unassignedtoyou-msg2';
222
			$body = wfMessage( $message, $title_text )->text() . " " . $link;
223
			$body .= "\n \n" . wfMessage( 'semantictasks-text-message' )->text() . "\n" . $text;
224
		} else {
225
			// status == ASSIGNED
226
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-taskassigned' )->text() . ' ' .
227
				$title_text;
228
			$message = 'semantictasks-assignedtoyou-msg2';
229
			$body = wfMessage( $message, $title_text )->text() . " " . $link;
230
			$body .= "\n \n" . wfMessage( 'semantictasks-text-message' )->text() . "\n" . $text;
231
		}
232
233
		if (!self::$user_mailer) {
234
			self::$user_mailer = new \ST\UserMailer(new \UserMailer());
235
		}
236
237
		self::$user_mailer->send( $assignees, $from, $subject, $body );
238
	}
239
240
	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...
241
		self::$user_mailer = $user_mailer;
242
	}
243
244
	/**
245
	 * Generates a diff txt
246
	 *
247
	 * Code is similar to DifferenceEngine::generateTextDiffBody
248
	 * @param Title $title
249
	 * @param IContextSource $context
250
	 * @return string
251
	 * @throws ComplexityException
252
	 * @throws MWException
253
	 */
254
	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...
255
		$revision = \Revision::newFromTitle( $title, 0 );
256
		if ($revision === null) {
257
			return '';
258
		}
259
		/** @todo The first parameter should be a Context. */
260
		$diff = new \DifferenceEngine( $context, $revision->getId(), 'prev' );
261
		// The DifferenceEngine::getDiffBody() method generates html,
262
		// so let's generate the txt diff manually:
263
		global $wgContLang;
264
		$diff->loadText();
265
		$otext = '';
266
		$ntext = '';
267
		if ( version_compare( MW_VERSION, '1.32', '<' ) ) {
268
			$otext = str_replace( "\r\n", "\n", \ContentHandler::getContentText( $diff->mOldContent ) );
269
			$ntext = str_replace( "\r\n", "\n", \ContentHandler::getContentText( $diff->mNewContent ) );
270
		} else {
271
			if ($diff->getOldRevision()) {
272
				$otext = str_replace( "\r\n", "\n", ContentHandler::getContentText( $diff->getOldRevision()->getContent( 'main' ) ) );
273
			}
274
			if ($diff->getNewRevision()) {
275
				$ntext = str_replace( "\r\n", "\n", ContentHandler::getContentText( $diff->getNewRevision()->getContent( 'main' ) ) );
276
			}
277
		}
278
		$ota = explode( "\n", $wgContLang->segmentForDiff( $otext ) );
279
		$nta = explode( "\n", $wgContLang->segmentForDiff( $ntext ) );
280
		// We use here the php diff engine included in MediaWiki
281
		$diffs = new \Diff( $ota, $nta );
282
		// And we ask for a txt formatted diff
283
		$formatter = new \UnifiedDiffFormatter();
284
		$diff_text = $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) );
285
		return $diff_text;
286
	}
287
288
	/**
289
	 * Run by the maintenance script to remind the assignees
290
	 *
291
	 * @return boolean
292
	 * @throws Exception
293
	 * @global string $wgSitename
294
	 * @global Language $wgLang
295
	 */
296
	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...
297
		global $wgSitename;
298
		global $stgPropertyReminderAt;
299
		global $stgPropertyAssignedTo;
300
		global $stgPropertyTargetDate;
301
		global $stgPropertyStatus;
302
303
		# Make this equal to midnight. Rational is that if users set today as the Target date with
304
		# reminders set to "0" so that the reminder happens on the deadline, the reminders will go
305
		# out even though now it is after the beginning of today and technically past the
306
		# target date.
307
		$today = wfTimestamp( TS_ISO_8601, strtotime( 'today midnight' ) );
308
309
		# Get tasks where a reminder is called for, whose status is either new or in progress, and
310
		# whose target date is in the future.
311
		$query_string = "[[$stgPropertyReminderAt::+]][[$stgPropertyStatus::New||In Progress]][[$stgPropertyTargetDate::≥ $today]]";
312
		$properties_to_display = array( $stgPropertyReminderAt, $stgPropertyAssignedTo, $stgPropertyTargetDate );
313
314
		$results = Query::getQueryResults( $query_string, $properties_to_display, true );
315
		if ( empty( $results ) ) {
316
			return false;
317
		}
318
319
		while ( $row = $results->getNext() ) {
320
			$task_name = $row[0]->getNextObject()->getTitle();
321
			$subject = '[' . $wgSitename . '] ' . wfMessage( 'semantictasks-reminder' )->text() . $task_name;
322
			// The following doesn't work, maybe because we use a cron job.
323
			// $link = $task_name->getFullURL();
324
			// So let's do it manually
325
			//$link = $wiki_url . $task_name->getPartialURL();
326
			// You know what? Let's try it again.
327
			$link = $task_name->getFullURL();
328
329
			$target_date = $row[3]->getNextObject();
330
			$tg_date = new DateTime( $target_date->getShortHTMLText() );
331
332
			while ( $reminder = $row[1]->getNextObject() ) {
333
				$remind_me_in = $reminder->getShortHTMLText();
334
				$date = new DateTime( 'today midnight' );
335
				$date->modify( "+$remind_me_in day" );
336
337
				if ( $tg_date === $date ) {
338
					global $wgLang;
339
					while ( $task_assignee = $row[2]->getNextObject() ) {
340
						$assignee_username = $task_assignee->getTitle()->getText();
341
						$assignee = User::newFromName( $assignee_username );
342
343
						$body = wfMessage( 'semantictasks-reminder-message2', $task_name,
344
							$wgLang->formatNum( $remind_me_in ), $link )->text();
345
						$assignee->sendMail( $subject, $body );
346
					}
347
				}
348
			}
349
		}
350
		return true;
351
	}
352
353
	/**
354
	 * Prints debugging information. $debugText is what you want to print, $debugVal
355
	 * is the level at which you want to print the information.
356
	 *
357
	 * @global boolean $wgSemanticTasksDebug
358
	 * @param string $debugText
359
	 * @param string $debugArr
360
	 * @access private
361
	 */
362
	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...
363
		global $wgSemanticTasksDebug;
364
365
		if ( $wgSemanticTasksDebug ) {
366
			if ( isset( $debugArr ) ) {
367
				$text = $debugText . ' ' . implode( '::', $debugArr );
368
				wfDebugLog( 'semantic-tasks', $text, false );
369
			} else {
370
				wfDebugLog( 'semantic-tasks', $debugText, false );
371
			}
372
		}
373
	}
374
375
}
376