From 7f42e9b4ed83d2d512d6394d8451cbc24a2baf7f Mon Sep 17 00:00:00 2001 From: st143971 Date: Sat, 23 Dec 2023 05:33:08 +0100 Subject: [PATCH 1/2] Reworked the slide search context selection. Should now work as intended for webservice. Now categorized under name (book) instead of name.pdf. --- externallib.php | 67 ++++++++++++++++++-------------- lang/de/block_slidefinder.php | 1 + lang/en/block_slidefinder.php | 1 + locallib.php | 2 +- templates/lrf_search.mustache | 72 ++++++++++++++++++++++------------- version.php | 2 +- 6 files changed, 88 insertions(+), 57 deletions(-) diff --git a/externallib.php b/externallib.php index e718fc3..8f1089e 100644 --- a/externallib.php +++ b/externallib.php @@ -67,7 +67,7 @@ public static function get_searched_locations_parameters() { * @param int $userid id of the user who initiates the search * @param int $courseid id of the course to search in * @param string $searchstring the string to search for - * @param int $contextlength the size of the context snippet on each side of the found $seach_string occurences in words + * @param int $contextlength the size of the context snippet on each side of the found $search_string occurences in words * * @return string json encoded array of arrays holding the 'filename', 'page_number', 'book_chapter_url' and 'context' * of each chapter/pdf-page the $searchterm was found @@ -88,8 +88,6 @@ public static function get_searched_locations($userid, $courseid, $searchstring, ) ); - $transaction = $DB->start_delegated_transaction(); - try { // User. if (!$user = $DB->get_record('user', array('id' => $userid))) { @@ -151,46 +149,59 @@ public static function get_searched_locations_returns() { * @return stdClass|null the given $page object with the additional $page->context or null if nothing was found */ private static function search_content($page, $searchterm, $contextlength) { - $content = ' ' . $page->content . ' '; + $content = $page->content; // Is the searched word in this page? if (!stristr($content, $searchterm)) { return; } - // Create a String with all occurences & context. - $context = ''; + // Split the text into words. + $words = preg_split('/\s+/', $content); + + $snippets = []; + $snippet_index = 0; - $index = self::index_of($content, $searchterm, 0); - $finalendindex = -1; - // For all $searchterm occurances. - while (0 <= $index) { - $startindex = $index; - $tempendindex = $index; + // Iterate through the words to find occurrences of the search word. + // Save the context snippet indices. + for ($i = 0; $i < count($words); $i++) { + if (stristr($words[$i], $searchterm)) { + // Calculate start and end indices for the context + $start = max(0, $i - $contextlength); + $end = min(count($words) - 1, $i + $contextlength); - // Get Context Words. - for ($i = 0; $i < $contextlength; $i++) { - $startindex = self::lastindex_of($content, ' ', $startindex - 1); - $tempendindex = self::index_of($content, ' ', $tempendindex + 1); - if ($tempendindex < $startindex) { - $tempendindex = strlen($content) - 1; + if ($snippet_index > 0 && $start - $snippets[$snippet_index - 1][1] < $contextlength) { + $snippets[$snippet_index - 1][1] = $end; + } else { + $snippets[] = [$start, $end]; + $snippet_index++; } } + } + + // Turn the snippet indices into actual text snippets. + for ($i = 0; $i < count($snippets); $i++) { + [$start, $end] = $snippets[$i]; + // Extract the context around the search word. + $snippet = implode(' ', array_slice($words, $start, $end - $start + 1)); - // Do the contexti have overlap or are they apart? - if ($startindex > $finalendindex) { - $context .= '...'; - $context .= self::substring($content, $startindex, $tempendindex); - } else { - $context .= self::substring($content, $finalendindex + 1, $tempendindex); + // Add "..." at the beginning if not at the start of the text. + if ($start > 0) { + $snippet = '...' . $snippet; } - // Next $searchterm occurance. - $finalendindex = $tempendindex; - $index = self::index_of($content, $searchterm, $index + 1); + // Add "..." at the end if not at the end of the text. + if ($end < count($words) - 1) { + $snippet .= '...'; + } + + // Update snippet with text. + $snippets[$i] = $snippet; } - $context .= '...'; + + // Create a String with all occurences & context. + $context = implode(' ... ', $snippets); $page->context = $context; return $page; diff --git a/lang/de/block_slidefinder.php b/lang/de/block_slidefinder.php index 56799cf..8968c07 100644 --- a/lang/de/block_slidefinder.php +++ b/lang/de/block_slidefinder.php @@ -31,6 +31,7 @@ $string['chapter'] = 'Kapitel'; $string['misconfigured_info'] = 'Die folgenden Dateien sind zwar als übereinstimmend gekennzeichnet, ' . 'wurden aber nicht korrekt eingerichtet. Vielleicht stimmt die Kapitelanzahl zwischen Buch und pdf nicht überein?'; +$string['pdf_replace'] = ' (Buch)'; // Search Field. $string['search'] = 'Suche...'; diff --git a/lang/en/block_slidefinder.php b/lang/en/block_slidefinder.php index 7e1aa7a..39899d7 100644 --- a/lang/en/block_slidefinder.php +++ b/lang/en/block_slidefinder.php @@ -32,6 +32,7 @@ $string['chapter'] = 'Chapter'; $string['misconfigured_info'] = 'The following files are flagged as matching but have not been set up correctly. ' . 'Maybe there is a chapter count mismatch between book and pdf?'; +$string['pdf_replace'] = ' (Book)'; // Search Field. $string['search'] = 'Search keyword...'; diff --git a/locallib.php b/locallib.php index 7b63cc4..f4515d5 100644 --- a/locallib.php +++ b/locallib.php @@ -163,7 +163,7 @@ function block_slidefinder_get_content_as_chapters($match) { for ($i = 0; $i < $pdfdetails['Pages']; $i++) { $chapter = new stdClass(); - $chapter->filename = $match->filename; + $chapter->filename = str_replace('.pdf', get_string('pdf_replace', 'block_slidefinder'), $match->filename); $chapter->section = $match->section; $chapter->page = $i + 1; $chapter->content = $pages[$i]->getText(); diff --git a/templates/lrf_search.mustache b/templates/lrf_search.mustache index a4996f0..c12bc55 100644 --- a/templates/lrf_search.mustache +++ b/templates/lrf_search.mustache @@ -114,38 +114,56 @@ * @param {*} page */ function lrfSearchContent(page) { - var _content = ' ' + page.content.toLowerCase() + ' '; - var _search = lrf_search_term.toLowerCase(); - // Is the searched word in this page? - if (!(_content.includes(_search))) return; - - // Create a String with all occurences & context - var context = ''; - - var index = _content.indexOf(_search); - var index_end = -1; - - while (0 <= index) { - var i_start = index; - var i_end = index; - for (var i = 0; i < context_length; i++) { - i_start = _content.lastIndexOf(' ', i_start - 1); - i_end = _content.indexOf(' ', i_end + 1); - if (i_end < i_start) { - i_end = _content.length; + if (!page.content.toLowerCase().includes(lrf_search_term.toLowerCase())) { + return; + } + + // Split the text into words. + let words = page.content.split(/\s+/); + + let snippets = []; + let snippetIndex = 0; + + // Iterate through the words to find occurrences of the search word. + // Save the context snippet indices. + for (let i = 0; i < words.length; i++) { + if (words[i].toLowerCase().includes(lrf_search_term.toLowerCase())) { + // Calculate start and end indices for the context. + let start = Math.max(0, i - context_length); + let end = Math.min(words.length - 1, i + context_length); + + if (snippetIndex > 0 && start - snippets[snippetIndex - 1][1] < context_length) { + snippets[snippetIndex - 1][1] = end; + } else { + snippets.push([start, end]); + snippetIndex++; } } - if (i_start > index_end) { - context += '...'; - context += page.content.substring(i_start, i_end); - } else { - context += page.content.substring(index_end + 1, i_end); + } + + // Turn the snippet indices into actual text snippets. + for (let i = 0; i < snippets.length; i++) { + let [start, end] = snippets[i]; + // Extract the context around the search word. + let snippet = words.slice(start, end + 1).join(' '); + + // Add "..." at the beginning if not at the start of the text. + if (start > 0) { + snippet = '...' + snippet; + } + + // Add "..." at the end if not at the end of the text. + if (end < words.length - 1) { + snippet += '...'; } - index_end = i_end; - index = _content.indexOf(_search, index + 1); + + // Update snippet with text. + snippets[i] = snippet; } - context += '...'; + + // Create a string with all occurrences & context. + let context = snippets.join(' ... '); page.context = context; if (!(page.filename in lrf_searched_content)) { diff --git a/version.php b/version.php index ee1efd8..ab6bdfd 100644 --- a/version.php +++ b/version.php @@ -23,7 +23,7 @@ */ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2023091801; // The current plugin version (Date: YYYYMMDDHH). +$plugin->version = 2023122310; // The current plugin version (Date: YYYYMMDDHH). $plugin->requires = 2020061510; // Requires this Moodle version. $plugin->component = 'block_slidefinder'; // Full name of the plugin (used for diagnostics). $plugin->release = '1.1.1'; From 3bec8d35eb4cb23419f177601bbc88c35db241e4 Mon Sep 17 00:00:00 2001 From: st143971 Date: Sat, 23 Dec 2023 05:44:09 +0100 Subject: [PATCH 2/2] fixed codesniffer warnings & errors --- block_slidefinder.php | 10 +++++----- db/access.php | 24 ++++++++++++------------ db/services.php | 10 +++++----- externallib.php | 33 ++++++++++++++++----------------- locallib.php | 18 +++++++++--------- 5 files changed, 47 insertions(+), 48 deletions(-) diff --git a/block_slidefinder.php b/block_slidefinder.php index fa57de9..fbb3b08 100644 --- a/block_slidefinder.php +++ b/block_slidefinder.php @@ -70,13 +70,13 @@ public function get_content() { // Restructure (for mustache) the name=>value list into a list of array objects having the name and value attribute. $hiddenparams = array_map(function ($name, $value) { - return array("name" => $name, "value" => $value); + return ["name" => $name, "value" => $value]; }, array_keys($hiddenparams), $hiddenparams); if ($cid == 0) { // My Moodle Page. if ($slidefinderid != 0) { // Course. - if (!$course = $DB->get_record('course', array('id' => $slidefinderid))) { + if (!$course = $DB->get_record('course', ['id' => $slidefinderid])) { throw new moodle_exception(get_string('error_course_not_found', 'block_slidefinder')); } // Does the user have access to the course? @@ -90,11 +90,11 @@ public function get_content() { 'action' => $this->page->url, 'course_selector_param_name' => BLOCK_SLIDEFINDER_SLIDEFINDER_PARAM, 'course_selector_options' => block_slidefinder_select_course_options($slidefinderid), - 'hidden_params' => $hiddenparams + 'hidden_params' => $hiddenparams, ]); } else { // Course Page. // Course. - if (!$course = $DB->get_record('course', array('id' => $cid))) { + if (!$course = $DB->get_record('course', ['id' => $cid])) { throw new moodle_exception(get_string('error_course_not_found', 'block_slidefinder')); } // Does the user have access to the course? @@ -125,7 +125,7 @@ public function get_content() { 'search_term' => $search, 'chapter_label' => get_string('chapter', get_class($this)), 'content' => base64_encode(json_encode($data[0])), - 'hidden_params' => $hiddenparams + 'hidden_params' => $hiddenparams, ]); } catch (\Throwable $th) { debugging($th); diff --git a/db/access.php b/db/access.php index d9bcef3..01c85e3 100644 --- a/db/access.php +++ b/db/access.php @@ -23,22 +23,22 @@ */ defined('MOODLE_INTERNAL') || die(); -$capabilities = array( - 'block/slidefinder:myaddinstance' => array( +$capabilities = [ + 'block/slidefinder:myaddinstance' => [ 'captype' => 'write', 'contextlevel' => CONTEXT_SYSTEM, - 'archetypes' => array( + 'archetypes' => [ 'guest' => CAP_ALLOW, - ), - 'clonepermissionsfrom' => 'moodle/my:manageblocks' - ), + ], + 'clonepermissionsfrom' => 'moodle/my:manageblocks', + ], - 'block/slidefinder:addinstance' => array( + 'block/slidefinder:addinstance' => [ 'captype' => 'write', 'contextlevel' => CONTEXT_BLOCK, - 'archetypes' => array( + 'archetypes' => [ 'user' => CAP_PREVENT, - ), - 'clonepermissionsfrom' => 'moodle/site:manageblocks' - ), -); + ], + 'clonepermissionsfrom' => 'moodle/site:manageblocks', + ], +]; diff --git a/db/services.php b/db/services.php index 4bedcb3..5633fd6 100644 --- a/db/services.php +++ b/db/services.php @@ -23,9 +23,9 @@ */ defined('MOODLE_INTERNAL') || die(); -$functions = array( +$functions = [ // Info: local_PLUGINNAME_FUNCTIONNAME is the name of the web service function that the client will call. - 'block_slidefinder_get_searched_locations' => array( + 'block_slidefinder_get_searched_locations' => [ // Class containing the external function. 'classname' => 'block_slidefinder_external', @@ -55,6 +55,6 @@ // Optional, only available for Moodle 3.1 onwards. // List of built-in services (by shortname) where the function will be included. // Services created manually via the Moodle interface are not supported. - 'services' => array() - ) -); + 'services' => [], + ], +]; diff --git a/externallib.php b/externallib.php index 8f1089e..a901273 100644 --- a/externallib.php +++ b/externallib.php @@ -37,7 +37,7 @@ class block_slidefinder_external extends external_api { */ public static function get_searched_locations_parameters() { return new external_function_parameters( - array( + [ 'userid' => new external_value( PARAM_INT, 'Id of the user using the webservice', @@ -56,8 +56,8 @@ public static function get_searched_locations_parameters() { 'contextlength' => new external_value( PARAM_INT, 'Number of words surrounding the found query word in each direction' - ) - ) + ), + ] ); } @@ -80,21 +80,21 @@ public static function get_searched_locations($userid, $courseid, $searchstring, // Validate parameter. $params = self::validate_parameters( self::get_searched_locations_parameters(), - array( + [ 'userid' => $userid, 'courseid' => $courseid, 'searchstring' => $searchstring, - 'contextlength' => $contextlength - ) + 'contextlength' => $contextlength, + ] ); try { // User. - if (!$user = $DB->get_record('user', array('id' => $userid))) { + if (!$user = $DB->get_record('user', ['id' => $userid])) { throw new moodle_exception(get_string('error_user_not_found', 'block_slidefinder')); } // Course. - if (!$course = $DB->get_record('course', array('id' => $courseid))) { + if (!$course = $DB->get_record('course', ['id' => $courseid])) { throw new moodle_exception(get_string('error_course_not_found', 'block_slidefinder')); } // Does the user have access to the course? @@ -113,7 +113,7 @@ public static function get_searched_locations($userid, $courseid, $searchstring, block_slidefinder_get_content_as_chapters_for_all_book_pdf_matches_from_course($courseid, $userid); // Get Search Results & Context for PDFs. - $results = array(); + $results = []; foreach ($chapters as $chapter) { $result = self::search_content($chapter, $searchstring, $contextlength); if ($result) { @@ -121,7 +121,7 @@ public static function get_searched_locations($userid, $courseid, $searchstring, 'filename' => $result->filename, 'page_number' => $result->page, 'book_chapter_url' => $result->bookurl, - 'context_snippet' => $result->context + 'context_snippet' => $result->context, ]; } } @@ -159,23 +159,22 @@ private static function search_content($page, $searchterm, $contextlength) { // Split the text into words. $words = preg_split('/\s+/', $content); - $snippets = []; - $snippet_index = 0; - + $snippets = []; + $snippetindex = 0; // Iterate through the words to find occurrences of the search word. // Save the context snippet indices. for ($i = 0; $i < count($words); $i++) { if (stristr($words[$i], $searchterm)) { - // Calculate start and end indices for the context + // Calculate start and end indices for the context. $start = max(0, $i - $contextlength); $end = min(count($words) - 1, $i + $contextlength); - if ($snippet_index > 0 && $start - $snippets[$snippet_index - 1][1] < $contextlength) { - $snippets[$snippet_index - 1][1] = $end; + if ($snippetindex > 0 && $start - $snippets[$snippetindex - 1][1] < $contextlength) { + $snippets[$snippetindex - 1][1] = $end; } else { $snippets[] = [$start, $end]; - $snippet_index++; + $snippetindex++; } } } diff --git a/locallib.php b/locallib.php index f4515d5..128cc0f 100644 --- a/locallib.php +++ b/locallib.php @@ -37,12 +37,12 @@ function block_slidefinder_get_content_as_chapters_for_all_book_pdf_matches_from_course($courseid, $userid) { global $DB; - $coursechapters = array(); - $misconfiguredcoursechapters = array(); + $coursechapters = []; + $misconfiguredcoursechapters = []; try { // Course. - if (!$course = $DB->get_record('course', array('id' => $courseid))) { + if (!$course = $DB->get_record('course', ['id' => $courseid])) { throw new moodle_exception(get_string('error_course_not_found', 'block_slidefinder')); } // Does the user have access to the course? @@ -78,7 +78,7 @@ function block_slidefinder_get_content_as_chapters_for_all_book_pdf_matches_from function block_slidefinder_get_all_book_pdf_matches_from_course($course) { // Get all PDFs from course. $fs = get_file_storage(); - $pdfs = array(); + $pdfs = []; foreach (get_all_instances_in_course('resource', $course) as $resource) { // Get all resources. $cm = get_coursemodule_from_instance('resource', $resource->id, $resource->course, false, MUST_EXIST); @@ -113,7 +113,7 @@ function block_slidefinder_get_all_book_pdf_matches_from_course($course) { } // Get all books from course. - $sectionedbooks = array(); + $sectionedbooks = []; $books = get_all_instances_in_course('book', $course); foreach ($books as $book) { $sectionedbooks[$book->section][$book->id] = @@ -121,7 +121,7 @@ function block_slidefinder_get_all_book_pdf_matches_from_course($course) { } // Get all book-PDF matches. - $matches = array(); + $matches = []; foreach ($pdfs as $pdf) { if (!isset($sectionedbooks[$pdf->section])) { continue; @@ -143,7 +143,7 @@ function block_slidefinder_get_all_book_pdf_matches_from_course($course) { * @return array list of objects containing the content and some metadata of one PDF page. */ function block_slidefinder_get_content_as_chapters($match) { - $chapters = array(); + $chapters = []; try { $fs = get_file_storage(); @@ -206,7 +206,7 @@ function block_slidefinder_get_book_chapter_url($bookid, $pagenum) { * @return array Array of courses the current user has access to. Position 1 is either selected course or selection message. */ function block_slidefinder_select_course_options(int $cid = 0) { - $courses = array(); + $courses = []; foreach (get_courses() as $course) { if (can_access_course($course)) { @@ -222,7 +222,7 @@ function block_slidefinder_select_course_options(int $cid = 0) { } } catch (\Throwable $th) { throw $th; - return array(); + return []; } } else { array_unshift($courses, (object)['id' => 0, 'value' => get_string('select_course', 'block_slidefinder')]);