From 5d052ab7953afa5d9518f28ae31ebc71928fe5f5 Mon Sep 17 00:00:00 2001 From: Thomas Hackl <hackl@data-quest.de> Date: Fri, 10 Jun 2022 08:33:40 +0200 Subject: [PATCH] process news posting to Matrix via cronjob so that news are pushed when their publish date is reached and not immediately --- MatrixPlugin.php | 31 +++-- MatrixPostNewsCronjob.php | 120 ++++++++++++++++++ controllers/matrix_chat.php | 7 +- migrations/05_post_news_to_matrix_cronjob.php | 53 ++++++++ models/MatrixRoom.php | 2 +- plugin.manifest | 2 +- 6 files changed, 199 insertions(+), 16 deletions(-) create mode 100644 MatrixPostNewsCronjob.php create mode 100644 migrations/05_post_news_to_matrix_cronjob.php diff --git a/MatrixPlugin.php b/MatrixPlugin.php index 92ab523..6daf2e1 100644 --- a/MatrixPlugin.php +++ b/MatrixPlugin.php @@ -32,8 +32,8 @@ class MatrixPlugin extends StudIPPlugin implements StandardPlugin NotificationCenter::addObserver($this, 'uninvite', 'CourseMemberDidDelete'); NotificationCenter::addObserver($this, 'unregister', 'UserDidDelete'); NotificationCenter::addObserver($this, 'deleteRoom', 'CourseDidDelete'); - NotificationCenter::addObserver($this, 'postNews', 'StudipNewsDidCreate'); - NotificationCenter::addObserver($this, 'postNews', 'StudipNewsDidUpdate'); + NotificationCenter::addObserver($this, 'updateCronjob', 'StudipNewsWillUpdate'); + NotificationCenter::addObserver($this, 'updateCronjob', 'StudipNewsDidDelete'); $this->addScript('assets/javascripts/matrixchat.js'); $this->addStylesheet('assets/stylesheets/matrixchat.scss'); @@ -94,6 +94,7 @@ class MatrixPlugin extends StudIPPlugin implements StandardPlugin public function getMetadata() { return [ + 'pluginname' => dgettext('matrix', 'Matrix-Chat'), 'summary' => dgettext('matrix', 'Chat via Matrix'), 'description' => dgettext('matrix', 'Matrix-Chaträume für Veranstaltungen'), 'category' => _('Kommunikation und Zusammenarbeit'), @@ -194,22 +195,28 @@ class MatrixPlugin extends StudIPPlugin implements StandardPlugin } /** - * Auto-post course news to associated Matrix room if configured. + * Remove news from entries relevant for cronjob. * * @param string $event * @param StudipNews $news */ - public function postNews($event, $news) + public function updateCronjob($event, $news) { - foreach ($news->news_ranges as $range) { - if ($range->course && MatrixRoom::hasRoom($range->course->id)) { - MatrixClient::get()->postMessage( - MatrixAccount::requireSystemAccount(), - MatrixRoom::find($range->course->id)->getLinkedRoom(), - 'Ankündigung: ' . $news->topic . '' . strip_tags($news->body), - '<strong>Ankündigung: ' . $news->topic . '</strong><br>' . $news->body + switch ($event) { + case 'StudipNewsWillUpdate': + if ($news->isFieldDirty('date')) { + DBManager::get()->execute( + "UPDATE `matrix_upcoming_news` SET `publish_at` = :publish WHERE `news_id` = :id", + ['publish' => $news->date, 'id' => $news->id] + ); + } + break; + case 'StudipNewsDidDelete': + DBManager::get()->execute( + "DELETE FROM `matrix_upcoming_news` WHERE `news_id` = :id", + ['id' => $news->id] ); - } + break; } } diff --git a/MatrixPostNewsCronjob.php b/MatrixPostNewsCronjob.php new file mode 100644 index 0000000..d4ac57f --- /dev/null +++ b/MatrixPostNewsCronjob.php @@ -0,0 +1,120 @@ +<?php +/** + * Class MatrixPostNewsCronjob + * Cronjob that checks if a course with attached Matrix chatroom has any news, registers them as "to do" + * and posts the news content when the news are published in Stud.IP. Publishing news happens at a date + * given per news entry, so this cannot be done "live" after the news has been created. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Thomas Hackl <hackl@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Matrix + */ + +require_once(__DIR__ . '/vendor/libpatrix/MatrixClient.php'); + +class MatrixPostNewsCronjob extends CronJob +{ + public static function getName() + { + return dgettext('matrix', 'Aktuelle Ankündigungen in Matrixraum veröffentlichen'); + } + + public static function getDescription() + { + return dgettext('matrix', 'Dieser Cronjob veröffentlicht eine Ankündigung im zugehörigen ' . + 'Matrixraum, wenn sie auch in der entsprechenden Veranstaltung öffentlich wird.'); + } + + public static function getParameters() + { + return [ + 'verbose' => [ + 'type' => 'boolean', + 'default' => false, + 'status' => 'optional', + 'description' => _('Sollen Ausgaben erzeugt werden'), + ], + ]; + } + + public function execute($last_result, $parameters = []) + { + /* + * First, check if there are news that need to be monitored until they are published. + * This means getting all news + * - that belong to a course with attaached Matrix room + * - that haven't expired + * - that haven't already been posted + * and inserting them into the monitoring table or updating them accordingly. + */ + DBManager::get()->execute("INSERT INTO `matrix_upcoming_news` + ( + SELECT DISTINCT n.`news_id`, r.`range_id`, n.`date`, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP() FROM `news` n + JOIN `news_range` r USING (`news_id`) + JOIN `matrix_rooms` m ON (m.`range_id` = r.`range_id`) + WHERE UNIX_TIMESTAMP() <= n.`date` + n.`expire` + ) + ON DUPLICATE KEY UPDATE `publish_at` = VALUES(`publish_at`), `chdate` = UNIX_TIMESTAMP()", + ['now' => time()] + ); + + /* + * Now walk through entries in monitoring table and push each entry + * to the corresponding chatroom if publish_date is reached. + */ + $news = DBManager::get()->fetchFirst( + "SELECT `news_id` FROM `matrix_upcoming_news` WHERE `publish_at` <= UNIX_TIMESTAMP() AND `posted` = 0" + ); + + // Verbose output + if ($parameters['verbose']) { + echo "Got news_ids:\n"; + print_r($news); + } + foreach (StudipNews::findMany($news) as $one) { + foreach ($one->news_ranges as $range) { + $success = false; + if ($range->course && MatrixRoom::hasRoom($range->course->id)) { + $room = MatrixRoom::find($range->course->id); + + $success = MatrixClient::get()->postMessage( + MatrixAccount::requireSystemAccount(), + $room->getLinkedRoom(), + 'Ankündigung: ' . $one->topic . ' ' . strip_tags($one->body), + '<strong>Ankündigung: ' . $one->topic . '</strong><br>' . $one->body + ); + + // Mark entry as done after message has been posted to Matrix room successfully. + if ($success) { + // Verbose output + if ($parameters['verbose']) { + echo sprintf( + "Posted news entry $1%s to Matrix room $2%s in course $3%s.\n", + $news->id, $room->matrix_room_id, $room->range_id + ); + } + + DBManager::get()->execute( + "UPDATE `matrix_upcoming_news` SET `posted` = 1 WHERE `news_id` = :id AND `range_id` = :range", + ['id' => $one->id, 'range' => $range->course->id] + ); + } + } + } + } + + // Finally: cleanup news entries that are already expired and need not be considered anymore. + DBManager::get()->execute("DELETE FROM `matrix_upcoming_news` WHERE `news_id` IN ( + SELECT DISTINCT n.`news_id` FROM `news` n + JOIN `matrix_upcoming_news` m USING (`news_id`) + WHERE n.`date` + n.`expire` < UNIX_TIMESTAMP() + )" + ); + + } +} diff --git a/controllers/matrix_chat.php b/controllers/matrix_chat.php index 83836ff..5c1507f 100644 --- a/controllers/matrix_chat.php +++ b/controllers/matrix_chat.php @@ -79,8 +79,11 @@ class MatrixChatController extends AuthenticatedController $this->hasToCreate = false; if ($this->account) { - //$room->requireMembership($this->account->getLinkedAccount()); - $invited = $room->requireInvitation($this->account->getLinkedAccount()); + if (Config::get()->MATRIX_AUTO_CREATE_ACCOUNTS) { + $room->requireMembership($this->account->getLinkedAccount()); + } else { + $invited = $room->requireInvitation($this->account->getLinkedAccount()); + } } } } diff --git a/migrations/05_post_news_to_matrix_cronjob.php b/migrations/05_post_news_to_matrix_cronjob.php new file mode 100644 index 0000000..8e37b54 --- /dev/null +++ b/migrations/05_post_news_to_matrix_cronjob.php @@ -0,0 +1,53 @@ +<?php + +/** + * Class PostNewsToMatrixCronjob + * Registers a cronjob for posting news at publish time + * to the corresponding Matrix chatroom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Thomas Hackl <hackl@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Matrix + */ + +require_once(__DIR__ . '/../MatrixPostNewsCronjob.php'); + +class PostNewsToMatrixCronjob extends Migration +{ + + public function description() + { + return 'Registers a cronjob for posting news at publish time to the corresponding Matrix chatroom.'; + } + + public function up() + { + /** + * Collect news entries that need to be published to a Matrix chatroom. + */ + DBManager::get()->execute("CREATE TABLE IF NOT EXISTS `matrix_upcoming_news` ( + `news_id` CHAR(32) NOT NULL COLLATE latin1_bin, + `range_id` CHAR(32) NOT NULL COLLATE latin1_bin, + `publish_at` INT, + `posted` TINYINT(1) NOT NULL DEFAULT 0, + `mkdate` INT NOT NULL, + `chdate` INT NOT NULL, + PRIMARY KEY (`news_id`, `range_id`) + ) ENGINE InnoDB ROW_FORMAT=DYNAMIC"); + + MatrixPostNewsCronjob::register()->schedulePeriodic(-15)->activate(); + } + + public function down() + { + MatrixPostNewsCronjob::unregister(); + + DBManager::get()->execute("DROP TABLE IF EXISTS `matrix_upcoming_news`"); + } + +} \ No newline at end of file diff --git a/models/MatrixRoom.php b/models/MatrixRoom.php index 14386e1..5494dea 100644 --- a/models/MatrixRoom.php +++ b/models/MatrixRoom.php @@ -124,7 +124,7 @@ class MatrixRoom extends SimpleORMap public function getLinkedRoom() { - return new Patrix\Room($this->matrix_room_id, Context::getHeaderLine()); + return new Patrix\Room($this->matrix_room_id, Context::get() ? Context::getHeaderLine() : ''); } public function requireMembership($account) diff --git a/plugin.manifest b/plugin.manifest index 77a70c2..a3622a7 100644 --- a/plugin.manifest +++ b/plugin.manifest @@ -1,7 +1,7 @@ pluginname=Matrix-Chat pluginclassname=MatrixPlugin origin=data-quest -version=1.2.1 +version=1.3 screenshot=assets/images/matrix_logo.png description=Matrix chat for Stud.IP courses studipMinVersion=4.5 -- GitLab