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

SemanticTasksMailer::mailAssigneesUpdatedTask()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 4
nc 3
nop 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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...
Unused Code introduced by
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
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...
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,
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...
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 ) {
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...
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) {
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...
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) {
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...
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() {
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...
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 ) {
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...
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