OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
111
app/Domain/Queue/Repositories/Queue.php
Normal file
111
app/Domain/Queue/Repositories/Queue.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Queue\Repositories;
|
||||
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Leantime\Core\Db\Db as DbCore;
|
||||
use Leantime\Domain\Queue\Workers\Workers;
|
||||
use Leantime\Domain\Users\Repositories\Users as UserRepo;
|
||||
|
||||
class Queue
|
||||
{
|
||||
private ConnectionInterface $db;
|
||||
|
||||
private UserRepo $users;
|
||||
|
||||
public function __construct(DbCore $db, UserRepo $users)
|
||||
{
|
||||
$this->db = $db->getConnection();
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
public function queueMessageToUsers(array $recipients, string $message, string $subject = '', int $projectId = 0): void
|
||||
{
|
||||
$recipients = array_unique($recipients);
|
||||
|
||||
foreach ($recipients as $recipient) {
|
||||
$thedate = date('Y-m-d H:i:s');
|
||||
// NEW : Allowing recipients to be emails or userIds
|
||||
// TODO : Accept a list of \user objects too ?
|
||||
if (is_int($recipient)) {
|
||||
$theuser = $this->users->getUser($recipient);
|
||||
} elseif (filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
|
||||
$theuser = $this->users->getUserByEmail($recipient);
|
||||
} else {
|
||||
// skip invalid users
|
||||
continue;
|
||||
}
|
||||
|
||||
// User might not be set because it's a new user
|
||||
if (! $theuser) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$userId = $theuser['id'];
|
||||
$userEmail = $theuser['username'];
|
||||
$msghash = md5($thedate.$subject.$message.$userEmail.$projectId);
|
||||
|
||||
try {
|
||||
$this->db->table('zp_queue')->insert([
|
||||
'msghash' => $msghash,
|
||||
'channel' => Workers::EMAILS->value,
|
||||
'userId' => $userId,
|
||||
'subject' => $subject,
|
||||
'message' => $message,
|
||||
'thedate' => $thedate,
|
||||
'projectId' => $projectId,
|
||||
]);
|
||||
} catch (\PDOException $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO later : lists messages per user or per project ?
|
||||
|
||||
public function listMessageInQueue(Workers $channel, mixed $recipients = null, int $projectId = 0): false|array
|
||||
{
|
||||
$results = $this->db->table('zp_queue')
|
||||
->where('channel', $channel->value)
|
||||
->orderBy('userId')
|
||||
->orderBy('projectId')
|
||||
->orderBy('thedate')
|
||||
->get();
|
||||
|
||||
return array_map(fn ($item) => (array) $item, $results->toArray());
|
||||
}
|
||||
|
||||
public function deleteMessageInQueue(string|array $msghashes): bool
|
||||
{
|
||||
// NEW : Allowing one hash or an array of them
|
||||
$thehashes = is_string($msghashes) ? [$msghashes] : $msghashes;
|
||||
|
||||
foreach ($thehashes as $msghash) {
|
||||
$this->db->table('zp_queue')
|
||||
->where('msghash', $msghash)
|
||||
->delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addMessageToQueue(Workers $channel, string $subject, string $message, int $userId, int $projectId = 0): void
|
||||
{
|
||||
$thedate = date('Y-m-d H:i:s');
|
||||
$msghash = md5($thedate.$subject.$message.$projectId);
|
||||
|
||||
try {
|
||||
$this->db->table('zp_queue')->insert([
|
||||
'msghash' => $msghash,
|
||||
'channel' => $channel->value,
|
||||
'userId' => $userId,
|
||||
'subject' => $subject,
|
||||
'message' => $message,
|
||||
'thedate' => $thedate,
|
||||
'projectId' => $projectId,
|
||||
]);
|
||||
} catch (\PDOException $e) {
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
89
app/Domain/Queue/Services/Queue.php
Normal file
89
app/Domain/Queue/Services/Queue.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Queue\Services;
|
||||
|
||||
use Leantime\Domain\Queue\Repositories\Queue as QueueRepository;
|
||||
use Leantime\Domain\Queue\Workers\DefaultWorker;
|
||||
use Leantime\Domain\Queue\Workers\EmailWorker;
|
||||
use Leantime\Domain\Queue\Workers\HttpRequestWorker;
|
||||
use Leantime\Domain\Queue\Workers\Workers;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
class Queue
|
||||
{
|
||||
private QueueRepository $queue;
|
||||
|
||||
public $availableWorkers = ['email', 'httprequest'];
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param QueueRepository $queue The queue repository.
|
||||
*/
|
||||
public function __construct(
|
||||
QueueRepository $queue
|
||||
) {
|
||||
// NEW Queuing messaging system
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the queue for a specific worker.
|
||||
*
|
||||
* @param Workers $worker The worker for which to process the queue.
|
||||
* @return bool Returns true if the queue was processed successfully, false otherwise.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function processQueue(Workers $worker): bool
|
||||
{
|
||||
|
||||
$messages = $this->queue->listMessageInQueue($worker);
|
||||
|
||||
if ($worker == Workers::EMAILS) {
|
||||
$worker = app()->make(EmailWorker::class);
|
||||
$worker->handleQueue($messages);
|
||||
}
|
||||
|
||||
if ($worker == Workers::HTTPREQUESTS) {
|
||||
$worker = app()->make(HttpRequestWorker::class);
|
||||
$worker->handleQueue($messages);
|
||||
}
|
||||
|
||||
if ($worker == Workers::DEFAULT) {
|
||||
$worker = app()->make(DefaultWorker::class);
|
||||
$worker->handleQueue($messages);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addToQueue(Workers $channel, string $subject, string $message, $projectId)
|
||||
{
|
||||
|
||||
$this->queue->addMessageToQueue(
|
||||
channel: $channel,
|
||||
subject: $subject,
|
||||
message: $message,
|
||||
projectId: $projectId,
|
||||
userId: session('userdata.id'));
|
||||
|
||||
}
|
||||
|
||||
public static function addJob(Workers $channel, string $subject, mixed $message, ?int $userId = null, ?int $projectId = null)
|
||||
{
|
||||
|
||||
$queue = app()->make(QueueRepository::class);
|
||||
|
||||
$queue->addMessageToQueue(
|
||||
channel: $channel,
|
||||
subject: $subject,
|
||||
message: serialize($message),
|
||||
projectId: $projectId ?? session('currentProject'),
|
||||
userId: $userId ?? session('userdata.id')
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
42
app/Domain/Queue/Workers/DefaultWorker.php
Normal file
42
app/Domain/Queue/Workers/DefaultWorker.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Queue\Workers;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Leantime\Domain\Queue\Repositories\Queue;
|
||||
use PHPUnit\Exception;
|
||||
|
||||
class DefaultWorker
|
||||
{
|
||||
public function __construct(
|
||||
private Queue $queue
|
||||
) {}
|
||||
|
||||
public function handleQueue($messages)
|
||||
{
|
||||
|
||||
foreach ($messages as $message) {
|
||||
try {
|
||||
$payload = safe_unserialize($message['message']);
|
||||
$subjectClass = $message['subject'];
|
||||
|
||||
$jobClass = app()->make($subjectClass);
|
||||
|
||||
$result = $jobClass->handle($payload);
|
||||
|
||||
if ($result) {
|
||||
$this->queue->deleteMessageInQueue($message['msghash']);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
Log::error('Worker was not successful');
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
137
app/Domain/Queue/Workers/EmailWorker.php
Normal file
137
app/Domain/Queue/Workers/EmailWorker.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Queue\Workers;
|
||||
|
||||
use Leantime\Core\Language;
|
||||
use Leantime\Core\Mailer;
|
||||
use Leantime\Domain\Queue\Repositories\Queue;
|
||||
use Leantime\Domain\Setting\Repositories\Setting;
|
||||
use Leantime\Domain\Users\Repositories\Users;
|
||||
|
||||
class EmailWorker
|
||||
{
|
||||
public function __construct(
|
||||
private Users $userRepo,
|
||||
private Setting $settingsRepo,
|
||||
private Mailer $mailer,
|
||||
private Queue $queue,
|
||||
private Language $language
|
||||
) {}
|
||||
|
||||
public function handleQueue($messages)
|
||||
{
|
||||
|
||||
$allMessagesToSend = [];
|
||||
$allMessagesToDelete = [];
|
||||
$n = 0;
|
||||
foreach ($messages as $message) {
|
||||
$n++;
|
||||
$currentUserId = $message['userId'];
|
||||
|
||||
// Don't send messages older than 2 weeks.
|
||||
$fromTz = new \DateTimeZone('UTC');
|
||||
$messageDate = \DateTime::createFromFormat('Y-m-d H:i:s', $message['thedate'], $fromTz);
|
||||
|
||||
$today = new \DateTime(datetime: 'now', timezone: $fromTz);
|
||||
|
||||
if ($messageDate->diff($today)->days <= 14) {
|
||||
|
||||
$allMessagesToSend[$currentUserId][$message['msghash']] = [
|
||||
'thedate' => $message['thedate'],
|
||||
'subject' => $message['subject'],
|
||||
'message' => $message['message'],
|
||||
'projectId' => $message['projectId'],
|
||||
];
|
||||
|
||||
}
|
||||
// DONE here : here we need a message id to allow deleting messages of the queue when they are sent
|
||||
// and here we need to group the messages in an array to know which messages are grouped to group-delete them
|
||||
// Discard all messages
|
||||
$allMessagesToDelete[$currentUserId][] = $message['msghash'];
|
||||
}
|
||||
|
||||
foreach ($allMessagesToSend as $currentUserId => $messageToSendToUser) {
|
||||
$theuser = $this->userRepo->getUser($currentUserId);
|
||||
|
||||
if ($theuser === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$recipient = $theuser['username'];
|
||||
|
||||
// DONE : Deal with users parameters to allow them define a maximum (and minimum ?) frequency to receive mails
|
||||
$lastMessageDate = strtotime($this->settingsRepo->getSetting('usersettings.'.$theuser['id'].'.lastMessageDate'));
|
||||
$nowDate = time();
|
||||
|
||||
// echo for DEBUG PURPOSE
|
||||
// debug_print("Last message to " . $recipient . " was on " . date('Y-m-d H:i:s', $lastMessageDate));
|
||||
$timeSince = abs($nowDate - $lastMessageDate);
|
||||
|
||||
// Get company message frequency default
|
||||
$messageFrequency = $this->settingsRepo->getSetting('companysettings.messageFrequency');
|
||||
|
||||
// Check if user has frequency set
|
||||
if (empty($messageFrequency)) {
|
||||
$messageFrequency = $this->settingsRepo->getSetting('usersettings.'.$theuser['id'].'.messageFrequency');
|
||||
}
|
||||
|
||||
// Last security to avoid flooding people.
|
||||
if (empty($messageFrequency)) {
|
||||
$messageFrequency = 900;
|
||||
}
|
||||
// echo for DEBUG PURPOSE
|
||||
// debug_print("The message frequency for " . $recipient . " : " . $messageFrequency);
|
||||
|
||||
if ($timeSince < $messageFrequency) {
|
||||
// echo for DEBUG PURPOSE
|
||||
// debug_print("Elapsed time not enough for " . $recipient . " : skipping till " . date("Y-m-d H:i:s", $lastMessageDate + $messageFrequency));
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO here : set up a true templating system to format the messages
|
||||
$formattedHTML = $this->doFormatMail($messageToSendToUser);
|
||||
|
||||
// DONE Tranlastion needed somewhere ?
|
||||
|
||||
// DONE : Send the message with PHPMailer here
|
||||
$this->mailer->setContext('latest_updates');
|
||||
if (count($messageToSendToUser) == 1) {
|
||||
reset($messageToSendToUser);
|
||||
$this->mailer->setSubject(current($messageToSendToUser)['subject']);
|
||||
} else {
|
||||
$this->mailer->setSubject($this->language->__('email_notifications.latest_updates_subject'));
|
||||
}
|
||||
$this->mailer->setHtml($formattedHTML);
|
||||
$to = [$recipient];
|
||||
|
||||
$this->mailer->sendMail($to, 'Leantime System');
|
||||
|
||||
// Delete the corresponding messages from the queue when the mail is sent
|
||||
// TODO here : only delete these if the send was successful
|
||||
// echo for DEBUG PURPOSE
|
||||
// debug_print("Messages send (about to delete) :");
|
||||
|
||||
$this->queue->deleteMessageInQueue($allMessagesToDelete[$currentUserId]);
|
||||
|
||||
// Store the last time a mail was sent to $recipient email
|
||||
$thedate = date('Y-m-d H:i:s');
|
||||
$this->settingsRepo->saveSetting('usersettings.'.$theuser['id'].'.lastMessageDate', $thedate);
|
||||
}
|
||||
}
|
||||
|
||||
// Fake template to be replaced by something better
|
||||
// TODO : Rework email templating system
|
||||
private function doFormatMail($messageToSendToUser): string
|
||||
{
|
||||
$outputHTML = $this->language->__('text.here_are_news')."<br/>\n";
|
||||
foreach ($messageToSendToUser as $chunk) {
|
||||
$outputHTML .= '<div style="border-top: 1px solid #ddd; margin: 3px; padding: 3px;">';
|
||||
$outputHTML .= '<div style="margin: 0px; padding: 0px; float : right">'.$chunk['thedate'].'</div>';
|
||||
$outputHTML .= '<div><p><em>'.$chunk['subject'].'</em></p>';
|
||||
$outputHTML .= $chunk['message'].'</div>';
|
||||
$outputHTML .= '</div>';
|
||||
}
|
||||
|
||||
return $outputHTML;
|
||||
}
|
||||
}
|
||||
44
app/Domain/Queue/Workers/HttpRequestWorker.php
Normal file
44
app/Domain/Queue/Workers/HttpRequestWorker.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Queue\Workers;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Leantime\Domain\Queue\Repositories\Queue;
|
||||
|
||||
class HttpRequestWorker
|
||||
{
|
||||
public function __construct(
|
||||
private Queue $queue,
|
||||
private Client $client
|
||||
) {}
|
||||
|
||||
public function handleQueue($messages)
|
||||
{
|
||||
|
||||
foreach ($messages as $request) {
|
||||
|
||||
try {
|
||||
|
||||
$subjectArray = safe_unserialize($request['subject'], []);
|
||||
$messageArray = safe_unserialize($request['message'], []);
|
||||
|
||||
$response = $this->client->request(
|
||||
$subjectArray['method'],
|
||||
$subjectArray['url'],
|
||||
$messageArray
|
||||
);
|
||||
|
||||
$this->queue->deleteMessageInQueue($request['msghash']);
|
||||
|
||||
} catch (GuzzleException $e) {
|
||||
report($e);
|
||||
|
||||
// Temp to clear out http requests
|
||||
$this->queue->deleteMessageInQueue($request['msghash']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
app/Domain/Queue/Workers/Workers.php
Normal file
11
app/Domain/Queue/Workers/Workers.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Queue\Workers;
|
||||
|
||||
enum Workers: string
|
||||
{
|
||||
case EMAILS = 'email';
|
||||
case HTTPREQUESTS = 'httprequests';
|
||||
|
||||
case DEFAULT = 'default';
|
||||
}
|
||||
29
app/Domain/Queue/register.php
Normal file
29
app/Domain/Queue/register.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Queue;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Leantime\Core\Events\EventDispatcher;
|
||||
use Leantime\Domain\Queue\Workers\Workers;
|
||||
|
||||
EventDispatcher::add_event_listener('leantime.core.console.consolekernel.schedule.cron', function ($params) {
|
||||
|
||||
if (get_class($scheduler = $params['schedule']) !== Schedule::class) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scheduler
|
||||
->call(fn () => app()->make(Services\Queue::class)->processQueue(Workers::EMAILS))
|
||||
->name('queue:emails')
|
||||
->everyMinute();
|
||||
|
||||
$scheduler
|
||||
->call(fn () => app()->make(Services\Queue::class)->processQueue(Workers::HTTPREQUESTS))
|
||||
->name('queue:httprequests')
|
||||
->everyFiveMinutes();
|
||||
|
||||
$scheduler
|
||||
->call(fn () => app()->make(Services\Queue::class)->processQueue(Workers::DEFAULT))
|
||||
->name('queue:default')
|
||||
->everyFiveMinutes();
|
||||
});
|
||||
Reference in New Issue
Block a user