Completed
Push — master ( 485e22...5184f3 )
by Morris
15:17
created

IMipPlugin::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 3
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2017, Georg Ehrke
5
 *
6
 * @author Thomas Müller <[email protected]>
7
 * @author Georg Ehrke <[email protected]>
8
 *
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
namespace OCA\DAV\CalDAV\Schedule;
25
26
use OCP\AppFramework\Utility\ITimeFactory;
27
use OCP\ILogger;
28
use OCP\Mail\IMailer;
29
use Sabre\VObject\Component\VCalendar;
30
use Sabre\VObject\DateTimeParser;
31
use Sabre\VObject\ITip;
32
use Sabre\CalDAV\Schedule\IMipPlugin as SabreIMipPlugin;
33
use Sabre\VObject\Recur\EventIterator;
34
35
/**
36
 * iMIP handler.
37
 *
38
 * This class is responsible for sending out iMIP messages. iMIP is the
39
 * email-based transport for iTIP. iTIP deals with scheduling operations for
40
 * iCalendar objects.
41
 *
42
 * If you want to customize the email that gets sent out, you can do so by
43
 * extending this class and overriding the sendMessage method.
44
 *
45
 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
46
 * @author Evert Pot (http://evertpot.com/)
47
 * @license http://sabre.io/license/ Modified BSD License
48
 */
49
class IMipPlugin extends SabreIMipPlugin {
50
51
	/** @var IMailer */
52
	private $mailer;
53
54
	/** @var ILogger */
55
	private $logger;
56
57
	/** @var ITimeFactory */
58
	private $timeFactory;
59
60
	const MAX_DATE = '2038-01-01';
61
62
	/**
63
	 * Creates the email handler.
64
	 *
65
	 * @param IMailer $mailer
66
	 * @param ILogger $logger
67
	 * @param ITimeFactory $timeFactory
68
	 */
69
	function __construct(IMailer $mailer, ILogger $logger, ITimeFactory $timeFactory) {
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...
70
		parent::__construct('');
71
		$this->mailer = $mailer;
72
		$this->logger = $logger;
73
		$this->timeFactory = $timeFactory;
74
	}
75
76
	/**
77
	 * Event handler for the 'schedule' event.
78
	 *
79
	 * @param ITip\Message $iTipMessage
80
	 * @return void
81
	 */
82
	function schedule(ITip\Message $iTipMessage) {
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...
83
84
		// Not sending any emails if the system considers the update
85
		// insignificant.
86
		if (!$iTipMessage->significantChange) {
87
			if (!$iTipMessage->scheduleStatus) {
88
				$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
89
			}
90
			return;
91
		}
92
93
		$summary = $iTipMessage->message->VEVENT->SUMMARY;
94
95
		if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') {
96
			return;
97
		}
98
99
		if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') {
100
			return;
101
		}
102
103
		// don't send out mails for events that already took place
104
		if ($this->isEventInThePast($iTipMessage->message)) {
105
			return;
106
		}
107
108
		$sender = substr($iTipMessage->sender, 7);
109
		$recipient = substr($iTipMessage->recipient, 7);
110
111
		$senderName = ($iTipMessage->senderName) ? $iTipMessage->senderName : null;
112
		$recipientName = ($iTipMessage->recipientName) ? $iTipMessage->recipientName : null;
113
114
		$subject = 'SabreDAV iTIP message';
115
		switch (strtoupper($iTipMessage->method)) {
116
			case 'REPLY' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
117
				$subject = 'Re: ' . $summary;
118
				break;
119
			case 'REQUEST' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
120
				$subject = $summary;
121
				break;
122
			case 'CANCEL' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
123
				$subject = 'Cancelled: ' . $summary;
124
				break;
125
		}
126
127
		$contentType = 'text/calendar; charset=UTF-8; method=' . $iTipMessage->method;
128
129
		$message = $this->mailer->createMessage();
130
131
		$message->setReplyTo([$sender => $senderName])
132
			->setTo([$recipient => $recipientName])
133
			->setSubject($subject)
134
			->setBody($iTipMessage->message->serialize(), $contentType);
135
		try {
136
			$failed = $this->mailer->send($message);
137
			if ($failed) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $failed of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
138
				$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' =>  implode(', ', $failed)]);
139
				$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
140
			}
141
			$iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
142
		} catch(\Exception $ex) {
143
			$this->logger->logException($ex, ['app' => 'dav']);
144
			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
145
		}
146
	}
147
148
	/**
149
	 * check if event took place in the past already
150
	 * @param VCalendar $vObject
151
	 * @return bool
152
	 */
153
	private function isEventInThePast(VCalendar $vObject) {
154
		$component = $vObject->VEVENT;
155
156
		$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
157
		// Finding the last occurrence is a bit harder
158 View Code Duplication
		if (!isset($component->RRULE)) {
159
			if (isset($component->DTEND)) {
160
				$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
161
			} elseif (isset($component->DURATION)) {
162
				$endDate = clone $component->DTSTART->getDateTime();
163
				// $component->DTEND->getDateTime() returns DateTimeImmutable
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
164
				$endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
165
				$lastOccurrence = $endDate->getTimeStamp();
166
			} elseif (!$component->DTSTART->hasTime()) {
167
				$endDate = clone $component->DTSTART->getDateTime();
168
				// $component->DTSTART->getDateTime() returns DateTimeImmutable
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
169
				$endDate = $endDate->modify('+1 day');
170
				$lastOccurrence = $endDate->getTimeStamp();
171
			} else {
172
				$lastOccurrence = $firstOccurrence;
173
			}
174
		} else {
175
			$it = new EventIterator($vObject, (string)$component->UID);
176
			$maxDate = new \DateTime(self::MAX_DATE);
177
			if ($it->isInfinite()) {
178
				$lastOccurrence = $maxDate->getTimestamp();
179
			} else {
180
				$end = $it->getDtEnd();
181
				while($it->valid() && $end < $maxDate) {
182
					$end = $it->getDtEnd();
183
					$it->next();
184
185
				}
186
				$lastOccurrence = $end->getTimestamp();
187
			}
188
		}
189
190
		$currentTime = $this->timeFactory->getTime();
191
		return $lastOccurrence < $currentTime;
192
	}
193
}
194