Commit | Line | Data |
---|---|---|
85a69ce1 EL |
1 | <?php |
2 | // This file is part of Moodle - http://moodle.org/ | |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | /** | |
18 | * This filter provides automatic linking to | |
19 | * glossary entries, aliases and categories when | |
20 | * found inside every Moodle text | |
21 | * | |
22 | * @package filter | |
23 | * @subpackage glossary | |
24 | * @copyright 2004 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} | |
25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
26 | */ | |
27 | ||
28 | defined('MOODLE_INTERNAL') || die(); | |
29 | ||
30 | /** | |
31 | * Glossary filtering | |
32 | * | |
33 | * TODO: erase the $GLOSSARY_EXCLUDECONCEPTS global => require format_text() | |
34 | * to be able to pass arbitrary $options['filteroptions']['glossary'] to filter_text() | |
35 | * TODO: fix linking to glossary categories | |
36 | */ | |
37 | class filter_glossary extends moodle_text_filter { | |
38 | ||
39 | public function filter($text, array $options = array()) { | |
40 | global $CFG, $DB, $GLOSSARY_EXCLUDECONCEPTS, $PAGE; | |
41 | ||
42 | // Trivial-cache - keyed on $cachedcontextid | |
43 | static $cachedcontextid; | |
44 | static $conceptlist; | |
45 | ||
46 | static $jsinitialised; // To control unique js init | |
47 | static $nothingtodo; // To avoid processing if no glossaries / concepts are found | |
48 | ||
49 | // Try to get current course | |
50 | if (!$courseid = get_courseid_from_context($this->context)) { | |
51 | $courseid = 0; | |
52 | } | |
53 | ||
54 | // Initialise/invalidate our trivial cache if dealing with a different context | |
55 | if (!isset($cachedcontextid) || $cachedcontextid !== $this->context->id) { | |
56 | $cachedcontextid = $this->context->id; | |
57 | $conceptlist = array(); | |
58 | $nothingtodo = false; | |
59 | } | |
60 | ||
61 | if ($nothingtodo === true) { | |
62 | return $text; | |
63 | } | |
64 | ||
65 | // Create a list of all the concepts to search for. It may be cached already. | |
66 | if (empty($conceptlist)) { | |
67 | ||
68 | // Find all the glossaries we need to examine | |
69 | if (!$glossaries = $DB->get_records_sql_menu(' | |
70 | SELECT g.id, g.name | |
71 | FROM {glossary} g, {course_modules} cm, {modules} m | |
72 | WHERE m.name = \'glossary\' | |
73 | AND cm.module = m.id | |
74 | AND cm.visible = 1 | |
75 | AND g.id = cm.instance | |
76 | AND g.usedynalink != 0 | |
77 | AND (g.course = ? OR g.globalglossary = 1) | |
78 | ORDER BY g.globalglossary, g.id', array($courseid))) { | |
79 | $nothingtodo = true; | |
80 | return $text; | |
81 | } | |
82 | ||
83 | // Make a list of glossary IDs for searching | |
84 | $glossarylist = implode(',', array_keys($glossaries)); | |
85 | ||
86 | // Pull out all the raw data from the database for entries, categories and aliases | |
87 | $entries = $DB->get_records_select('glossary_entries', | |
88 | 'glossaryid IN ('.$glossarylist.') AND usedynalink != 0 AND approved != 0 ', null, '', | |
89 | 'id,glossaryid, concept, casesensitive, 0 AS category, fullmatch'); | |
90 | ||
91 | $categories = $DB->get_records_select('glossary_categories', | |
92 | 'glossaryid IN ('.$glossarylist.') AND usedynalink != 0', null, '', | |
93 | 'id,glossaryid,name AS concept, 1 AS casesensitive, 1 AS category, 1 AS fullmatch'); | |
94 | ||
95 | $aliases = $DB->get_records_sql(' | |
96 | SELECT ga.id, ge.id AS entryid, ge.glossaryid, | |
97 | ga.alias AS concept, ge.concept AS originalconcept, | |
98 | casesensitive, 0 AS category, fullmatch | |
99 | FROM {glossary_alias} ga, | |
100 | {glossary_entries} ge | |
101 | WHERE ga.entryid = ge.id | |
102 | AND ge.glossaryid IN ('.$glossarylist.') | |
103 | AND ge.usedynalink != 0 | |
104 | AND ge.approved != 0', null); | |
105 | ||
106 | // Combine them into one big list | |
107 | $concepts = array(); | |
108 | if ($entries and $categories) { | |
109 | $concepts = array_merge($entries, $categories); | |
110 | } else if ($categories) { | |
111 | $concepts = $categories; | |
112 | } else if ($entries) { | |
113 | $concepts = $entries; | |
114 | } | |
115 | ||
116 | if ($aliases) { | |
117 | $concepts = array_merge($concepts, $aliases); | |
118 | } | |
119 | ||
120 | if (!empty($concepts)) { | |
121 | foreach ($concepts as $key => $concept) { | |
122 | // Trim empty or unlinkable concepts | |
123 | $currentconcept = trim(strip_tags($concept->concept)); | |
124 | if (empty($currentconcept)) { | |
125 | unset($concepts[$key]); | |
126 | continue; | |
127 | } else { | |
128 | $concepts[$key]->concept = $currentconcept; | |
129 | } | |
130 | ||
131 | // Rule out any small integers. See bug 1446 | |
132 | $currentint = intval($currentconcept); | |
133 | if ($currentint && (strval($currentint) == $currentconcept) && $currentint < 1000) { | |
134 | unset($concepts[$key]); | |
135 | } | |
136 | } | |
137 | } | |
138 | ||
139 | if (empty($concepts)) { | |
140 | $nothingtodo = true; | |
141 | return $text; | |
142 | } | |
143 | ||
144 | usort($concepts, 'filter_glossary::sort_entries_by_length'); | |
145 | ||
146 | $strcategory = get_string('category', 'glossary'); | |
147 | ||
148 | // Loop through all the concepts, setting up our data structure for the filter | |
149 | $conceptlist = array(); // We will store all the concepts here | |
150 | ||
151 | foreach ($concepts as $concept) { | |
152 | $glossaryname = str_replace(':', '-', $glossaries[$concept->glossaryid]); | |
153 | if ($concept->category) { // Link to a category | |
154 | $title = strip_tags($glossaryname.': '.$strcategory.' '.$concept->concept); | |
155 | $href_tag_begin = '<a class="glossary autolink glossaryid'.$concept->glossaryid.'" title="'.$title.'" '. | |
156 | 'href="'.$CFG->wwwroot.'/mod/glossary/view.php?g='.$concept->glossaryid. | |
157 | '&mode=cat&hook='.$concept->id.'">'; | |
158 | } else { // Link to entry or alias | |
159 | if (!empty($concept->originalconcept)) { // We are dealing with an alias (so show and point to original) | |
160 | $title = str_replace('"', "'", strip_tags($glossaryname.': '.$concept->originalconcept)); | |
161 | $concept->id = $concept->entryid; | |
162 | } else { // This is an entry | |
163 | $title = str_replace('"', "'", strip_tags($glossaryname.': '.$concept->concept)); | |
164 | } | |
165 | // hardcoding dictionary format in the URL rather than defaulting | |
166 | // to the current glossary format which may not work in a popup. | |
167 | // for example "entry list" means the popup would only contain | |
168 | // a link that opens another popup. | |
169 | $link = new moodle_url('/mod/glossary/showentry.php', array('courseid'=>$courseid, 'eid'=>$concept->id, 'displayformat'=>'dictionary')); | |
170 | $attributes = array( | |
171 | 'href' => $link, | |
172 | 'title'=> $title, | |
173 | 'class'=> 'glossary autolink glossaryid'.$concept->glossaryid); | |
174 | ||
175 | // this flag is optionally set by resource_pluginfile() | |
176 | // if processing an embedded file use target to prevent getting nested Moodles | |
177 | if (isset($CFG->embeddedsoforcelinktarget) && $CFG->embeddedsoforcelinktarget) { | |
178 | $attributes['target'] = '_top'; | |
179 | } | |
180 | ||
181 | $href_tag_begin = html_writer::start_tag('a', $attributes); | |
182 | } | |
183 | $conceptlist[] = new filterobject($concept->concept, $href_tag_begin, '</a>', | |
184 | $concept->casesensitive, $concept->fullmatch); | |
185 | } | |
186 | ||
187 | $conceptlist = filter_remove_duplicates($conceptlist); | |
188 | ||
189 | if (empty($jsinitialised)) { | |
190 | // Add a JavaScript event to open popup's here. This only ever need to be | |
191 | // added once! | |
192 | $PAGE->requires->yui_module('moodle-mod_glossary-autolinker', 'M.mod_glossary.init_filter_autolinking', array(array('courseid'=>$courseid))); | |
193 | $jsinitialised = true; | |
194 | } | |
195 | } | |
196 | ||
197 | if (!empty($GLOSSARY_EXCLUDECONCEPTS)) { | |
198 | $reducedconceptlist=array(); | |
199 | foreach($conceptlist as $concept) { | |
200 | if(!in_array($concept->phrase,$GLOSSARY_EXCLUDECONCEPTS)) { | |
201 | $reducedconceptlist[]=$concept; | |
202 | } | |
203 | } | |
204 | return filter_phrases($text, $reducedconceptlist); | |
205 | } | |
206 | ||
207 | return filter_phrases($text, $conceptlist); // Actually search for concepts! | |
208 | } | |
209 | ||
210 | ||
211 | private static function sort_entries_by_length($entry0, $entry1) { | |
212 | $len0 = strlen($entry0->concept); | |
213 | $len1 = strlen($entry1->concept); | |
214 | ||
215 | if ($len0 < $len1) { | |
216 | return 1; | |
217 | } else if ($len0 > $len1) { | |
218 | return -1; | |
219 | } else { | |
220 | return 0; | |
221 | } | |
222 | } | |
223 | } |