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() | |
85a69ce1 EL |
35 | */ |
36 | class filter_glossary extends moodle_text_filter { | |
37 | ||
38 | public function filter($text, array $options = array()) { | |
39 | global $CFG, $DB, $GLOSSARY_EXCLUDECONCEPTS, $PAGE; | |
40 | ||
41 | // Trivial-cache - keyed on $cachedcontextid | |
42 | static $cachedcontextid; | |
43 | static $conceptlist; | |
44 | ||
45 | static $jsinitialised; // To control unique js init | |
46 | static $nothingtodo; // To avoid processing if no glossaries / concepts are found | |
47 | ||
48 | // Try to get current course | |
49 | if (!$courseid = get_courseid_from_context($this->context)) { | |
50 | $courseid = 0; | |
51 | } | |
52 | ||
53 | // Initialise/invalidate our trivial cache if dealing with a different context | |
54 | if (!isset($cachedcontextid) || $cachedcontextid !== $this->context->id) { | |
55 | $cachedcontextid = $this->context->id; | |
56 | $conceptlist = array(); | |
57 | $nothingtodo = false; | |
58 | } | |
59 | ||
c3963fbd | 60 | if (($nothingtodo === true) || (!has_capability('mod/glossary:view', $this->context))) { |
85a69ce1 EL |
61 | return $text; |
62 | } | |
63 | ||
64 | // Create a list of all the concepts to search for. It may be cached already. | |
65 | if (empty($conceptlist)) { | |
66 | ||
67 | // Find all the glossaries we need to examine | |
68 | if (!$glossaries = $DB->get_records_sql_menu(' | |
69 | SELECT g.id, g.name | |
70 | FROM {glossary} g, {course_modules} cm, {modules} m | |
71 | WHERE m.name = \'glossary\' | |
72 | AND cm.module = m.id | |
73 | AND cm.visible = 1 | |
74 | AND g.id = cm.instance | |
75 | AND g.usedynalink != 0 | |
76 | AND (g.course = ? OR g.globalglossary = 1) | |
77 | ORDER BY g.globalglossary, g.id', array($courseid))) { | |
78 | $nothingtodo = true; | |
79 | return $text; | |
80 | } | |
81 | ||
82 | // Make a list of glossary IDs for searching | |
83 | $glossarylist = implode(',', array_keys($glossaries)); | |
84 | ||
85 | // Pull out all the raw data from the database for entries, categories and aliases | |
86 | $entries = $DB->get_records_select('glossary_entries', | |
87 | 'glossaryid IN ('.$glossarylist.') AND usedynalink != 0 AND approved != 0 ', null, '', | |
88 | 'id,glossaryid, concept, casesensitive, 0 AS category, fullmatch'); | |
89 | ||
90 | $categories = $DB->get_records_select('glossary_categories', | |
91 | 'glossaryid IN ('.$glossarylist.') AND usedynalink != 0', null, '', | |
92 | 'id,glossaryid,name AS concept, 1 AS casesensitive, 1 AS category, 1 AS fullmatch'); | |
93 | ||
94 | $aliases = $DB->get_records_sql(' | |
95 | SELECT ga.id, ge.id AS entryid, ge.glossaryid, | |
96 | ga.alias AS concept, ge.concept AS originalconcept, | |
97 | casesensitive, 0 AS category, fullmatch | |
98 | FROM {glossary_alias} ga, | |
99 | {glossary_entries} ge | |
100 | WHERE ga.entryid = ge.id | |
101 | AND ge.glossaryid IN ('.$glossarylist.') | |
102 | AND ge.usedynalink != 0 | |
103 | AND ge.approved != 0', null); | |
104 | ||
105 | // Combine them into one big list | |
106 | $concepts = array(); | |
107 | if ($entries and $categories) { | |
108 | $concepts = array_merge($entries, $categories); | |
109 | } else if ($categories) { | |
110 | $concepts = $categories; | |
111 | } else if ($entries) { | |
112 | $concepts = $entries; | |
113 | } | |
114 | ||
115 | if ($aliases) { | |
116 | $concepts = array_merge($concepts, $aliases); | |
117 | } | |
118 | ||
119 | if (!empty($concepts)) { | |
120 | foreach ($concepts as $key => $concept) { | |
121 | // Trim empty or unlinkable concepts | |
122 | $currentconcept = trim(strip_tags($concept->concept)); | |
123 | if (empty($currentconcept)) { | |
124 | unset($concepts[$key]); | |
125 | continue; | |
126 | } else { | |
127 | $concepts[$key]->concept = $currentconcept; | |
128 | } | |
129 | ||
130 | // Rule out any small integers. See bug 1446 | |
131 | $currentint = intval($currentconcept); | |
132 | if ($currentint && (strval($currentint) == $currentconcept) && $currentint < 1000) { | |
133 | unset($concepts[$key]); | |
134 | } | |
135 | } | |
136 | } | |
137 | ||
138 | if (empty($concepts)) { | |
139 | $nothingtodo = true; | |
140 | return $text; | |
141 | } | |
142 | ||
143 | usort($concepts, 'filter_glossary::sort_entries_by_length'); | |
144 | ||
145 | $strcategory = get_string('category', 'glossary'); | |
146 | ||
147 | // Loop through all the concepts, setting up our data structure for the filter | |
148 | $conceptlist = array(); // We will store all the concepts here | |
149 | ||
150 | foreach ($concepts as $concept) { | |
151 | $glossaryname = str_replace(':', '-', $glossaries[$concept->glossaryid]); | |
152 | if ($concept->category) { // Link to a category | |
d307a1a7 | 153 | // TODO: Fix this string usage |
85a69ce1 | 154 | $title = strip_tags($glossaryname.': '.$strcategory.' '.$concept->concept); |
d307a1a7 | 155 | $href_tag_begin = '<a class="glossary autolink category glossaryid'.$concept->glossaryid.'" title="'.$title.'" '. |
85a69ce1 EL |
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, | |
d307a1a7 | 173 | 'class'=> 'glossary autolink concept glossaryid'.$concept->glossaryid); |
85a69ce1 EL |
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! | |
d307a1a7 EL |
192 | $PAGE->requires->yui_module( |
193 | 'moodle-filter_glossary-autolinker', | |
194 | 'M.filter_glossary.init_filter_autolinking', | |
195 | array(array('courseid' => $courseid))); | |
85a69ce1 EL |
196 | $jsinitialised = true; |
197 | } | |
198 | } | |
199 | ||
200 | if (!empty($GLOSSARY_EXCLUDECONCEPTS)) { | |
201 | $reducedconceptlist=array(); | |
202 | foreach($conceptlist as $concept) { | |
203 | if(!in_array($concept->phrase,$GLOSSARY_EXCLUDECONCEPTS)) { | |
204 | $reducedconceptlist[]=$concept; | |
205 | } | |
206 | } | |
207 | return filter_phrases($text, $reducedconceptlist); | |
208 | } | |
209 | ||
210 | return filter_phrases($text, $conceptlist); // Actually search for concepts! | |
211 | } | |
212 | ||
213 | ||
214 | private static function sort_entries_by_length($entry0, $entry1) { | |
215 | $len0 = strlen($entry0->concept); | |
216 | $len1 = strlen($entry1->concept); | |
217 | ||
218 | if ($len0 < $len1) { | |
219 | return 1; | |
220 | } else if ($len0 > $len1) { | |
221 | return -1; | |
222 | } else { | |
223 | return 0; | |
224 | } | |
225 | } | |
226 | } |