Passed
Push — master ( ac90fa...4b79e7 )
by Joas
17:17 queued 12s
created
apps/dav/lib/BackgroundJob/UserStatusAutomation.php 1 patch
Indentation   +162 added lines, -162 removed lines patch added patch discarded remove patch
@@ -39,166 +39,166 @@
 block discarded – undo
39 39
 use Sabre\VObject\Recur\RRuleIterator;
40 40
 
41 41
 class UserStatusAutomation extends TimedJob {
42
-	protected IDBConnection $connection;
43
-	protected IJobList $jobList;
44
-	protected LoggerInterface $logger;
45
-	protected IManager $manager;
46
-	protected IConfig $config;
47
-
48
-	public function __construct(ITimeFactory $timeFactory,
49
-								IDBConnection $connection,
50
-								IJobList $jobList,
51
-								LoggerInterface $logger,
52
-								IManager $manager,
53
-								IConfig $config) {
54
-		parent::__construct($timeFactory);
55
-		$this->connection = $connection;
56
-		$this->jobList = $jobList;
57
-		$this->logger = $logger;
58
-		$this->manager = $manager;
59
-		$this->config = $config;
60
-
61
-		// Interval 0 might look weird, but the last_checked is always moved
62
-		// to the next time we need this and then it's 0 seconds ago.
63
-		$this->setInterval(0);
64
-	}
65
-
66
-	/**
67
-	 * @inheritDoc
68
-	 */
69
-	protected function run($argument) {
70
-		if (!isset($argument['userId'])) {
71
-			$this->jobList->remove(self::class, $argument);
72
-			$this->logger->info('Removing invalid ' . self::class . ' background job');
73
-			return;
74
-		}
75
-
76
-		$userId = $argument['userId'];
77
-		$automationEnabled = $this->config->getUserValue($userId, 'dav', 'user_status_automation', 'no') === 'yes';
78
-		if (!$automationEnabled) {
79
-			$this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the setting is disabled');
80
-			$this->jobList->remove(self::class, $argument);
81
-			return;
82
-		}
83
-
84
-		$property = $this->getAvailabilityFromPropertiesTable($userId);
85
-
86
-		if (!$property) {
87
-			$this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no availability settings');
88
-			$this->jobList->remove(self::class, $argument);
89
-			return;
90
-		}
91
-
92
-		$isCurrentlyAvailable = false;
93
-		$nextPotentialToggles = [];
94
-
95
-		$now = $this->time->getDateTime();
96
-		$lastMidnight = (clone $now)->setTime(0, 0);
97
-
98
-		$vObject = Reader::read($property);
99
-		foreach ($vObject->getComponents() as $component) {
100
-			if ($component->name !== 'VAVAILABILITY') {
101
-				continue;
102
-			}
103
-			/** @var VAvailability $component */
104
-			$availables = $component->getComponents();
105
-			foreach ($availables as $available) {
106
-				/** @var Available $available */
107
-				if ($available->name === 'AVAILABLE') {
108
-					/** @var \DateTimeImmutable $originalStart */
109
-					/** @var \DateTimeImmutable $originalEnd */
110
-					[$originalStart, $originalEnd] = $available->getEffectiveStartEnd();
111
-
112
-					// Little shenanigans to fix the automation on the day the rules were adjusted
113
-					// Otherwise the $originalStart would match rules for Thursdays on a Friday, etc.
114
-					// So we simply wind back a week and then fastForward to the next occurrence
115
-					// since today's midnight, which then also accounts for the week days.
116
-					$effectiveStart = \DateTime::createFromImmutable($originalStart)->sub(new \DateInterval('P7D'));
117
-					$effectiveEnd = \DateTime::createFromImmutable($originalEnd)->sub(new \DateInterval('P7D'));
118
-
119
-					try {
120
-						$it = new RRuleIterator((string) $available->RRULE, $effectiveStart);
121
-						$it->fastForward($lastMidnight);
122
-
123
-						$startToday = $it->current();
124
-						if ($startToday && $startToday <= $now) {
125
-							$duration = $effectiveStart->diff($effectiveEnd);
126
-							$endToday = $startToday->add($duration);
127
-							if ($endToday > $now) {
128
-								// User is currently available
129
-								// Also queuing the end time as next status toggle
130
-								$isCurrentlyAvailable = true;
131
-								$nextPotentialToggles[] = $endToday->getTimestamp();
132
-							}
133
-
134
-							// Availability enabling already done for today,
135
-							// so jump to the next recurrence to find the next status toggle
136
-							$it->next();
137
-						}
138
-
139
-						if ($it->current()) {
140
-							$nextPotentialToggles[] = $it->current()->getTimestamp();
141
-						}
142
-					} catch (\Exception $e) {
143
-						$this->logger->error($e->getMessage(), ['exception' => $e]);
144
-					}
145
-				}
146
-			}
147
-		}
148
-
149
-		if (empty($nextPotentialToggles)) {
150
-			$this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no valid availability rules set');
151
-			$this->jobList->remove(self::class, $argument);
152
-			$this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
153
-			return;
154
-		}
155
-
156
-		$nextAutomaticToggle = min($nextPotentialToggles);
157
-		$this->setLastRunToNextToggleTime($userId, $nextAutomaticToggle - 1);
158
-
159
-		if ($isCurrentlyAvailable) {
160
-			$this->logger->debug('User is currently available, reverting DND status if applicable');
161
-			$this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
162
-		} else {
163
-			$this->logger->debug('User is currently NOT available, reverting call status if applicable and then setting DND');
164
-			// The DND status automation is more important than the "Away - In call" so we also restore that one if it exists.
165
-			$this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_CALL, IUserStatus::AWAY);
166
-			$this->manager->setUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true);
167
-		}
168
-		$this->logger->debug('User status automation ran');
169
-	}
170
-
171
-	protected function setLastRunToNextToggleTime(string $userId, int $timestamp): void {
172
-		$query = $this->connection->getQueryBuilder();
173
-
174
-		$query->update('jobs')
175
-			->set('last_run', $query->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
176
-			->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
177
-		$query->executeStatement();
178
-
179
-		$this->logger->debug('Updated user status automation last_run to ' . $timestamp . ' for user ' . $userId);
180
-	}
181
-
182
-	/**
183
-	 * @param string $userId
184
-	 * @return false|string
185
-	 */
186
-	protected function getAvailabilityFromPropertiesTable(string $userId) {
187
-		$propertyPath = 'calendars/' . $userId . '/inbox';
188
-		$propertyName = '{' . Plugin::NS_CALDAV . '}calendar-availability';
189
-
190
-		$query = $this->connection->getQueryBuilder();
191
-		$query->select('propertyvalue')
192
-			->from('properties')
193
-			->where($query->expr()->eq('userid', $query->createNamedParameter($userId)))
194
-			->andWhere($query->expr()->eq('propertypath', $query->createNamedParameter($propertyPath)))
195
-			->andWhere($query->expr()->eq('propertyname', $query->createNamedParameter($propertyName)))
196
-			->setMaxResults(1);
197
-
198
-		$result = $query->executeQuery();
199
-		$property = $result->fetchOne();
200
-		$result->closeCursor();
201
-
202
-		return $property;
203
-	}
42
+    protected IDBConnection $connection;
43
+    protected IJobList $jobList;
44
+    protected LoggerInterface $logger;
45
+    protected IManager $manager;
46
+    protected IConfig $config;
47
+
48
+    public function __construct(ITimeFactory $timeFactory,
49
+                                IDBConnection $connection,
50
+                                IJobList $jobList,
51
+                                LoggerInterface $logger,
52
+                                IManager $manager,
53
+                                IConfig $config) {
54
+        parent::__construct($timeFactory);
55
+        $this->connection = $connection;
56
+        $this->jobList = $jobList;
57
+        $this->logger = $logger;
58
+        $this->manager = $manager;
59
+        $this->config = $config;
60
+
61
+        // Interval 0 might look weird, but the last_checked is always moved
62
+        // to the next time we need this and then it's 0 seconds ago.
63
+        $this->setInterval(0);
64
+    }
65
+
66
+    /**
67
+     * @inheritDoc
68
+     */
69
+    protected function run($argument) {
70
+        if (!isset($argument['userId'])) {
71
+            $this->jobList->remove(self::class, $argument);
72
+            $this->logger->info('Removing invalid ' . self::class . ' background job');
73
+            return;
74
+        }
75
+
76
+        $userId = $argument['userId'];
77
+        $automationEnabled = $this->config->getUserValue($userId, 'dav', 'user_status_automation', 'no') === 'yes';
78
+        if (!$automationEnabled) {
79
+            $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the setting is disabled');
80
+            $this->jobList->remove(self::class, $argument);
81
+            return;
82
+        }
83
+
84
+        $property = $this->getAvailabilityFromPropertiesTable($userId);
85
+
86
+        if (!$property) {
87
+            $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no availability settings');
88
+            $this->jobList->remove(self::class, $argument);
89
+            return;
90
+        }
91
+
92
+        $isCurrentlyAvailable = false;
93
+        $nextPotentialToggles = [];
94
+
95
+        $now = $this->time->getDateTime();
96
+        $lastMidnight = (clone $now)->setTime(0, 0);
97
+
98
+        $vObject = Reader::read($property);
99
+        foreach ($vObject->getComponents() as $component) {
100
+            if ($component->name !== 'VAVAILABILITY') {
101
+                continue;
102
+            }
103
+            /** @var VAvailability $component */
104
+            $availables = $component->getComponents();
105
+            foreach ($availables as $available) {
106
+                /** @var Available $available */
107
+                if ($available->name === 'AVAILABLE') {
108
+                    /** @var \DateTimeImmutable $originalStart */
109
+                    /** @var \DateTimeImmutable $originalEnd */
110
+                    [$originalStart, $originalEnd] = $available->getEffectiveStartEnd();
111
+
112
+                    // Little shenanigans to fix the automation on the day the rules were adjusted
113
+                    // Otherwise the $originalStart would match rules for Thursdays on a Friday, etc.
114
+                    // So we simply wind back a week and then fastForward to the next occurrence
115
+                    // since today's midnight, which then also accounts for the week days.
116
+                    $effectiveStart = \DateTime::createFromImmutable($originalStart)->sub(new \DateInterval('P7D'));
117
+                    $effectiveEnd = \DateTime::createFromImmutable($originalEnd)->sub(new \DateInterval('P7D'));
118
+
119
+                    try {
120
+                        $it = new RRuleIterator((string) $available->RRULE, $effectiveStart);
121
+                        $it->fastForward($lastMidnight);
122
+
123
+                        $startToday = $it->current();
124
+                        if ($startToday && $startToday <= $now) {
125
+                            $duration = $effectiveStart->diff($effectiveEnd);
126
+                            $endToday = $startToday->add($duration);
127
+                            if ($endToday > $now) {
128
+                                // User is currently available
129
+                                // Also queuing the end time as next status toggle
130
+                                $isCurrentlyAvailable = true;
131
+                                $nextPotentialToggles[] = $endToday->getTimestamp();
132
+                            }
133
+
134
+                            // Availability enabling already done for today,
135
+                            // so jump to the next recurrence to find the next status toggle
136
+                            $it->next();
137
+                        }
138
+
139
+                        if ($it->current()) {
140
+                            $nextPotentialToggles[] = $it->current()->getTimestamp();
141
+                        }
142
+                    } catch (\Exception $e) {
143
+                        $this->logger->error($e->getMessage(), ['exception' => $e]);
144
+                    }
145
+                }
146
+            }
147
+        }
148
+
149
+        if (empty($nextPotentialToggles)) {
150
+            $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no valid availability rules set');
151
+            $this->jobList->remove(self::class, $argument);
152
+            $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
153
+            return;
154
+        }
155
+
156
+        $nextAutomaticToggle = min($nextPotentialToggles);
157
+        $this->setLastRunToNextToggleTime($userId, $nextAutomaticToggle - 1);
158
+
159
+        if ($isCurrentlyAvailable) {
160
+            $this->logger->debug('User is currently available, reverting DND status if applicable');
161
+            $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
162
+        } else {
163
+            $this->logger->debug('User is currently NOT available, reverting call status if applicable and then setting DND');
164
+            // The DND status automation is more important than the "Away - In call" so we also restore that one if it exists.
165
+            $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_CALL, IUserStatus::AWAY);
166
+            $this->manager->setUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true);
167
+        }
168
+        $this->logger->debug('User status automation ran');
169
+    }
170
+
171
+    protected function setLastRunToNextToggleTime(string $userId, int $timestamp): void {
172
+        $query = $this->connection->getQueryBuilder();
173
+
174
+        $query->update('jobs')
175
+            ->set('last_run', $query->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
176
+            ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
177
+        $query->executeStatement();
178
+
179
+        $this->logger->debug('Updated user status automation last_run to ' . $timestamp . ' for user ' . $userId);
180
+    }
181
+
182
+    /**
183
+     * @param string $userId
184
+     * @return false|string
185
+     */
186
+    protected function getAvailabilityFromPropertiesTable(string $userId) {
187
+        $propertyPath = 'calendars/' . $userId . '/inbox';
188
+        $propertyName = '{' . Plugin::NS_CALDAV . '}calendar-availability';
189
+
190
+        $query = $this->connection->getQueryBuilder();
191
+        $query->select('propertyvalue')
192
+            ->from('properties')
193
+            ->where($query->expr()->eq('userid', $query->createNamedParameter($userId)))
194
+            ->andWhere($query->expr()->eq('propertypath', $query->createNamedParameter($propertyPath)))
195
+            ->andWhere($query->expr()->eq('propertyname', $query->createNamedParameter($propertyName)))
196
+            ->setMaxResults(1);
197
+
198
+        $result = $query->executeQuery();
199
+        $property = $result->fetchOne();
200
+        $result->closeCursor();
201
+
202
+        return $property;
203
+    }
204 204
 }
Please login to merge, or discard this patch.