Skip to content

Commit d2fa24b

Browse files
committed
Merge branch 'main' into issue-1348
2 parents 51119e6 + ef74ece commit d2fa24b

20 files changed

+325
-56
lines changed

bootstrap.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
StudipAutoloader::addAutoloadPath(__DIR__ . '/lib', 'Opencast');
44

55
// adding observer
6-
NotificationCenter::addObserver('Opencast\Models\Videos', 'addToCoursePlaylist', 'OpencastCourseSync');
76
NotificationCenter::addObserver('Opencast\Models\Videos', 'parseEvent', 'OpencastVideoSync');
87
NotificationCenter::addObserver('Opencast\Models\Videos', 'checkEventACL', 'OpencastVideoSync');
98
NotificationCenter::addObserver('Opencast\Models\VideosUserPerms', 'setPermissions', 'OpencastVideoSync');
9+
NotificationCenter::addObserver('Opencast\Models\Helpers', 'mapEventUserSeriesUserPerms', 'OpencastVideoSync');
1010
NotificationCenter::addObserver('Opencast\Models\Helpers', 'notifyUsers', 'OpencastNotifyUsers');
1111

bootstrap_migrations.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
StudipAutoloader::addAutoloadPath(__DIR__ . '/lib', 'Opencast');
44

55
// adding observer
6-
NotificationCenter::addObserver('Opencast\Models\Videos', 'addToCoursePlaylist', 'OpencastCourseSync');
76
NotificationCenter::addObserver('Opencast\Models\Videos', 'parseEvent', 'OpencastVideoSync');
87
NotificationCenter::addObserver('Opencast\Models\Videos', 'checkEventACL', 'OpencastVideoSync');
98
NotificationCenter::addObserver('Opencast\Models\VideosUserPerms', 'setPermissions', 'OpencastVideoSync');

cronjobs/opencast_discover_videos.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Opencast\Models\PlaylistVideos;
1010
use Opencast\Models\WorkflowConfig;
1111
use Opencast\Models\REST\ApiEventsClient;
12+
use Opencast\Models\Helpers;
1213

1314
class OpencastDiscoverVideos extends CronJob
1415
{
@@ -107,6 +108,14 @@ public function execute($last_result, $parameters = array())
107108
continue;
108109
}
109110

111+
// Is the episode related to this studip?
112+
// We need to check this, because it might happen that the Opencast server is connected to multiple Stud.IP instances,
113+
// and we only want to process events that are related to this Stud.IP instance.
114+
if (!Helpers::isEventInAnyKnownSeries($event)) {
115+
echo '[Skipped] Event not related to this Stud.IP instance, skipping: ' . $event->identifier . "\n";
116+
continue;
117+
}
118+
110119
// only add videos / reinspect videos if they are readily processed
111120
if ($event->status == 'EVENTS.EVENTS.STATUS.PROCESSED') {
112121
$event_ids[] = $event->identifier;
@@ -140,9 +149,12 @@ public function execute($last_result, $parameters = array())
140149
echo 'found new video in Opencast #'. $config['id'] .': ' . $current_event->identifier . ' (' . $current_event->title . ")\n";
141150

142151
$video = Videos::findOneBySql("episode = ?", [$current_event->identifier]);
143-
$is_livestream = (bool) $video->is_livestream ?? false;
152+
$is_livestream = (bool) ($video->is_livestream ?? false);
144153

154+
// Set a flag to determine a new state of the video!
155+
$is_new = false;
145156
if (!$video) {
157+
$is_new = true;
146158
$video = new Videos;
147159
}
148160

@@ -160,6 +172,11 @@ public function execute($last_result, $parameters = array())
160172

161173
self::parseEvent($current_event, $video);
162174

175+
// Make sure the new videos are only get processed by the add to course playlist.
176+
if ($is_new) {
177+
Videos::addToCoursePlaylist($current_event, $video);
178+
}
179+
163180
// remove video from checklist for playlist videos (if even present)
164181
unset($playlist_videos[$current_event->identifier]);
165182
}

lib/Models/Filter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ public function __construct($params)
6161
$filters = \json_decode($params['filters'], true);
6262
foreach($filters as $filter) {
6363
if (in_array($filter['type'], self::$ALLOWED_FILTERS)) {
64+
$compare = $filter['compare'] ?? '!=';
6465
$this->filters[] = [
6566
'type' => $filter['type'],
6667
'value' => preg_replace('/[^0-9a-zA-Z\w\ ]/', '', $filter['value']),
67-
'compare' => $filter['compare'] == '=' ? '=' : '!='
68+
'compare' => $compare == '=' ? '=' : '!='
6869
];
6970
}
7071
}

lib/Models/Helpers.php

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88
use Opencast\VersionHelper;
99
use Opencast\Providers\Perm;
1010
use Opencast\Models\Videos;
11+
use Opencast\Models\REST\ApiEventsClient;
12+
use Opencast\Models\REST\ApiWorkflowsClient;
1113

