Files
ops-Gazelle/app/StaffPM.php
2025-07-28 16:55:17 +02:00

248 lines
7.4 KiB
PHP

<?php
namespace Gazelle;
class StaffPM extends BaseObject {
final public const tableName = 'staff_pm_conversations';
public function flush(): static {
unset($this->info);
return $this;
}
public function link(): string {
return sprintf('<a href="%s">%s</a>', $this->url(), display_str($this->subject()));
}
public function location(): string {
return 'staffpm.php?action=viewconv&id=' . $this->id;
}
public function flushUser(User $user): static {
self::$cache->delete_multi([
"num_staff_pms_" . $user->id,
"staff_pm_new_" . $user->id,
]);
return $this;
}
public function info(): array {
if (isset($this->info)) {
return $this->info;
}
$this->info = self::$db->rowAssoc("
SELECT spc.Subject AS subject,
spc.UserID AS user_id,
spc.Level AS class_level,
coalesce(p.Name, concat('Level ', spc.Level))
AS userclass_name,
spc.AssignedToUser AS assigned_user_id,
spc.Unread AS unread,
spc.Status AS status,
spc.Date AS date
FROM staff_pm_conversations spc
LEFT JOIN permissions p USING (Level)
WHERE spc.ID = ?
", $this->id
);
return $this->info;
}
public function assignedUserId(): int {
return (int)$this->info()['assigned_user_id'];
}
public function classLevel(): int {
return $this->info()['class_level'];
}
public function date(): string {
return $this->info()['date'];
}
public function inProgress(): bool {
return $this->info()['status'] !== 'Resolved';
}
public function isResolved(): bool {
return $this->info()['status'] === 'Resolved';
}
public function isUnread(): bool {
return (bool)$this->info()['unread'];
}
public function subject(): string {
return $this->info()['subject'];
}
public function unassigned(): bool {
return $this->assignedUserId() > 0;
}
public function userId(): int {
return (int)$this->info()['user_id'];
}
public function userclassName(): string {
return $this->info()['userclass_name'];
}
/**
* Can the viewer view this message?
* - They created it or it is assigned to them
* - They are FLS and it has not yet been assigned
* - They are Staff and the conversation is viewable at their class level
*
* @return bool Can they see it?
*/
public function visible(User $viewer): bool {
return in_array($viewer->id(), [$this->userId(), $this->assignedUserId()])
|| ($viewer->isFLS() && $this->classLevel() == 0)
|| ($viewer->isStaff() && $this->classLevel() <= $viewer->privilege()->effectiveClassLevel());
}
public function assign(User $to, User $by): int {
self::$db->prepared_query("
UPDATE staff_pm_conversations SET
Status = 'Unanswered',
AssignedToUser = ?,
Level = ?
WHERE ID = ?
", $to->id(), $to->privilege()->effectiveClassLevel(), $this->id,
);
$affected = self::$db->affected_rows();
$this->flush();
$this->flushUser($to);
$this->flushUser($by);
return $affected;
}
public function assignClass(int $level, User $viewer): int {
self::$db->prepared_query("
UPDATE staff_pm_conversations SET
Status = 'Unanswered',
AssignedToUser = NULL,
Level = ?
WHERE ID = ?",
$level, $this->id
);
$affected = self::$db->affected_rows();
$this->flush();
$this->flushUser($viewer);
return $affected;
}
public function markAsRead(User $viewer): int {
self::$db->prepared_query("
UPDATE staff_pm_conversations SET
Unread = false
WHERE ID = ?
", $this->id
);
$affected = self::$db->affected_rows();
$this->flush();
$this->flushUser($viewer);
return $affected;
}
public function reply(User $user, string $message): int {
self::$db->begin_transaction();
self::$db->prepared_query("
INSERT INTO staff_pm_messages
(UserID, Message, ConvID)
VALUES (?, ?, ?)
", $user->id, $message, $this->id
);
$affected = self::$db->affected_rows();
self::$db->prepared_query("
UPDATE staff_pm_conversations SET
Date = now(),
Unread = true,
Status = ?
WHERE ID = ?
", $user->isStaffPMReader() ? 'Open' : 'Unanswered', $this->id
);
self::$db->commit();
$this->flush();
$this->flushUser($user);
self::$cache->delete_value("staff_pm_new_{$this->userId()}");
$notifMan = new \Gazelle\Manager\Notification();
$pushToken = $notifMan->pushableTokensById([$this->userId()], \Gazelle\Enum\NotificationType::STAFFPM);
$notifMan->push($pushToken,
"Someone replied to your Staff PM", $this->subject(), SITE_URL . '/' . $this->location());
return $affected;
}
protected function modifyStatus(User $user, string $status, ?int $resolver): int {
self::$db->prepared_query("
UPDATE staff_pm_conversations SET
Date = now(),
Status = ?,
ResolverID = ?
WHERE ID = ?
", $status, $resolver, $this->id
);
$affected = self::$db->affected_rows();
$this->flush();
$this->flushUser($user);
return $affected;
}
public function resolve(User $user): int {
return $this->modifyStatus($user, 'Resolved', $user->id);
}
public function unresolve(User $user): int {
return $this->modifyStatus($user, 'Unanswered', null);
}
public function thread(): array {
self::$db->prepared_query("
SELECT spm.ID AS id,
spm.UserID AS user_id,
um.Username AS username,
spm.SentDate AS sent_date,
spm.Message AS body
FROM staff_pm_messages spm
INNER JOIN users_main um ON (um.ID = spm.UserID)
WHERE spm.ConvID = ?
ORDER BY spm.SentDate
", $this->id
);
return self::$db->to_array(false, MYSQLI_ASSOC);
}
public function postBody(int $postId): ?string {
foreach ($this->thread() as $post) {
if ($post['id'] === $postId) {
return $post['body'];
}
}
return null;
}
public function postUserId(int $postId): ?int {
foreach ($this->thread() as $post) {
if ($post['id'] === $postId) {
return $post['user_id'];
}
}
return null;
}
public function remove(): int {
self::$db->prepared_query("
DELETE spc, spm
FROM staff_pm_conversations spc
LEFT JOIN staff_pm_messages spm ON (spm.ConvID = spc.ID)
WHERE spc.ID = ?
", $this->id
);
$affected = self::$db->affected_rows();
$this->flush();
return $affected;
}
}