diff --git a/filter/glossary/db/install.php b/filter/glossary/db/install.php new file mode 100644 index 00000000000..76bf66f1bc8 --- /dev/null +++ b/filter/glossary/db/install.php @@ -0,0 +1,40 @@ +. + +/** + * Glossary filter post install hook + * + * @package filter + * @subpackage glossary + * @copyright 2011 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +function xmldb_filter_glossary_install() { + global $DB; + + // If the legacy mod/glossary filter is installed we need to: + // 1- Delete new filter (filter_active and filter_config) information, in order to + // 2- Usurpate the identity of the legacy filter by moving all its + // information to filter/glossary + // If the legacy mod/glossary filter was not installed, no action is needed + if ($DB->record_exists('filter_active', array('filter' => 'mod/glossary'))) { + $DB->delete_records('filter_active', array('filter' => 'filter/glossary')); + $DB->set_field('filter_active', 'filter', 'filter/glossary', array('filter' => 'mod/glossary')); + } +} diff --git a/filter/glossary/filter.php b/filter/glossary/filter.php new file mode 100644 index 00000000000..85926269440 --- /dev/null +++ b/filter/glossary/filter.php @@ -0,0 +1,226 @@ +. + +/** + * This filter provides automatic linking to + * glossary entries, aliases and categories when + * found inside every Moodle text + * + * @package filter + * @subpackage glossary + * @copyright 2004 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Glossary filtering + * + * TODO: erase the $GLOSSARY_EXCLUDECONCEPTS global => require format_text() + * to be able to pass arbitrary $options['filteroptions']['glossary'] to filter_text() + */ +class filter_glossary extends moodle_text_filter { + + public function filter($text, array $options = array()) { + global $CFG, $DB, $GLOSSARY_EXCLUDECONCEPTS, $PAGE; + + // Trivial-cache - keyed on $cachedcontextid + static $cachedcontextid; + static $conceptlist; + + static $jsinitialised; // To control unique js init + static $nothingtodo; // To avoid processing if no glossaries / concepts are found + + // Try to get current course + if (!$courseid = get_courseid_from_context($this->context)) { + $courseid = 0; + } + + // Initialise/invalidate our trivial cache if dealing with a different context + if (!isset($cachedcontextid) || $cachedcontextid !== $this->context->id) { + $cachedcontextid = $this->context->id; + $conceptlist = array(); + $nothingtodo = false; + } + + if ($nothingtodo === true) { + return $text; + } + + // Create a list of all the concepts to search for. It may be cached already. + if (empty($conceptlist)) { + + // Find all the glossaries we need to examine + if (!$glossaries = $DB->get_records_sql_menu(' + SELECT g.id, g.name + FROM {glossary} g, {course_modules} cm, {modules} m + WHERE m.name = \'glossary\' + AND cm.module = m.id + AND cm.visible = 1 + AND g.id = cm.instance + AND g.usedynalink != 0 + AND (g.course = ? OR g.globalglossary = 1) + ORDER BY g.globalglossary, g.id', array($courseid))) { + $nothingtodo = true; + return $text; + } + + // Make a list of glossary IDs for searching + $glossarylist = implode(',', array_keys($glossaries)); + + // Pull out all the raw data from the database for entries, categories and aliases + $entries = $DB->get_records_select('glossary_entries', + 'glossaryid IN ('.$glossarylist.') AND usedynalink != 0 AND approved != 0 ', null, '', + 'id,glossaryid, concept, casesensitive, 0 AS category, fullmatch'); + + $categories = $DB->get_records_select('glossary_categories', + 'glossaryid IN ('.$glossarylist.') AND usedynalink != 0', null, '', + 'id,glossaryid,name AS concept, 1 AS casesensitive, 1 AS category, 1 AS fullmatch'); + + $aliases = $DB->get_records_sql(' + SELECT ga.id, ge.id AS entryid, ge.glossaryid, + ga.alias AS concept, ge.concept AS originalconcept, + casesensitive, 0 AS category, fullmatch + FROM {glossary_alias} ga, + {glossary_entries} ge + WHERE ga.entryid = ge.id + AND ge.glossaryid IN ('.$glossarylist.') + AND ge.usedynalink != 0 + AND ge.approved != 0', null); + + // Combine them into one big list + $concepts = array(); + if ($entries and $categories) { + $concepts = array_merge($entries, $categories); + } else if ($categories) { + $concepts = $categories; + } else if ($entries) { + $concepts = $entries; + } + + if ($aliases) { + $concepts = array_merge($concepts, $aliases); + } + + if (!empty($concepts)) { + foreach ($concepts as $key => $concept) { + // Trim empty or unlinkable concepts + $currentconcept = trim(strip_tags($concept->concept)); + if (empty($currentconcept)) { + unset($concepts[$key]); + continue; + } else { + $concepts[$key]->concept = $currentconcept; + } + + // Rule out any small integers. See bug 1446 + $currentint = intval($currentconcept); + if ($currentint && (strval($currentint) == $currentconcept) && $currentint < 1000) { + unset($concepts[$key]); + } + } + } + + if (empty($concepts)) { + $nothingtodo = true; + return $text; + } + + usort($concepts, 'filter_glossary::sort_entries_by_length'); + + $strcategory = get_string('category', 'glossary'); + + // Loop through all the concepts, setting up our data structure for the filter + $conceptlist = array(); // We will store all the concepts here + + foreach ($concepts as $concept) { + $glossaryname = str_replace(':', '-', $glossaries[$concept->glossaryid]); + if ($concept->category) { // Link to a category + // TODO: Fix this string usage + $title = strip_tags($glossaryname.': '.$strcategory.' '.$concept->concept); + $href_tag_begin = ''; + } else { // Link to entry or alias + if (!empty($concept->originalconcept)) { // We are dealing with an alias (so show and point to original) + $title = str_replace('"', "'", strip_tags($glossaryname.': '.$concept->originalconcept)); + $concept->id = $concept->entryid; + } else { // This is an entry + $title = str_replace('"', "'", strip_tags($glossaryname.': '.$concept->concept)); + } + // hardcoding dictionary format in the URL rather than defaulting + // to the current glossary format which may not work in a popup. + // for example "entry list" means the popup would only contain + // a link that opens another popup. + $link = new moodle_url('/mod/glossary/showentry.php', array('courseid'=>$courseid, 'eid'=>$concept->id, 'displayformat'=>'dictionary')); + $attributes = array( + 'href' => $link, + 'title'=> $title, + 'class'=> 'glossary autolink concept glossaryid'.$concept->glossaryid); + + // this flag is optionally set by resource_pluginfile() + // if processing an embedded file use target to prevent getting nested Moodles + if (isset($CFG->embeddedsoforcelinktarget) && $CFG->embeddedsoforcelinktarget) { + $attributes['target'] = '_top'; + } + + $href_tag_begin = html_writer::start_tag('a', $attributes); + } + $conceptlist[] = new filterobject($concept->concept, $href_tag_begin, '', + $concept->casesensitive, $concept->fullmatch); + } + + $conceptlist = filter_remove_duplicates($conceptlist); + + if (empty($jsinitialised)) { + // Add a JavaScript event to open popup's here. This only ever need to be + // added once! + $PAGE->requires->yui_module( + 'moodle-filter_glossary-autolinker', + 'M.filter_glossary.init_filter_autolinking', + array(array('courseid' => $courseid))); + $jsinitialised = true; + } + } + + if (!empty($GLOSSARY_EXCLUDECONCEPTS)) { + $reducedconceptlist=array(); + foreach($conceptlist as $concept) { + if(!in_array($concept->phrase,$GLOSSARY_EXCLUDECONCEPTS)) { + $reducedconceptlist[]=$concept; + } + } + return filter_phrases($text, $reducedconceptlist); + } + + return filter_phrases($text, $conceptlist); // Actually search for concepts! + } + + + private static function sort_entries_by_length($entry0, $entry1) { + $len0 = strlen($entry0->concept); + $len1 = strlen($entry1->concept); + + if ($len0 < $len1) { + return 1; + } else if ($len0 > $len1) { + return -1; + } else { + return 0; + } + } +} diff --git a/filter/glossary/lang/en/filter_glossary.php b/filter/glossary/lang/en/filter_glossary.php new file mode 100644 index 00000000000..5ca0606f76a --- /dev/null +++ b/filter/glossary/lang/en/filter_glossary.php @@ -0,0 +1,28 @@ +. + +/** + * Strings for filter_glossary + * + * @package filter + * @subpackage glossary + * @copyright 2011 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['filtername'] = 'Glossary auto-linking'; diff --git a/filter/glossary/styles.css b/filter/glossary/styles.css new file mode 100644 index 00000000000..8a78bdb2b6d --- /dev/null +++ b/filter/glossary/styles.css @@ -0,0 +1 @@ +#glossaryfilteroverlayprogress {position:fixed;top:50%;width:100%;text-align:center;} diff --git a/filter/glossary/version.php b/filter/glossary/version.php new file mode 100644 index 00000000000..4628335de18 --- /dev/null +++ b/filter/glossary/version.php @@ -0,0 +1,32 @@ +. + +/** + * Glossary filter version information + * + * @package filter + * @subpackage glossary + * @copyright 2011 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2011102800; +$plugin->requires = 2011102700; // Requires this Moodle version +$plugin->component= 'filter_glossary'; + +$plugin->dependencies = array('mod_glossary' => 2011102800); diff --git a/mod/glossary/yui/autolinker/autolinker.js b/filter/glossary/yui/autolinker/autolinker.js similarity index 90% rename from mod/glossary/yui/autolinker/autolinker.js rename to filter/glossary/yui/autolinker/autolinker.js index e8935f321cb..46d04bca80f 100644 --- a/mod/glossary/yui/autolinker/autolinker.js +++ b/filter/glossary/yui/autolinker/autolinker.js @@ -1,6 +1,6 @@ -YUI.add('moodle-mod_glossary-autolinker', function(Y) { +YUI.add('moodle-filter_glossary-autolinker', function(Y) { - var AUTOLINKERNAME = 'Glossary autolinker', + var AUTOLINKERNAME = 'Glossary filter autolinker', URL = 'url', POPUPNAME = 'name', POPUPOPTIONS = 'options', @@ -33,7 +33,7 @@ YUI.add('moodle-mod_glossary-autolinker', function(Y) { //display a progress indicator var title = ''; - var content = Y.Node.create('
'); + var content = Y.Node.create('
'); var o = new Y.Overlay({ headerContent : title, bodyContent : content @@ -61,7 +61,7 @@ YUI.add('moodle-mod_glossary-autolinker', function(Y) { }; Y.io(fullurl, cfg); - }, Y.one(document.body), 'a.glossary.autolink'); + }, Y.one(document.body), 'a.glossary.autolink.concept'); }, display_callback : function(content) { try { @@ -126,9 +126,9 @@ YUI.add('moodle-mod_glossary-autolinker', function(Y) { } }); - M.mod_glossary = M.mod_glossary || {}; - M.mod_glossary.init_filter_autolinking = function(config) { + M.filter_glossary = M.filter_glossary || {}; + M.filter_glossary.init_filter_autolinking = function(config) { return new AUTOLINKER(config); } -}, '@VERSION@', {requires:['base','node','event-delegate','overlay','moodle-enrol-notification']}); \ No newline at end of file +}, '@VERSION@', {requires:['base','node','event-delegate','overlay','moodle-enrol-notification']}); diff --git a/mod/glossary/filter.php b/mod/glossary/filter.php deleted file mode 100644 index 85090f4f2fc..00000000000 --- a/mod/glossary/filter.php +++ /dev/null @@ -1,200 +0,0 @@ -get_records_sql_menu('SELECT g.id, g.name - FROM {glossary} g, {course_modules} cm, {modules} m - WHERE m.name = \'glossary\' AND - cm.module = m.id AND - cm.visible = 1 AND - g.id = cm.instance AND - g.usedynalink <> 0 AND - (g.course = ? OR g.globalglossary = 1) - ORDER BY g.globalglossary, g.id', array($courseid))) { - $nothingtodo = true; - return $text; - } - - /// Make a list of glossary IDs for searching - $glossarylist = implode(',', array_keys($glossaries)); - - - /// Pull out all the raw data from the database for entries, categories and aliases - $entries = $DB->get_records_select('glossary_entries', - 'glossaryid IN ('.$glossarylist.') AND usedynalink != 0 AND approved != 0 ', null, '', - 'id,glossaryid, concept, casesensitive, 0 AS category, fullmatch'); - - $categories = $DB->get_records_select('glossary_categories', - 'glossaryid IN ('.$glossarylist.') AND usedynalink != 0', null, '', - 'id,glossaryid,name AS concept, 1 AS casesensitive, 1 AS category, 1 AS fullmatch'); - - $aliases = $DB->get_records_sql('SELECT ga.id, ge.id AS entryid, ge.glossaryid, ga.alias AS concept, ge.concept AS originalconcept, - casesensitive, 0 AS category, fullmatch - FROM {glossary_alias} ga, - {glossary_entries} ge - WHERE ga.entryid = ge.id - AND ge.glossaryid IN ('.$glossarylist.') - AND ge.usedynalink != 0 - AND ge.approved != 0', null); - - - /// Combine them into one big list - $concepts = array(); - if ($entries and $categories) { - $concepts = array_merge($entries, $categories); - } else if ($categories) { - $concepts = $categories; - } else if ($entries) { - $concepts = $entries; - } - - if ($aliases) { - $concepts = array_merge($concepts, $aliases); - } - - if (!empty($concepts)) { - foreach ($concepts as $key => $concept) { - /// Trim empty or unlinkable concepts - $currentconcept = trim(strip_tags($concept->concept)); - if (empty($currentconcept)) { - unset($concepts[$key]); - continue; - } else { - $concepts[$key]->concept = $currentconcept; - } - - /// Rule out any small integers. See bug 1446 - $currentint = intval($currentconcept); - if ($currentint && (strval($currentint) == $currentconcept) && $currentint < 1000) { - unset($concepts[$key]); - } - } - } - - if (empty($concepts)) { - $nothingtodo = true; - return $text; - } - - usort($concepts, 'glossary_sort_entries_by_length'); - - $strcategory = get_string('category', 'glossary'); - - - /// Loop through all the concepts, setting up our data structure for the filter - - $conceptlist = array(); /// We will store all the concepts here - - foreach ($concepts as $concept) { - - $glossaryname = str_replace(':', '-', $glossaries[$concept->glossaryid]); - - if ($concept->category) { // Link to a category - $title = strip_tags($glossaryname.': '.$strcategory.' '.$concept->concept); - $href_tag_begin = ''; - - } else { // Link to entry or alias - if (!empty($concept->originalconcept)) { // We are dealing with an alias (so show and point to original) - $title = str_replace('"', "'", strip_tags($glossaryname.': '.$concept->originalconcept)); - $concept->id = $concept->entryid; - } else { // This is an entry - $title = str_replace('"', "'", strip_tags($glossaryname.': '.$concept->concept)); - } - // hardcoding dictionary format in the URL rather than defaulting - // to the current glossary format which may not work in a popup. - // for example "entry list" means the popup would only contain - // a link that opens another popup. - $link = new moodle_url('/mod/glossary/showentry.php', array('courseid'=>$courseid, 'eid'=>$concept->id, 'displayformat'=>'dictionary')); - $attributes = array( - 'href' => $link, - 'title'=> $title, - 'class'=> 'glossary autolink glossaryid'.$concept->glossaryid); - - // this flag is optionally set by resource_pluginfile() - // if processing an embedded file use target to prevent getting nested Moodles - if (isset($CFG->embeddedsoforcelinktarget) && $CFG->embeddedsoforcelinktarget) { - $attributes['target'] = '_top'; - } - - $href_tag_begin = html_writer::start_tag('a', $attributes); - } - $conceptlist[] = new filterobject($concept->concept, $href_tag_begin, '', - $concept->casesensitive, $concept->fullmatch); - } - - $conceptlist = filter_remove_duplicates($conceptlist); - - if (empty($jsinitialised)) { - // Add a JavaScript event to open popup's here. This only ever need to be - // added once! - $PAGE->requires->yui_module('moodle-mod_glossary-autolinker', 'M.mod_glossary.init_filter_autolinking', array(array('courseid'=>$courseid))); - $jsinitialised = true; - } - } - - if(!empty($GLOSSARY_EXCLUDECONCEPTS)) { - $reducedconceptlist=array(); - foreach($conceptlist as $concept) { - if(!in_array($concept->phrase,$GLOSSARY_EXCLUDECONCEPTS)) { - $reducedconceptlist[]=$concept; - } - } - return filter_phrases($text, $reducedconceptlist); - } - - return filter_phrases($text, $conceptlist); // Actually search for concepts! -} - - -function glossary_sort_entries_by_length ($entry0, $entry1) { - $len0 = strlen($entry0->concept); - $len1 = strlen($entry1->concept); - - if ($len0 < $len1) { - return 1; - } else if ($len0 > $len1) { - return -1; - } else { - return 0; - } -} - - - diff --git a/mod/glossary/styles.css b/mod/glossary/styles.css index d7c005bcff1..a4a52c692d7 100644 --- a/mod/glossary/styles.css +++ b/mod/glossary/styles.css @@ -27,5 +27,3 @@ #page-mod-glossary-view table.glossarycategoryheader th {padding:0px;} #page-mod-glossary-showentry #page-content {min-width:600px;} - -#glossaryoverlayprogress {position:fixed;top:50%;width:100%;text-align:center;} \ No newline at end of file diff --git a/mod/glossary/version.php b/mod/glossary/version.php index 6210db6b2d3..c6e8ac9d8a3 100644 --- a/mod/glossary/version.php +++ b/mod/glossary/version.php @@ -1,12 +1,30 @@ . -///////////////////////////////////////////////////////////////////////////////// -/// Code fragment to define the version of glossary -/// This fragment is called by moodle_needs_upgrading() and /admin/index.php -///////////////////////////////////////////////////////////////////////////////// +/** + * Glossary module version information + * + * @package mod + * @subpackage glossary + * @copyright 2011 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ -$module->version = 2011052300; -$module->requires = 2011052300; // Requires this Moodle version +defined('MOODLE_INTERNAL') || die(); + +$module->version = 2011102800; +$module->requires = 2011102700; // Requires this Moodle version $module->cron = 0; // Period for cron to check this module (secs) - -