1214
class Helpers
1315
{
16+
const RECORDED_SERIES_ID_CACHE_ID = 'OpencastV3/series/recorded_series_ids';
17+
1418
static function getConfigurationstate()
1519
{
1620
$stmt = DBManager::get()->prepare("SELECT COUNT(*) AS c FROM oc_config WHERE active = 1");
@@ -426,4 +430,131 @@ public static function isWorldReadable($oc_acls)
426430

427431
return $has_anonymous_role;
428432
}
433+
434+
/**
435+
* Retrieves all known recorded Opencast series IDs from the cache or database.
436+
*
437+
* This method returns an array of all series IDs that are known to the system,
438+
* combining both user-specific and seminar-specific series. It first attempts
439+
* to read the list from the cache. If the cache is empty or if the $force
440+
* parameter is set to true, it queries the database for all user and seminar
441+
* series IDs, merges and deduplicates them, and then updates the cache.
442+
*
443+
* @param bool $force If true, forces a refresh from the database instead of using the cache.
444+
* @return array List of unique recorded series IDs.
445+
*/
446+
public static function getAllRecordedSeriesIds(bool $force = false)
447+
{
448+
$cache = \StudipCacheFactory::getCache();
449+
$all_known_seriesids = $cache->read(self::RECORDED_SERIES_ID_CACHE_ID);
450+
if ($force || empty($all_known_seriesids)) {
451+
$combined_records = [];
452+
$user_series_ids =\SimpleCollection::createFromArray(
453+
UserSeries::findBySql('1')
454+
)->toArray();
455+
$seminar_series_ids =\SimpleCollection::createFromArray(
456+
SeminarSeries::findBySql('1')
457+
)->toArray();
458+
$combined_records = array_merge($user_series_ids, $seminar_series_ids);
459+
$all_known_seriesids = array_column($combined_records, 'series_id');
460+
$all_known_seriesids = array_unique($all_known_seriesids);
461+
$cache->write(self::RECORDED_SERIES_ID_CACHE_ID, $all_known_seriesids);
462+
}
463+
return $all_known_seriesids;
464+
}
465+
466+
/**
467+
* Determines whether a given Opencast event belongs to any know series in this Stud.IP instance.
468+
*
469+
* This method checks if the provided Opencast event's series ID (`is_part_of`)
470+
* is known to the current Stud.IP system. If the event does not have a series ID,
471+
* it is considered valid for this Stud.IP instance. Otherwise, the method checks
472+
* if the series ID exists in the list of all recorded series IDs known to Stud.IP.
473+
*
474+
* @param object $oc_event The Opencast event object to check.
475+
* @return bool True if the event belongs to this Stud.IP instance, false otherwise.
476+
*/
477+
public static function isEventInAnyKnownSeries($oc_event)
478+
{
479+
if (empty($oc_event->is_part_of)) {
480+
// No series id, so we consider it as a valid event for this studip to be processed!
481+
return true;
482+
}
483+
484+
$all_known_seriesids = self::getAllRecordedSeriesIds();
485+
486+
if (in_array($oc_event->is_part_of, $all_known_seriesids)) {
487+
return true;
488+
}
489+
490+
return false;
491+
}
492+
493+
/**
494+
* Invalidates the cache for recorded series IDs.
495+
*
496+
* This method clears the cache entry that stores all known recorded series IDs.
497+
* It should be called whenever a series is created or deleted to ensure
498+
* that the cache remains consistent with the database.
499+
* @see \Opencast\Models\UserSeries
500+
* @see \Opencast\Models\SeminarSeries
501+
*/
502+
public static function invalidateRecordedSeriesIdsCache()
503+
{
504+
$cache = \StudipCacheFactory::getCache();
505+
$cache->expire(self::RECORDED_SERIES_ID_CACHE_ID);
506+
}
507+
508+
/**
509+
* Gives the events without series id a chance of getting one by mapping user perms and user series.
510+
*
511+
* @Notification OpencastVideoSync
512+
*
513+
* @param string $eventType
514+
* @param object $event
515+
* @param Opencast\Models\Videos $video
516+
*/
517+
public static function mapEventUserSeriesUserPerms($eventType, $event, $video)
518+
{
519+
if (!empty($event->is_part_of)) {
520+
// Already has a series id, then we are done here!
521+
return;
522+
}
523+
524+
// Get the (a) video owner.
525+
$video_owner = VideosUserPerms::findOneBySQL('video_id = ? AND perm = ?', [$video->id, 'owner']);
526+
if (empty($video_owner)) {
527+
// No owner, then we have nothing to do here!
528+
return;
529+
}
530+
531+
// Make sure the owner has a user series!
532+
$user_series = null;
533+
534+
$all_user_series = UserSeries::getSeries($video_owner->user_id);
535+
// Enforce user series creation!
536+
if (empty($all_user_series)) {
537+
$user_series = UserSeries::createSeries($video_owner->user_id);
538+
} else {
539+
$user_series = $all_user_series[0];
540+
}
541+
542+
// Update the event with the new series id.
543+
$api_event_client = ApiEventsClient::getInstance($video->config_id);
544+
545+
$metadata[] = [
546+
'id' => 'isPartOf',
547+
'value' => $user_series['series_id']
548+
];
549+
$response = $api_event_client->updateMetadata($video->episode, $metadata);
550+
$republish = in_array($response['code'], [200, 204]) === true;
551+
552+
if ($republish) {
553+
$api_wf_client = ApiWorkflowsClient::getInstance($video->config_id);
554+
555+
if ($api_wf_client->republish($video->episode)) {
556+
echo 'Event metadata has been updated by the owner specific series id: ' . $video->episode . "\n";
557+
}
558+
}
559+
}
429560
}

