|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00027 defined('MOODLE_INTERNAL') || die(); 00028 00030 define('TEXTFILTER_ON', 1); 00032 define('TEXTFILTER_INHERIT', 0); 00034 define('TEXTFILTER_OFF', -1); 00036 define('TEXTFILTER_DISABLED', -9999); 00037 00043 define('TEXTFILTER_EXCL_SEPARATOR', '-%-'); 00044 00045 00058 class filter_manager { 00063 protected $textfilters = array(); 00064 00069 protected $stringfilters = array(); 00070 00072 protected $stringfilternames = array(); 00073 00075 protected static $singletoninstance; 00076 00077 protected function __construct() { 00078 $this->stringfilternames = filter_get_string_filters(); 00079 } 00080 00084 public static function instance() { 00085 global $CFG; 00086 if (is_null(self::$singletoninstance)) { 00087 if (!empty($CFG->perfdebug)) { 00088 self::$singletoninstance = new performance_measuring_filter_manager(); 00089 } else { 00090 self::$singletoninstance = new self(); 00091 } 00092 } 00093 return self::$singletoninstance; 00094 } 00095 00101 protected function load_filters($context) { 00102 $filters = filter_get_active_in_context($context); 00103 $this->textfilters[$context->id] = array(); 00104 $this->stringfilters[$context->id] = array(); 00105 foreach ($filters as $filtername => $localconfig) { 00106 $filter = $this->make_filter_object($filtername, $context, $localconfig); 00107 if (is_null($filter)) { 00108 continue; 00109 } 00110 $this->textfilters[$context->id][] = $filter; 00111 if (in_array($filtername, $this->stringfilternames)) { 00112 $this->stringfilters[$context->id][] = $filter; 00113 } 00114 } 00115 } 00116 00126 protected function make_filter_object($filtername, $context, $localconfig) { 00127 global $CFG; 00128 $path = $CFG->dirroot .'/'. $filtername .'/filter.php'; 00129 if (!is_readable($path)) { 00130 return null; 00131 } 00132 include_once($path); 00133 00134 $filterclassname = 'filter_' . basename($filtername); 00135 if (class_exists($filterclassname)) { 00136 return new $filterclassname($context, $localconfig); 00137 } 00138 00139 // TODO: deprecated since 2.2, will be out in 2.3, see MDL-29996 00140 $legacyfunctionname = basename($filtername) . '_filter'; 00141 if (function_exists($legacyfunctionname)) { 00142 return new legacy_filter($legacyfunctionname, $context, $localconfig); 00143 } 00144 00145 return null; 00146 } 00147 00155 protected function apply_filter_chain($text, $filterchain, array $options = array()) { 00156 foreach ($filterchain as $filter) { 00157 $text = $filter->filter($text, $options); 00158 } 00159 return $text; 00160 } 00161 00167 protected function get_text_filters($context) { 00168 if (!isset($this->textfilters[$context->id])) { 00169 $this->load_filters($context); 00170 } 00171 return $this->textfilters[$context->id]; 00172 } 00173 00179 protected function get_string_filters($context) { 00180 if (!isset($this->stringfilters[$context->id])) { 00181 $this->load_filters($context); 00182 } 00183 return $this->stringfilters[$context->id]; 00184 } 00185 00194 public function filter_text($text, $context, array $options = array()) { 00195 $text = $this->apply_filter_chain($text, $this->get_text_filters($context), $options); 00197 $text = str_replace(array('<nolink>', '</nolink>'), '', $text); 00198 return $text; 00199 } 00200 00208 public function filter_string($string, $context) { 00209 return $this->apply_filter_chain($string, $this->get_string_filters($context)); 00210 } 00211 00217 public function text_filtering_hash($context) { 00218 $filters = $this->get_text_filters($context); 00219 $hashes = array(); 00220 foreach ($filters as $filter) { 00221 $hashes[] = $filter->hash(); 00222 } 00223 return implode('-', $hashes); 00224 } 00225 } 00226 00238 class null_filter_manager { 00242 public function filter_text($text, $context, $options) { 00243 return $text; 00244 } 00245 00249 public function filter_string($string, $context) { 00250 return $string; 00251 } 00252 00256 public function text_filtering_hash() { 00257 return ''; 00258 } 00259 } 00260 00271 class performance_measuring_filter_manager extends filter_manager { 00273 protected $filterscreated = 0; 00274 protected $textsfiltered = 0; 00275 protected $stringsfiltered = 0; 00276 00283 protected function make_filter_object($filtername, $context, $localconfig) { 00284 $this->filterscreated++; 00285 return parent::make_filter_object($filtername, $context, $localconfig); 00286 } 00287 00294 public function filter_text($text, $context, array $options = array()) { 00295 $this->textsfiltered++; 00296 return parent::filter_text($text, $context, $options); 00297 } 00298 00304 public function filter_string($string, $context) { 00305 $this->stringsfiltered++; 00306 return parent::filter_string($string, $context); 00307 } 00308 00312 public function get_performance_summary() { 00313 return array(array( 00314 'contextswithfilters' => count($this->textfilters), 00315 'filterscreated' => $this->filterscreated, 00316 'textsfiltered' => $this->textsfiltered, 00317 'stringsfiltered' => $this->stringsfiltered, 00318 ), array( 00319 'contextswithfilters' => 'Contexts for which filters were loaded', 00320 'filterscreated' => 'Filters created', 00321 'textsfiltered' => 'Pieces of content filtered', 00322 'stringsfiltered' => 'Strings filtered', 00323 )); 00324 } 00325 } 00326 00336 abstract class moodle_text_filter { 00338 protected $context; 00340 protected $localconfig; 00341 00348 public function __construct($context, array $localconfig) { 00349 $this->context = $context; 00350 $this->localconfig = $localconfig; 00351 } 00352 00356 public function hash() { 00357 return __CLASS__; 00358 } 00359 00367 public abstract function filter($text, array $options = array()); 00368 } 00369 00381 class legacy_filter extends moodle_text_filter { 00383 protected $filterfunction; 00384 protected $courseid; 00385 00393 public function __construct($filterfunction, $context, array $localconfig) { 00394 parent::__construct($context, $localconfig); 00395 $this->filterfunction = $filterfunction; 00396 $this->courseid = get_courseid_from_context($this->context); 00397 } 00398 00404 public function filter($text, array $options = array()) { 00405 if ($this->courseid) { 00406 // old filters are called only when inside courses 00407 return call_user_func($this->filterfunction, $this->courseid, $text); 00408 } else { 00409 return $text; 00410 } 00411 } 00412 } 00413 00424 class filterobject { 00426 var $phrase; 00427 var $hreftagbegin; 00428 var $hreftagend; 00430 var $casesensitive; 00431 var $fullmatch; 00433 var $replacementphrase; 00434 var $work_phrase; 00435 var $work_hreftagbegin; 00436 var $work_hreftagend; 00437 var $work_casesensitive; 00438 var $work_fullmatch; 00439 var $work_replacementphrase; 00441 var $work_calculated; 00442 00453 function filterobject($phrase, $hreftagbegin = '<span class="highlight">', 00454 $hreftagend = '</span>', 00455 $casesensitive = false, 00456 $fullmatch = false, 00457 $replacementphrase = NULL) { 00458 00459 $this->phrase = $phrase; 00460 $this->hreftagbegin = $hreftagbegin; 00461 $this->hreftagend = $hreftagend; 00462 $this->casesensitive = $casesensitive; 00463 $this->fullmatch = $fullmatch; 00464 $this->replacementphrase= $replacementphrase; 00465 $this->work_calculated = false; 00466 00467 } 00468 } 00469 00480 function filter_get_name($filter) { 00481 // TODO: should we be using pluginname here instead? , see MDL-29998 00482 list($type, $filter) = explode('/', $filter); 00483 switch ($type) { 00484 case 'filter': 00485 $strfiltername = get_string('filtername', 'filter_' . $filter); 00486 if (substr($strfiltername, 0, 2) != '[[') { 00487 // found a valid string. 00488 return $strfiltername; 00489 } 00490 // Fall through to try the legacy location. 00491 00492 // TODO: deprecated since 2.2, will be out in 2.3, see MDL-29996 00493 case 'mod': 00494 $strfiltername = get_string('filtername', $filter); 00495 if (substr($strfiltername, 0, 2) == '[[') { 00496 $strfiltername .= ' (' . $type . '/' . $filter . ')'; 00497 } 00498 return $strfiltername; 00499 00500 default: 00501 throw new coding_exception('Unknown filter type ' . $type); 00502 } 00503 } 00504 00513 function filter_get_all_installed() { 00514 global $CFG; 00515 $filternames = array(); 00516 // TODO: deprecated since 2.2, will be out in 2.3, see MDL-29996 00517 $filterlocations = array('mod', 'filter'); 00518 foreach ($filterlocations as $filterlocation) { 00519 // TODO: move get_list_of_plugins() to get_plugin_list() 00520 $filters = get_list_of_plugins($filterlocation); 00521 foreach ($filters as $filter) { 00522 // MDL-29994 - Ignore mod/data and mod/glossary filters forever, this will be out in 2.3 00523 if ($filterlocation == 'mod' && ($filter == 'data' || $filter == 'glossary')) { 00524 continue; 00525 } 00526 $path = $filterlocation . '/' . $filter; 00527 if (is_readable($CFG->dirroot . '/' . $path . '/filter.php')) { 00528 $strfiltername = filter_get_name($path); 00529 $filternames[$path] = $strfiltername; 00530 } 00531 } 00532 } 00533 collatorlib::asort($filternames); 00534 return $filternames; 00535 } 00536 00550 function filter_set_global_state($filter, $state, $sortorder = false) { 00551 global $DB; 00552 00553 // Check requested state is valid. 00554 if (!in_array($state, array(TEXTFILTER_ON, TEXTFILTER_OFF, TEXTFILTER_DISABLED))) { 00555 throw new coding_exception("Illegal option '$state' passed to filter_set_global_state. " . 00556 "Must be one of TEXTFILTER_ON, TEXTFILTER_OFF or TEXTFILTER_DISABLED."); 00557 } 00558 00559 // Check sortorder is valid. 00560 if ($sortorder !== false) { 00561 if ($sortorder < 1 || $sortorder > $DB->get_field('filter_active', 'MAX(sortorder)', array()) + 1) { 00562 throw new coding_exception("Invalid sort order passed to filter_set_global_state."); 00563 } 00564 } 00565 00566 // See if there is an existing record. 00567 $syscontext = get_context_instance(CONTEXT_SYSTEM); 00568 $rec = $DB->get_record('filter_active', array('filter' => $filter, 'contextid' => $syscontext->id)); 00569 if (empty($rec)) { 00570 $insert = true; 00571 $rec = new stdClass; 00572 $rec->filter = $filter; 00573 $rec->contextid = $syscontext->id; 00574 } else { 00575 $insert = false; 00576 if ($sortorder === false && !($rec->active == TEXTFILTER_DISABLED xor $state == TEXTFILTER_DISABLED)) { 00577 $sortorder = $rec->sortorder; 00578 } 00579 } 00580 00581 // Automatic sort order. 00582 if ($sortorder === false) { 00583 if ($state == TEXTFILTER_DISABLED && $insert) { 00584 $prevmaxsortorder = $DB->get_field('filter_active', 'MAX(sortorder)', array()); 00585 } else { 00586 $prevmaxsortorder = $DB->get_field_select('filter_active', 'MAX(sortorder)', 'active <> ?', array(TEXTFILTER_DISABLED)); 00587 } 00588 if (empty($prevmaxsortorder)) { 00589 $sortorder = 1; 00590 } else { 00591 $sortorder = $prevmaxsortorder + 1; 00592 if (!$insert && $state == TEXTFILTER_DISABLED) { 00593 $sortorder = $prevmaxsortorder; 00594 } 00595 } 00596 } 00597 00598 // Move any existing records out of the way of the sortorder. 00599 if ($insert) { 00600 $DB->execute('UPDATE {filter_active} SET sortorder = sortorder + 1 WHERE sortorder >= ?', array($sortorder)); 00601 } else if ($sortorder != $rec->sortorder) { 00602 $sparesortorder = $DB->get_field('filter_active', 'MIN(sortorder)', array()) - 1; 00603 $DB->set_field('filter_active', 'sortorder', $sparesortorder, array('filter' => $filter, 'contextid' => $syscontext->id)); 00604 if ($sortorder < $rec->sortorder) { 00605 $DB->execute('UPDATE {filter_active} SET sortorder = sortorder + 1 WHERE sortorder >= ? AND sortorder < ?', 00606 array($sortorder, $rec->sortorder)); 00607 } else if ($sortorder > $rec->sortorder) { 00608 $DB->execute('UPDATE {filter_active} SET sortorder = sortorder - 1 WHERE sortorder <= ? AND sortorder > ?', 00609 array($sortorder, $rec->sortorder)); 00610 } 00611 } 00612 00613 // Insert/update the new record. 00614 $rec->active = $state; 00615 $rec->sortorder = $sortorder; 00616 if ($insert) { 00617 $DB->insert_record('filter_active', $rec); 00618 } else { 00619 $DB->update_record('filter_active', $rec); 00620 } 00621 } 00622 00628 function filter_is_enabled($filter) { 00629 return array_key_exists($filter, filter_get_globally_enabled()); 00630 } 00631 00638 function filter_get_globally_enabled() { 00639 static $enabledfilters = null; 00640 if (is_null($enabledfilters)) { 00641 $filters = filter_get_global_states(); 00642 $enabledfilters = array(); 00643 foreach ($filters as $filter => $filerinfo) { 00644 if ($filerinfo->active != TEXTFILTER_DISABLED) { 00645 $enabledfilters[$filter] = $filter; 00646 } 00647 } 00648 } 00649 return $enabledfilters; 00650 } 00651 00659 function filter_get_string_filters() { 00660 global $CFG; 00661 $stringfilters = array(); 00662 if (!empty($CFG->filterall) && !empty($CFG->stringfilters)) { 00663 $stringfilters = explode(',', $CFG->stringfilters); 00664 $stringfilters = array_combine($stringfilters, $stringfilters); 00665 } 00666 return $stringfilters; 00667 } 00668 00677 function filter_set_applies_to_strings($filter, $applytostrings) { 00678 $stringfilters = filter_get_string_filters(); 00679 $numstringfilters = count($stringfilters); 00680 if ($applytostrings) { 00681 $stringfilters[$filter] = $filter; 00682 } else { 00683 unset($stringfilters[$filter]); 00684 } 00685 if (count($stringfilters) != $numstringfilters) { 00686 set_config('stringfilters', implode(',', $stringfilters)); 00687 set_config('filterall', !empty($stringfilters)); 00688 } 00689 } 00690 00700 function filter_set_local_state($filter, $contextid, $state) { 00701 global $DB; 00702 00703 // Check requested state is valid. 00704 if (!in_array($state, array(TEXTFILTER_ON, TEXTFILTER_OFF, TEXTFILTER_INHERIT))) { 00705 throw new coding_exception("Illegal option '$state' passed to filter_set_local_state. " . 00706 "Must be one of TEXTFILTER_ON, TEXTFILTER_OFF or TEXTFILTER_INHERIT."); 00707 } 00708 00709 if ($contextid == get_context_instance(CONTEXT_SYSTEM)->id) { 00710 throw new coding_exception('You cannot use filter_set_local_state ' . 00711 'with $contextid equal to the system context id.'); 00712 } 00713 00714 if ($state == TEXTFILTER_INHERIT) { 00715 $DB->delete_records('filter_active', array('filter' => $filter, 'contextid' => $contextid)); 00716 return; 00717 } 00718 00719 $rec = $DB->get_record('filter_active', array('filter' => $filter, 'contextid' => $contextid)); 00720 $insert = false; 00721 if (empty($rec)) { 00722 $insert = true; 00723 $rec = new stdClass; 00724 $rec->filter = $filter; 00725 $rec->contextid = $contextid; 00726 } 00727 00728 $rec->active = $state; 00729 00730 if ($insert) { 00731 $DB->insert_record('filter_active', $rec); 00732 } else { 00733 $DB->update_record('filter_active', $rec); 00734 } 00735 } 00736 00746 function filter_set_local_config($filter, $contextid, $name, $value) { 00747 global $DB; 00748 $rec = $DB->get_record('filter_config', array('filter' => $filter, 'contextid' => $contextid, 'name' => $name)); 00749 $insert = false; 00750 if (empty($rec)) { 00751 $insert = true; 00752 $rec = new stdClass; 00753 $rec->filter = $filter; 00754 $rec->contextid = $contextid; 00755 $rec->name = $name; 00756 } 00757 00758 $rec->value = $value; 00759 00760 if ($insert) { 00761 $DB->insert_record('filter_config', $rec); 00762 } else { 00763 $DB->update_record('filter_config', $rec); 00764 } 00765 } 00766 00775 function filter_unset_local_config($filter, $contextid, $name) { 00776 global $DB; 00777 $DB->delete_records('filter_config', array('filter' => $filter, 'contextid' => $contextid, 'name' => $name)); 00778 } 00779 00791 function filter_get_local_config($filter, $contextid) { 00792 global $DB; 00793 return $DB->get_records_menu('filter_config', array('filter' => $filter, 'contextid' => $contextid), '', 'name,value'); 00794 } 00795 00807 function filter_get_all_local_settings($contextid) { 00808 global $DB; 00809 $context = get_context_instance(CONTEXT_SYSTEM); 00810 return array( 00811 $DB->get_records('filter_active', array('contextid' => $contextid), 'filter', 'filter,active'), 00812 $DB->get_records('filter_config', array('contextid' => $contextid), 'filter,name', 'filter,name,value'), 00813 ); 00814 } 00815 00829 function filter_get_active_in_context($context) { 00830 global $DB, $FILTERLIB_PRIVATE; 00831 00832 if (!isset($FILTERLIB_PRIVATE)) { 00833 $FILTERLIB_PRIVATE = new stdClass(); 00834 } 00835 00836 // Use cache (this is a within-request cache only) if available. See 00837 // function filter_preload_activities. 00838 if (isset($FILTERLIB_PRIVATE->active) && 00839 array_key_exists($context->id, $FILTERLIB_PRIVATE->active)) { 00840 return $FILTERLIB_PRIVATE->active[$context->id]; 00841 } 00842 00843 $contextids = str_replace('/', ',', trim($context->path, '/')); 00844 00845 // The following SQL is tricky. It is explained on 00846 // http://docs.moodle.org/dev/Filter_enable/disable_by_context 00847 $sql = "SELECT active.filter, fc.name, fc.value 00848 FROM (SELECT f.filter, MAX(f.sortorder) AS sortorder 00849 FROM {filter_active} f 00850 JOIN {context} ctx ON f.contextid = ctx.id 00851 WHERE ctx.id IN ($contextids) 00852 GROUP BY filter 00853 HAVING MAX(f.active * " . $DB->sql_cast_2signed('ctx.depth') . 00854 ") > -MIN(f.active * " . $DB->sql_cast_2signed('ctx.depth') . ") 00855 ) active 00856 LEFT JOIN {filter_config} fc ON fc.filter = active.filter AND fc.contextid = $context->id 00857 ORDER BY active.sortorder"; 00858 $rs = $DB->get_recordset_sql($sql); 00859 00860 // Masssage the data into the specified format to return. 00861 $filters = array(); 00862 foreach ($rs as $row) { 00863 if (!isset($filters[$row->filter])) { 00864 $filters[$row->filter] = array(); 00865 } 00866 if (!is_null($row->name)) { 00867 $filters[$row->filter][$row->name] = $row->value; 00868 } 00869 } 00870 00871 $rs->close(); 00872 00873 return $filters; 00874 } 00875 00881 function filter_preload_activities(course_modinfo $modinfo) { 00882 global $DB, $FILTERLIB_PRIVATE; 00883 00884 if (!isset($FILTERLIB_PRIVATE)) { 00885 $FILTERLIB_PRIVATE = new stdClass(); 00886 } 00887 00888 // Don't repeat preload 00889 if (!isset($FILTERLIB_PRIVATE->preloaded)) { 00890 $FILTERLIB_PRIVATE->preloaded = array(); 00891 } 00892 if (!empty($FILTERLIB_PRIVATE->preloaded[$modinfo->get_course_id()])) { 00893 return; 00894 } 00895 $FILTERLIB_PRIVATE->preloaded[$modinfo->get_course_id()] = true; 00896 00897 // Get contexts for all CMs 00898 $cmcontexts = array(); 00899 $cmcontextids = array(); 00900 foreach ($modinfo->get_cms() as $cm) { 00901 $modulecontext = get_context_instance(CONTEXT_MODULE, $cm->id); 00902 $cmcontextids[] = $modulecontext->id; 00903 $cmcontexts[] = $modulecontext; 00904 } 00905 00906 // Get course context and all other parents... 00907 $coursecontext = get_context_instance(CONTEXT_COURSE, $modinfo->get_course_id()); 00908 $parentcontextids = explode('/', substr($coursecontext->path, 1)); 00909 $allcontextids = array_merge($cmcontextids, $parentcontextids); 00910 00911 // Get all filter_active rows relating to all these contexts 00912 list ($sql, $params) = $DB->get_in_or_equal($allcontextids); 00913 $filteractives = $DB->get_records_select('filter_active', "contextid $sql", $params); 00914 00915 // Get all filter_config only for the cm contexts 00916 list ($sql, $params) = $DB->get_in_or_equal($cmcontextids); 00917 $filterconfigs = $DB->get_records_select('filter_config', "contextid $sql", $params); 00918 00919 // Note: I was a bit surprised that filter_config only works for the 00920 // most specific context (i.e. it does not need to be checked for course 00921 // context if we only care about CMs) however basede on code in 00922 // filter_get_active_in_context, this does seem to be correct. 00923 00924 // Build course default active list. Initially this will be an array of 00925 // filter name => active score (where an active score >0 means it's active) 00926 $courseactive = array(); 00927 00928 // Also build list of filter_active rows below course level, by contextid 00929 $remainingactives = array(); 00930 00931 // Array lists filters that are banned at top level 00932 $banned = array(); 00933 00934 // Add any active filters in parent contexts to the array 00935 foreach ($filteractives as $row) { 00936 $depth = array_search($row->contextid, $parentcontextids); 00937 if ($depth !== false) { 00938 // Find entry 00939 if (!array_key_exists($row->filter, $courseactive)) { 00940 $courseactive[$row->filter] = 0; 00941 } 00942 // This maths copes with reading rows in any order. Turning on/off 00943 // at site level counts 1, at next level down 4, at next level 9, 00944 // then 16, etc. This means the deepest level always wins, except 00945 // against the -9999 at top level. 00946 $courseactive[$row->filter] += 00947 ($depth + 1) * ($depth + 1) * $row->active; 00948 00949 if ($row->active == TEXTFILTER_DISABLED) { 00950 $banned[$row->filter] = true; 00951 } 00952 } else { 00953 // Build list of other rows indexed by contextid 00954 if (!array_key_exists($row->contextid, $remainingactives)) { 00955 $remainingactives[$row->contextid] = array(); 00956 } 00957 $remainingactives[$row->contextid][] = $row; 00958 } 00959 } 00960 00961 // Chuck away the ones that aren't active 00962 foreach ($courseactive as $filter=>$score) { 00963 if ($score <= 0) { 00964 unset($courseactive[$filter]); 00965 } else { 00966 $courseactive[$filter] = array(); 00967 } 00968 } 00969 00970 // Loop through the contexts to reconstruct filter_active lists for each 00971 // cm on the course 00972 if (!isset($FILTERLIB_PRIVATE->active)) { 00973 $FILTERLIB_PRIVATE->active = array(); 00974 } 00975 foreach ($cmcontextids as $contextid) { 00976 // Copy course list 00977 $FILTERLIB_PRIVATE->active[$contextid] = $courseactive; 00978 00979 // Are there any changes to the active list? 00980 if (array_key_exists($contextid, $remainingactives)) { 00981 foreach ($remainingactives[$contextid] as $row) { 00982 if ($row->active > 0 && empty($banned[$row->filter])) { 00983 // If it's marked active for specific context, add entry 00984 // (doesn't matter if one exists already) 00985 $FILTERLIB_PRIVATE->active[$contextid][$row->filter] = array(); 00986 } else { 00987 // If it's marked inactive, remove entry (doesn't matter 00988 // if it doesn't exist) 00989 unset($FILTERLIB_PRIVATE->active[$contextid][$row->filter]); 00990 } 00991 } 00992 } 00993 } 00994 00995 // Process all config rows to add config data to these entries 00996 foreach ($filterconfigs as $row) { 00997 if (isset($FILTERLIB_PRIVATE->active[$row->contextid][$row->filter])) { 00998 $FILTERLIB_PRIVATE->active[$row->contextid][$row->filter][$row->name] = $row->value; 00999 } 01000 } 01001 } 01002 01015 function filter_get_available_in_context($context) { 01016 global $DB; 01017 01018 // The complex logic is working out the active state in the parent context, 01019 // so strip the current context from the list. 01020 $contextids = explode('/', trim($context->path, '/')); 01021 array_pop($contextids); 01022 $contextids = implode(',', $contextids); 01023 if (empty($contextids)) { 01024 throw new coding_exception('filter_get_available_in_context cannot be called with the system context.'); 01025 } 01026 01027 // The following SQL is tricky, in the same way at the SQL in filter_get_active_in_context. 01028 $sql = "SELECT parent_states.filter, 01029 CASE WHEN fa.active IS NULL THEN " . TEXTFILTER_INHERIT . " 01030 ELSE fa.active END AS localstate, 01031 parent_states.inheritedstate 01032 FROM (SELECT f.filter, MAX(f.sortorder) AS sortorder, 01033 CASE WHEN MAX(f.active * " . $DB->sql_cast_2signed('ctx.depth') . 01034 ") > -MIN(f.active * " . $DB->sql_cast_2signed('ctx.depth') . ") THEN " . TEXTFILTER_ON . " 01035 ELSE " . TEXTFILTER_OFF . " END AS inheritedstate 01036 FROM {filter_active} f 01037 JOIN {context} ctx ON f.contextid = ctx.id 01038 WHERE ctx.id IN ($contextids) 01039 GROUP BY f.filter 01040 HAVING MIN(f.active) > " . TEXTFILTER_DISABLED . " 01041 ) parent_states 01042 LEFT JOIN {filter_active} fa ON fa.filter = parent_states.filter AND fa.contextid = $context->id 01043 ORDER BY parent_states.sortorder"; 01044 return $DB->get_records_sql($sql); 01045 } 01046 01053 function filter_get_global_states() { 01054 global $DB; 01055 $context = get_context_instance(CONTEXT_SYSTEM); 01056 return $DB->get_records('filter_active', array('contextid' => $context->id), 'sortorder', 'filter,active,sortorder'); 01057 } 01058 01065 function filter_delete_all_for_filter($filter) { 01066 global $DB; 01067 if (substr($filter, 0, 7) == 'filter/') { 01068 unset_all_config_for_plugin('filter_' . basename($filter)); 01069 } 01070 $DB->delete_records('filter_active', array('filter' => $filter)); 01071 $DB->delete_records('filter_config', array('filter' => $filter)); 01072 } 01073 01079 function filter_delete_all_for_context($contextid) { 01080 global $DB; 01081 $DB->delete_records('filter_active', array('contextid' => $contextid)); 01082 $DB->delete_records('filter_config', array('contextid' => $contextid)); 01083 } 01084 01093 function filter_has_global_settings($filter) { 01094 global $CFG; 01095 $settingspath = $CFG->dirroot . '/' . $filter . '/filtersettings.php'; 01096 return is_readable($settingspath); 01097 } 01098 01105 function filter_has_local_settings($filter) { 01106 global $CFG; 01107 $settingspath = $CFG->dirroot . '/' . $filter . '/filterlocalsettings.php'; 01108 return is_readable($settingspath); 01109 } 01110 01118 function filter_context_may_have_filter_settings($context) { 01119 return $context->contextlevel != CONTEXT_BLOCK && $context->contextlevel != CONTEXT_USER; 01120 } 01121 01133 function filter_phrases($text, &$link_array, $ignoretagsopen=NULL, $ignoretagsclose=NULL, 01134 $overridedefaultignore=false) { 01135 01136 global $CFG; 01137 01138 static $usedphrases; 01139 01140 $ignoretags = array(); //To store all the enclosig tags to be completely ignored 01141 $tags = array(); //To store all the simple tags to be ignored 01142 01143 if (!$overridedefaultignore) { 01144 // A list of open/close tags that we should not replace within 01145 // Extended to include <script>, <textarea>, <select> and <a> tags 01146 // Regular expression allows tags with or without attributes 01147 $filterignoretagsopen = array('<head>' , '<nolink>' , '<span class="nolink">', 01148 '<script(\s[^>]*?)?>', '<textarea(\s[^>]*?)?>', 01149 '<select(\s[^>]*?)?>', '<a(\s[^>]*?)?>'); 01150 $filterignoretagsclose = array('</head>', '</nolink>', '</span>', 01151 '</script>', '</textarea>', '</select>','</a>'); 01152 } else { 01153 // Set an empty default list 01154 $filterignoretagsopen = array(); 01155 $filterignoretagsclose = array(); 01156 } 01157 01158 // Add the user defined ignore tags to the default list 01159 if ( is_array($ignoretagsopen) ) { 01160 foreach ($ignoretagsopen as $open) { 01161 $filterignoretagsopen[] = $open; 01162 } 01163 foreach ($ignoretagsclose as $close) { 01164 $filterignoretagsclose[] = $close; 01165 } 01166 } 01167 01171 $filterinvalidprefixes = '([^\W_])'; 01172 $filterinvalidsuffixes = '([^\W_])'; 01173 01175 $text = preg_replace('/([#*%])/','\1\1',$text); 01176 01177 01179 filter_save_ignore_tags($text,$filterignoretagsopen,$filterignoretagsclose,$ignoretags); 01180 01182 filter_save_tags($text,$tags); 01183 01185 $size = sizeof($link_array); 01186 for ($n=0; $n < $size; $n++) { 01187 $linkobject =& $link_array[$n]; 01188 01191 if (empty($linkobject->phrase)) { 01192 continue; 01193 } 01194 01196 $intcurrent = intval($linkobject->phrase); 01197 if (!empty($intcurrent) && strval($intcurrent) == $linkobject->phrase && $intcurrent < 1000) { 01198 continue; 01199 } 01200 01202 if (!$linkobject->work_calculated) { 01203 if (!isset($linkobject->hreftagbegin) or !isset($linkobject->hreftagend)) { 01204 $linkobject->work_hreftagbegin = '<span class="highlight"'; 01205 $linkobject->work_hreftagend = '</span>'; 01206 } else { 01207 $linkobject->work_hreftagbegin = $linkobject->hreftagbegin; 01208 $linkobject->work_hreftagend = $linkobject->hreftagend; 01209 } 01210 01213 $linkobject->work_hreftagbegin = preg_replace('/([#*%])/','\1\1',$linkobject->work_hreftagbegin); 01214 01215 if (empty($linkobject->casesensitive)) { 01216 $linkobject->work_casesensitive = false; 01217 } else { 01218 $linkobject->work_casesensitive = true; 01219 } 01220 if (empty($linkobject->fullmatch)) { 01221 $linkobject->work_fullmatch = false; 01222 } else { 01223 $linkobject->work_fullmatch = true; 01224 } 01225 01227 $linkobject->work_phrase = strip_tags($linkobject->phrase); 01228 01231 $linkobject->work_phrase = preg_replace('/([#*%])/','\1\1',$linkobject->work_phrase); 01232 01234 if ($linkobject->replacementphrase) { //We have specified a replacement phrase 01236 $linkobject->work_replacementphrase = strip_tags($linkobject->replacementphrase); 01237 } else { //The replacement is the original phrase as matched below 01238 $linkobject->work_replacementphrase = '$1'; 01239 } 01240 01242 $linkobject->work_phrase = preg_quote($linkobject->work_phrase, '/'); 01243 01245 $linkobject->work_calculated = true; 01246 01247 } 01248 01250 if (!empty($CFG->filtermatchoneperpage)) { 01251 if (!empty($usedphrases) && in_array($linkobject->work_phrase,$usedphrases)) { 01252 continue; 01253 } 01254 } 01255 01257 $modifiers = ($linkobject->work_casesensitive) ? 's' : 'isu'; // works in unicode mode! 01258 01261 if ($linkobject->work_fullmatch) { 01262 $notfullmatches = array(); 01263 $regexp = '/'.$filterinvalidprefixes.'('.$linkobject->work_phrase.')|('.$linkobject->work_phrase.')'.$filterinvalidsuffixes.'/'.$modifiers; 01264 01265 preg_match_all($regexp,$text,$list_of_notfullmatches); 01266 01267 if ($list_of_notfullmatches) { 01268 foreach (array_unique($list_of_notfullmatches[0]) as $key=>$value) { 01269 $notfullmatches['<*'.$key.'*>'] = $value; 01270 } 01271 if (!empty($notfullmatches)) { 01272 $text = str_replace($notfullmatches,array_keys($notfullmatches),$text); 01273 } 01274 } 01275 } 01276 01278 if (!empty($CFG->filtermatchonepertext) || !empty($CFG->filtermatchoneperpage)) { 01279 $resulttext = preg_replace('/('.$linkobject->work_phrase.')/'.$modifiers, 01280 $linkobject->work_hreftagbegin. 01281 $linkobject->work_replacementphrase. 01282 $linkobject->work_hreftagend, $text, 1); 01283 } else { 01284 $resulttext = preg_replace('/('.$linkobject->work_phrase.')/'.$modifiers, 01285 $linkobject->work_hreftagbegin. 01286 $linkobject->work_replacementphrase. 01287 $linkobject->work_hreftagend, $text); 01288 } 01289 01290 01292 if ($resulttext != $text) { 01294 $text = $resulttext; 01296 filter_save_ignore_tags($text,$filterignoretagsopen,$filterignoretagsclose,$ignoretags); 01298 filter_save_tags($text,$tags); 01300 if (!empty($CFG->filtermatchoneperpage)) { 01301 $usedphrases[] = $linkobject->work_phrase; 01302 } 01303 } 01304 01305 01307 if (!empty($notfullmatches)) { 01308 $text = str_replace(array_keys($notfullmatches),$notfullmatches,$text); 01309 unset($notfullmatches); 01310 } 01311 } 01312 01314 01315 if (!empty($tags)) { 01316 $text = str_replace(array_keys($tags), $tags, $text); 01317 } 01318 01319 if (!empty($ignoretags)) { 01320 $ignoretags = array_reverse($ignoretags); 01321 $text = str_replace(array_keys($ignoretags),$ignoretags,$text); 01322 } 01323 01325 $text = preg_replace('/([#*%])(\1)/','\1',$text); 01326 01328 $text = filter_add_javascript($text); 01329 01330 01331 return $text; 01332 } 01333 01339 function filter_remove_duplicates($linkarray) { 01340 01341 $concepts = array(); // keep a record of concepts as we cycle through 01342 $lconcepts = array(); // a lower case version for case insensitive 01343 01344 $cleanlinks = array(); 01345 01346 foreach ($linkarray as $key=>$filterobject) { 01347 if ($filterobject->casesensitive) { 01348 $exists = in_array($filterobject->phrase, $concepts); 01349 } else { 01350 $exists = in_array(moodle_strtolower($filterobject->phrase), $lconcepts); 01351 } 01352 01353 if (!$exists) { 01354 $cleanlinks[] = $filterobject; 01355 $concepts[] = $filterobject->phrase; 01356 $lconcepts[] = moodle_strtolower($filterobject->phrase); 01357 } 01358 } 01359 01360 return $cleanlinks; 01361 } 01362 01374 function filter_save_ignore_tags(&$text, $filterignoretagsopen, $filterignoretagsclose, &$ignoretags) { 01375 01377 foreach ($filterignoretagsopen as $ikey=>$opentag) { 01378 $closetag = $filterignoretagsclose[$ikey]; 01380 $opentag = str_replace('/','\/',$opentag); // delimit forward slashes 01381 $closetag = str_replace('/','\/',$closetag); // delimit forward slashes 01382 $pregexp = '/'.$opentag.'(.*?)'.$closetag.'/is'; 01383 01384 preg_match_all($pregexp, $text, $list_of_ignores); 01385 foreach (array_unique($list_of_ignores[0]) as $key=>$value) { 01386 $prefix = (string)(count($ignoretags) + 1); 01387 $ignoretags['<#'.$prefix.TEXTFILTER_EXCL_SEPARATOR.$key.'#>'] = $value; 01388 } 01389 if (!empty($ignoretags)) { 01390 $text = str_replace($ignoretags,array_keys($ignoretags),$text); 01391 } 01392 } 01393 } 01394 01403 function filter_save_tags(&$text, &$tags) { 01404 01405 preg_match_all('/<([^#%*].*?)>/is',$text,$list_of_newtags); 01406 foreach (array_unique($list_of_newtags[0]) as $ntkey=>$value) { 01407 $prefix = (string)(count($tags) + 1); 01408 $tags['<%'.$prefix.TEXTFILTER_EXCL_SEPARATOR.$ntkey.'%>'] = $value; 01409 } 01410 if (!empty($tags)) { 01411 $text = str_replace($tags,array_keys($tags),$text); 01412 } 01413 } 01414 01421 function filter_add_javascript($text) { 01422 global $CFG; 01423 01424 if (stripos($text, '</html>') === FALSE) { 01425 return $text; // this is not a html file 01426 } 01427 if (strpos($text, 'onclick="return openpopup') === FALSE) { 01428 return $text; // no popup - no need to add javascript 01429 } 01430 $js =" 01431 <script type=\"text/javascript\"> 01432 <!-- 01433 function openpopup(url,name,options,fullscreen) { 01434 fullurl = \"".$CFG->httpswwwroot."\" + url; 01435 windowobj = window.open(fullurl,name,options); 01436 if (fullscreen) { 01437 windowobj.moveTo(0,0); 01438 windowobj.resizeTo(screen.availWidth,screen.availHeight); 01439 } 01440 windowobj.focus(); 01441 return false; 01442 } 01443 // --> 01444 </script>"; 01445 if (stripos($text, '</head>') !== FALSE) { 01446 //try to add it into the head element 01447 $text = str_ireplace('</head>', $js.'</head>', $text); 01448 return $text; 01449 } 01450 01451 //last chance - try adding head element 01452 return preg_replace("/<html.*?>/is", "\\0<head>".$js.'</head>', $text); 01453 }