diff options
| author | Jan-Hendrik Willms <tleilax+github@gmail.com> | 2021-07-22 16:07:19 +0200 |
|---|---|---|
| committer | Jan-Hendrik Willms <tleilax+github@gmail.com> | 2021-07-22 16:19:12 +0200 |
| commit | a3da1483a9e689846179159355badfec8073dbec (patch) | |
| tree | 770dcca6bdf5f6f2a11b0e7fcbbeda6919a3fc52 /lib/activities | |
current code from svn, revision 62608
Diffstat (limited to 'lib/activities')
| -rw-r--r-- | lib/activities/Activity.php | 192 | ||||
| -rw-r--r-- | lib/activities/ActivityObserver.php | 43 | ||||
| -rw-r--r-- | lib/activities/ActivityProvider.php | 25 | ||||
| -rw-r--r-- | lib/activities/Context.php | 124 | ||||
| -rw-r--r-- | lib/activities/CourseContext.php | 83 | ||||
| -rwxr-xr-x | lib/activities/CoursewareContext.php | 56 | ||||
| -rwxr-xr-x | lib/activities/CoursewareProvider.php | 38 | ||||
| -rw-r--r-- | lib/activities/DocumentsProvider.php | 131 | ||||
| -rw-r--r-- | lib/activities/Filter.php | 89 | ||||
| -rw-r--r-- | lib/activities/ForumProvider.php | 54 | ||||
| -rw-r--r-- | lib/activities/InstituteContext.php | 80 | ||||
| -rw-r--r-- | lib/activities/MessageProvider.php | 82 | ||||
| -rw-r--r-- | lib/activities/NewsProvider.php | 134 | ||||
| -rw-r--r-- | lib/activities/ParticipantsProvider.php | 84 | ||||
| -rw-r--r-- | lib/activities/ScheduleProvider.php | 83 | ||||
| -rw-r--r-- | lib/activities/Stream.php | 190 | ||||
| -rw-r--r-- | lib/activities/SystemContext.php | 56 | ||||
| -rw-r--r-- | lib/activities/UserContext.php | 74 | ||||
| -rw-r--r-- | lib/activities/WikiProvider.php | 125 |
19 files changed, 1743 insertions, 0 deletions
diff --git a/lib/activities/Activity.php b/lib/activities/Activity.php new file mode 100644 index 0000000..b8ed662 --- /dev/null +++ b/lib/activities/Activity.php @@ -0,0 +1,192 @@ +<?php + +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class Activity extends \SimpleORMap +{ + public + $object_url, + $object_route; + + private $context_object; + + const GC_MAX_DAYS = 366; // Garbage collector removes activities after 366 days + + private static $allowed_verbs = [ + 'answered', + 'attempted', + 'attended', + 'completed', + 'created', + 'deleted', + 'edited', + 'experienced', + 'failed', + 'imported', + 'interacted', + 'passed', + 'shared', + 'sent', + 'voided' + ]; + + /** + * {@inheritdoc} + */ + protected static function configure($config = []) + { + $config['db_table'] = 'activities'; + $config['additional_fields']['object_url'] = ['get' => 'getUrlList']; + $config['additional_fields']['object_route'] = ['get' => 'getRoute']; + + parent::configure($config); + } + + /** + * return a string representation for this activity + * + * @return string + */ + public function __toString() + { + return $this->content['content']; + } + + /** + * set one of the allowed verbs + * + * @param string $verb + * + * @throws \InvalidArgumentException + */ + public function setVerb($verb) + { + if (is_null($verb)) { + return; + } + + if (in_array($verb, self::$allowed_verbs) === false) { + throw new \InvalidArgumentException("That verb is not allowed."); + } + + $this->content['verb'] = $verb; + } + + /** + * Add a url to the list of urls + * + * @param type $url + * @param type $name + */ + public function addUrl($url, $name) + { + $this->object_url[$url] = $name; + } + + /** + * Return assoc array of urls + * [[url => description]] + * + * @return Array + */ + public function getUrlList() + { + return $this->object_url ?: []; + } + + /** + * Return api route of the content object + * + * @return string + */ + public function getRoute() + { + return $this->object_route; + } + + public function setContextObject(Context $context) + { + $this->context_object = $context; + } + + public function getContextObject() + { + return $this->context_object; + } + + /** + * Returns a format string as placeholder for the object in question + * (in a grammatical / lexical sense) + * + * @return string + */ + public function verbToText() + { + $translation = [ + 'answered' => _('beantwortete %s'), + 'attempted' => _('versuchte %s'), + 'attended' => _('nahm teil an %s'), + 'completed' => _('beendete %s'), + 'created' => _('erstellte %s'), + 'deleted' => _('löschte %s'), + 'edited' => _('bearbeitete %s'), + 'experienced' => _('erlebte %s'), + 'failed' => _('verfehlte %s'), + 'imported' => _('importierte %s'), + 'interacted' => _('interagierte mit %s'), + 'passed' => _('bestand %s'), + 'shared' => _('teilte %s'), + 'sent' => _('sendete %s'), + 'voided' => _('löschte %s') + ]; + + return ($translation[$this->verb]); + } + + /** + * Garbage collector for the activities. + * Removes all activites older than GC_MAX_DAYS (default: 366). + */ + public static function doGarbageCollect() + { + $stmt = \DBManager::get()->prepare('DELETE FROM activities WHERE mkdate < ?'); + + $stmt->execute([ + time() - self::GC_MAX_DAYS * 24 * 60 * 60] + ); + + //Expire Cache + \StudipCacheFactory::getCache()->expire('activity/oldest_activity'); + } + + /** + * Returns the oldest existing activity + * + * @return Array + */ + public static function getOldestActivity() + { + $cache = \StudipCacheFactory::getCache(); + $cache_key = 'activity/oldest_activity'; + + if (!$activity = unserialize($cache->read($cache_key))) { + $activity = self::findBySQL('1 ORDER BY mkdate ASC LIMIT 1'); + + if (!empty($activity)) { + $cache->write($cache_key, serialize($activity)); + } else { + return false; + } + } + + return $activity; + } + + +} diff --git a/lib/activities/ActivityObserver.php b/lib/activities/ActivityObserver.php new file mode 100644 index 0000000..80652cf --- /dev/null +++ b/lib/activities/ActivityObserver.php @@ -0,0 +1,43 @@ +<?php + +/** + * @author André Klaßen <klassen@elan-ev.de> + * @author Till Glöggler <tgloeggl@uos.de> + * @license GPL 2 or later + */ + + +namespace Studip\Activity; + +class ActivityObserver +{ + /** + * Register for Notifications the providers shall respond to + * + */ + public static function initialize() + { + \NotificationCenter::addObserver('Studip\Activity\MessageProvider', 'postActivity','MessageDidSend'); + + // Notifications for ParticipantsProvider + \NotificationCenter::addObserver('\Studip\Activity\ParticipantsProvider', 'postActivity','UserDidEnterCourse'); + \NotificationCenter::addObserver('\Studip\Activity\ParticipantsProvider', 'postActivity','UserDidLeaveCourse'); + + //Notifications for DocumentsProvider + \NotificationCenter::addObserver('\Studip\Activity\DocumentsProvider', 'postActivity','FileRefDidCreate'); + \NotificationCenter::addObserver('\Studip\Activity\DocumentsProvider', 'postActivity','FileRefDidUpdate'); + \NotificationCenter::addObserver('\Studip\Activity\DocumentsProvider', 'postActivity','FileRefDidDelete'); + + //Notifications for NewsProvider + \NotificationCenter::addObserver('\Studip\Activity\NewsProvider', 'postActivity','StudipNewsDidCreate'); + + //Notifications for WikiProvider + \NotificationCenter::addObserver('\Studip\Activity\WikiProvider', 'postActivity','WikiPageDidCreate'); + \NotificationCenter::addObserver('\Studip\Activity\WikiProvider', 'postActivity','WikiPageDidDelete'); + //this is rather pointless and annoying + //\NotificationCenter::addObserver('\Studip\Activity\WikiProvider', 'postActivity','WikiPageDidUpdate'); + + //Notifications for ScheduleProvider (Course) + \NotificationCenter::addObserver('\Studip\Activity\ScheduleProvider', 'postActivity','CourseDidChangeSchedule'); + } +} diff --git a/lib/activities/ActivityProvider.php b/lib/activities/ActivityProvider.php new file mode 100644 index 0000000..16d474a --- /dev/null +++ b/lib/activities/ActivityProvider.php @@ -0,0 +1,25 @@ +<?php + +/** + * @author André Klaßen <klassen@elan-ev.de> + * @author Till Glöggler <tgloeggl@uos.de> + * @license GPL 2 or later + */ + + +namespace Studip\Activity; + +interface ActivityProvider +{ + /** + * Fill in the url, route and any lengthy content for the passed activity + * + * @param Studip\Activity\Activity $activity + */ + public function getActivityDetails($activity); + + /** + * Human readable name for the current provider to be used in the activity-title + */ + public static function getLexicalField(); +} diff --git a/lib/activities/Context.php b/lib/activities/Context.php new file mode 100644 index 0000000..3f84452 --- /dev/null +++ b/lib/activities/Context.php @@ -0,0 +1,124 @@ +<?php + +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +abstract class Context +{ + protected + $provider, + $observer; + + + /** + * return array, listing all active providers in this context + * + * @return array + */ + abstract protected function getProvider(); + + /** + * get id denoting the context (user_id, course_id, institute_id, ...) + * + * @return string + */ + abstract public function getRangeId(); + + /** + * get type of context (f.e. user, system, course, institute, ...) + * + * @return string + */ + abstract public function getContextType(); + + /** + * get type of context (f.e. user, system, course, institute, ...) + * + * @return string + */ + abstract public function getContextFullname($format = 'default'); + + /** + * Return user for who wants to watch his and related activities + * + * @return object a user object + */ + public function getObserver() + { + return $this->observer; + } + + /** + * get list of activities as array for the current context + * + * @param \Studip\Activity\Filter $filter + * + * @return array + */ + public function getActivities(Filter $filter) + { + $providers = $this->filterProvider($this->getProvider(), $filter); + $activities = Activity::findAndMapBySQL( + function ($activity) use ($providers) { + if (isset($providers[$activity->provider])) { // provider is available + $activity->setContextObject($this); + if ($providers[$activity->provider]->getActivityDetails($activity)) { + return $activity; + } + } + }, + 'context = ? AND context_id = ? AND mkdate >= ? AND mkdate <= ? ORDER BY mkdate DESC' + , + [$this->getContextType(), $this->getRangeId(), $filter->getStartDate(), $filter->getEndDate()]); + return array_filter($activities); + } + + /** + * Add a provider to this context + * + * @param string $provider the name for the provider + * @param string $class_name the class that belongs to the provider + */ + protected function addProvider($class_name) + { + $reflectionClass = new \ReflectionClass($class_name); + $this->provider[$class_name] = $reflectionClass->newInstanceArgs(); + } + + /** + * Filter the passed the providers to match the passed filter + * + * @param type $providers + * @param \Studip\Activity\Filter $filter + * @return type + */ + protected function filterProvider($providers, Filter $filter) + { + $filtered_providers = []; + + if (empty($filter->getType())) { + $filtered_providers = $providers; + } else { + foreach ($providers as $provider) { + $ctype = $this->getContextType(); + $filtered_classes = $filter->getType()->$ctype; + + if (is_array($filtered_classes)) { + foreach ($filtered_classes as $class) { + $iclass = 'Studip\\Activity\\' .ucfirst($class) .'Provider'; + if ($provider instanceof $iclass) { + $filtered_providers[$iclass] = $provider; + } + } + } + } + } + + return $filtered_providers; + } +} diff --git a/lib/activities/CourseContext.php b/lib/activities/CourseContext.php new file mode 100644 index 0000000..3822847 --- /dev/null +++ b/lib/activities/CourseContext.php @@ -0,0 +1,83 @@ +<?php + +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class CourseContext extends Context +{ + private + $course; + + /** + * create new course-context + * + * @param string $seminar_id + */ + function __construct($course, $observer) + { + $this->course = $course; + $this->observer = $observer; + } + + /** + * {@inheritdoc} + */ + protected function getProvider() + { + if (!$this->provider) { + $course = $this->course; + + $module_provider = [ + 'CoreForum' => 'ForumProvider', + 'CoreParticipants' => 'ParticipantsProvider', + 'CoreDocuments' => 'DocumentsProvider', + 'CoreWiki' => 'WikiProvider', + 'CoreSchedule' => 'ScheduleProvider' + ]; + + foreach ($course->tools as $tool) { + $studip_module = $tool->getStudipModule(); + if($studip_module) { + if (isset($module_provider[get_class($studip_module)])) { + $this->addProvider('Studip\Activity\\'. $module_provider[get_class($studip_module)]); + } elseif ($studip_module instanceof ActivityProvider) { + $this->provider[$studip_module->getPluginName()] = $studip_module; + } + } + } + //news + $this->addProvider('Studip\Activity\NewsProvider'); + } + + return $this->provider; + } + + /** + * {@inheritdoc} + */ + public function getRangeId() + { + return $this->course->id; + } + + /** + * {@inheritdoc} + */ + public function getContextType() + { + return \Context::COURSE; + } + + /** + * {@inheritdoc} + */ + public function getContextFullname($format = 'default') + { + return $this->course->getFullname($format); + } +} diff --git a/lib/activities/CoursewareContext.php b/lib/activities/CoursewareContext.php new file mode 100755 index 0000000..a7bd83b --- /dev/null +++ b/lib/activities/CoursewareContext.php @@ -0,0 +1,56 @@ +<?php + +namespace Studip\Activity; + +class CoursewareContext extends Context +{ + + public function __construct($courseware, $observer) + { + $this->courseware = $courseware; + $this->observer = $observer; + + $id = explode('_' , $this->courseware->id); + $this->context = $id[0]; + $this->range_id = $id[1]; + } + + protected function getProvider() + { + $this->addProvider('Studip\Activity\CoursewareProvider'); + + return $this->provider; + } + + public function getRangeId() + { + return $this->range_id; + } + + public function getContextType() + { + if($this->context == 'user') { + return \Context::USER; + } + + if ($this->content == 'course') { + return \Context::COURSE; + } + } + + public function getContextFullname($format = 'default') + { + if($this->context == 'user') { + $user = \User::find($this->range_id); + + return $user->getFullname($format); + } + + if($this->context == 'course') { + $course = \Course::find($this->range_id); + + return $course->getFullname($format); + } + } + +}
\ No newline at end of file diff --git a/lib/activities/CoursewareProvider.php b/lib/activities/CoursewareProvider.php new file mode 100755 index 0000000..5e5faae --- /dev/null +++ b/lib/activities/CoursewareProvider.php @@ -0,0 +1,38 @@ +<?php + +namespace Studip\Activity; + + +class CoursewareProvider implements ActivityProvider +{ + + public function getActivityDetails($activity) + { + $structural_element = \Courseware\StructuralElement::find($activity->object_id); + if (!$structural_element) { + return false; + } + $payload = json_decode($structural_element['payload']); + + $activity->content = formatReady($payload['description']); + + if ($activity->context == "course") { + $url = \URLHelper::getURL('dispatch.php/course/courseware/?cid='). $activity->context_id . '#/structural_element/' . $structural_element->id; + $activity->object_url = [ + $url => _('Zur Courseware in der Veranstaltung') + ]; + } elseif ($activity->context == "user") { + $url = \URLHelper::getURL('dispatch.php/contents/my_contents'). '#/structural_element/' . $structural_element->id; + $activity->object_url = [ + $url => _('Zur eigenen Courseware') + ]; + } + + return true; + } + + public static function getLexicalField() + { + return _('eine Courseware-Aktivität'); + } +}
\ No newline at end of file diff --git a/lib/activities/DocumentsProvider.php b/lib/activities/DocumentsProvider.php new file mode 100644 index 0000000..0a770de --- /dev/null +++ b/lib/activities/DocumentsProvider.php @@ -0,0 +1,131 @@ +<?php + +/** + * @author André Klaßen <klassen@elan-ev.de> + * @author Till Glöggler <tgloeggl@uos.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class DocumentsProvider implements ActivityProvider +{ + + /** + * get the details for the passed activity + * + * @param object $activity the activity to fill with details, passed by reference + */ + public function getActivityDetails($activity) + { + $activity->content = \htmlReady($activity->content); + + $document = \FileRef::find($activity->object_id); + + // check, if current observer has access to document + if (!$document || !$activity->getContextObject() || !$document->folder->getTypedFolder()->isFileDownloadable($document, $activity->getContextObject()->getObserver()->id)) { + return false; + } + + if ($activity->context == "course") { + $url = \URLHelper::getUrl("dispatch.php/course/files/flat?cid={$activity->context_id}"); + $route = \URLHelper::getURL('api.php/file/' . $activity->object_id, NULL, true); + + $activity->object_url = [ + $url => _('Zum Dateibereich der Veranstaltung') + ]; + } elseif ($activity->context == "institute") { + $url = \URLHelper::getUrl("dispatch.php/institute/files/flat?cid={$activity->context_id}"); + $route= null; + + $activity->object_url = [ + $url => _('Zum Dateibereich der Einrichtung') + ]; + } + + $activity->object_route = $route; + + return true; + } + + /** + * posts an activity for a given notification event + * + * @param String $event a notification for an activity + * @param \FileRef $document information which a relevant for the activity + */ + public static function postActivity($event, $file_ref) + { + $user_id = $file_ref->user_id; + $file_name = $file_ref->name; + $course_id = $file_ref->folder->range_id; + $file_id = $file_ref->id; + + $type = $file_ref->folder->range_type; + if ($type == 'course') { + $course = \Course::find($course_id); + } elseif ($type == 'institute') { + $course = \Institute::find($course_id); + } + + if (!isset($course)) { + return; + } + + if (in_array($event, ['FileRefDidCreate'])) { + $verb = 'created'; + if ($type == 'course') { + $summary = _('Die Datei %s wurde von %s in der Veranstaltung "%s" hochgeladen.'); + } else { + $summary = _('Die Datei %s wurde von %s in der Einrichtung "%s" hochgeladen.'); + } + $summary = sprintf($summary,$file_name, get_fullname($user_id) ,$course->name); + $mkdate = $file_ref->mkdate; + } elseif (in_array($event, ['FileRefDidUpdate'])) { + $verb = 'edited'; + if ($type == 'course') { + $summary = _('Die Datei %s wurde von %s in der Veranstaltung "%s" aktualisiert.'); + } else { + $summary = _('Die Datei %s wurde von %s in der Einrichtung "%s" aktualisiert.'); + } + $summary = sprintf($summary,$file_name, get_fullname($user_id), $course->name); + $mkdate = $file_ref->chdate; + } elseif (in_array($event, ['FileRefDidDelete'])) { + $verb = 'voided'; + if ($type == 'course') { + $summary = _('Die Datei %s wurde von %s in der Veranstaltung "%s" gelöscht.'); + } else { + $summary = _('Die Datei %s wurde von %s in der Einrichtung "%s" gelöscht.'); + } + $summary = sprintf($summary,$file_name, get_fullname($user_id), $course->name); + $mkdate = $file_ref->chdate; + } else { + return; + } + + if (isset($verb)) { + $activity = Activity::create( + [ + 'provider' => __CLASS__, + 'context' => $type, + 'context_id' => $course_id, + 'content' => $summary, + 'actor_type' => 'user', // who initiated the activity? + 'actor_id' => $user_id, // id of initiator + 'verb' => $verb, // the activity type + 'object_id' => $file_id, // the id of the referenced object + 'object_type' => 'documents', // type of activity object + 'mkdate' => $mkdate + ] + ); + } + } + + /** + * {@inheritdoc} + */ + public static function getLexicalField() + { + return _('eine Datei'); + } +} diff --git a/lib/activities/Filter.php b/lib/activities/Filter.php new file mode 100644 index 0000000..8e1947e --- /dev/null +++ b/lib/activities/Filter.php @@ -0,0 +1,89 @@ +<?php +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class Filter +{ + private + $start_date, + $end_date, + $type, + $verb; + + /** + * + * @param string $start_date + */ + public function setStartDate($start_date) + { + $this->start_date = $start_date; + } + + /** + * + * @return string + */ + public function getStartDate() + { + return $this->start_date; + } + + /** + * + * @param string $end_date + */ + public function setEndDate($end_date) + { + $this->end_date = $end_date; + } + + /** + * + * @return string + */ + public function getEndDate() + { + return $this->end_date; + } + + /** + * + * @param string $type + */ + public function setType($type) + { + $this->type = $type; + } + + /** + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * + * @return string + */ + public function getVerb() + { + return $this->verb; + } + + /** + * + * @param string $verb + */ + public function setVerb($verb) + { + $this->verb = $verb; + } +} diff --git a/lib/activities/ForumProvider.php b/lib/activities/ForumProvider.php new file mode 100644 index 0000000..49260c3 --- /dev/null +++ b/lib/activities/ForumProvider.php @@ -0,0 +1,54 @@ +<?php + +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later3 + */ + + +namespace Studip\Activity; + +require_once 'public/plugins_packages/core/Forum/models/ForumEntry.php'; + +class ForumProvider implements ActivityProvider +{ + /** + * get the details for the passed activity + * + * @param object $activity the activity to fill with details, passed by reference + */ + public function getActivityDetails($activity) + { + $post = \ForumEntry::getEntry($activity->object_id); + + if (!$post) { + return false; + } + + $activity->content = formatReady($post['content']); + + $url = \PluginEngine::getURL('CoreForum', [], 'index/index/' . $post['topic_id'] + .'?cid='. $post['seminar_id'] .'&highlight_topic='. $post['topic_id'] + .'#'. $post['topic_id']); + + $route = \URLHelper::getURL('api.php/forum_entry/' . $post['topic_id'], NULL, true); + + $activity->object_url = [ + $url => _('Zum Forum der Veranstaltung') + ]; + + $activity->object_route = $route; + + return true; + } + + /** + * {@inheritdoc} + */ + public static function getLexicalField() + { + return _('einen Forenbeitrag'); + } + +} diff --git a/lib/activities/InstituteContext.php b/lib/activities/InstituteContext.php new file mode 100644 index 0000000..9b7d5c4 --- /dev/null +++ b/lib/activities/InstituteContext.php @@ -0,0 +1,80 @@ +<?php + +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class InstituteContext extends Context +{ + private $institute; + + /** + * create new institute-context + * + * @param string $institute_id + */ + public function __construct($institute, $observer) + { + $this->institute = $institute; + $this->observer = $observer; + } + + /** + * {@inheritdoc} + */ + protected function getProvider() + { + if (!$this->provider) { + $institute = $this->institute; + + $module_provider = [ + 'CoreForum' => 'ForumProvider', + 'CoreDocuments' => 'DocumentsProvider', + 'CoreWiki' => 'WikiProvider', + ]; + + foreach ($institute->tools as $tool) { + $studip_module = $tool->getStudipModule(); + if($studip_module) { + if (isset($module_provider[get_class($studip_module)])) { + $this->addProvider('Studip\Activity\\'. $module_provider[get_class($studip_module)]); + } elseif ($studip_module instanceof ActivityProvider) { + $this->provider[$studip_module->getPluginName()] = $studip_module; + } + } + } + //news + $this->addProvider('Studip\Activity\NewsProvider'); + } + + return $this->provider; + } + + /** + * {@inheritdoc} + */ + public function getRangeId() + { + return $this->institute->id; + } + + /** + * {@inheritdoc} + */ + public function getContextType() + { + return \Context::INSTITUTE; + } + + /** + * {@inheritdoc} + */ + public function getContextFullname($format = 'default') + { + return $this->institute->getFullname($format); + } +} diff --git a/lib/activities/MessageProvider.php b/lib/activities/MessageProvider.php new file mode 100644 index 0000000..9d16831 --- /dev/null +++ b/lib/activities/MessageProvider.php @@ -0,0 +1,82 @@ +<?php + +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class MessageProvider implements ActivityProvider +{ + /** + * get the details for the passed activity + * + * @param object $activity the acitivty to fill with details, passed by reference + */ + public function getActivityDetails($activity) + { + $message = \Message::find($activity->object_id); + + if (!$message + || !$activity->getContextObject() + || !$message->permissionToRead($activity->getContextObject()->getObserver()->id)) + { + return false; + } + + $activity->content = formatReady($message->message); + + $url = \URLHelper::getUrl("dispatch.php/messages/read/{$message->id}", ['cid' => null]); + + $route = \URLHelper::getURL('api.php/message/' . $message->id, NULL, true); + + $activity->object_url = [ + $url => _('Zur Nachricht') + ]; + + $activity->object_route = $route; + + return true; + } + + + /** + * posts an activity for a given notification event + * + * @param String $event a notification for an activity + * @param Array $info information which a relevant for the activity + */ + public static function postActivity($event, $message_id, $data) + { + foreach ($data['rec_id'] as $rec_id) { + + // activity for receipent + $activity = Activity::create( + [ + 'provider' => __CLASS__, + 'context' => 'user', + 'context_id' => $rec_id, + 'content' => NULL, + 'actor_type' => 'user', // who initiated the activity? + 'actor_id' => $data['user_id'], // id of initiator + 'verb' => 'sent', // the activity type + 'object_id' => $message_id, // the id of the referenced object + 'object_type' => 'message', // type of activity object + 'mkdate' => time() + ] + ); + } + + } + + /** + * {@inheritdoc} + */ + public static function getLexicalField() + { + return _('eine Nachricht'); + } + +} diff --git a/lib/activities/NewsProvider.php b/lib/activities/NewsProvider.php new file mode 100644 index 0000000..eed7fe7 --- /dev/null +++ b/lib/activities/NewsProvider.php @@ -0,0 +1,134 @@ +<?php + +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class NewsProvider implements ActivityProvider +{ + private function getUrlForContext($news, $activity) + { + switch ($activity->context) { + case 'course': + return [ + \URLHelper::getUrl('dispatch.php/course/overview/?cid=' . $activity->context_id . '&contentbox_type=news&contentbox_open=' . $activity->object_id) => _('Ankündigungen in der Veranstaltung') + ]; + break; + + case 'institute': + return [ + \URLHelper::getUrl('dispatch.php/institute/overview?auswahl=' . $activity->context_id) => _('Ankündigungen in der Einrichtung') + ]; + break; + + case 'system': + return [ + \URLHelper::getUrl('dispatch.php/start?contentbox_type=news&contentbox_open='. $news->getId() .'#'. $news->getId()) => _('Ankündigungen auf der Startseite') + ]; + break; + + case 'user': + return [ + \URLHelper::getUrl('dispatch.php/profile/?username='. get_username($activity->context_id) + . '&contentbox_type=news&contentbox_open='. $news->getId() .'#'. $news->getId()) => _('Ankündigungen auf der Profilseite') + ]; + break; + } + } + + /** + * posts an activity for a given notification event + * + * @param String $event a notification for an activity + * @param String $news + */ + public static function postActivity($event, $news) + { + // delete any old activities for this id + $activities = Activity::findBySql('object_id = ?', [$news->id]); + + foreach ($activities as $activity) { + $activity->delete(); + } + + $mkdate = time(); + + // iterate over every news-range and create approbriate activity + foreach ($news->news_ranges as $range) { + $context_id = $range->range_id; + + switch ($range->type) { + case 'user': + $context = 'user'; + break; + case 'inst': + case 'fak': + $context = 'institute'; + break; + case 'sem': + $context = 'course'; + break; + case 'global': + $context = 'system'; + $context_id = 'system'; + break; + } + if (isset($context)) { + $activity = Activity::create( + [ + 'provider' => __CLASS__, + 'context' => $context, + 'context_id' => $context_id, + 'content' => null, + 'actor_type' => 'user', // who initiated the activity? + 'actor_id' => $news->user_id, // id of initiator + 'verb' => 'created', // the activity type + 'object_id' => $news->id, // the id of the referenced object + 'object_type' => 'news', // type of activity object + 'mkdate' => $mkdate + ] + ); + } + + } + } + + + /** + * get the details for the passed activity + * + * @param object $activity the activity to fill with details, passed by reference + */ + public function getActivityDetails($activity) + { + $news = new \StudipNews($activity->object_id); + + // do not show unpublished news + if ($news->date > time()) { + return false; + } + + $activity->content = '<b>' . htmlReady((string) $news->topic) + .'</b><br>'. formatReady((string) $news->body); + + $url = self::getUrlForContext($news, $activity); + $route = \URLHelper::getURL('api.php/news/' . $news->id, NULL, true); + + $activity->object_url = $url; + $activity->object_route = $route; + + return true; + } + /** + * {@inheritdoc} + */ + public static function getLexicalField() + { + return _('eine Ankündigung'); + } + +} diff --git a/lib/activities/ParticipantsProvider.php b/lib/activities/ParticipantsProvider.php new file mode 100644 index 0000000..50bad46 --- /dev/null +++ b/lib/activities/ParticipantsProvider.php @@ -0,0 +1,84 @@ +<?php + +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class ParticipantsProvider implements ActivityProvider +{ + + /** + * posts an activity for a given notification event + * + * @param String $event a notification for an activity + * @param String $course_id + * @param String $user_id + */ + public static function postActivity($event, $course_id, $user_id) + { + $course = \Course::find($course_id); + + if ($event == 'UserDidEnterCourse') { + $verb = 'created'; + $summary = _('%s wurde in die Veranstaltung "%s" eingetragen.'); + $summary = sprintf($summary, get_fullname($user_id), $course->name); + } elseif ($event == 'UserDidLeaveCourse') { + $verb = 'voided'; + $summary = _('%s wurde aus der Veranstaltung "%s" ausgetragen.'); + $summary = sprintf($summary, get_fullname($user_id), $course->name); + } + + $type = get_object_type($course_id); + + $activity = Activity::create( + [ + 'provider' => __CLASS__, + 'context' => ($type == 'sem') ? 'course' : 'institute', + 'context_id' => $course_id, + 'content' => $summary, + 'actor_type' => 'user', // who initiated the activity? + 'actor_id' => $GLOBALS['user']->id, // id of initiator + 'verb' => $verb, // the activity type + 'object_id' => $course_id, // the id of the referenced object + 'object_type' => 'participants', // type of activity object + 'mkdate' => time(), + ] + ); + + } + + /** + * get the details for the passed activity + * + * @param object $activity the activity to fill with details, passed by reference + */ + public function getActivityDetails($activity) + { + $activity->content = htmlReady($activity->content); + + $url = \URLHelper::getUrl("dispatch.php/course/members/index", ['cid' => $activity->context_id]); + + $route = \URLHelper::getURL('api.php/course/' . $activity->context_id, NULL, true); + + $activity->object_url = [ + $url => _('Zur Veranstaltung') + ]; + + $activity->object_route = $route; + + return true; + } + + /** + * {@inheritdoc} + */ + public static function getLexicalField() + { + return _('eine/n Teilnehmer/in'); + } + +} diff --git a/lib/activities/ScheduleProvider.php b/lib/activities/ScheduleProvider.php new file mode 100644 index 0000000..208b9b3 --- /dev/null +++ b/lib/activities/ScheduleProvider.php @@ -0,0 +1,83 @@ +<?php + +/** + * @author André Klaßen <klassen@elan-ev.de> + * @author Till Glöggler <tgloeggl@uos.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class ScheduleProvider implements ActivityProvider +{ + /** + * get the details for the passed activity + * + * @param object $activity the activity to fill with details, passed by reference + */ + public function getActivityDetails($activity) + { + $activity->content = htmlReady($activity->content); + + $url = \URLHelper::getUrl("dispatch.php/course/dates?cid={$activity->context_id}"); + $route = \URLHelper::getURL('api.php/course/' . $activity->context_id . '/events', NULL, true); + + $activity->object_url = [ + $url => _('Zum Ablaufplan der Veranstaltung') + ]; + + $activity->object_route = $route; + + return true; + } + + /** + * posts an activity for a given notification event + * + * @param String $event a notification for an activity + * @param Array $sem Seminar-class for the notification + */ + public static function postActivity($event, $sem) + { + $range_id = $sem->getId(); + + $type = get_object_type($range_id); + if ($type == 'sem') { + $course = \Course::find($range_id); + } + + $user_id = $GLOBALS['user']->id; + $mkdate = time(); + + if ($event == 'CourseDidChangeSchedule') { + $verb = 'edited'; + $summary = _('Der Ablaufplan wurde in der Veranstaltung "%s" von %s aktualisiert.'); + $summary = sprintf($summary, $course->name, get_fullname($user_id)); + } + + $activity = Activity::create( + [ + 'provider' => __CLASS__, + 'context' => ($type == 'sem') ? 'course' : 'institute', + 'context_id' => $range_id, + 'content' => $summary, + 'actor_type' => 'user', // who initiated the activity? + 'actor_id' => $user_id, // id of initiator + 'verb' => $verb, // the activity type + 'object_id' => $range_id, // the id of the referenced object + 'object_type' => 'schedule', // type of activity object + 'mkdate' => $mkdate + ] + ); + + } + + /** + * {@inheritdoc} + */ + public static function getLexicalField() + { + return _('einen Eintrag im Ablaufplan'); + } + +} diff --git a/lib/activities/Stream.php b/lib/activities/Stream.php new file mode 100644 index 0000000..27daf76 --- /dev/null +++ b/lib/activities/Stream.php @@ -0,0 +1,190 @@ +<?php + +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class Stream implements \ArrayAccess, \Countable, \IteratorAggregate +{ + private $activities; + + /** + * creates a stream representing the activities for the passed contexts, + * filter by time (if any) + * + * @param array $contexts All contexts that need to be considered + * @param \Studip\Activity\Filter $filter + * + * @throws \InvalidArgumentException + */ + public function __construct($contexts, Filter $filter) + { + if (!is_array($contexts)) { + $contexts = [$contexts]; + } + + foreach ($contexts as $context) { + if (!$context instanceof Context) { + throw new \InvalidArgumentException(); + } + } + + if (!$filter instanceof Filter) { + throw new \InvalidArgumentException(); + } + + //fetch avaible contextes in given timespan + $available_contexts = \DBManager::get()->fetchGroupedPairs( + "SELECT DISTINCT context,context_id FROM activities WHERE mkdate BETWEEN ? AND ?", + [$filter->getStartDate(), $filter->getEndDate()]); + + //fetch activities only for contextes with known activities + $activities = array_flatten(array_values(array_filter(array_map( + function ($context) use ($filter, $available_contexts) { + if (isset($available_contexts[$context->getContextType()]) + && in_array($context->getRangeId(), $available_contexts[$context->getContextType()])) { + return $context->getActivities($filter); + } + }, $contexts)) + )); + + $new_activities = []; + + foreach ($activities as $activity) { + // generate an id for the activity, considering some basic object parameters + $id = md5($activity->provider . $activity->content . + $activity->verb . $activity->object_type . $activity->mkdate); + + if ($new_activities[$id]) { + $url = key($activity->object_url); + $name = current($activity->object_url); + next($activity->object_url); + $new_activities[$id]->addUrl($url, $name); + } else { + $new_activities[$id] = $activity; + } + } + + // sort activites by their mkdate + usort($new_activities, function($a, $b) { + return $b->mkdate - $a->mkdate; + }); + + $this->activities = $new_activities; + } + + /** + * ArrayAccess: Check whether the given offset exists. + */ + public function offsetExists($offset) + { + return isset($this->activities[$offset]); + } + + /** + * ArrayAccess: Get the value at the given offset. + */ + public function offsetGet($offset) + { + return $this->activities[$offset]; + } + + /** + * ArrayAccess: Set the value at the given offset. + */ + public function offsetSet($offset, $value) + { + $this->activities[$offset] = $value; + } + + /** + * ArrayAccess: unset the value at the given offset (not applicable) + */ + public function offsetUnset($offset) + { + unset($this->activities[$offset]); + } + + /** + * IteratorAggregate + */ + public function getIterator() + { + return new \ArrayIterator($this->activities); + } + + /** + * Countable + */ + public function count() + { + return sizeof($this->activities); + } + + /** + * return representation of the current stream as an array + * + * @return array + */ + public function toArray() + { + $activities = []; + + foreach ($this as $key => $activity) { + $activities[$key] = $activity->toArray(); + + // add i18n auto generated title prefix + $title = ''; + + // $class = '\\Studip\\Activity\\' . ucfirst($activity->provider) . 'Provider'; + $class = $activity->provider; + $object_text = $class::getLexicalField(); + + if (in_array($activity->actor_id, ['____%system%____', 'system']) !== false) { + $actor = _('Stud.IP'); + } elseif ($activity->actor_type === 'anonymous') { + $actor = _('Anonym'); + } else { + $actor = get_fullname($activity->actor_id); + } + $context_name = $activity->getContextObject()->getContextFullname(); + + switch ($activity->context) { + case 'course': + $title = $actor .' ' + . sprintf($activity->verbToText(), + $object_text . sprintf(_(' im Kurs "%s"'), $context_name) + ); + break; + + case 'institute': + $title = $actor .' ' + . sprintf($activity->verbToText(), + $object_text . sprintf(_(' in der Einrichtung "%s"'), $context_name) + ); + break; + + case 'system': + $title = $actor .' ' + . sprintf($activity->verbToText(), _('allen')) .' ' + . $object_text; + break; + + case 'user': + $title = $actor .' ' + . sprintf($activity->verbToText(), $context_name) .' ' + . $object_text; + break; + + } + + $activities[$key]['title'] = $title; + } + + return $activities; + } +} diff --git a/lib/activities/SystemContext.php b/lib/activities/SystemContext.php new file mode 100644 index 0000000..ddc5872 --- /dev/null +++ b/lib/activities/SystemContext.php @@ -0,0 +1,56 @@ +<?php + +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class SystemContext extends Context +{ + /** + * create new user-context + * + * @param string $user_id + */ + public function __construct($observer) + { + $this->observer = $observer; + } + + /** + * {@inheritdoc} + */ + protected function getProvider() + { + $this->addProvider('Studip\Activity\NewsProvider'); + + return $this->provider; + } + + /** + * {@inheritdoc} + */ + public function getRangeId() + { + return 'system'; + } + + /** + * {@inheritdoc} + */ + public function getContextType() + { + return 'system'; + } + + /** + * {@inheritdoc} + */ + public function getContextFullname($format = 'default') + { + return _('Stud.IP'); + } +} diff --git a/lib/activities/UserContext.php b/lib/activities/UserContext.php new file mode 100644 index 0000000..0ff8921 --- /dev/null +++ b/lib/activities/UserContext.php @@ -0,0 +1,74 @@ +<?php + +/** + * @author André Klaßen <klassen@elan-ev.de> + * @author Till Glöggler <gloeggler@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class UserContext extends Context +{ + private $user; + + /** + * create new user-context + * + * @param string $user_id + */ + public function __construct($user, $observer) + { + $this->user = $user; + $this->observer = $observer; + } + + /** + * {@inheritdoc} + */ + public function getRangeId() + { + return $this->user->id; + } + + /** + * {@inheritdoc} + */ + protected function getProvider() + { + + if (!$this->provider) { + $this->addProvider('Studip\Activity\NewsProvider'); + + if ($this->user->id === $this->observer->id) { + $this->addProvider('Studip\Activity\MessageProvider'); + } + + foreach (\PluginManager::getInstance()->getPlugins(ActivityProvider::class) as $plugin) { + if ($plugin instanceof \HomepagePlugin + && $plugin->isActivated($this->user->id, 'user') + ) { + $this->provider[] = $plugin; + } + } + } + + return $this->provider; + } + + /** + * {@inheritdoc} + */ + public function getContextType() + { + return \Context::USER; + } + + /** + * {@inheritdoc} + */ + public function getContextFullname($format = 'default') + { + return $this->user->getFullname($format); + } +} diff --git a/lib/activities/WikiProvider.php b/lib/activities/WikiProvider.php new file mode 100644 index 0000000..e412279 --- /dev/null +++ b/lib/activities/WikiProvider.php @@ -0,0 +1,125 @@ +<?php + +/** + * @author Till Glöggler <tgloeggl@uos.de> + * @author André Klaßen <klassen@elan-ev.de> + * @license GPL 2 or later + */ + +namespace Studip\Activity; + +class WikiProvider implements ActivityProvider +{ + /** + * get the details for the passed activity + * + * @param object $activity the activity to fill with details, passed by reference + */ + public function getActivityDetails($activity) + { + // Check visibility of wiki page + $page = \WikiPage::findLatestPage($activity->context_id, $activity->object_id); + if ($page && !$page->isVisibleTo($GLOBALS['user'])) { + return false; + } + + $activity->content = \htmlReady($activity->content); + + if ($activity->context === 'course') { + $url = \URLHelper::getURL('wiki.php', ['cid' => $activity->context_id, 'keyword' => $activity->object_id]); + $route = \URLHelper::getURL("api.php/course/{$activity->context_id}/wiki/{$activity->object_id}", null, true); + + $activity->object_url = [ + $url => _('Zum Wiki der Veranstaltung'), + ]; + + $activity->object_route = $route; + + } elseif ($activity->context === 'institute') { + $url = \URLHelper::getURL('wiki.php', ['cid' => $activity->context_id, 'keyword' => $activity->object_id]); + $route= null; + + $activity->object_url = [ + $url => _('Zum Wiki der Einrichtung') + ]; + + $activity->object_route = $route; + } + + return true; + } + + /** + * posts an activity for a given notification event + * + * @param String $event a notification for an activity + * @param \WikiPage $info information which a relevant for the activity + */ + public static function postActivity($event, $info) + { + $range_id = $info['range_id']; + $keyword = $info['keyword']; + + $type = get_object_type($range_id); + if ($type === 'sem') { + $course = \Course::find($range_id); + } else { + $course = \Institute::find($range_id); + } + + $user_id = $GLOBALS['user']->id; + $mkdate = time(); + + + if ($event === 'WikiPageDidCreate' && $info['version'] > 1) { + $event = 'WikiPageDidUpdate'; + } + + if ($event === 'WikiPageDidCreate') { + $verb = 'created'; + if ($type === 'sem') { + $summary = _('Die Wiki-Seite %s wurde von %s in der Veranstaltung "%s" angelegt.'); + } else { + $summary = _('Die Wiki-Seite %s wurde von %s in der Einrichtung "%s" angelegt.'); + } + } elseif ($event === 'WikiPageDidUpdate') { + $verb = 'edited'; + if ($type === 'sem') { + $summary = _('Die Wiki-Seite %s wurde von %s in der Veranstaltung "%s" aktualisiert.'); + } else { + $summary = _('Die Wiki-Seite %s wurde von %s in der Einrichtung "%s" aktualisiert.'); + } + } elseif ($event === 'WikiPageDidDelete') { + $verb = 'voided'; + if ($type === 'sem') { + $summary = _('Die Wiki-Seite %s wurde von %s in der Veranstaltung "%s" gelöscht.'); + } else { + $summary = _('Die Wiki-Seite %s wurde von %s in der Einrichtung "%s" gelöscht.'); + } + } + + $summary = sprintf($summary, $keyword, get_fullname($user_id), $course->name); + + $activity = Activity::create([ + 'provider' => __CLASS__, + 'context' => $type === 'sem' ? 'course' : 'institute', + 'context_id' => $range_id, + 'content' => $summary, + 'actor_type' => 'user', // who initiated the activity? + 'actor_id' => $user_id, // id of initiator + 'verb' => $verb, // the activity type + 'object_id' => $keyword, // the id of the referenced object + 'object_type' => 'wiki', // type of activity object + 'mkdate' => $mkdate, + ]); + + } + + /** + * {@inheritdoc} + */ + public static function getLexicalField() + { + return _('eine Wiki-Seite'); + } +} |
