From 06bc1d02d80a4c238682dfedc1b022e66aa1022d Mon Sep 17 00:00:00 2001 From: Ron Lucke Date: Fri, 10 Jun 2022 08:33:42 +0000 Subject: StEP00365: Suchfunktion auf Courswareseiten Merge request studip/studip!636 --- app/controllers/contents/courseware.php | 6 + app/controllers/course/courseware.php | 6 + app/views/course/courseware/search_widget.php | 1 + db/migrations/5.2.11_courseware_search.php | 101 ++++++++++++ .../globalsearch/GlobalSearchCourseware.php | 177 +++++++++++++++++++++ resources/assets/stylesheets/scss/courseware.scss | 10 ++ .../vue/components/courseware/CoursewareRibbon.vue | 13 +- .../courseware/CoursewareSearchResults.vue | 80 ++++++++++ .../courseware/CoursewareSearchWidget.vue | 72 +++++++++ .../courseware/CoursewareStructuralElement.vue | 4 - resources/vue/components/courseware/IndexApp.vue | 20 ++- .../vue/store/courseware/courseware.module.js | 23 +++ 12 files changed, 504 insertions(+), 9 deletions(-) create mode 100644 app/views/course/courseware/search_widget.php create mode 100644 db/migrations/5.2.11_courseware_search.php create mode 100644 lib/classes/globalsearch/GlobalSearchCourseware.php create mode 100644 resources/vue/components/courseware/CoursewareSearchResults.vue create mode 100644 resources/vue/components/courseware/CoursewareSearchWidget.vue diff --git a/app/controllers/contents/courseware.php b/app/controllers/contents/courseware.php index 854f35d..f618d79 100755 --- a/app/controllers/contents/courseware.php +++ b/app/controllers/contents/courseware.php @@ -123,6 +123,12 @@ class Contents_CoursewareController extends AuthenticatedController ); $sidebar->addWidget($actions)->addLayoutCSSClass('courseware-action-widget'); + $views = new TemplateWidget( + _('Suche'), + $this->get_template_factory()->open('course/courseware/search_widget') + ); + $sidebar->addWidget($views)->addLayoutCSSClass('courseware-search-widget'); + $views = new \TemplateWidget( _('Ansichten'), $this->get_template_factory()->open('course/courseware/view_widget') diff --git a/app/controllers/course/courseware.php b/app/controllers/course/courseware.php index 0e609e7..7c41756 100755 --- a/app/controllers/course/courseware.php +++ b/app/controllers/course/courseware.php @@ -117,6 +117,12 @@ class Course_CoursewareController extends AuthenticatedController $sidebar->addWidget($actions)->addLayoutCSSClass('courseware-action-widget'); $views = new TemplateWidget( + _('Suche'), + $this->get_template_factory()->open('course/courseware/search_widget') + ); + $sidebar->addWidget($views)->addLayoutCSSClass('courseware-search-widget'); + + $views = new TemplateWidget( _('Ansichten'), $this->get_template_factory()->open('course/courseware/view_widget') ); diff --git a/app/views/course/courseware/search_widget.php b/app/views/course/courseware/search_widget.php new file mode 100644 index 0000000..6be4ed2 --- /dev/null +++ b/app/views/course/courseware/search_widget.php @@ -0,0 +1 @@ + diff --git a/db/migrations/5.2.11_courseware_search.php b/db/migrations/5.2.11_courseware_search.php new file mode 100644 index 0000000..324770c --- /dev/null +++ b/db/migrations/5.2.11_courseware_search.php @@ -0,0 +1,101 @@ +prepare(" + SELECT * + FROM config + WHERE field = 'GLOBALSEARCH_MODULES' + "); + $statement->execute(); + $config = $statement->fetch(PDO::FETCH_ASSOC); + $config['value'] = json_decode($config['value'], true); + $config['value']['GlobalSearchCourseware'] = [ + 'order' => 14, + 'active' => true, + 'fulltext' => true + ]; + + $statement = DBManager::get()->prepare(" + UPDATE config + SET `value` = :json + WHERE field = 'GLOBALSEARCH_MODULES' + "); + $statement->execute([ + 'json' => json_encode($config['value']) + ]); + + $statement = DBManager::get()->prepare(" + SELECT * + FROM config_values + WHERE field = 'GLOBALSEARCH_MODULES' + "); + $statement->execute(); + $config = $statement->fetch(PDO::FETCH_ASSOC); + if ($config) { + $config['value'] = json_decode($config['value'], true); + $config['value']['GlobalSearchCourseware'] = [ + 'order' => 14, + 'active' => true, + 'fulltext' => true + ]; + + $statement = DBManager::get()->prepare(" + UPDATE config_values + SET `value` = :json + WHERE field = 'GLOBALSEARCH_MODULES' + "); + $statement->execute([ + 'json' => json_encode($config['value']) + ]); + } + } + + public function down() + { + $statement = DBManager::get()->prepare(" + SELECT * + FROM config_values + WHERE field = 'GLOBALSEARCH_MODULES' + "); + $statement->execute(); + $config = $statement->fetch(PDO::FETCH_ASSOC); + if ($config) { + $config['value'] = json_decode($config['value'], true); + unset($config['value']['GlobalSearchCourseware']); + $statement = DBManager::get()->prepare(" + UPDATE config_values + SET `value` = :json + WHERE field = 'GLOBALSEARCH_MODULES' + "); + $statement->execute([ + 'json' => json_encode($config['value']) + ]); + } + + $statement = DBManager::get()->prepare(" + SELECT * + FROM config + WHERE field = 'GLOBALSEARCH_MODULES' + "); + $statement->execute(); + $config = $statement->fetch(PDO::FETCH_ASSOC); + $config['value'] = json_decode($config['value'], true); + unset($config['value']['GlobalSearchCourseware']); + $statement = DBManager::get()->prepare(" + UPDATE config + SET `value` = :json + WHERE field = 'GLOBALSEARCH_MODULES' + "); + $statement->execute([ + 'json' => json_encode($config['value']) + ]); + + } +} diff --git a/lib/classes/globalsearch/GlobalSearchCourseware.php b/lib/classes/globalsearch/GlobalSearchCourseware.php new file mode 100644 index 0000000..9f1f243 --- /dev/null +++ b/lib/classes/globalsearch/GlobalSearchCourseware.php @@ -0,0 +1,177 @@ + + * @category Stud.IP + * @since 5.2 + */ +class GlobalSearchCourseware extends GlobalSearchModule implements GlobalSearchFulltext +{ + /** + * Returns the displayname for this module + * + * @return string + */ + public static function getName() + { + return _('Courseware'); + } + + /** + * Returns the filters that are displayed in the sidebar of the global search. + * + * @return array Filters for this class. + */ + public static function getFilters() + { + return []; + } + + public static function getSQL($search, $filter, $limit) + { + if (!$search) { + return null; + } + + $query = DBManager::get()->quote("%{$search}%"); + if ($filter['rangeId']) { + $range_id = $filter['rangeId']; + $sql = "(SELECT `cw_structural_elements` . `id` AS id, CONCAT('', 'cw_structural_elements') AS type + FROM `cw_structural_elements` + WHERE (`title` LIKE {$query} OR `payload` LIKE {$query}) + AND `range_id` = '{$range_id}' + ORDER BY `cw_structural_elements`.`mkdate` DESC) + UNION ( + SELECT se . `id` AS id, CONCAT('', 'cw_containers') AS type + FROM `cw_containers` c + JOIN cw_structural_elements se + ON se . `id` = c . `structural_element_id` + WHERE c. `payload` LIKE {$query} + AND `container_type` != 'list' + AND se . `range_id` = '{$range_id}' + ORDER BY c . `mkdate` DESC) + UNION ( + SELECT se . `id` AS id, CONCAT('', 'cw_blocks') AS type + FROM `cw_blocks` b + JOIN cw_containers c + ON c.id = b.container_id + JOIN cw_structural_elements se + ON se . `id` = c . `structural_element_id` + WHERE b.payload LIKE {$query} + AND se . `range_id` = '{$range_id}' + ORDER BY b . `mkdate` DESC + ) LIMIT {$limit}"; + } else { + $sql = "(SELECT `cw_structural_elements` . `id` AS id, CONCAT('', 'cw_structural_elements') AS type + FROM `cw_structural_elements` + WHERE (`title` LIKE {$query} OR `payload` LIKE {$query}) + ORDER BY `cw_structural_elements`.`mkdate` DESC) + UNION ( + SELECT se . `id` AS id, CONCAT('', 'cw_containers') AS type + FROM `cw_containers` c + JOIN cw_structural_elements se + ON se . `id` = c . `structural_element_id` + WHERE c. `payload` LIKE {$query} + AND `container_type` != 'list' + ORDER BY c . `mkdate` DESC) + UNION ( + SELECT se . `id` AS id, CONCAT('', 'cw_blocks') AS type + FROM `cw_blocks` b + JOIN cw_containers c + ON c.id = b.container_id + JOIN cw_structural_elements se + ON se . `id` = c . `structural_element_id` + WHERE b.payload LIKE {$query} + ORDER BY b . `mkdate` DESC + ) LIMIT {$limit}"; + } + + return $sql; + } + + public static function filter($data, $search) + { + $user = $GLOBALS['user']; + $structural_element = StructuralElement::find($data['id']); + if ($structural_element->canRead($user)) { + if ($data['type'] === 'cw_structural_elements') { + $description = self::mark($structural_element->payload['description'], $search, true); + } + if ($data['type'] === 'cw_containers') { + $description = _('Suchbegriff wurde in einem Abschnitt gefunden'); + } + if ($data['type'] === 'cw_blocks') { + $description = _('Suchbegriff wurde in einem Block gefunden'); + } + $pageData = self::getPageData($structural_element); + $date = new DateTime(); + $date->setTimestamp($structural_element->chdate); + + return [ + 'name' => self::mark($structural_element->title, $search, true), + 'description' => $description, + 'url' => $pageData['url'], + 'img' => $structural_element->image ? $structural_element->getImageUrl(): null, + 'additional' => '' . $pageData['originName'] . '', + 'date' => $date->format('d.m.Y H:i'), + 'structural-element-id' => $structural_element->id + ]; + } + return []; + } + + private static function getPageData(StructuralElement $structural_element): Array + { + $url = ''; + $originUrl = ''; + $originName = ''; + if ($structural_element->range_type === 'course') { + $url = URLHelper::getURL( + "dispatch.php/course/courseware?cid={$structural_element->range_id}#/structural_element/{$structural_element->id}", + [], + true); + $originUrl = URLHelper::getURL( + "dispatch.php/course/overview?cid={$structural_element->range_id}", + [], + true); + $originName = Course::find($structural_element->range_id)->name; + } + if ($structural_element->range_type === 'user') { + $url = URLHelper::getURL( + "dispatch.php/contents/courseware/courseware#/structural_element/{$structural_element->id}", + [], + true); + $originUrl = URLHelper::getURL( + "dispatch.php/contents/courseware/index", + [], + true); + $originName = _('Persönliche Lernmaterialien'); + } + + return array( + 'url' => $url, + 'originUrl' => $originUrl, + 'originName' => $originName + ); + } + + public static function enable() + { + } + + public static function disable() + { + } + + public static function getSearchURL($searchterm) + { + return null; + } + +} diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss index 4db8ab0..175cbd7 100755 --- a/resources/assets/stylesheets/scss/courseware.scss +++ b/resources/assets/stylesheets/scss/courseware.scss @@ -280,6 +280,10 @@ $consum_ribbon_width: calc(100% - 58px); .cw-ribbon-nav { display: flex; min-width: 75px; + + &.single-icon { + min-width: 45px; + } } .cw-ribbon-breadcrumb { @@ -326,6 +330,12 @@ $consum_ribbon_width: calc(100% - 58px); .cw-ribbon-wrapper-right { display: flex; + + button { + border: none; + background-color: transparent; + cursor: pointer; + } } .cw-ribbon-button { diff --git a/resources/vue/components/courseware/CoursewareRibbon.vue b/resources/vue/components/courseware/CoursewareRibbon.vue index 051ae43..4b18c15 100755 --- a/resources/vue/components/courseware/CoursewareRibbon.vue +++ b/resources/vue/components/courseware/CoursewareRibbon.vue @@ -3,7 +3,7 @@
-
- -
+ - - + + + + +
+