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

src/SemanticTasksMailer.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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