lib/Models/REST/SeriesClient.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,16 @@ public function createSeriesForSeminar($course_id)
7676
*/
7777
private static function getSeriesDC($course_id)
7878
{
79-
$course = new \Seminar($course_id);
80-
$name = $course->getName() . ' - ' . $course->getStartSemesterName();
81-
$license = "© " . gmdate('Y') . " " . $GLOBALS['UNI_NAME_CLEAN'];
82-
$inst = \Institute::find($course->institut_id);
79+
$uni_name_clean = \Config::get()->getValue('UNI_NAME_CLEAN') ?: 'unbekannt';
80+
$course = new \Seminar($course_id);
81+
$name = $course->getName() . ' - ' . $course->getStartSemesterName();
82+
$license = "© " . gmdate('Y') . " " . $uni_name_clean;
83+
$inst = \Institute::find($course->institut_id);
8384

8485
$publisher = (string)$inst->name;
8586
$instructors = $course->getMembers('dozent');
8687
$instructor = array_shift($instructors);
87-
$contributor = \Config::get()->getValue('UNI_NAME_CLEAN') ?: 'unbekannt';
88+
$contributor = $uni_name_clean;
8889
$creator = $instructor['fullname'];
8990
$language = 'de';
9091

lib/Models/REST/WorkflowClient.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,17 @@ public function getDefinitions()
4545
/**
4646
* Returns a revised collection of all tagged Workflow definitions
4747
*
48-
* @return array tagged Workflow Instances
48+
* @return array|bool tagged Workflow Instances, or false if something goes wrong!
4949
*/
5050
public function getTaggedWorkflowDefinitions()
5151
{
5252
$wf_defs = self::getDefinitions();
5353

54+
// We break the process if the return result is false, indicating the connection error!
55+
if ($wf_defs === false) {
56+
return false;
57+
}
58+
5459
$tagged_wfs = array();
5560
if (!empty($wf_defs)) {
5661
foreach ($wf_defs as $wf_def) {
@@ -69,8 +74,8 @@ public function getTaggedWorkflowDefinitions()
6974
$tagged_wfs[] = array(
7075
'id' => $wf_def->id,
7176
'title' => $wf_def->title,
72-
'description' => $wf_def->description,
73-
'tag' => $wf_def->tags->tag
77+
'description' => $wf_def->description ?? null,
78+
'tag' => $wf_def->tags->tag ?? null
7479
);
7580
}
7681
}

lib/Models/SeminarSeries.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,22 @@ public static function ensureSeriesExists($config_id, $course_id, $series_id)
124124

125125
return false;
126126
}
127+
128+
/**
129+
* Invalidate the cache of recorded series IDs before storing a series.
130+
* Then proceeds with the parent store method.
131+
*/
132+
public function store() {
133+
Helpers::invalidateRecordedSeriesIdsCache();
134+
return parent::store();
135+
}
136+
137+
/**
138+
* Invalidate the cache of recorded series IDs before deleting a series.
139+
* Then proceeds with the parent delete method.
140+
*/
141+
public function delete() {
142+
Helpers::invalidateRecordedSeriesIdsCache();
143+
return parent::delete();
144+
}
127145
}

lib/Models/UserSeries.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,22 @@ public static function createSeries($user_id) {
5353
// Throw error
5454
}
5555
}
56+
57+
/**
58+
* Invalidate the cache of recorded series IDs before storing a series.
59+
* Then proceeds with the parent store method.
60+
*/
61+
public function store() {
62+
Helpers::invalidateRecordedSeriesIdsCache();
63+
return parent::store();
64+
}
65+
66+
/**
67+
* Invalidate the cache of recorded series IDs before deleting a series.
68+
* Then proceeds with the parent delete method.
69+
*/
70+
public function delete() {
71+
Helpers::invalidateRecordedSeriesIdsCache();
72+
return parent::delete();
73+
}
5674
}

0 commit comments

Comments
 (0)