diff --git a/classes/text_filter.php b/classes/text_filter.php new file mode 100644 index 0000000..b5db92c --- /dev/null +++ b/classes/text_filter.php @@ -0,0 +1,5357 @@ +. + +namespace filter_filtercodes; + +/** + * Main filter code for FilterCodes. + * + * @package filter_filtercodes + * @copyright 2017-2024 TNG Consulting Inc. - www.tngconsulting.ca + * @author Michael Milette + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use block_online_users\fetcher; +use \core_table\local\filter\integer_filter; +use \core_user\table\participants_filterset; +use \core_user\table\participants_search; +use Endroid\QrCode\QrCode; + +require_once($CFG->dirroot . '/course/renderer.php'); + +/** + * Extends the moodle_text_filter class to provide plain text support for new tags. + * + * @copyright 2017-2024 TNG Consulting Inc. - www.tngconsulting.ca + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class text_filter extends \core_filters\text_filter { + /** @var object $archetypes Object array of Moodle archetypes. */ + public $archetypes = []; + /** @var array $customroles array of Roles key is shortname and value is the id */ + private static $customroles = []; + /** + * @var array $customrolespermissions array of Roles key is shortname + context_id and the value is a boolean showing if + * user is allowed + */ + private static $customrolespermissions = []; + + /** + * Constructor: Get the role IDs associated with each of the archetypes. + */ + public function __construct() { + + // Note: This array must correspond to the one in function hasminarchetype. + $archetypelist = ['manager' => 1, 'coursecreator' => 2, 'editingteacher' => 3, 'teacher' => 4, 'student' => 5]; + foreach ($archetypelist as $archetype => $level) { + $roleids = []; + // Build array of roles. + foreach (get_archetype_roles($archetype) as $role) { + $roleids[] = $role->id; + } + $this->archetypes[$archetype] = (object) ['level' => $level, 'roleids' => $roleids]; + } + } + + /** + * Determine if any of the user's roles includes specified archetype. + * + * @param string $archetype Name of archetype. + * @return boolean Does: true, Does not: false. + */ + private function hasarchetype($archetype) { + // If not logged in or is just a guestuser, definitely doesn't have the archetype we want. + if (!isloggedin() || isguestuser()) { + return false; + } + + // Handle caching of results. + static $archetypes = []; + if (isset($archetypes[$archetype])) { + return $archetypes[$archetype]; + } + + global $USER, $PAGE; + $archetypes[$archetype] = false; + if (is_role_switched($PAGE->course->id)) { // Has switched roles. + $context = \context_course::instance($PAGE->course->id); + $id = $USER->access['rsw'][$context->path]; + $archetypes[$archetype] = in_array($id, $this->archetypes[$archetype]->roleids); + } else { + // For each of the roles associated with the archetype, check if the user has one of the roles. + foreach ($this->archetypes[$archetype]->roleids as $roleid) { + if (user_has_role_assignment($USER->id, $roleid, $PAGE->context->id)) { + $archetypes[$archetype] = true; + } + } + } + return $archetypes[$archetype]; + } + + /** + * Determine if the user only has a specified archetype amongst the user's role and no others. + * Example: Can be a student but not also be a teacher or manager. + * + * @param string $archetype Name of archetype. + * @return boolean Does: true, Does not: false. + */ + private function hasonlyarchetype($archetype) { + if ($this->hasarchetype($archetype)) { + $archetypes = array_keys($this->archetypes); + foreach ($archetypes as $archetypename) { + if ($archetypename != $archetype && $this->hasarchetype($archetypename)) { + return false; + } + } + global $PAGE; + if (is_role_switched($PAGE->course->id)) { + // Ignore site admin status if we have switched roles. + return true; + } else { + return is_siteadmin(); + } + } + return false; + } + + /** + * Determine if the user has the specified archetype or one with elevated capabilities. + * Example: Can be a teacher, course creator, manager or Administrator but not a student. + * + * @param string $minarchetype Name of archetype. + * @return boolean User meets minimum archetype requirement: true, does not: false. + */ + private function hasminarchetype($minarchetype) { + // Note: This array must start with one blank entry followed by the same list found in in __construct(). + $archetypelist = ['', 'manager', 'coursecreator', 'editingteacher', 'teacher', 'student']; + // For each archetype level between the one specified and 'manager'. + for ($level = $this->archetypes[$minarchetype]->level; $level >= 1; $level--) { + // Check to see if any of the user's roles correspond to the archetype. + if ($this->hasarchetype($archetypelist[$level])) { + return true; + } + } + // Return true regardless of the archetype if we are an administrator and not in a switched role. + global $PAGE; + return !is_role_switched($PAGE->course->id) && is_siteadmin(); + } + + /** + * Checks if a user has a custom role or not within the current context. + * + * @param string $roleshortname The role's shortname. + * @param integer $contextid The context where the tag appears. + * @return boolean True if user has custom role, otherwise, false. + */ + private function hascustomrole($roleshortname, $contextid = 0) { + $keytocheck = $roleshortname . '-' . $contextid; + if (!isset(self::$customrolespermissions[$keytocheck])) { + global $USER, $DB; + if (!isset(self::$customroles[$roleshortname])) { + self::$customroles[$roleshortname] = $DB->get_field('role', 'id', ['shortname' => $roleshortname]); + } + $hasrole = false; + if (self::$customroles[$roleshortname]) { + $hasrole = user_has_role_assignment($USER->id, self::$customroles[$roleshortname], $contextid); + } + self::$customrolespermissions[$keytocheck] = $hasrole; + } + + return self::$customrolespermissions[$keytocheck]; + } + + /** + * Determine if the specified user has the specified role anywhere in the system. + * + * @param string $roleshortname Role shortname. + * @param integer $userid The user's ID. + * @return boolean True if the user has the role, false if they do not. + */ + private function hasarole($roleshortname, $userid) { + // Cache list of user's roles. + static $list; + + if (!isset($list)) { + // Not cached yet? We can take care of that. + $list = []; + if (isloggedin() && !isguestuser()) { + // We only track logged-in roles. + global $DB; + // Retrieve list of role names. + $rolenames = $DB->get_records('role'); + // Retrieve list of my roles across all contexts. + $userroles = $DB->get_records('role_assignments', ['userid' => $userid]); + // For each of my roles, add the roll name to the list. + foreach ($userroles as $role) { + if (!empty($rolenames[$role->roleid]->shortname)) { + // There should always be a role name for each role id but you can't be too careful these days. + $list[] = $rolenames[$role->roleid]->shortname; + } + } + $list = array_unique($list); + if (is_siteadmin()) { + // Admin is not an actual role, but we can use our imagination for convenience. + $list[] = 'administrator'; + } + } + } + return in_array(strtolower($roleshortname), $list); + } + + /** + * Returns the URL of a blank Avatar as a square image. + * + * @param integer $size Width of desired image in pixels. + * @return MOODLE_URL URL to image of avatar image. + */ + private function getblankavatarurl($size) { + global $PAGE, $CFG; + $img = 'u/' . ($size > 100 ? 'f3' : ($size > 35 ? 'f1' : 'f2')); + $renderer = $PAGE->get_renderer('core'); + if ($CFG->branch >= 33) { + $url = $renderer->image_url($img); + } else { + $url = $renderer->pix_url($img); // Deprecated as of Moodle 3.3. + } + return (new \moodle_url($url))->out(); + } + + /** + * Retrieves the URL for the user's profile picture, if one is available. + * + * @param object $user The Moodle user object for which we want a photo. + * @param mixed $size Can be sm|md|lg or an integer 2|1|3 or an integer size in pixels > 3. + * @return string URL to the photo image file but with $1 for the size. + */ + private function getprofilepictureurl($user, $size = 'md') { + global $PAGE; + + $sizes = ['sm' => 35, '2' => 35, 'md' => 100, '1' => 100, 'lg' => 512, '3' => 512]; + if (empty($px = $sizes[$size])) { + $px = $size; // Size was specified in pixels. + } + $userpicture = new user_picture($user); + $userpicture->size = $px; // Size in pixels. + $url = $userpicture->get_url($PAGE); + return $url; + } + + /** + * Retrieves specified profile fields for a user. + * + * This method fetches and caches user profile fields from the database. If the fields have + * already been retrieved for the current request, the cached values are returned. This method + * supports fetching both core and custom user profile fields. + * + * @param object $user The user object whose profile fields are being retrieved. + * @param array $fields optional An array of field names to retrieve. If empty/not specified, only custom fields are retrieved. + * @return array An associative array of field names and their values for the specified user. + */ + private function getuserprofilefields($user, $fields = []) { + global $DB; + + static $profilefields; + static $lastfields; + + // If we have already cached the profile fields and data, return them. + if (isset($profilefields) && $lastfields == $fields) { + return $profilefields; + } + + $profilefields = []; + if (!isloggedin()) { + return $profilefields; + } + + // Get custom user profile fields, their value and visibilit. Only works for authenticated users. + $sql = "SELECT f.shortname, f.visible, f.datatype, COALESCE(d.data, '') AS value + FROM {user_info_field} f + LEFT JOIN {user_info_data} d ON f.id = d.fieldid AND d.userid = :userid + ORDER BY f.shortname;"; + $params = ['userid' => $user->id]; + // Determine if restricted to only visible fields. + if (!empty(get_config('filter_filtercodes', 'ifprofilefiedonlyvisible'))) { + $params['visible'] = 1; + } + $profilefields = $DB->get_records_sql($sql, $params); + + // Add core user profile fields. + foreach ($fields as $field) { + // Skip fields that don't exist (likely a typo). + if (isset($user->$field)) { + $profilefields[$field] = (object)['shortname' => $field, 'visible' => '1', + 'datatype' => 'text', 'value' => $user->$field]; + } + } + $lastfields = $fields; + + return $profilefields; + } + + /** + * Retrieves the user's groupings for a course. + * + * @param integer $courseid The course ID. + * @param integer $userid The user ID. + * @return array An array of groupings for the specified user in the specified course. + */ + private function getusergroupings($courseid, $userid) { + global $DB; + + return $DB->get_records_sql('SELECT gp.id, gp.name, gp.idnumber + FROM {user} u + INNER JOIN {groups_members} gm ON u.id = gm.userid + INNER JOIN {groups} g ON g.id = gm.groupid + INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid + INNER JOIN {groupings} gp ON gp.id = gg.groupingid + WHERE g.courseid = ? AND u.id = ? + GROUP BY gp.id + ORDER BY gp.name ASC', [$courseid, $userid]); + } + + /** + * Determine if running on http or https. Same as Moodle's is_https() except that it is backwards compatible to Moodle 2.7. + * + * @return boolean true if protocol is https, false if http. + */ + private function ishttps() { + global $CFG; + if ($CFG->branch >= 28) { + $ishttps = is_https(); // Available as of Moodle 2.8. + } else { + $ishttps = (filter_input(INPUT_SERVER, 'HTTPS') === 'on'); + } + return $ishttps; + } + + /** + * Determine if access is from a web service. + * + * @return boolean true if a web service, false if web browser. + */ + private function iswebservice() { + global $ME; + // If this is a web service or the Moodle mobile app... + $isws = (WS_SERVER || (strstr($ME, "webservice/") !== false && optional_param('token', '', PARAM_ALPHANUM))); + return $isws; + } + + /** + * Generates HTML code for a reCAPTCHA. + * + * @return string HTML Code for reCAPTCHA or blank if logged-in or Moodle reCAPTCHA is not configured. + */ + private function getrecaptcha() { + global $CFG; + // Is user not logged-in or logged-in as guest? + if (!isloggedin() || isguestuser()) { + // If Moodle reCAPTCHA configured. + if (!empty($CFG->recaptchaprivatekey) && !empty($CFG->recaptchapublickey)) { + // Yes? Generate reCAPTCHA. + if (file_exists($CFG->libdir . '/recaptchalib_v2.php')) { + // For reCAPTCHA 2.0. + require_once($CFG->libdir . '/recaptchalib_v2.php'); + return recaptcha_get_challenge_html(RECAPTCHA_API_URL, $CFG->recaptchapublickey); + } else { + // For reCAPTCHA 1.0. + require_once($CFG->libdir . '/recaptchalib.php'); + return recaptcha_get_html($CFG->recaptchapublickey, null, $this->ishttps()); + } + } else if ($CFG->debugdisplay == 1) { // If debugging is set to DEVELOPER... + // Show indicator that {reCAPTCHA} tag is not required. + return 'Warning: The reCAPTCHA tag is not required here.'; + } + } + // Logged-in as non-guest user (reCAPTCHA is not required) or Moodle reCAPTCHA not configured. + // Don't generate reCAPTCHA. + return ''; + } + + /** + * Scrape HTML (callback) + * + * Extract content from another web page. + * Example: Can be used to extract a shared privacy policy across your websites. + * + * @param string $url URL address of content source. + * @param string $tag HTML tag that contains the information we want to retrieve. + * @param string $class (optional) HTML tag class attribute we should match. + * @param string $id (optional) HTML tag id attribute we should match. + * @param string $code (optional) any URL encoded HTML code you want to insert after the retrieved content. + * @return string Extracted content+optional code. If content is unavailable, returns message to contact webmaster. + */ + private function scrapehtml($url, $tag = '', $class = '', $id = '', $code = '') { + // Retrieve content. If the URL fails, return a message. + $content = @file_get_contents($url); + if (empty($content)) { + return get_string('contentmissing', 'filter_filtercodes'); + } + + // Disable warnings. + $libxmlpreviousstate = libxml_use_internal_errors(true); + + // Load content into DOM object. + $dom = new DOMDocument(); + $dom->loadHTML($content); + + // Clear suppressed warnings. + libxml_clear_errors(); + libxml_use_internal_errors($libxmlpreviousstate); + + // Scrape out the content we want. If not found, return everything. + $xpath = new DOMXPath($dom); + + // If a tag was not specified. + if (empty($tag)) { + $tag .= '*'; // Match any tag. + } + $query = "//{$tag}"; + + // If a class was specified. + if (!empty($class)) { + $query .= "[@class=\"{$class}\"]"; + } + + // If an id was specified. + if (!empty($id)) { + $query .= "[@id=\"{$id}\"]"; + } + + $tag = $xpath->query($query); + $tag = $tag->item(0); + + return $dom->saveXML($tag) . urldecode($code); + } + + /** + * Convert a number of bytes (e.g. filesize) into human readable format. + * + * @param float $bytes Raw number of bytes. + * @return string Bytes in human readable format. + */ + private function humanbytes($bytes) { + if ($bytes === false || $bytes < 0 || is_null($bytes) || $bytes > 1.0E+26) { + // If invalid number of bytes, or value is more than about 84,703.29 Yottabyte (YB), assume it is infinite. + $str = '∞'; // Could not determine, assume infinite. + } else { + static $unit; + if (!isset($unit)) { + $units = ['sizeb', 'sizekb', 'sizemb', 'sizegb', 'sizetb', 'sizeeb', 'sizezb', 'sizeyb']; + $units = get_strings($units, 'filter_filtercodes'); + $units = array_values((array) $units); + } + $base = 1024; + $factor = min((int) log($bytes, $base), count($units) - 1); + $precision = [0, 2, 2, 1, 1, 1, 1, 0]; + $str = sprintf("%1.{$precision[$factor]}f", $bytes / pow($base, $factor)) . ' ' . $units[$factor]; + } + return $str; + } + + /** + * Correctly format a list as "A, B and C". + * + * @param array $list An array of numbers or strings. + * @return string The formatted string. + */ + private function formatlist($list) { + // Save and remove last item in list from array. + $last = array_pop($list); + if ($list) { + // Combine list using language list separator. + $list = implode(get_string('listsep', 'langconfig') . ' ', $list); + // Add last item separated by " and ". + $string = get_string('and', 'moodle', ['one' => $list, 'two' => $last]); + } else { + // Only one item in the list. No formatting required. + $string = $last; + } + return $string; + } + + /** + * Convert string containg one or more attribute="value" pairs into an associative array. + * + * @param string $attrs One or more attribute="value" pairs. + * @return array Associative array of attributes and values. + */ + private function attribstoarray($attrs) { + $arr = []; + + if (preg_match_all('/\s*(?:([a-z0-9-]+)\s*=\s*"([^"]*)")|(?:\s+([a-z0-9-]+)(?=\s*|>|\s+[a..z0-9]+))/i', $attrs, $matches)) { + // For each attribute in the string, add associated value to the array. + for ($i = 0; $i < count($matches[0]); $i++) { + if ($matches[3][$i]) { + $arr[$matches[3][$i]] = null; + } else { + $arr[$matches[1][$i]] = $matches[2][$i]; + } + } + } + return $arr; + } + + /** + * Render cards for provided category. + * + * @param object $category Category object. + * @param boolean $categoryshowpic Set to true to display a category image. False displays no image. + * @return string HTML rendering of category cars. + */ + private function rendercategorycard($category, $categoryshowpic) { + global $OUTPUT; + + if (!$category->visible) { + $dimmed = 'opacity: 0.5;'; + } else { + $dimmed = ''; + } + + $url = (new \moodle_url('/course/index.php', ['categoryid' => $category->id]))->out(); + if ($categoryshowpic) { + $imgurl = $OUTPUT->get_generated_image_for_id($category->id + 65535); + $html = '
  • + +
    +
    ' + . $category->name . '
    '; + } else { + $html = '
  • ' . + '' . $category->name; + } + $html .= '
  • ' . PHP_EOL; + return $html; + } + + /** + * Render course cards for list of course ids. Not visible for hidden courses or if it has expired. + * + * @param array $rcourseids Array of course ids. + * @param string $format orientation/layout of course cards. + * @return string HTML of course cars. + */ + private function rendercoursecards($rcourseids, $format = 'vertical') { + global $CFG, $OUTPUT, $PAGE, $SITE; + + $content = ''; + $isadmin = (is_siteadmin() && !is_role_switched($PAGE->course->id)); + + foreach ($rcourseids as $courseid) { + if ($courseid == $SITE->id) { // Skip site. + continue; + } + $course = get_course($courseid); + $context = \context_course::instance($course->id); + // Course will be displayed if its visibility is set to Show AND (either has no end date OR a future end date). + $visible = ($course->visible && (empty($course->enddate) || time() < $course->enddate)); + // Courses not visible will be still visible to site admins or users with viewhiddencourses capability. + if (!$visible && !($isadmin || has_capability('moodle/course:viewhiddencourses', $context))) { + // Skip if the course is not visible to user or course is the "site". + continue; + } + + // Load image from course image. If none, generate a course image based on the course ID. + $context = \context_course::instance($courseid); + if ($course instanceof stdClass) { + $course = new \core_course_list_element($course); + } + $coursefiles = $course->get_course_overviewfiles(); + $imgurl = ''; + if ($CFG->branch >= 311) { + $imgurl = \core_course\external\course_summary_exporter::get_course_image($course); + } else { // Previous to Moodle 3.11. + foreach ($coursefiles as $file) { + if ($isimage = $file->is_valid_image()) { + // The file_encode_url() function is deprecated as per MDL-31071 but still in wide use. + $imgurl = file_encode_url("/pluginfile.php", '/' . $file->get_contextid() . '/' + . $file->get_component() . '/' . $file->get_filearea() . $file->get_filepath() + . $file->get_filename(), !$isimage); + $imgurl = (new \moodle_url($imgurl))->out(); + break; + } + } + } + if (empty($imgurl)) { + $imgurl = $OUTPUT->get_generated_image_for_id($courseid); + } + $courseurl = (new \moodle_url('/course/view.php', ['id' => $courseid]))->out(); + + switch ($format) { + case 'vertical': + $content .= ' +
    + +
    +
    ' . get_string('course') . ': ' + . $course->get_formatted_name() . '
    +
    +
    + '; + break; + case 'horizontal': + global $DB; + $category = $DB->get_record('course_categories', ['id' => $course->category]); + $category = $category->name; + + $summary = $course->summary == null ? '' : $course->summary; + $summary = substr($summary, -4) == '
    ' ? substr($summary, 0, strlen($summary) - 4) : $summary; + + $content .= ' +
    +
    +
    + +
    +
    +
    +

    + ' + . get_string('category') . ': ' . $category . + ' +

    +

    + + ' . get_string('course') . ': ' + . $course->get_formatted_name() . + ' +

    +
    ' + . get_string('summary') . ': ' . $summary . + '
    +
    +
    +
    +
    + '; + break; + case 'table': + global $DB; + $category = $DB->get_record('course_categories', ['id' => $course->category]); + $category = $category->name; + + $course->summary == null ? '' : $course->summary; + $summary = substr($summary, -4) == '
    ' ? substr($summary, 0, strlen($summary) - 4) : $summary; + + $content .= ' + + ' . $course->get_formatted_name() . ' + ' . $category . ' + ' . $summary . ' + + '; + break; + } + } + return $content; + } + + /** + * Get course card including format, header and footer. + * + * @param string $format card format. + * @return object $cards->format, $cards->header, $cards->footer + */ + private function getcoursecardinfo($format = null) { + static $cards; + if (is_object($cards)) { + return $cards; + } + $cards = new stdClass(); + if (empty($format)) { + $cards->format = get_config('filter_filtercodes', 'coursecardsformat'); + } else { + $cards->format = $format; + } + switch ($cards->format) { + case 'table': + $cards->header = ' + + + + + + + + + + '; + $cards->footer = ' + +
    ' . get_string('course') . '' . get_string('category') . '' . get_string('description') . '
    '; + break; + case 'horizontal': + $cards->header = '
    '; + $cards->footer = '
    '; + break; + default: + $cards->format = 'vertical'; + $cards->header = '
    '; + $cards->footer = '
    '; + } + return $cards; + } + + /** + * Generate a user link of a specified type if logged-in. + * + * @param string $clinktype Type of link to generate. Options include: email, message, profile, phone1. + * @param object $user A user object. + * @param string $name The name to be displayed. + * + * @return string Generated link. + */ + private function userlink($clinktype, $user, $name) { + if (!isloggedin() || isguestuser()) { + $clinktype = ''; // No link, only name. + } + switch ($clinktype) { + case 'email': + $link = '' . $name . ''; + break; + case 'message': + $link = '' . $name . ''; + break; + case 'profile': + $link = '' . $name . ''; + break; + case 'phone1': + if (!empty($user->phone1)) { + $link = '' . $name . ''; + } else { + $link = $name; + } + break; + default: + $link = $name; + } + return $link; + } + + /** + * Generate base64 encoded data img of QR Code. + * + * @param string $text Text to be encoded. + * @param string $label Label to display below QR code. + * @return string Base64 encoded data image. + */ + private function qrcode($text, $label = '') { + if (empty($text)) { + return ''; + } + global $CFG; + require_once($CFG->dirroot . '/filter/filtercodes/thirdparty/QrCode/src/QrCode.php'); + $code = new QrCode(); + $code->setText($text); + $code->setErrorCorrection('high'); + $code->setPadding(0); + $code->setSize(480); + $code->setLabelFontSize(16); + $code->setLabel($label); + $src = 'data:image/png;base64,' . base64_encode($code->get('png')); + return $src; + } + + /** + * Course completion progress percentage. + * + * @return int completion progress percentage + */ + private function completionprogress() { + static $progresspercent; + if (!isset($progresspercent)) { + global $PAGE; + $course = $PAGE->course; + $progresspercent = -1; // Disabled: -1. + if ( + $course->enablecompletion == 1 + && isloggedin() + && !isguestuser() + && \context_system::instance() != 'page-site-index' + ) { + $progresspercent = (int) \core_completion\progress::get_course_progress_percentage($course); + } + } + + return $progresspercent; + } + + /** + * Generator Tags + * + * This function processes tags that generate content that could potentially include additional tags. + * + * @param string $text The unprocessed text. Passed by refernce. + * @return boolean True of there are more tags to be processed, otherwise false. + */ + private function generatortags(&$text) { + global $CFG, $PAGE, $DB; + + $replace = []; // Array of key/value filterobjects. + + // If there are {menu...} tags. + if (stripos($text, '{menu') !== false) { + // Tag: {menuadmin}. + // Description: Displays a menu of useful links for site administrators when added to the custom menu. + // Parameters: None. + if (stripos($text, '{menuadmin}') !== false) { + $theme = $PAGE->theme->name; + $menu = ''; + if ($this->hasminarchetype('editingteacher')) { + $menu .= '{fa fa-wrench} {getstring}admin{/getstring}' . PHP_EOL; + } + if ($this->hasminarchetype('coursecreator')) { // If a course creator or above. + $menu .= '-{getstring}administrationsite{/getstring}|/admin/search.php' . PHP_EOL; + $menu .= '-{toggleeditingmenu}' . PHP_EOL; + $menu .= '-Moodle Academy|https://moodle.academy/' . PHP_EOL; + $menu .= '-###' . PHP_EOL; + } + if ($this->hasminarchetype('manager')) { // If a manager or above. + $menu .= '-{getstring}user{/getstring}: {getstring:admin}usermanagement{/getstring}|/admin/user.php' . PHP_EOL; + $menu .= '{ifminsitemanager}' . PHP_EOL; + $menu .= '-{getstring}user{/getstring}: {getstring:mnet}profilefields{/getstring}|/user/profile/index.php' . + PHP_EOL; + $menu .= '-###' . PHP_EOL; + $menu .= '{/ifminsitemanager}' . PHP_EOL; + $menu .= '-{getstring}course{/getstring}: {getstring:admin}coursemgmt{/getstring}|/course/management.php' . + '?categoryid={categoryid}' . PHP_EOL; + $menu .= '-{getstring}course{/getstring}: {getstring}new{/getstring}|/course/edit.php' . + '?category={categoryid}&returnto=topcat' . PHP_EOL; + $menu .= '-{getstring}course{/getstring}: {getstring}searchcourses{/getstring}|/course/search.php' . PHP_EOL; + } + if ($this->hasminarchetype('editingteacher')) { + $menu .= '-{getstring}course{/getstring}: {getstring}restore{/getstring}|/backup/restorefile.php' . + '?contextid={coursecontextid}' . PHP_EOL; + $menu .= '{ifincourse}' . PHP_EOL; + $menu .= '-{getstring}course{/getstring}: {getstring}backup{/getstring}|/backup/backup.php?id={courseid}' . + PHP_EOL; + if (stripos($text, '{menucoursemore}') === false) { + $menu .= '-{getstring}course{/getstring}: {getstring}participants{/getstring}|/user/index.php?id={courseid}' + . PHP_EOL; + $menu .= '-{getstring}course{/getstring}: {getstring:badges}badges{/getstring}|/badges/view.php' . + '?type=2&id={courseid}' . PHP_EOL; + $menu .= '-{getstring}course{/getstring}: {getstring}reports{/getstring}|/course/admin.php' . + '?courseid={courseid}#linkcoursereports' . PHP_EOL; + } + $menu .= '-{getstring}course{/getstring}: {getstring:enrol}enrolmentinstances{/getstring}|/enrol/instances.php' + . '?id={courseid}' . PHP_EOL; + $menu .= '-{getstring}course{/getstring}: {getstring}reset{/getstring}|/course/reset.php?id={courseid}' + . PHP_EOL; + $menu .= '-Course: Layoutit|https://www.layoutit.com/build" target="popup" ' . + 'onclick="window.open(\'https://www.layoutit.com/build\',\'popup\',\'width=1340,height=700\');' . + ' return false;|Bootstrap Page Builder' . PHP_EOL; + $menu .= '{/ifincourse}' . PHP_EOL; + $menu .= '-###' . PHP_EOL; + } + if ($this->hasminarchetype('manager')) { // If a manager or above. + $menu .= '-{getstring}site{/getstring}: {getstring}reports{/getstring}|/admin/category.php?category=reports' . + PHP_EOL; + } + if (is_siteadmin() && !is_role_switched($PAGE->course->id)) { // If an administrator. + $menu .= '-{getstring}site{/getstring}: {getstring:admin}additionalhtml{/getstring}|/admin/settings.php' . + '?section=additionalhtml' . PHP_EOL; + $menu .= '-{getstring}site{/getstring}: {getstring:admin}frontpage{/getstring}|/admin/settings.php' . + '?section=frontpagesettings|Including site name' . PHP_EOL; + $menu .= '-{getstring}site{/getstring}: {getstring:admin}plugins{/getstring}|/admin/search.php#linkmodules' . + PHP_EOL; + $menu .= '-{getstring}site{/getstring}: {getstring:admin}supportcontact{/getstring}|/admin/settings.php' . + '?section=supportcontact' . PHP_EOL; + + if ($CFG->branch >= 404) { + $label = 'themesettingsadvanced'; + $section = 'themesettingsadvanced'; + } else { + $label = 'themesettings'; + $section = 'themesettings'; + } + $menu .= '-{getstring}site{/getstring}: {getstring:admin}' . $label . '{/getstring}|/admin/settings.php' . + '?section=' . $section . '|Including custom menus, designer mode, theme in URL' . PHP_EOL; + + if (file_exists($CFG->dirroot . '/theme/' . $theme . '/settings.php')) { + require_once($CFG->libdir . '/adminlib.php'); + if (admin_get_root()->locate('theme_' . $theme)) { + // Settings use categories interface URL. + $url = '/admin/category.php?category=theme_' . $theme . PHP_EOL; + } else { + // Settings use tabs interface URL. + $url = '/admin/settings.php?section=themesetting' . $theme . PHP_EOL; + } + $menu .= '-{getstring}site{/getstring}: {getstring:admin}currenttheme{/getstring}|' . $url; + } + $menu .= '-{getstring}site{/getstring}: {getstring}notifications{/getstring} ({getstring}admin{/getstring})' . + '|/admin/index.php' . PHP_EOL; + } + $replace['/\{menuadmin\}/i'] = $menu; + } + + // Tag: {menucoursemore}. + // Description: Show a "More" menu containing most of 4.x secondary menu. Useful if theme with pre-4.x style navigation. + // Parameters: None. + if (stripos($text, '{menucoursemore}') !== false) { + $menu = ''; + $menu .= '{ifincourse}' . PHP_EOL; + if ($CFG->branch >= 400) { + $menu .= '{getstring}moremenu{/getstring}' . PHP_EOL; + } else { + $menu .= '{getstring:filter_filtercodes}moremenu{/getstring}' . PHP_EOL; + } + $menu .= '-{getstring}course{/getstring}|/course/view.php?id={courseid}' . PHP_EOL; + if ($this->hasminarchetype('editingteacher')) { + $menu .= '-{getstring}settings{/getstring}|/course/edit.php?id={courseid}' . PHP_EOL; + } + $menu .= '-{getstring}participants{/getstring}|/user/index.php?id={courseid}' . PHP_EOL; + $menu .= '-{getstring}grades{/getstring}|/grade/report/index.php?id={courseid}' . PHP_EOL; + if ($this->hasminarchetype('editingteacher')) { + $menu .= '-{getstring}reports{/getstring}|/report/view.php?courseid={courseid}' . PHP_EOL; + $menu .= '-###' . PHP_EOL; + $menu .= '-{getstring:question}questionbank{/getstring}|/question/edit.php?courseid={courseid}' . PHP_EOL; + if ($CFG->branch >= 39) { + $menu .= '-{getstring}contentbank{/getstring}|/contentbank/index.php?contextid={coursecontextid}' . PHP_EOL; + } + $menu .= '-{getstring:completion}coursecompletion{/getstring}|/course/completion.php?id={courseid}' . PHP_EOL; + $menu .= '-{getstring:badges}badges{/getstring}|/badges/view.php?type=2&id={courseid}' . PHP_EOL; + } + $pluginame = '{getstring:competency}competencies{/getstring}'; + $menu .= '-' . $pluginame . '|/admin/tool/lp/coursecompetencies.php?courseid={courseid}' . PHP_EOL; + if ($this->hasminarchetype('editingteacher')) { + $menu .= '-{getstring:admin}filters{/getstring}|/filter/manage.php?contextid={coursecontextid}' . PHP_EOL; + } + if ($CFG->branch >= 402) { + $menu .= '-{getstring:enrol}unenrolme{/getstring}|{courseunenrolurl}' . PHP_EOL; + } else { + $menu .= '-{getstring:filter_filtercodes}unenrolme{/getstring}|{courseunenrolurl}' . PHP_EOL; + } + if ($this->hasminarchetype('editingteacher')) { + $menu .= '-{getstring:mod_lti}courseexternaltools{/getstring}|/mod/lti/coursetools.php?id={courseid}' . PHP_EOL; + if ($CFG->branch >= 311) { + $pluginame = '{getstring:tool_brickfield}pluginname{/getstring}'; + $menu .= '-' . $pluginame . '|/admin/tool/brickfield/index.php?courseid={courseid}' . PHP_EOL; + } + $menu .= '-{getstring}coursereuse{/getstring}|/backup/import.php?id={courseid}' . PHP_EOL; + } + $menu .= '{/ifincourse}' . PHP_EOL; + $replace['/\{menucoursemore\}/i'] = $menu; + } + + // Tag: {menudev}. + // Description: Displays a menu of useful links for site administrators when added to the custom menu. + // Parameters: None. + if (stripos($text, '{menudev}') !== false) { + $menu = ''; + if (is_siteadmin() && !is_role_switched($PAGE->course->id)) { // If a site administrator. + $menu .= '-{getstring:tool_installaddon}installaddons{/getstring}|/admin/tool/installaddon' . PHP_EOL; + $menu .= '-###' . PHP_EOL; + $menu .= '-{getstring:admin}debugging{/getstring}|/admin/settings.php?section=debugging' . PHP_EOL; + $menu .= '-{getstring:admin}purgecachespage{/getstring}|/admin/purgecaches.php' . PHP_EOL; + $menu .= '-###' . PHP_EOL; + if (file_exists(dirname(__FILE__) . '/../../local/adminer/index.php')) { + $menu .= '-{getstring:local_adminer}pluginname{/getstring}|/local/adminer' . PHP_EOL; + } + if (file_exists(dirname(__FILE__) . '/../../local/codechecker/index.php')) { + $menu .= '-{getstring:local_codechecker}pluginname{/getstring}|/local/codechecker' . PHP_EOL; + } + if (file_exists(dirname(__FILE__) . '/../../local/moodlecheck/index.php')) { + $menu .= '-{getstring:local_moodlecheck}pluginname{/getstring}|/local/moodlecheck' . PHP_EOL; + } + if (file_exists(dirname(__FILE__) . '/../../admin/tool/pluginskel/index.php')) { + $menu .= '-{getstring:tool_pluginskel}pluginname{/getstring}|/admin/tool/pluginskel' . PHP_EOL; + } + if (file_exists(dirname(__FILE__) . '/../../local/tinyfilemanager/index.php')) { + $menu .= '-{getstring:local_tinyfilemanager}pluginname{/getstring}|/local/tinyfilemanager' . PHP_EOL; + } + $menu .= '-{getstring}phpinfo{/getstring}|/admin/phpinfo.php' . PHP_EOL; + $menu .= '-###' . PHP_EOL; + $menu .= '-{getstring:filter_filtercodes}pagebuilder{/getstring}|' + . '{getstring:filter_filtercodes}pagebuilderlink{/getstring}"' + . ' target="popup" onclick="window.open(\'{getstring:filter_filtercodes}pagebuilderlink{/getstring}\'' + . ',\'popup\',\'width=1340,height=700\'); return false;' . PHP_EOL; + $menu .= '-{getstring:filter_filtercodes}photoeditor{/getstring}|' + . '{getstring:filter_filtercodes}photoeditorlink{/getstring}"' + . ' target="popup" onclick="window.open(\'{getstring:filter_filtercodes}photoeditorlink{/getstring}\'' + . ',\'popup\',\'width=1340,height=700\'); return false;' . PHP_EOL; + $menu .= '-{getstring:filter_filtercodes}screenrec{/getstring}|' + . '{getstring:filter_filtercodes}screenreclink{/getstring}"' + . ' target="popup" onclick="window.open(\'{getstring:filter_filtercodes}screenreclink{/getstring}\'' + . ',\'popup\',\'width=1340,height=700\'); return false;' . PHP_EOL; + $menu .= '-###' . PHP_EOL; + $menu .= '-Dev docs|https://moodle.org/development|Moodle.org ({getstring}english{/getstring})' . PHP_EOL; + $menu .= '-Dev forum|https://moodle.org/mod/forum/view.php?id=55|Moodle.org ({getstring}english{/getstring})' . + PHP_EOL; + $menu .= '-Tracker|https://tracker.moodle.org/|Moodle.org ({getstring}english{/getstring})' . PHP_EOL; + $menu .= '-AMOS|https://lang.moodle.org/|Moodle.org ({getstring}english{/getstring})' . PHP_EOL; + $menu .= '-WCAG 2.1|https://www.w3.org/WAI/WCAG21/quickref/|W3C ({getstring}english{/getstring})' . PHP_EOL; + $menu .= '-###' . PHP_EOL; + $menu .= '-DevTuts|https://www.youtube.com/watch?v=UY_pcs4HdDM|{getstring}english{/getstring}' . PHP_EOL; + $menu .= '-Moodle Development School|https://moodledev.moodle.school/|{getstring}english{/getstring}' . PHP_EOL; + $menuurl = 'https://moodle.academy/course/index.php?categoryid=4'; + $menu .= '-Moodle Dev Academy|' . $menuurl . '|{getstring}english{/getstring}' . PHP_EOL; + } + $replace['/\{menudev\}/i'] = $menu; + } + + // Tag: {menuthemes}. + // Description: Theme switcher for custom menu. Only for administrators. Not available after POST. + // Parameters: None. + // Allow Theme Changes on URL must be enabled for this to have any effect. + if (stripos($text, '{menuthemes}') !== false) { + $menu = ''; + if (is_siteadmin() && empty($_POST)) { // If a site administrator. + if (get_config('core', 'allowthemechangeonurl')) { + $url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") + . "://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; + $url .= (strpos($url, '?') ? '&' : '?'); + $themeslist = \core_component::get_plugin_list('theme'); + $menu = ''; + foreach ($themeslist as $theme => $themedir) { + $themename = ucfirst(get_string('pluginname', 'theme_' . $theme)); + $menu .= '-' . $themename . '|' . $url . 'theme=' . $theme . PHP_EOL; + } + + // If an administrator, add links to Advanced Theme Settings and to Current theme settings. + if (is_siteadmin() && !is_role_switched($PAGE->course->id)) { + $theme = $PAGE->theme->name; + $menu = 'Themes' . PHP_EOL . $menu; + if ($CFG->branch >= 404) { + $label = 'themesettingsadvanced'; + $section = 'themesettingsadvanced'; + } else { + $label = 'themesettings'; + $section = 'themesettings'; + } + + $menu .= '-###' . PHP_EOL; + $menu .= '-{getstring:admin}' . $label . '{/getstring}|/admin/settings.php' . + '?section=' . $section . '|Including custom menus, designer mode, theme in URL' . PHP_EOL; + if (file_exists($CFG->dirroot . '/theme/' . $theme . '/settings.php')) { + require_once($CFG->libdir . '/adminlib.php'); + if (admin_get_root()->locate('theme_' . $theme)) { + // Settings using categories interface URL. + $url = '/admin/category.php?category=theme_' . $theme . PHP_EOL; + } else { + // Settings using tabs interface URL. + $url = '/admin/settings.php?section=themesetting' . $theme . PHP_EOL; + } + $menu .= '-{getstring:admin}currenttheme{/getstring}|' . $url; + } + } + } + } + $replace['/\{menuthemes\}/i'] = $menu; + } + + // Tag: {menuwishlist}. + // Description: Displays a list of wishlisted courses for the Primary (Custom) menu with an option to add or remove + // the current course from the wishlist. The list will be sorted alphabetically. If there are no courses in the + // wishlist or we are on a site page, a message will be displayed. + // Parameters: None. + if (stripos($text, '{menuwishlist}') !== false) { + // If not logged in, or guest user, do not display the Wishlist. + if (!isloggedin() || isguestuser()) { + $menu = ''; + } else { + global $USER, $DB, $PAGE; + + // Get the user's wishlist from the user_preference table. + $wishlist = $DB->get_record('user_preferences', [ + 'userid' => $USER->id, + 'name' => 'filter_filtercodes_wishlist', + ]); + $wishlist = $wishlist ? explode(',', $wishlist->value) : []; + + // Generate the list of wishlisted courses. + $menu = ''; + foreach ($wishlist as $courseid) { + $course = $DB->get_record('course', ['id' => $courseid]); + if ($course) { + $courseurl = (new \moodle_url('/course/view.php', ['id' => $course->id]))->out(); + $menu .= '-' . format_string($course->fullname) . '|' . $courseurl . "\n"; + } + } + if (!empty($menu)) { + // Sort course names. + $menu = explode("\n", $menu); + $menu = array_filter($menu, 'strlen'); + usort($menu, 'strnatcasecmp'); + $menu = trim(implode("\n", $menu)); + } + + // Check if the current course is in the wishlist. + if ($PAGE->course->id === SITEID) { + if (empty($menu)) { + $menu = '-' . get_string('wishlist_nocourses', 'filter_filtercodes') . "\n"; + } + } else { + if (!empty($menu)) { + $menu .= "\n-###\n"; + } + $action = in_array($PAGE->course->id, $wishlist) ? 'remove' : 'add'; + $url = (new \moodle_url('/filter/filtercodes/wishlist.php', [ + 'courseid' => $PAGE->course->id, + 'action' => $action, + ]))->out(); + $menu .= '-' . get_string('wishlist_' . $action, 'filter_filtercodes') . '|' . $url . "\n"; + } + $menu = get_string('wishlist', 'filter_filtercodes') . "\n" . $menu; + } + + // Replace the {menuwishlist} tag with the generated wishlist output, if any. + $replace['/\{menuwishlist\}/i'] = $menu; + } + } + + // Check if any {course*} or %7Bcourse*%7D tags. Note: There is another course tags section further down. + $coursetagsexist = (stripos($text, '{course') !== false || stripos($text, '%7Bcourse') !== false); + if ($coursetagsexist) { + // Tag: {coursesummary} or {coursesummary courseid}. + // Description: Course summary as defined in the course settings. + // Optional parameters: Course id. Default is to use the current course, or site summary if not in a course. + if (stripos($text, '{coursesummary') !== false) { + if (stripos($text, '{coursesummary}') !== false) { + // No course ID specified. + $coursecontext = \context_course::instance($PAGE->course->id); + $PAGE->course->summary == null ? '' : $PAGE->course->summary; + $replace['/\{coursesummary\}/i'] = format_text( + $PAGE->course->summary, + FORMAT_HTML, + ['context' => $coursecontext] + ); + } + if (stripos($text, '{coursesummary ') !== false) { + // Course ID was specified. + preg_match_all('/\{coursesummary ([0-9]+)\}/', $text, $matches); + // Eliminate course IDs. + $courseids = array_unique($matches[1]); + $coursecontext = \context_course::instance($PAGE->course->id); + foreach ($courseids as $id) { + $course = $DB->get_record('course', ['id' => $id]); + if (!empty($course)) { + $course->summary == null ? '' : $course->summary; + $replace['/\{coursesummary ' . $course->id . '\}/isuU'] = format_text( + $course->summary, + FORMAT_HTML, + ['context' => $coursecontext] + ); + } + } + unset($matches, $course, $courseids, $id); + } + } + } + + // Tag: {formquickquestion} + // Tag: {formcheckin} + // Tag: {formcontactus} + // Tag: {formcourserequest} + // Tag: {formsupport} + // Tag: {formsesskey} + // + // Description: Tags used to generate pre-define forms for use with ContactForm plugin. + // Parameters: None. + if (stripos($text, '{form') !== false) { + $pre = '
    ' . get_string($form, 'filter_filtercodes') . $post; + } else { + $replace['/\{' . $form . '\}/i'] = ''; + } + } + } + // These work regardless of whether you are logged-in or not. + foreach (['formcontactus', 'formcourserequest', 'formsupport'] as $form) { + if (stripos($text, '{' . $form . '}') !== false) { + $formcode = get_string($form, 'filter_filtercodes'); + $replace['/\{' . $form . '\}/i'] = $pre . $form . '">' . $formcode . $post; + } else { + $replace['/\{' . $form . '\}/i'] = ''; + } + } + + // Tag: {formsesskey}. + if (stripos($text, '{formsesskey}') !== false) { + $replace['/\{formsesskey\}/i'] = ''; + $replace['/\{formsesskey\}/i'] .= ''; + } + } + + // Tag: {global_[custom]}. + // Description: Global Custom tags as defined in plugin settings. + // Parameters: custom: Name of custom global tag from FilterCodes settings. + if (stripos($text, '{global_') !== false) { + // Get total number of defined global block tags. + $globaltagcount = get_config('filter_filtercodes', 'globaltagcount'); + for ($i = 1; $i <= $globaltagcount; $i++) { + // Get name of tag. + $tag = get_config('filter_filtercodes', 'globalname' . $i); + // If defined and tag exists in the content. + if (!empty($tag) && stripos($text, '{global_' . $tag . '}') !== false) { + // Replace the tag with new content. + $content = get_config('filter_filtercodes', 'globalcontent' . $i); + $replace['/\{global_' . $tag . '\}/i'] = $content; + } + } + unset($i); + unset($globaltagcount); + unset($tag); + unset($content); + } + + // Tag: {teamcards}. + // Description: Displays a series of card for each contact on the site. Configurable in FilterCodes settings. + // Note: Included selected roles in Site Administration > Appearance > Course > Course Contacts. + // Parameters: None. + if (stripos($text, '{teamcards}') !== false) { + global $OUTPUT, $DB; + + $sql = 'SELECT DISTINCT u.id, u.username, u.firstname, u.lastname, u.email, u.picture, u.imagealt, u.firstnamephonetic, + u.lastnamephonetic, u.middlename, u.alternatename, u.description, u.phone1 + FROM {course} c, {role_assignments} ra, {user} u, {context} ct + WHERE c.id = ct.instanceid AND ra.roleid in (?) AND ra.userid = u.id AND ct.id = ra.contextid + AND u.suspended = 0 AND u.deleted = 0 + ORDER BY u.lastname desc, u.firstname'; + $users = $DB->get_records_sql($sql, [$CFG->coursecontact]); + + $cards = ''; + if (count($users)) { + $clinktype = get_config('filter_filtercodes', 'teamcardslinktype'); + $cardformat = get_config('filter_filtercodes', 'teamcardsformat'); + $narrowpage = get_config('filter_filtercodes', 'narrowpage'); + + switch ($cardformat) { // Show as info icon. + case 'infoicon': + $info = get_string('info'); + $prewrap = ''; + break; + case 'brief': // Show as text. + $prewrap = '

    '; + $postwrap = '

    '; + break; + case 'verbose': // Show as text. + break; + default: // Don't show user description. + $cardformat = ''; + } + + // Prepare some strings. + $linksr = [ + '' => '', + 'email' => get_string('issueremail', 'badges'), + 'message' => get_string('message', 'message'), + 'profile' => get_string('profile'), + 'phone' => get_string('phone'), + ]; + if ($cardformat == 'verbose') { + if (empty($CFG->enablegravatar)) { + $blankavatarurl = $this->getblankavatarurl(150); + } + foreach ($users as $user) { + $cards .= '
    '; + $name = '

    ' . get_string('fullnamedisplay', null, $user) . '

    '; + $cards .= $this->userlink($clinktype, $user, $name); + if (empty($user->picture) && empty($CFG->enablegravatar)) { + $cards .= ''; + } else { + $cards .= $OUTPUT->user_picture($user, [ + 'size' => '150', + 'class' => 'img-fluid pull-left p-1 border mr-4', + 'link' => false, 'visibletoscreenreaders' => false, + ]); + } + $cards .= format_string($user->description); + $cards .= '

    '; + } + } else { + if (empty($CFG->enablegravatar)) { + $blankavatarurl = $this->getblankavatarurl(250); + } + $cards .= '
    '; + foreach ($users as $user) { + $cards .= '
    '; + if (empty($user->picture) && empty($CFG->enablegravatar)) { + $cards .= ''; + } else { + $cards .= $OUTPUT->user_picture($user, [ + 'size' => '250', + 'class' => 'img-fluid', + 'link' => false, + 'visibletoscreenreaders' => false, + ]); + } + $name = '

    ' . get_string('fullnamedisplay', null, $user) . + '

    '; + $cards .= $this->userlink($clinktype, $user, $name); + if (!empty($user->description) && !empty($cardformat)) { + $cards .= $prewrap . format_string($user->description) . $postwrap; + } + $cards .= '
    '; + } + $cards .= '
    '; + } + } + $replace['/\{teamcards\}/i'] = $cards; + unset($cards, $users, $sql, $info, $prewrap, $postwrap, $cardformat); + } + + // Custom Course Fields - First implemented in Moodle 3.7. + if ($CFG->branch >= 37) { + // Tag: {course_field_shortname}. + // Description: Content from the custom course field specified by its shortname. + // Required Parameters: shortname of a custom course field. + if (stripos($text, '{course_field_') !== false) { + // Cached the custom course field data. + static $coursefields; + if (!isset($coursefields)) { + $handler = \core_course\customfield\course_handler::create(); + $coursefields = $handler->export_instance_data_object($PAGE->course->id, true); + $fieldsvisible = $handler->export_instance_data_object($PAGE->course->id); + // Blank out the fields that should not be displayed. + foreach ($coursefields as $field => $value) { + if (empty($fieldsvisible->$field)) { + $coursefields->$field = ''; + } + } + } + $coursecontext = \context_course::instance($PAGE->course->id); + foreach ($coursefields as $field => $value) { + $shortname = strtolower($field); + // If the tag exists and it is not hidden in the custom course field's settings. + if (stripos($text, '{course_field_' . $shortname . '}') !== false) { + $replace['/\{course_field_' . $shortname . '\}/i'] = format_text( + $value, + FORMAT_HTML, + ['context' => $coursecontext] + ); + } + } + } + + // Tag: {course_fields}. + // Description: All content from the custom user profile fields specified by shortname as set in the user's profile. + // Parameters: None. + if (stripos($text, '{course_fields}') !== false) { + // Display all custom course fields. + $customfields = ''; + if ($PAGE->course instanceof stdClass) { + $thiscourse = new \core_course_list_element($PAGE->course); + } + if ($thiscourse->has_custom_fields()) { + $handler = \core_course\customfield\course_handler::create(); + $customfields = $handler->display_custom_fields_data($thiscourse->get_custom_fields()); + } + $coursecontext = \context_course::instance($PAGE->course->id); + $replace['/\{course_fields\}/i'] = format_text($customfields, FORMAT_HTML, ['context' => $coursecontext]); + } + } + + if (stripos($text, '{dashboard_siteinfo}') !== false) { + if (is_siteadmin() && !is_role_switched($PAGE->course->id)) { // If an administrator. + $appbytes = @disk_free_space('.'); + $databytes = @disk_free_space($CFG->dataroot); + $disktxt = $this->humanbytes($databytes); + if ($appbytes != $databytes) { + $disktxt = 'app: ' . $disktxt . ' | data: ' . $this->humanbytes($databytes); + } + + $cards = []; + $cards[] = [ + 'icon' => 'fa-database', + 'label' => 'Available disk space', + 'info' => $disktxt, + ]; + $cards[] = [ + 'icon' => 'fa-graduation-cap', + 'label' => get_string('courses'), + 'info' => get_string('total') . ' {coursecount}', + ]; + $cards[] = [ + 'icon' => 'fa-users', + 'label' => get_string('users'), + 'info' => get_string('active') . ' {usersonline} | ' . get_string('total') . ' {usersactive}', + ]; + $totalcards = count($cards); + + $content = ' +
    +

    Site info dashboard

    +
    + '; + for ($card = 0; $card < $totalcards; $card++) { + $content .= ' +
    +
    + +

    ' . $cards[$card]['label'] . '

    +

    ' . $cards[$card]['info'] . '

    +
    +
    + '; + } + $content .= ' +
    +
    + '; + $coursecontext = \context_course::instance($PAGE->course->id); + $replace['/\{dashboard_siteinfo\}/i'] = format_text($content, FORMAT_HTML, ['context' => $coursecontext]); + } else { + $replace['/\{dashboard_siteinfo\}/i'] = ''; + } + } + + /* ---------------- Apply all of the filtercodes so far. ---------------*/ + + return $this->replacetags($text, $replace); + } + + /** + * Handle escaped tags. + * + * @param string $text Content to be processed. + * @return string Processed text. + * + * Note: First time this function is called, it will escape all tags that should not be processed. + * The second time it is called, it will turn escaped tags back into unprocessed plain text tags. + */ + private function escapedtags($text) { + static $escapedtags; + static $escapedtagsenc; + static $escapebraces; + + // Don't process if this feature is disabled. + if (!isset($escapebraces)) { + $escapebraces = !empty(get_config('filter_filtercodes', 'escapebraces')); + if (!$escapebraces) { + return $text; + } + } + + if (!isset($escapedtags) || !isset($escapedtagsenc)) { + // First time called, temporarily replace the escaped tags so they will not be processed by FilterCodes. + + // Regular tags. + $escapedtags = (strpos($text, '[{') !== false && strpos($text, '}]') !== false); + if ($escapedtags) { + $text = str_replace('[{', chr(2), $text); + $text = str_replace('}]', chr(3), $text); + } + + // Encoded tags. + $escapedtagsenc = (strpos($text, '[%7B') !== false && strpos($text, '%7D]') !== false); + if ($escapedtagsenc) { + $text = str_replace('[%7B', chr(4), $text); + $text = str_replace('%7D]', chr(5), $text); + } + } else { + // Second time called, complete the process of putting back the tags, but not escaped. + + // Regular tags. + if ($escapedtags) { + $text = str_replace(chr(2), '{', $text); + $text = str_replace(chr(3), '}', $text); + } + $escapedtags = null; + + // Encoded tags. + if ($escapedtagsenc) { + $text = str_replace(chr(4), '%7B', $text); + $text = str_replace(chr(5), '%7D', $text); + } + $escapedtagsenc = null; + } + + return $text; + } + + /** + * Applies all filters defined in $replace to the $text. + * + * @param string $text Content to be processed. Passed by reference. + * @param array $replace Array in the format Key=Regex, Value=To be applied. Passed by reference. + * @return boolean True of there are more changes, otherwise false. + */ + private function replacetags(&$text, &$replace) { + $newtext = null; + $moretags = true; + if (count($replace) > 0) { + $newtext = preg_replace(array_keys($replace), array_values($replace), $text); + if (!is_null($newtext)) { + $text = $newtext; + if (strpos($text, '{') === false && strpos($text, '%7B') === false) { + $moretags = false; + } + } + $replace = []; + } + return $moretags; + } + + /** + * Main filter function called by Moodle. + * + * @param string $text Content to be filtered. + * @param array $options Moodle filter options. None are implemented in this plugin. + * @return string Content with filters applied. + */ + public function filter($text, array $options = []) { + global $CFG, $SITE, $PAGE, $USER, $DB; + + if (strpos($text, '{') === false && strpos($text, '%7B') === false) { + return $text; + } + + // Declare some of the static variables. + static $profilefields; + static $profiledata; + static $mygroupslist; + static $mygroupingslist; + + $replace = []; // Array of key/value filterobjects. + + // Handle escaped tags to be ignored. Remove them so they don't get processed if the option to [{escape braces}] is enabled. + $text = $this->escapedtags($text); + + // START: Process tags that may end up containing other tags first. + + // ...===================================================================================================================. + // Tags that may create more content which could possibly include tags. These need to be processed first. + // ...===================================================================================================================. + + // Loop through the tags that may have embedded tags until these generator tags have all been proceseed. + + $loop = 0; // We only support tags nested up to 3 deep - to handle circular references. + do { + $moretags = $this->generatortags($text); + } while ($loop++ < 3 && $moretags); + + // We can now process all other tags including ones added by the code above. + + // ...===================================================================================================================. + // Tags that may be used as parameters by other tags should be processed before the tags that may include them. + // ...===================================================================================================================. + + // Tag: {lang}. + // Description: First 2-letters, in lowercase, of current language of user interface. + // Parameters: None. + if (stripos($text, '{lang}') !== false) { + // Replace with 2-letter current primary language. + $replace['/\{lang\}/i'] = substr(current_language(), 0, 2); + } + + // Tag: {preferredlanguage}. + // Description: First 2-letters, in lowercase, of the user's preferred language as set in their profile. + // Parameters: None. + if (stripos($text, '{preferredlanguage}') !== false) { + if (isloggedin() && !isguestuser()) { + // If user does not have a preferred language, default to the system default language. + $preflang = empty($USER->lang) ? $CFG->lang : $USER->lang; + if ($preflang == 'en') { + $langconfig = $CFG->dirroot . '/lang/en/langconfig.php'; + } else { + $langconfig = $CFG->dataroot . '/lang/' . $preflang . '/langconfig.php'; + } + // Ignore parents here for now. + $string = []; + include($langconfig); + if (!empty($string['thislanguage'])) { + $replace['/\{preferredlanguage\}/i'] = '' . $string['thislanguage'] . ''; + } else { // This should never happen since the known user already exists. + $replace['/\{preferredlanguage\}/i'] = get_string('unknown', 'notes'); + } + } else { + $replace['/\{preferredlanguage\}/i'] = ''; + } + unset($preflang, $langconfig, $string); + } + + // Tag: %7Buserid%7D. + // Description: Alias for {userid}. Useful for encoded urls. + // Parameters: None. + if (stripos($text, '%7Buserid%7D') !== false) { + $text = str_replace('%7Buserid%7D', '{userid}', $text); + } + + // Tag: {userid}. + // Description: User's user ID. + // Parameters: None. + if (stripos($text, '{userid}') !== false) { + $replace['/\{userid\}/i'] = $USER->id; + } + + // Tags: {courseid... + if (stripos($text, '{course') !== false || stripos($text, '%7Bcourseid') !== false) { + $courseid = 1; // Default to site. + if ($PAGE->pagetype == 'enrol-index') { + // Make it work, even when we are on the enrolment page. + $courseid = optional_param('id', $courseid, PARAM_INT); + } else { + $courseid = $PAGE->course->id; + } + + // Tag: %7Bcourseid%7D. + // Description: An alias for {courseid}. Useful for encoded URLs. + // Parameters: None. + if (stripos($text, '%7Bcourseid%7D') !== false) { + $text = str_replace('%7Bcourseid%7D', '{courseid}', $text); + } + + // Tag: {courseid}. + // Description: Course ID. Will be 1 (SITE) if not in a course. + // Parameters: None. + if (stripos($text, '{courseid}') !== false) { + $replace['/\{courseid\}/i'] = $courseid; + } + + // Tag: {coursegradepercent}. + // Description: Current overall course grade as a percentage. + // Parameters: None. + if (version_compare(PHP_VERSION, '7.0.0') >= 0 && stripos($text, '{coursegradepercent}') !== false) { + require_once($CFG->libdir . '/gradelib.php'); + require_once($CFG->dirroot . '/grade/querylib.php'); + $gradeobj = grade_get_course_grade($USER->id, $PAGE->course->id); + if (!empty($grademax = floatval($gradeobj->item->grademax))) { + // Avoid divide by 0 error if no grades have been defined. + $grade = floatval($grademax) > 0 ? (int) ($gradeobj->grade / floatval($grademax) * 100) : 0; + } else { + $grade = 0; + } + $replace['/\{coursegradepercent\}/i'] = $grade; + } + + // Tag: {courseprogresspercent}. + // Description: Course completion progress percentage as a number. + // Parameters: None. + if (stripos($text, '{courseprogresspercent}') !== false) { + $progress = $this->completionprogress(); + if ($progress != -1) { // Is enabled. + $replace['/\{courseprogresspercent\}/i'] = $progress; + } else { + $replace['/\{courseprogresspercent\}/i'] = ''; + } + unset($progress); + } + + // Tag: %7Bcoursecontextid%7D. + // Description: Alias for {coursecontextid}. Useful for encoded URLs. + // Parameters: None. + if (stripos($text, '%7Bcoursecontextid%7D') !== false) { + $text = str_replace('%7Bcoursecontextid%7D', '{coursecontextid}', $text); + } + + // Tag: {coursecontextid}. + // Description: Course context id. + // Parameters: None. + if (stripos($text, '{coursecontextid}') !== false) { + $context = \context_course::instance($PAGE->course->id); + $coursecontextid = isset($PAGE->course->id) ? $context->id : 1; + $replace['/\{coursecontextid\}/i'] = $coursecontextid; + } + + // Tag: %7Bcoursemoduleid%7D. + // Description: Alias for {coursemoduleid}. Useful for encoded URLs. + // Parameters: None. + if (stripos($text, '%7Bcoursemoduleid%7D') !== false) { + $text = str_replace('7Bcoursemoduleid%7D', '{coursemoduleid}', $text); + } + + // Tag: {coursemoduleid}. + // Description: Course module id. + // Parameters: None. + // Note: %7Bcoursemoduleid%7D is an alias for {coursemoduleid}. Useful for encoded URLs. + if (stripos($text, '{coursemoduleid}') !== false) { + if (isset($PAGE->cm->id)) { + $replace['/\{coursemoduleid\}/isu'] = $PAGE->cm->id; + } + } + + // Tag: {courseshortname}. + // Description: The short name of this course. If not in a course, will use the site's shortname. + // Parameters: None. + if (stripos($text, '{courseshortname}') !== false) { + $course = $PAGE->course; + if ($course->id == $SITE->id) { // Front page - use site name. + $replace['/\{courseshortname\}/i'] = format_string($SITE->shortname); + } else { // In a course - use course full name. + $coursecontext = \context_course::instance($course->id); + $replace['/\{courseshortname\}/i'] = format_string($course->shortname, true, ['context' => $coursecontext]); + } + } + } + + // Tag: {categoryid}. + // Description: Category ID in which the current course is located. + // Parameters: None. + if (stripos($text, '{categoryid}') !== false) { + if (empty($PAGE->course->category)) { + // If we are not in a course, check if categoryid is part of URL (ex: course lists). + $catid = optional_param('categoryid', 0, PARAM_INT); + } else { + // Retrieve the category id of the course we are in. + $catid = $PAGE->course->category; + } + $replace['/\{categoryid\}/i'] = $catid; + } + + if (stripos($text, '{refer') !== false) { + // Tag: {referer}. + // Description: Alias for {referrer} tag. For backwards compatibility with original incorrect spelling of the tag. + // Parameters: None. + if (stripos($text, '{referer}') !== false) { + $text = str_replace('{referer}', '{referrer}', $text); + } + + // Tag: {referrer}. + // Description: URL that brought the user to the current page. + // Parameters: None. + if (stripos($text, '{referrer}') !== false) { + if ($CFG->branch >= 28) { + $replace['/\{referrer\}/i'] = get_local_referer(false); + } else { + $replace['/\{referrer\}/i'] = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''; + } + } + } + + // Tag: %7Bwwwroot%7D. + // Description: Alias for {wwwroot}. + // Parameters: None. + if (stripos($text, '%7Bwwwroot%7D') !== false) { + $text = str_replace('%7Bwwwroot%7D', '{wwwroot}', $text); + } + + // Tag: {wwwroot}. + // Description: URL of the site's webroot. + // Parameters: None. + if (stripos($text, '{wwwroot}') !== false) { + $replace['/\{wwwroot\}/i'] = $CFG->wwwroot; + } + + // Tag: {pagepath}. + // Description: Path of the current page without wwwroot. + // Parameters: None. + if (stripos($text, '{pagepath}') !== false) { + $url = (is_object($PAGE->url) ? $PAGE->url->out_as_local_url() : ''); + if (strpos($url, '?') === false && strpos($url, '#') === false) { + $url .= '?'; + } + $replace['/\{pagepath\}/i'] = $url; + } + + if (stripos($text, '{thisurl') !== false) { + $url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . + "://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; + + // Tag: {thisurl}. + // Description: Complete URL of the current page. + // Parameters: None. + if (stripos($text, '{thisurl}') !== false) { + $replace['/\{thisurl\}/i'] = $url; + } + // Tag: {thisurl_enc}. + // Description: Complete URL of the current page - URL encoded for use as a parameter of a URL. + // Parameters: None. + if (stripos($text, '{thisurl_enc}') !== false) { + $replace['/\{thisurl_enc\}/i'] = rawurlencode($url); + } + } + + // Tag: {protocol}. + // Description: Protocol used to access the website (http or https). + // Parameters: None. + if (stripos($text, '{protocol}') !== false) { + $replace['/\{protocol\}/i'] = 'http' . ($this->ishttps() ? 's' : ''); + } + + // Tag: {ipaddress}. + // Description: IP Address of the web client accessing the page. + // Parameters: None. + if (stripos($text, '{ipaddress}') !== false) { + $replace['/\{ipaddress\}/i'] = getremoteaddr(); + } + + // Tag: {sesskey}. + // Description: Moodle Session key. Does not work in forums. May be disabled in FilterCodes settings. + // Parameters: None. + if (get_config('filter_filtercodes', 'enable_sesskey')) { + if ((!isset($PAGE->cm->modname) || $PAGE->cm->modname != 'forum') && $PAGE->pagetype != 'admin-cron') { + if (stripos($text, '{sesskey}') !== false) { + // Tag: {sesskey}. + $replace['/\{sesskey\}/i'] = sesskey(); + } + // Tag: %7Bsesskey%7D (for encoded URLs). + if (stripos($text, '%7Bsesskey%7D') !== false) { + $replace['/%7Bsesskey%7D/i'] = sesskey(); + } + } + } + + // Tag: %7Bsectionid%7D. + // Description: Alias of {sectionid}. + // Parameters: None. + if (stripos($text, '%7Bsectionid%7D') !== false) { + $text = str_replace('%7Bsectionid%7D', '{sectionid}', $text); + } + + // Tag: {sectionid}. + // Description: The course section id in which the current activity is located. + // Parameters: None. + if (stripos($text, '{sectionid}') !== false) { + $replace['/\{sectionid\}/i'] = @$PAGE->cm->sectionnum; + } + + // Tag: {getstring:component_name}stringidentifier{/getstring} or {getstring}stringidentifier{/getstring}. + // Description: Retrieves Moodle string. + // Optional Parameter: Component name. If component_name (plugin) is not specified, will default to "moodle". + // Required Content: The string identifier. + if (stripos($text, '{/getstring}') !== false) { + // Replace {getstring:} tag and parameters with retrieved content. + $newtext = preg_replace_callback( + '/\{getstring:?(\w*)\}(\w+)\{\/getstring\}/isuU', + function ($matches) use ($CFG) { + if ($strexists = get_string_manager()->string_exists($matches[2], $matches[1]) && $CFG->branch >= 28) { + $strexists = !get_string_manager()->string_deprecated($matches[2], $matches[1]); + } + if ($strexists) { + return get_string($matches[2], $matches[1]); + } else { + return "{getstring" . (!empty($matches[1]) ? ":$matches[1]" : '') . "}$matches[2]{/getstring}"; + } + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Tag: {courseunenrolurl}. + // Description: URL to unenrol from a course. + // Parameters: None. + if (stripos($text, '{courseunenrolurl}') !== false) { + require_once($CFG->libdir . '/enrollib.php'); + $course = $PAGE->course; + $coursecontext = \context_course::instance($course->id); + $replace['/\{courseunenrolurl\}/i'] = ''; + if ($course->id != SITEID && isloggedin() && !isguestuser() && is_enrolled($coursecontext)) { + $plugins = enrol_get_plugins(true); + $instances = enrol_get_instances($course->id, true); + foreach ($instances as $instance) { + if (!isset($plugins[$instance->enrol])) { + continue; + } + $plugin = $plugins[$instance->enrol]; + if ($unenrollink = $plugin->get_unenrolself_link($instance)) { + $replace['/\{courseunenrolurl\}/i'] = $unenrollink->out(); + break; + } + } + } + } + + // Tag: {fa fa-icon-name} + // Description: FontAwesome 4.7 and 6.0 icons. + // Required Parameter: 'fa' can be fa|fas|fa-solid|fab|fa-brands. Additional options available if using FontAwesome Pro. + // Required Parameter: icon-name. See full list at https://fontawesome.com/v4/icons/ or https://fontawesome.com/v6/icons/. + // Note that FontAwesome 6.x icons are only included with Moodle 4.2+. + if (stripos($text, '{fa') !== false) { + // Replace {fa...} tag and parameters with FontAwesome HTML. + $regex = '/\{fa('; + $regex .= 's|-solid|'; // Solid - included with Moodle. + $regex .= 'b|-brands|'; // Brands - included with Moodle. + // The rest require the FontAwesome Pro. + $regex .= 'r|-regular|'; + $regex .= 'l|-light|'; + $regex .= 't|-thin|'; + $regex .= 'd|-duotone|'; + $regex .= 'ss|-sharp\s+fa-solid|'; + $regex .= 'sr|-sharp\s+fa-regular|'; + $regex .= 'sl|-sharp\s+fa-light|'; + $regex .= 'st|-sharp\s+fa-thin|'; + $regex .= 'sd|-sharp\s+fa-duotone'; + $regex .= '){0,1}\s+fa-([a-z0-9 -]+)\}/isuU'; + $newtext = preg_replace_callback( + $regex, + function ($matches) { + $matches[0] = $matches[0] == null ? '' : $matches[0]; + return ''; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Tag: {glyphicon glyphion-name}. + // Description: Glyphicon icons. + // Required Parameter: name. + // Note: Glyphicons Font/CSS must be loaded as part of your theme. + if (stripos($text, '{glyphicon ') !== false) { + // Replace {glyphicon glyphicon-...} tag and parameters with Glyphicons HTML. + $newtext = preg_replace_callback( + '/\{glyphicon\sglyphicon-([a-z0-9 -]+)\}/isuU', + function ($matches) { + $matches[0] = $matches[0] == null ? '' : $matches[0]; + return ''; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Tag: {multilang xx}...{/multilang}. + // Description: Works just like the Moodle's Multi-Language filter except it's a plain text tag. No more HTML editing! + // Required Parameter: xx: The language. + // Requires content between tags. + // Note: This tag has a dependency on Moodle's Multi-Language Content filter being enabled. That filter + // must be below FilterCodes in Site Administration > Plugins > Manage Filters. This does not do any filtering of its own. + // For more information on the Multi-Language Content filter see https://docs.moodle.org/en/Multi-language_content_filter. + if (stripos($text, '{/multilang}') !== false) { + // This is specifically to make it easier to use Moodle's own multi-language filter. + $replace['/\{multilang\s+([a-z-]+)\}(.*)\{\/multilang\}/isuU'] = '$2'; + } + + // Tag: {firstaccessdate} or {firstaccessdate dateTimeFormat}. + // Description: Date that the user first accessed the site. + // Optional parameters: dateTimeFormat - either one of Moodle's built-in data/time formats or php's strftime. + if (stripos($text, '{firstaccessdate') !== false) { + if (isloggedin() && !isguestuser() && !empty($USER->firstaccess)) { + // Replace {firstaccessdate} tag with formatted date. + if (stripos($text, '{firstaccessdate}') !== false) { + $replace['/\{firstaccessdate\}/i'] = userdate($USER->firstaccess, get_string('strftimedatefullshort')); + } + // Replace {firstaccessdate dateTimeFormat} tag and parameters with formatted date. + if (stripos($text, '{firstaccessdate ') !== false) { + $newtext = preg_replace_callback( + '/\{firstaccessdate\s+(.+)\}/isuU', + function ($matches) use ($USER) { + // Check if this is a built-in Moodle date/time format. + if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { + // It is! Get the strftime string. + $matches[1] = get_string($matches[1], 'langconfig'); + } + return userdate($USER->firstaccess, $matches[1]); + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + } else { + $replace['/\{firstaccessdate(.*)\}/i'] = get_string('never'); + } + } + + // Tag: {lastlogin} or {lastlogin dateTimeFormat}. + // Description: Date that the user last logged in to the site. + // Optional parameters: dateTimeFormat - either one of Moodle's built-in data/time formats or php's strftime. + if (stripos($text, '{lastlogin') !== false) { + if (isloggedin() && !isguestuser() && !empty($USER->lastlogin)) { + // Replace {lastlogin} tag with formatted date. + if (stripos($text, '{lastlogin}') !== false) { + $replace['/\{lastlogin\}/i'] = userdate($USER->lastlogin, get_string('strftimedatetimeshort')); + } + // Replace {lastlogin dateTimeFormat} tag and parameters with formatted date. + if (stripos($text, '{lastlogin ') !== false) { + $newtext = preg_replace_callback( + '/\{lastlogin\s+(.+)\}/isuU', + function ($matches) use ($USER) { + // Check if this is a built-in Moodle date/time format. + if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { + // It is! Get the strftime string. + $matches[1] = get_string($matches[1], 'langconfig'); + } + return userdate($USER->lastlogin, $matches[1]); + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + } else { + $replace['/\{lastlogin(.*)\}/i'] = get_string('never'); + } + } + + // Tag: {coursestartdate} or {coursestartdate dateTimeFormat courseid}. + // Description: The course start date. + // Optional Parameters: dateTimeFormat - either in a Moodle datetime format or a PHP strftime format. + // Optional Parameters: id - id of a course. + if (stripos($text, '{coursestartdate') !== false) { + // Replace {coursestartdate} tag with formatted date. + if (stripos($text, '{coursestartdate}') !== false) { + if (!empty($PAGE->course->startdate)) { + $startdate = $PAGE->course->startdate; + } else { + $startdate = $DB->get_field_select('course', 'startdate', 'id = :id', ['id' => $PAGE->course->id]); + } + if (!empty($startdate)) { + $replace['/\{coursestartdate\}/i'] = userdate($startdate, get_string('strftimedatefullshort')); + } else { + $replace['/\{coursestartdate(.*)\}/isuU'] = get_string('notyetstarted', 'completion'); + } + } + + // Replace {coursestartdate dateTimeFormat} tag and parameters with formatted date. + if (stripos($text, '{coursestartdate ') !== false) { + $newtext = preg_replace_callback( + '/\{coursestartdate\s(.*)(\s\d+)?\}/isuU', + function ($matches) use ($PAGE, $DB) { + + // Optional date/time format. + if (is_numeric($matches[1])) { + // Only the course ID was specified. + $matches[2] = trim($matches[1]); // Course ID. + $matches[1] = ''; // Date/time format. + } else { + $matches[2] = empty($matches[2]) ? $PAGE->course->id : trim($matches[2]); // Course ID. + $matches[1] = trim($matches[1]); + } + + // Optional course ID. + if (empty($matches[2])) { // No course ID, use current course. + if (!empty($PAGE->course->startdate)) { + $startdate = $PAGE->course->startdate; + } else { + $startdate = $DB->get_field_select( + 'course', + 'startdate', + 'id = :id', + ['id' => $PAGE->course->id] + ); + } + } else { // Course ID was specifed. + $course = $DB->get_record('course', ['id' => $matches[2]]); + if (!empty($course)) { + $startdate = $course->startdate; + if (!empty($course->startdate)) { + $startdate = $course->startdate; + } else { + $startdate = $DB->get_field_select( + 'course', + 'startdate', + 'id = :id', + ['id' => $course->id] + ); + } + } else { + // Should only happen if course does not exist. + $startdate = 1; // December 31, 1969. + } + } + + // Check if this is a built-in Moodle date/time format. + if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { + // It is! Get the strftime string. + $matches[1] = get_string($matches[1], 'langconfig'); + } + + // Format the date. + if (!empty($startdate)) { + $startdate = userdate($startdate, $matches[1]); + } else { + $startdate = get_string('notyetstarted', 'completion'); + } + + return $startdate; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } else { + $replace['/\{coursestartdate(.*)\}/isuU'] = get_string('notyetstarted', 'completion'); + } + } + + // Tag: {courseenddate} or {coursesenddate dateTimeFormat courseid}. + // Description: The course end date. + // Optional Parameters: dateTimeFormat - either in a Moodle datetime format or a PHP strftime format. + // Optional Parameters: id - id of a course. + if (stripos($text, '{courseenddate') !== false) { + // Replace {courseenddate} tag with formatted date. + if (stripos($text, '{courseenddate}') !== false) { + if (empty($PAGE->course->enddate)) { + $enddate = $PAGE->course->enddate; + } else { + $enddate = $DB->get_field_select('course', 'enddate', 'id = :id', ['id' => $PAGE->course->id]); + } + if (!empty($enddate)) { + $replace['/\{courseenddate\}/i'] = userdate($enddate, get_string('strftimedatefullshort')); + } else { + $replace['/\{courseenddate(.*)\}/isuU'] = get_string('none'); + } + } + + // Replace {courseenddate dateTimeFormat} tag and parameters with formatted date. + if (stripos($text, '{courseenddate ') !== false) { + $newtext = preg_replace_callback( + '/\{courseenddate\s(.*)(\s\d+)?\}/isuU', + function ($matches) use ($PAGE, $DB) { + + // Optional date/time format. + if (is_numeric($matches[1])) { + // Only the course ID was specified. + $matches[2] = trim($matches[1]); // Course ID. + $matches[1] = ''; // Date/time format. + } else { + $matches[2] = empty($matches[2]) ? $PAGE->course->id : trim($matches[2]); // Course ID. + $matches[1] = trim($matches[1]); + } + + // Optional course ID. + if (empty($matches[2])) { // No course ID, use current course. + if (!empty($PAGE->course->enddate)) { + $enddate = $PAGE->course->enddate; + } else { + $enddate = $DB->get_field_select( + 'course', + 'enddate', + 'id = :id', + ['id' => $PAGE->course->id] + ); + } + } else { // Course ID was specifed. + $course = $DB->get_record('course', ['id' => $matches[2]]); + if (!empty($course)) { + $enddate = $course->enddate; + if (!empty($course->enddate)) { + $enddate = $course->enddate; + } else { + $enddate = $DB->get_field_select( + 'course', + 'enddate', + 'id = :id', + ['id' => $course->id] + ); + } + } else { + // Should only happen if course does not exist. + $enddate = 1; // December 31, 1969. + } + } + + // Check if this is a built-in Moodle date/time format. + if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { + // It is! Get the strftime string. + $matches[1] = get_string($matches[1], 'langconfig'); + } + + // Format the date. + if (!empty($enddate)) { + $enddate = userdate($enddate, $matches[1]); + } else { + $enddate = get_string('none'); + } + + return $enddate; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } else { + $replace['/\{courseenddate(.*)\}/isuU'] = get_string('none'); + } + } + + // Tag: {coursecompletiondate} or {coursecompletiondate dateTimeFormat}. + // Description: The course completion date. + // Optional Parameters: dateTimeFormat - either in a Moodle datetime format or a PHP strftime format. + if (stripos($text, '{coursecompletiondate') !== false) { + if ( + $PAGE->course + && isset($CFG->enablecompletion) + && $CFG->enablecompletion == 1 // COMPLETION_ENABLED. + && $PAGE->course->enablecompletion + ) { + $ccompletion = new completion_completion(['userid' => $USER->id, 'course' => $PAGE->course->id]); + $incomplete = get_string('notcompleted', 'completion'); + } else { // Completion not enabled. + $incomplete = get_string('completionnotenabled', 'completion'); + } + if (!empty($ccompletion->timecompleted)) { + // Replace {coursecompletiondate} tag with formatted date. + if (stripos($text, '{coursecompletiondate}') !== false) { + $replace['/\{coursecompletiondate\}/i'] = userdate( + $ccompletion->timecompleted, + get_string('strftimedatefullshort') + ); + } + // Replace {coursecompletiondate dateTimeFormat} tag and parameters with formatted date. + if (stripos($text, '{coursecompletiondate ') !== false) { + $newtext = preg_replace_callback( + '/\{coursecompletiondate\s+(.+)\}/isuU', + function ($matches) use ($ccompletion) { + // Check if this is a built-in Moodle date/time format. + if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { + // It is! Get the strftime string. + $matches[1] = get_string($matches[1], 'langconfig'); + } + return userdate($ccompletion->timecompleted, $matches[1]); + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + } else { + $replace['/\{coursecompletiondate(.*)\}/isuU'] = $incomplete; + } + } + + // Tag: {courseenrolmentdate} or {courseenrolmentdate dateTimeFormat}. + // Description: The course enrolment date. + // Optional Parameters: dateTimeFormat - either in a Moodle datetime format or a PHP strftime format. + if (stripos($text, '{courseenrolmentdate') !== false) { + $sql = ' + SELECT ue.timecreated + FROM {user} u + JOIN {user_enrolments} ue ON ue.userid = u.id + JOIN {enrol} e ON ue.enrolid = e.id + WHERE ue.userid = :userid AND e.courseid = :courseid + '; + $thisuser = $DB->get_records_sql($sql, ['userid' => $USER->id, 'courseid' => $PAGE->course->id]); + if (count($thisuser)) { + // Gets the first key of the array. + reset($thisuser); + $datecreated = key($thisuser); + // Replace {courseenrolmentdate} tag with formatted date. + if (stripos($text, '{courseenrolmentdate}') !== false) { + $replace['/\{courseenrolmentdate\}/i'] = userdate($datecreated, get_string('strftimedatefullshort')); + } + // Replace {courseenrolmentdate dateTimeFormat} tag and parameters with formatted date. + if (stripos($text, '{courseenrolmentdate ') !== false) { + $newtext = preg_replace_callback( + '/\{courseenrolmentdate\s+(.+)\}/isuU', + function ($matches) use ($datecreated) { + // Check if this is a built-in Moodle date/time format. + if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { + // It is! Get the strftime string. + $matches[1] = get_string($matches[1], 'langconfig'); + } + return userdate($datecreated, $matches[1]); + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + } else { + $replace['/\{courseenrolmentdate(.*)\}/isuU'] = ''; + } + } + + // Tag: {now} or {now dateTimeFormat}. + // Description: Current year, 4 digits. + // Optional parameter: dateTimeFormat - either one of Moodle's built-in data/time formats or php's strftime. + if (stripos($text, '{now') !== false) { + // Replace {now} tag with formatted date. + $now = time(); + if (stripos($text, '{now}') !== false) { + $replace['/\{now\}/i'] = userdate($now, get_string('strftimedatefullshort')); + } + // Replace {now dateTimeFormat} tag and parameters with formatted date. + if (stripos($text, '{now ') !== false) { + $newtext = preg_replace_callback( + '/\{now\s+(.+)\}/isuU', + function ($matches) use ($now) { + // Check if this is a built-in Moodle date/time format. + if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { + // It is! Get the strftime string. + $matches[1] = get_string($matches[1], 'langconfig'); + } + return userdate($now, $matches[1]); + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + unset($now); + } + + // Tag: {keyboard}text{/keyboard}. + // Description: Wraps the text inside a set of HTML tags. + // Parameters: Any text. + if (stripos($text, '{/keyboard}') !== false) { + $replace['/\{keyboard\}(.*)\{\/keyboard\}/isuU'] = '$1'; + } + + /* ---------------- Apply all of the filtercodes so far. ---------------*/ + + if ($this->replacetags($text, $replace) == false) { + // No more tags? Put back the escaped tags, if any, and return the string. + $text = $this->escapedtags($text); + return $text; + } + + // ...===================================================================================================================. + // The rest of the tags below. Put tags above if they generate more tags or will be used as parameters for other tags. + // ...===================================================================================================================. + + // Simple tags that don't ever have parameters. + + // Substitutions. + + $u = $USER; + if (!isloggedin() || isguestuser()) { + $u->firstname = get_string('defaultfirstname', 'filter_filtercodes'); + $u->lastname = get_string('defaultsurname', 'filter_filtercodes'); + } + $u->fullname = trim(get_string('fullnamedisplay', null, $u)); + + // Tag: {firstname}. + // Description: User's first name as set in their profile. + // Parameters: None. + if (stripos($text, '{firstname}') !== false) { + $replace['/\{firstname\}/i'] = $u->firstname; + } + + // Tag: {surname}. + // Description: Alias for {lastname}. + // Parameters: None. + if (stripos($text, '{surname}') !== false) { + $text = str_replace('{surname}', '{lastname}', $text); + } + + // Tag: {lastname}. + // Description: User's last name as set in their profile. + // Parameters: None. + if (stripos($text, '{lastname}') !== false) { + $replace['/\{lastname\}/i'] = $u->lastname; + } + + // Tag: {fullname}. + // Description: User's full name as set in their profile. + // Parameters: None. + if (stripos($text, '{fullname}') !== false) { + $replace['/\{fullname\}/i'] = $u->fullname; + } + + // Tag: {alternatename}. + // Description: User's alternate name as set in their profile. + // Parameters: None. + if (stripos($text, '{alternatename}') !== false) { + // If alternate name is empty, use firstname instead. + if (isloggedin() && !isguestuser() && (!is_null($USER->alternatename) && !empty(trim($USER->alternatename)))) { + $replace['/\{alternatename\}/i'] = $USER->alternatename; + } else { + $replace['/\{alternatename\}/i'] = $u->firstname; + } + } + + // Tag: {email}. + // Description: User's email address as set in their profile. + // Parameters: None. + if (stripos($text, '{email}') !== false) { + $replace['/\{email\}/i'] = isloggedin() && !isguestuser() ? $USER->email : ''; + } + + // Tag: {city}. + // Description: User's city as set in their profile. + // Parameters: None. + if (stripos($text, '{city}') !== false) { + $replace['/\{city\}/i'] = isloggedin() && !isguestuser() ? $USER->city : ''; + } + + // Tag: {country}. + // Description: User's country as set in their profile. + // Parameters: None. + if (stripos($text, '{country}') !== false) { + if (isloggedin() && !isguestuser() && !empty($USER->country)) { + $replace['/\{country\}/i'] = get_string($USER->country, 'countries'); + } else { + $replace['/\{country\}/i'] = ''; + } + } + // Tag: {timezone}. + // Description: User's time zone as set in their profile. + // Parameters: None. + if (stripos($text, '{timezone}') !== false) { + if (isloggedin() && !isguestuser() && !empty($USER->timezone)) { + if ($USER->timezone == '99') { // Default is system timezone. + $replace['/\{timezone\}/i'] = \core_date::get_default_php_timezone(); + } else { + $replace['/\{timezone\}/i'] = \core_date::get_localised_timezone($USER->timezone); + } + } + } + + // Tag: {institution}. + // Description: User's institution as set in their profile. + // Parameters: None. + if (stripos($text, '{institution}') !== false) { + $replace['/\{institution\}/i'] = isloggedin() && !isguestuser() ? $USER->institution : ''; + } + + // Tag: {department}. + // Description: User's department as set in their profile. + // Parameters: None. + if (stripos($text, '{department}') !== false) { + $replace['/\{department\}/i'] = isloggedin() && !isguestuser() ? $USER->department : ''; + } + + // Tag: {idnumber}. + // Description: idnumber as specified in the user's profile. + // Parameters: None. + if (stripos($text, '{idnumber}') !== false) { + $replace['/\{idnumber\}/i'] = isloggedin() && !isguestuser() ? $USER->idnumber : ''; + } + + // Tag: {webpage} + // Description: Social field in user's profile. This migrates from pre-Moodle 3.11 - for backwards compatibility. + // Parameters: None. + if (stripos($text, '{webpage}') !== false) { + if ($CFG->branch >= 311) { + $text = str_replace('{webpage}', '{profile_field_webpage}', $text); + } else { + $replace['/\{webpage\}/i'] = isloggedin() && !isguestuser() ? $USER->url : ''; + } + } + + // Tag: {diskfreespace}. + // Description: Free space of Moodle application volume. + // Parameters: None. + if (stripos($text, '{diskfreespace}') !== false) { + $bytes = @disk_free_space('.'); + $replace['/\{diskfreespace\}/i'] = $this->humanbytes($bytes); + } + + // Tag: {diskfreespacedata}. + // Description: Free space of Moodle data volume. + // Parameters: None. + if (stripos($text, '{diskfreespacedata}') !== false) { + $bytes = @disk_free_space($CFG->dataroot); + $replace['/\{diskfreespacedata\}/i'] = $this->humanbytes($bytes); + } + + // Tags starting with: {support...}. + if (stripos($text, '{support') !== false) { + // Tag: {supportname}. + // Description: Support name for the site from Moodle settings. + // None. + if (stripos($text, '{supportname}') !== false) { + if (empty($CFG->supportname)) { + $replace['/\{supportname\}/i'] = get_string('notavailable', 'filter_filtercodes'); + } else { + $replace['/\{supportname\}/i'] = $CFG->supportname; + } + } + + // Tag: {supportemail}. + // Description: Support email address for the site from Moodle settings. + // None. + if (stripos($text, '{supportemail}') !== false) { + if (empty($CFG->supportname)) { + $replace['/\{supportemail\}/i'] = get_string('notavailable', 'filter_filtercodes'); + } else { + $replace['/\{supportemail\}/i'] = $CFG->supportemail; + } + } + + // Tag: {supportpage}. + // Description: URL of Support for the site from Moodle settings. + // None. + if (stripos($text, '{supportpage}') !== false) { + if (empty($CFG->supportname)) { + $replace['/\{supportpage\}/i'] = ''; + } else { + $replace['/\{supportpage\}/i'] = $CFG->supportpage; + } + } + + // Tag: {supportservicespage}. + // Description: URL of support services page from Moodle settings. + // None. + if ($CFG->branch >= 402 && stripos($text, '{supportservicespage}') !== false) { + $replace['/\{supportservicespage\}/i'] = $CFG->servicespage; + } + } + + if (stripos($text, '{site') !== false) { + // Tag: {sitename}. + // Description: The full name of the site name. + // Parameters: None. + if (stripos($text, '{sitename') !== false) { + $sitecontext = \context_system::instance(); + $replace['/\{sitename\}/i'] = format_string($SITE->fullname, true, ['context' => $sitecontext]); + } + + // Tag: {sitesummary}. + // Description: Site summary as defined in the Front Page/Site Home Settings. + // Parameters: None. + if (stripos($text, '{sitesummary}') !== false) { + $replace['/\{sitesummary\}/i'] = $SITE->fullname; + } + + // Tag: {siteyear}. + // Description: Current year, 4 digits. + // Parameters: None. + if (stripos($text, '{siteyear}') !== false) { + $replace['/\{siteyear\}/i'] = date('Y'); + } + // Tag: {sitelogourl} or %7Bsitelogourl%7D. + // Description: URL of site logo. + // Parameters: None. + if (stripos($text, '{sitelogourl}') !== false) { + global $OUTPUT; + $replace['/\{sitelogourl\}/i'] = '' . $OUTPUT->get_logo_url(); + } + if (stripos($text, '%7Bsitelogourl%7D') !== false) { + global $OUTPUT; + $replace['/\%7Bsitelogourl\%7D/i'] = '' . $OUTPUT->get_logo_url(); + } + } + + /* ---------------- Apply all of the filtercodes so far. ---------------*/ + + if ($this->replacetags($text, $replace) == false) { + // No more tags? Put back the escaped tags, if any, and return the string. + $text = $this->escapedtags($text); + return $text; + } + + if (stripos($text, '{profile') !== false) { + // Tag: {profile_field_shortname}. + // Description: Contents of the custom user profile field. Will apply formating to datetime and checkbox type fields. + // Required Parameters: shortname of a custom profile field. + if (stripos($text, '{profile_field') !== false) { + $isuser = (isloggedin() && !isguestuser()); + // Cached the defined custom profile fields and data. + if (!isset($profilefields)) { + $profilefields = $DB->get_records('user_info_field', null, '', 'id, datatype, shortname, visible, param3'); + if ($isuser && !empty($profilefields)) { + $profiledata = $DB->get_records_menu('user_info_data', ['userid' => $USER->id], '', 'fieldid, data'); + } + } + $showhidden = get_config('filter_filtercodes', 'showhiddenprofilefields'); + foreach ($profilefields as $field) { + // If the tag exists and is not set to "Not visible" in the custom profile field's settings. + if ( + $isuser + && stripos($text, '{profile_field_' . $field->shortname . '}') !== false + && ($field->visible != '0' || !empty($showhidden)) + ) { + $data = isset($profiledata[$field->id]) ? trim($profiledata[$field->id]) : '' . PHP_EOL; + switch ($field->datatype) { // Format data for some field types. + case 'datetime': + // Include date and time or just date? + $datetimeformat = !empty($field->param3) ? 'strftimedaydatetime' : 'strftimedate'; + $data = empty($data) ? '' : userdate($data, get_string($datetimeformat, 'langconfig')); + break; + case 'checkbox': + // 1 = Yes, 0 = No + $data = empty($data) ? get_string('no') : get_string('yes'); + break; + } + $replace['/\{profile_field_' . $field->shortname . '\}/i'] = $data; + } else { + $replace['/\{profile_field_' . $field->shortname . '\}/i'] = ''; + } + } + } + + // Tag: {profilefullname}. + // Description: Full name of current user. + // Parameters: None. + if (stripos($text, '{profilefullname}') !== false) { + $fullname = ''; + if (isloggedin() && !isguestuser()) { + $fullname = get_string('fullnamedisplay', null, $USER); + if ($PAGE->pagelayout == 'mypublic' && $PAGE->pagetype == 'user-profile') { + $userid = optional_param('userid', optional_param( + 'user', + optional_param('id', $USER->id, PARAM_INT), + PARAM_INT + ), PARAM_INT); + if ($user = $DB->get_record('user', ['id' => $userid, 'deleted' => 0])) { + $fullname = get_string('fullnamedisplay', null, $user); + } + } + } + $replace['/\{profilefullname\}/i'] = $fullname; + unset($fullname); + } + } + + // Tag: {scrape url="" }. + // Description: Scrapes content from an external HTML page. Cannot scrape secure pages from sites that requires login. + // Optional parameters: You may use any combination of the following: tag="..." class="..." id="..." code="...". + if (get_config('filter_filtercodes', 'enable_scrape') && stripos($text, '{scrape ') !== false) { + // Replace {scrape} tag and its attributes with retrieved content. + $newtext = preg_replace_callback( + '/\{scrape\s+(.*)\}/isuU', + function ($matches) { + // Parse the scrape tag's atributes. + $matches[0] = $matches[0] == null ? '' : strip_tags($matches[0]); + $attribs = substr($matches[0], 1, -1); + $scrape = $this->attribstoarray($attribs); + $url = isset($scrape['url']) ? $scrape['url'] : ''; + $tag = isset($scrape['tag']) ? $scrape['tag'] : ''; + $class = isset($scrape['class']) ? $scrape['class'] : ''; + $id = isset($scrape['id']) ? $scrape['id'] : ''; + $code = isset($scrape['code']) ? $scrape['code'] : ''; + // If nothing else, we must have a URL parameter. + if (empty($url)) { + return "SCRAPE error: Missing or invalid required URL parameter."; + } + // Replace {scrape} tag and its attributes with retrieved content. + return $this->scrapehtml($url, $tag, $class, $id, $code); + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Any {user*} tags. + if (stripos($text, '{user') !== false || stripos($text, '%7Buser') !== false) { + // Tag: {username}. + // Description: User's username as defined in their profile. When not logged in, uses predefined name in language file. + // Parameters: None. + if (stripos($text, '{username}') !== false) { + $replace['/\{username\}/i'] = isloggedin() + && !isguestuser() ? $USER->username : get_string('defaultusername', 'filter_filtercodes'); + } + + // These tags: {userpictureurl} and {userpictureimg}. + if (stripos($text, '{userpicture') !== false) { + // Tag: {userpictureurl size}. + // Description: URL of user's picture as set in their profile. + // Parameters: Sizes: sm|md|lg or an integer 2|1|3 or an integer size in pixels > 3. + if (stripos($text, '{userpictureurl ') !== false) { + $newtext = preg_replace_callback( + '/\{userpictureurl\s+(\w+)\}/isuU', + function ($matches) use ($USER) { + return $this->getprofilepictureurl($USER, $matches[1]); + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Tag: {userpictureimg size}. + // Description: URL of user's picture as set in their profile, wrapped in an HTML img tag. + // Parameters: Sizes: sm|md|lg or an integer 2|1|3 or an integer size in pixels > 3. + if (stripos($text, '{userpictureimg ') !== false) { + $newtext = preg_replace_callback( + '/\{userpictureimg\s+(\w+)\}/isuU', + function ($matches) use ($USER) { + $url = $this->getprofilepictureurl($USER, $matches[1]); + $tag = '' . $USER->fullname . ''; + return $tag; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + } + + // Tag: {userdescription}. + // Description: Description as set in user's profile. + // Parameters: None. + if (stripos($text, '{userdescription}') !== false) { + if (isloggedin() && !isguestuser()) { + $user = $DB->get_record('user', ['id' => $USER->id], 'description', MUST_EXIST); + $replace['/\{userdescription\}/i'] = format_text($user->description, $USER->descriptionformat); + unset($user); + } else { + $replace['/\{userdescription\}/i'] = ''; + } + } + + // Tag: {usercount}. + // Description: A count of the total number of users on the site. Includes suspended and unconfirmed users. + // Parameters: None. + if (stripos($text, '{usercount}') !== false) { + // Count total number of current users on the site. + // Exclude deleted users, admin and guest. + $cnt = $DB->count_records('user', ['deleted' => 0]) - 2; + $replace['/\{usercount\}/i'] = $cnt; + } + + // Tag: {usersactive}. + // Description: A count of the total number of active users on the site. + // Parameters: None. + if (stripos($text, '{usersactive}') !== false) { + // Count total number of current users on the site. + // Exclude deleted, suspended and unconfirmed users, admin and guest. + $cnt = $DB->count_records('user', ['deleted' => 0, 'suspended' => 0, 'confirmed' => 1]) - 2; + $replace['/\{usersactive\}/i'] = $cnt; + } + + // Tag: {usersonline}. + // Description: A count of the total number of users currently online on the site within the last 5 minutes. + // Parameters: None. + if (stripos($text, '{usersonline}') !== false) { + $timetosee = 300; // Within last number of seconds (300 = 5 minutes). + if (isset($CFG->block_online_users_timetosee)) { + $timetosee = $CFG->block_online_users_timetosee * 60; + } + $now = time(); + + // Calculate if we are in separate groups. + $isseparategroups = ($PAGE->course->groupmode == SEPARATEGROUPS + && $PAGE->course->groupmodeforce + && !has_capability('moodle/site:accessallgroups', $PAGE->context)); + + // Get the user current group. + $thisgroup = $isseparategroups ? groups_get_course_group($PAGE->course) : null; + + $onlineusers = new fetcher( + $thisgroup, + $now, + $timetosee, + $PAGE->context, + $PAGE->context->contextlevel, + $PAGE->course->id + ); + + // Count online users. + $usersonline = $onlineusers->count_users(); + $replace['/\{usersonline\}/i'] = $usersonline; + } + + // Tag: {userscountrycount}. + // Description: A count of the total number countries that users are in as set in their user profile. + // Parameters: None. + if (stripos($text, '{userscountrycount}') !== false) { + $count = $DB->count_records_sql('SELECT COUNT(DISTINCT country) FROM {user} WHERE id > 2'); + $replace['/\{userscountrycount\}/i'] = $count; + } + } + + // Check if any {course*} or %7Bcourse*%7D tags. Note: There is another course tags section further up. + $coursetagsexist = (stripos($text, '{course') !== false || stripos($text, '%7Bcourse') !== false); + if ($coursetagsexist) { + // Tag: {coursecontacts}. + // Description: Get list of course contacts based on settings in Site Administration > Appearances > Courses. + // Parameters: None. + if (stripos($text, '{coursecontacts}') !== false) { + $contacts = ''; + // If course (not site pages) with contacts. + if ($PAGE->course->id) { + $course = new \core_course_list_element($PAGE->course); + if ($course->has_course_contacts()) { + // Get tag settings. + $cshowpic = get_config('filter_filtercodes', 'coursecontactshowpic'); + $cshowdesc = get_config('filter_filtercodes', 'coursecontactshowdesc'); + $clinktype = get_config('filter_filtercodes', 'coursecontactlinktype'); + + // Prepare some strings. + $linksr = ['' => '', + 'email' => get_string('issueremail', 'badges'), + 'message' => get_string('message', 'message'), + 'profile' => get_string('profile'), + 'phone' => get_string('phone'), + ]; + $iconclass = ['' => '', + 'email' => 'fa fa-envelope-o', + 'message' => 'fa fa-comment-o', + 'profile' => 'fa fa-user-o', + 'phone' => 'fa fa-mobile', + ]; + + $cnt = 0; + foreach ($course->get_course_contacts() as $coursecontact) { + $icon = ' '; + + $contacts .= '
  • '; + + // Get list of course contacts based on settings in Site Administration > Appearances > Courses. + // Get list of user's roles in the course. + $rolenames = array_map(function ($role) { + return $role->displayname; + }, $coursecontact['roles']); + + // Retrieve contact's profile information. + $user = $DB->get_record( + 'user', + ['id' => $coursecontact['user']->id], + $fields = '*', + $strictness = IGNORE_MULTIPLE + ); + $fullname = get_string('fullnamedisplay', null, $user); + if ($cshowpic) { + $imgurl = $this->getprofilepictureurl($user, 3); + $contacts .= '' . $fullname
+                                    . ''; + $cnt++; + } + + $contactsclose = '' . $linksr[$clinktype] . ': '; + $contactsclose .= $fullname . ''; + + $contacts .= '' . implode(", ", $rolenames) . ': '; + + switch ($clinktype) { + case 'email': + $contacts .= $icon . ''; + $contacts .= $contactsclose; + break; + case 'message': + $contacts .= $icon . ''; + $contacts .= $contactsclose; + break; + case 'profile': + $contacts .= $icon . ''; + $contacts .= $contactsclose; + break; + case 'phone1' && !empty($user->phone1): + $contacts .= $icon . ''; + $contacts .= $contactsclose; + break; + default: // Default is no-link. + $contacts .= $fullname; + break; + } + if ($cshowdesc && !empty($user->description)) { + $contacts .= '' . + $user->description . ''; + } + $contacts .= '
  • '; + } + } + } + + if (empty($contacts)) { + $replace['/\{coursecontacts\}/i'] = get_string('nocontacts', 'message'); + } else { + $replace['/\{coursecontacts\}/i'] = '
      ' . + $contacts . '
    '; + } + unset($contacts, $contactsclose, $fullname, $url, $user, $rolenames, $icon, $iconclass); + unset($linksr, $clinktype, $cshowpic); + } + + // Tag: {courseparticipantcount}. + // Description: Get a the number of participants in the course. This includes anyone registered in the course. + // Parameters: None. + if (stripos($text, '{courseparticipantcount}') !== false) { + static $courseparticipantcount; + require_once($CFG->dirroot . '/user/lib.php'); + if (!isset($courseparticipants)) { + $sql = "SELECT COUNT(1) + FROM {user_enrolments} ue + JOIN {enrol} e ON e.id = ue.enrolid + WHERE e.courseid = :courseid"; + $params = ['courseid' => $PAGE->course->id]; + $courseparticipantcount = $DB->count_records_sql($sql, $params); + } + $replace['/\{courseparticipantcount\}/i'] = $courseparticipantcount; + } + + // Tag: {coursecount students|students:active}. + // Requires one of two parameters: + // Optional Parameters: "students" - Filter limiting to just users with the role of student; or + // Optional Parameters: "students:active" - Filter limiting to student who have not been suspended. + // Description: Get just the number of "students" in the course. + if (stripos($text, '{coursecount students}') !== false) { + if ($CFG->branch >= 32) { + $coursecontext = \context_course::instance($PAGE->course->id); + $role = $DB->get_record('role', ['shortname' => 'student']); + $students = get_role_users($role->id, $coursecontext); + $cnt = count($students); + unset($students); + } else { + $cnt = ''; + } + $replace['/\{coursecount students\}/i'] = $cnt; + } + if (stripos($text, '{coursecount students:active}') !== false) { + $sql = "SELECT COUNT(DISTINCT ue.userid) + FROM {user_enrolments} ue + JOIN {enrol} e ON e.id = ue.enrolid + JOIN {course} c ON c.id = e.courseid + JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = 50 + JOIN {role_assignments} ra ON ra.contextid = ctx.id AND ra.userid = ue.userid + JOIN {role} r ON r.id = ra.roleid AND r.shortname = 'student' + WHERE ue.status = 0 AND e.courseid = :courseid"; + $cnt = $DB->count_records_sql($sql, ['courseid' => $PAGE->course->id]); + $replace['/\{coursecount students:active\}/i'] = $cnt; + } + + // Tag: {coursecount}. + // Description: The total number of courses. + // Parameters: None. + // Note that there are parametered vesions above giving it a completely different purpose. + if (stripos($text, '{coursecount}') !== false) { + // Count courses excluding front page. + $cnt = $DB->count_records('course', []) - 1; + $replace['/\{coursecount\}/i'] = $cnt; + } + + // Tag: {courseidnumber}. + // Description: The course idnumber as set in the course settings. + // Parameters: None. + if (stripos($text, '{courseidnumber}') !== false) { + $replace['/\{courseidnumber\}/i'] = $PAGE->course->idnumber; + } + + // Tag: {coursename}. + // Description: The full name of a course, or the site name if not in a course. + // Parameters: None. + if (stripos($text, '{coursename') !== false) { + if (stripos($text, '{coursename}') !== false) { + // No course ID was specified. + $course = $PAGE->course; + if ($course->id == $SITE->id) { // If not in a course, use the site name. + $coursecontext = \context_system::instance(); + $replace['/\{coursename\}/i'] = format_string( + $SITE->fullname, + true, + ['context' => $coursecontext] + ); + } else { // If in a course - use course full name. + $coursecontext = \context_course::instance($course->id); + $replace['/\{coursename\}/i'] = format_string( + $course->fullname, + true, + ['context' => $coursecontext] + ); + } + } + if (stripos($text, '{coursename ') !== false) { + // Course ID was specified. + preg_match_all('/\{coursename ([0-9]+)\}/', $text, $matches); + // Eliminate course IDs. + $courseids = array_unique($matches[1]); + $coursecontext = \context_system::instance(); + foreach ($courseids as $id) { + $course = $DB->get_record('course', ['id' => $id]); + if (!empty($course)) { + $replace['/\{coursename ' . $course->id . '\}/isuU'] = format_string( + $course->fullname, + true, + ['context' => $coursecontext] + ); + } + } + unset($matches, $course, $courseids, $id); + } + } + + if (stripos($text, '{courseimage') !== false) { + $course = $PAGE->course; + if ($CFG->branch >= 33) { + $imgurl = \core_course\external\course_summary_exporter::get_course_image($course); + } else { // Previous to Moodle 3.3. + $imgurl = ''; + $context = \context_course::instance($course->id); + if ($course instanceof stdClass) { + $course = new \core_course_list_element($course); + } + $coursefiles = $course->get_course_overviewfiles(); + foreach ($coursefiles as $file) { + if ($isimage = $file->is_valid_image()) { + $filename = '/' . $file->get_contextid() . '/' . $file->get_component() + . '/' . $file->get_filearea() . $file->get_filepath() . $file->get_filename(); + $imgurl = file_encode_url("/pluginfile.php", $filename, !$isimage); + break; + } + } + } + if (empty($imgurl)) { + global $OUTPUT; + $imgurl = $OUTPUT->get_generated_image_for_id($course->id); + } + + // Tag: {courseimage}. + // Description: Course image as rendeable HTML img tag. + // Parameters: None. + if (stripos($text, '{courseimage}') !== false) { + $replace['/\{courseimage\}/i'] = ''; + } + + // Tag: {courseimage-url}. + // Description: Course image URL. + // Parameters: none. + if (stripos($text, '{courseimage-url}') !== false) { + $replace['/\{courseimage-url\}/i'] = $imgurl; + } + } + + // Tag: {coursecount}. + // Description: The total number of courses. + // Parameters: None. + if (stripos($text, '{coursecount}') !== false) { + // Count courses excluding front page. + $cnt = $DB->count_records('course', []) - 1; + $replace['/\{coursecount\}/i'] = $cnt; + } + + // Tag: {coursesactive}. + // Description: The total number of active visible courses: visibility set to Show, started, not ended. + // Parameters: None. + if (stripos($text, '{coursesactive}') !== false) { + // Count current courses (between start and end date, if any) set to Show - excluding front page. + $today = time(); + $sql = "SELECT COUNT(id) + FROM {course} + WHERE visible = 1 + AND startdate <= :today + AND (enddate > :today2 OR enddate = 0);"; + // Subtract one for site course, where id = 1. + $cnt = $DB->count_records_sql($sql, ['today' => $today, 'today2' => $today]) - 1; + $replace['/\{coursesactive\}/i'] = $cnt; + } + + // Tag: {coursegrade}. + // Description: Overall grade in a courses, with percentage symbol. + // Parameters: None. + if (version_compare(PHP_VERSION, '7.0.0') >= 0 && stripos($text, '{coursegrade}') !== false) { + require_once($CFG->libdir . '/gradelib.php'); + require_once($CFG->dirroot . '/grade/querylib.php'); + $gradeobj = grade_get_course_grade($USER->id, $PAGE->course->id); + $grade = 0; + if (!empty($grademax = floatval($gradeobj->item->grademax))) { + // Avoid divide by 0 error if no grades have been defined. + $grade = floatval($grademax) > 0 ? (int) ($gradeobj->grade / floatval($grademax) * 100) : 0; + } + $replace['/\{coursegrade\}/i'] = get_string('percents', '', $grade); + } + + if (stripos($text, '{courseprogress') !== false) { + $progress = $this->completionprogress(); + + // Tag: {courseprogress}. + // Description: Course completion progress percentage as formatted text. + // Parameters: None. + if (stripos($text, '{courseprogress}') !== false) { + if ($progress != -1) { // Is enabled. + $replace['/\{courseprogress\}/i'] = '' + . get_string('aria:courseprogress', 'block_myoverview') . ' ' + . get_string('completepercent', 'block_myoverview', $progress); + } else { + $replace['/\{courseprogress\}/i'] = ''; + } + } + + // Tag: {courseprogressbar}. + // Description: Course completion progress bar. + // Parameters: None. + if (stripos($text, '{courseprogressbar}') !== false) { + if ($progress != -1) { // Is enabled. + $replace['/\{courseprogressbar\}/i'] = ' +
    +
    +
    +
    '; + } else { + $replace['/\{courseprogressbar\}/i'] = ''; + } + } + unset($progress); + } + + // Tag: {coursecards} and {coursecards }. + // Description: Courses in a category branch as cards + // Optional Parameters: The category ID number. + if (stripos($text, '{coursecards') !== false) { + global $OUTPUT; + + $chelper = new coursecat_helper(); + $chelper->set_show_courses(20)->set_courses_display_options([ + 'recursive' => true, + 'limit' => $CFG->frontpagecourselimit, + 'viewmoreurl' => (new \moodle_url('/course/index.php'))->out(), + 'viewmoretext' => new lang_string('fulllistofcourses'), + ]); + + $chelper->set_attributes(['class' => 'frontpage-course-list-all']); + // Find all coursecards tags where category ID was specified. + preg_match_all('/\{coursecards ([0-9]+)\}/', $text, $matches); + // Check if tag with no category. + $nocat = (stripos($text, '{coursecards}') !== false); + if ($nocat) { + $matches[1][] = 0; + } + // Eliminate duplicate categories. + $categories = array_unique($matches[1]); + + $card = $this->getcoursecardinfo(); + + foreach ($categories as $catid) { + try { + $coursecat = \core_course_category::get($catid); + // Get list of courses in this category. + $courses = $coursecat->get_courses($chelper->get_courses_display_options()); + } catch (Exception $e) { + // Course category not found or not accessible. + // No courses available. + $courses = []; + } + + $rcourseids = array_keys($courses); + if (count($rcourseids) > 0) { + $content = $this->rendercoursecards($rcourseids, $card->format); + } else { + $content = ''; + } + if ($catid == 0 && $nocat) { + $replace['/\{coursecards\}/i'] = !empty($content) ? $card->header . $content . $card->footer : ''; + } + $replace['/\{coursecards ' . $catid . '\}/isuU'] = + !empty($content) ? $card->header . $content . $card->footer : ''; + } + } + + // Tag: {coursecard courseid}. + // Description: Display a course card for the specified course id. + // Optional Parameters: a courseid number. If not specified, will use the current course's id or the site id (1). + if (stripos($text, '{coursecard ') !== false) { + $re = '/\{coursecard\s([\s\d]+)\}/isuU'; + $found = preg_match_all($re, $text, $matches); + $matches = array_combine(array_values($matches[0]), array_values($matches[1])); + $card = $this->getcoursecardinfo(); + foreach ($matches as $key => $match) { + $courseids = explode(' ', $match); + + // Only keep valid course ids. + $courseids = array_map('trim', $courseids); // Remove extra spaces. + $courseids = array_filter($courseids); // Remove empty elements. + $courseids = array_unique($courseids); // Remove duplicates. + foreach ($courseids as $key => $courseid) { + $course = $DB->get_record('course', ['id' => $courseid]); + if ($course === false) { + // Course not found. Remove it from the list. + unset($courseids[$key]); + } + } + // Create cards for existing courses that are visible to user. + $content = $this->rendercoursecards($courseids, $card->format); + $replace['/\{coursecard ' . $match . '\}/isuU'] = + !empty($content) ? $card->header . $content . $card->footer : ''; + } + } + + // Tag: {coursecardsbyenrol}. + // Description: Display list of 10 most popular courses by enrolment count (tested with MySQL and PostgreSQL). + // Parameters: None. + if (stripos($text, '{coursecardsbyenrol}') !== false) { + $sql = "SELECT c.id, c.fullname, COUNT(*) AS enrolments + FROM {course} c + JOIN (SELECT DISTINCT e.courseid, ue.id AS userid + FROM {user_enrolments} ue + JOIN {enrol} e ON e.id = ue.enrolid) ue ON ue.courseid = c.id + GROUP BY c.id, c.fullname + ORDER BY 3 DESC, c.fullname"; + $courses = $DB->get_records_sql($sql, [], 0, get_config('filter_filtercodes', 'coursecardsbyenrol')); + $rcourseids = array_keys($courses); + if (count($rcourseids) > 0) { + $card = $this->getcoursecardinfo(); + $content = $this->rendercoursecards($rcourseids, $card->format); + } else { + $card = new stdClass(); + $card->header = ''; + $card->footer = ''; + $content = ''; + } + $replace['/\{coursecardsbyenrol\}/i'] = !empty($content) ? $card->header . $content . $card->footer : ''; + } + + // Tag: {courserequest}. + // Description: Link to Request a Course form. + // Parameters: None. + if (stripos($text, '{courserequest}') !== false) { + // Add request a course link. + $context = \context_system::instance(); + if (!empty($CFG->enablecourserequests) && has_capability('moodle/course:request', $context)) { + $link = '
    ' . get_string('requestcourse') . ''; + } else { + $link = ''; + } + $replace['/\{courserequest\}/i'] = $link; + } + + if (stripos($text, '{courserequestmenu') !== false) { + // Add request a course link. + $context = \context_system::instance(); + if (!empty($CFG->enablecourserequests) && has_capability('moodle/course:request', $context)) { + // Tag: {courserequestmenu0}. + // Description: Link to Request a Course form formatted for use as a top level custom menu item. + // Parameters: None. + if (stripos($text, '{courserequestmenu0}') !== false) { + // Top level menu. + $link = get_string('requestcourse') . '|' . (new \moodle_url('/course/request.php'))->out(); + $replace['/\{courserequestmenu0\}/i'] = $link; + } + + // Tag: {courserequestmenu}. + // Description: Link to Request a Course form formatted for use as a second level custom menu item. + // Parameters: None. + if (stripos($text, '{courserequestmenu}') !== false) { + // Not top level menu. + $link = '-###' . PHP_EOL; + $link .= '-' . get_string('requestcourse') . '|' . (new \moodle_url('/course/request.php'))->out(); + $replace['/\{courserequestmenu\}/i'] = $link; + } + } else { + $replace['/\{courserequestmenu\}/i'] = ''; + } + } + } + + // These tags: {mycourses} and {mycoursesmenu} and {mycoursescards}. + if (stripos($text, '{mycourse') !== false || stripos($text, '{myccourse') !== false) { + if (isloggedin() && !isguestuser()) { + // Retrieve list of user's enrolled courses. + $sortorder = 'visible DESC'; + // Prevent undefined $CFG->navsortmycoursessort errors. + if (empty($CFG->navsortmycoursessort)) { + $CFG->navsortmycoursessort = 'sortorder'; + } + // Append the chosen sortorder. + $sortorder = $sortorder . ',' . $CFG->navsortmycoursessort . ' ASC'; + $mycourses = enrol_get_my_courses('fullname,id', $sortorder); + $myccourses = []; + + // Save and remove completed courses from the list. + if ( + isset($CFG->enablecompletion) && $CFG->enablecompletion == 1 // COMPLETION_ENABLED. + && get_config('filter_filtercodes', 'hidecompletedcourses') + ) { + foreach ($mycourses as $key => $mycourse) { + $ccompletion = new completion_completion(['userid' => $USER->id, 'course' => $mycourse->id]); + if (!empty($ccompletion->timecompleted)) { + // Save course to list of completed courses. + $myccourses[] = $mycourses[$key]; + // Remove completed course from the list. + unset($mycourses[$key]); + } + } + } + + // Messages to display if not enrolled in any courses or have not yet completed some courses. + // Start by assuming that we are not enrolled in any courses. + $emptylist = get_string(($CFG->branch >= 29 ? 'notenrolled' : 'nocourses'), 'grades'); + $emptycclist = $emptylist; + if (!empty($mycourses)) { // Enrolled in some courses. + $emptylist = ''; + } + if (empty($myccourses)) { // Not completed any courses. + $emptycclist = get_string('nocompletedcourses', 'filter_filtercodes'); + } + + // Tag: {mycourses}. + // Description: An unordered list of links to enrolled courses. + // Parameters: None. + if (stripos($text, '{mycourses}') !== false) { + $list = ''; + foreach ($mycourses as $mycourse) { + $list .= '
  • ' . + $mycourse->fullname . '
  • '; + } + $replace['/\{mycourses\}/i'] = '
      ' . (empty($list) ? "
    • $emptylist
    • " : $list) . '
    '; + unset($list); + } + + // Tag: {myccourses}. + // Description: An unordered list of links to completed courses. + // Parameters: None. + if (stripos($text, '{myccourses}') !== false) { + $list = ''; + foreach ($myccourses as $myccourse) { + $list .= '
  • ' . + $myccourse->fullname . '
  • '; + } + $replace['/\{myccourses\}/i'] = '
      ' . (empty($list) ? "
    • $emptycclist
    • " : $list) . '
    '; + unset($list); + } + + // Tag: {mycoursesmenu}. + // Description: A custom menu list of enrolled course names with links. + // Parameters: None. + if (stripos($text, '{mycoursesmenu}') !== false) { + $list = ''; + foreach ($mycourses as $mycourse) { + $list .= '-' . $mycourse->fullname . '|' . + (new \moodle_url('/course/view.php', ['id' => $mycourse->id]))->out() . PHP_EOL; + } + $replace['/\{mycoursesmenu\}/i'] = '-' . (empty($list) ? $emptylist : $list); + unset($list); + } + + // Tag: {mycoursescards}. + // Description: Generates a course card for each enrolled course. + // Parameters: None. + if (stripos($text, '{mycoursescards}') !== false) { + $list = ''; + $courseids = []; + foreach ($mycourses as $mycourse) { + $courseids[] = $mycourse->id; + } + // If enrolled in at least one course, generate cards. + if (!empty($courseids)) { + $card = $this->getcoursecardinfo(); + $list = $card->header . $this->rendercoursecards($courseids, $card->format) . $card->footer; + } + $replace['/\{mycoursescards\}/i'] = (empty($list) ? $emptylist : $list); + unset($list); + } + + // Tag: {mycoursescards }. + // Description: Generates a course card for each enrolled course in the specified category. + // Optional Parameters: One or more category ids separated by a space. + if (stripos($text, '{mycoursescards ') !== false) { + // Get the card format. + $card = $this->getcoursecardinfo(); + // Find all of the mycoursescards tags where category ID was specified. + preg_match_all('/{mycoursescards ([^}]*)}/', $text, $matches); + // For each tag. + foreach ($matches[0] as $key => $tag) { + $catids = array_map('intval', array_filter(explode(' ', $matches[1][$key]), 'is_numeric')); + // For each category in each tag. + $content = ''; + foreach ($catids as $catid) { + // Get all the enrolled courses in the specified category for the user. + $courses = $DB->get_records_sql( + "SELECT c.* + FROM {course} c + JOIN {enrol} e ON e.courseid = c.id + JOIN {user_enrolments} ue ON ue.enrolid = e.id + WHERE ue.userid = ? AND c.category = ? + ORDER BY c.shortname", + [$USER->id, $catid] + ); + // Make an array of the course ids and render the course cards. + $courseids = array_column($courses, 'id'); + $content .= $this->rendercoursecards($courseids, $card->format); + } + if (!empty($content)) { + $replace['/' . $tag . '/isuU'] = $card->header . $content . $card->footer; + } + } + unset($card); + unset($matches); + unset($catids); + unset($catid); + unset($content); + unset($courses); + unset($courseids); + } + } else { // Not logged in. + // Replace tags with message indicating that you need to be logged in. + $replace['/\{mycourses\}/i'] = '
    • ' . get_string('loggedinnot') . '
    '; + $replace['/\{myccourses\}/i'] = '
    • ' . get_string('loggedinnot') . '
    '; + $replace['/\{mycoursesmenu\}/i'] = '-' . get_string('loggedinnot') . PHP_EOL; + $replace['/\{mycoursescards[^}]*\}/i'] = '

    ' . get_string('loggedinnot') . '

    '; + } + } + + // Tag: {editingtoggle}. + // Description: Is "off" if in edit page mode. Otherwise "on". Useful for creating Turn Editing On/Off links. + // Parameters: None. + if (stripos($text, '{editingtoggle}') !== false) { + $replace['/\{editingtoggle\}/i'] = ($PAGE->user_is_editing() ? 'off' : 'on'); + } + + // Tag: {toggleeditingmenu}. + // Description: Creates menu link to toggle editing on and off. + // Parameters: None. + if (stripos($text, '{toggleeditingmenu}') !== false) { + $editmode = ($PAGE->user_is_editing() ? 'off' : 'on'); + $edittext = get_string('turnediting' . $editmode); + if ($PAGE->bodyid == 'page-site-index' && $PAGE->pagetype == 'site-index') { // Front page. + $replace['/\{toggleeditingmenu\}/i'] = $edittext . '|' . (new \moodle_url( + '/course/view.php', + ['id' => $PAGE->course->id, 'sesskey' => sesskey(), 'edit' => $editmode] + ))->out(); + } else { // All other pages. + $replace['/\{toggleeditingmenu\}/i'] = $edittext . '|' . (new \moodle_url( + $PAGE->url, + ['edit' => $editmode, 'adminedit' => $editmode, 'sesskey' => sesskey()] + ))->out() . PHP_EOL; + } + } + + // Tags starting with: {categor...}. + if (stripos($text, '{categor') !== false) { + if (empty($PAGE->course->category)) { + // If we are not in a course, check if categoryid is part of URL (ex: course lists). + $catid = optional_param('categoryid', 0, PARAM_INT); + } else { + // Retrieve the category id of the course we are in. + $catid = $PAGE->course->category; + } + + if (!empty($catid)) { + $category = $DB->get_record('course_categories', ['id' => $catid]); + } + + // Tag: {categoryname}. + // Description: Name of category in which the current course is located. + // Parameters: None. + if (stripos($text, '{categoryname}') !== false) { + if (!empty($catid)) { + // If category is not 0, get category name. + $replace['/\{categoryname\}/i'] = $category->name; + } else { + // Otherwise, category has no name. + $replace['/\{categoryname\}/i'] = ''; + } + } + + // Tag: {categorynumber}. + // Description: categorynumber of the category in which the current course is located, as set in the category settings. + // Parameters: None. + if (stripos($text, '{categorynumber}') !== false) { + if (!empty($catid)) { + // If category is not 0, get category number. + $replace['/\{categorynumber\}/i'] = $category->idnumber; + } else { + // Otherwise, category has no number. + $replace['/\{categorynumber\}/i'] = ''; + } + } + + // Tag: {categorydescription}. + // Description: Description of the category in which the current course is located, as set in the category settings. + // Parameters: None. + if (stripos($text, '{categorydescription}') !== false) { + if (!empty($catid)) { + // If category is not 0, get category description. + $catcontext = \context_coursecat::instance($category->id); + // Resolve embedded URLs that might be in the description. + $description = file_rewrite_pluginfile_urls( + $category->description, + 'pluginfile.php', + $catcontext->id, + 'coursecat', + 'description', + 0 + ); + $replace['/\{categorydescription\}/i'] = $description; + } else { + // Otherwise, category has no description. + $replace['/\{categorydescription\}/i'] = ''; + } + } + + // Tag: {categories}. + // Description: An unordered list of links to categories. + // Parameters: None. + if (stripos($text, '{categories}') !== false) { + // Retrieve list of all categories. + if ($CFG->branch >= 36) { // Moodle 3.6+. + $categories = \core_course_category::make_categories_list(); + } else { + require_once($CFG->libdir . '/coursecatlib.php'); + $categories = coursecat::make_categories_list(); + } + $list = ''; + foreach ($categories as $id => $name) { + $list .= '
  • ' . $name . '
  • '; + } + $list = !empty($list) ? '
      ' . $list . '
    ' : ''; + $replace['/\{categories\}/i'] = $list; + unset($tag); + unset($list); + } + + // Tag: {categoriesmenu}. + // Description: A list of categories with links - for use in the custom menu as a submenu. + // Parameters: None. + if (stripos($text, '{categoriesmenu}') !== false) { + // Retrieve list of all categories. + if ($CFG->branch >= 36) { // Moodle 3.6+. + $categories = \core_course_category::make_categories_list(); + } else { + require_once($CFG->libdir . '/coursecatlib.php'); + $categories = coursecat::make_categories_list(); + } + $list = ''; + foreach ($categories as $id => $name) { + $list .= '-' . $name . '|/course/index.php?categoryid=' . $id . PHP_EOL; + } + $replace['/\{categoriesmenu\}/i'] = $list; + unset($tag); + unset($list); + } + + // Tag: {categories0}. + // Description: An unordered list of links to top level categories. + // Parameters: None. + if (stripos($text, '{categories0}') !== false) { + // Display hidden categories if visibility user is siteadmin or role has moodle/category:viewhiddencategories. + $context = \context_system::instance(); + $isadmin = (is_siteadmin() && !is_role_switched($PAGE->course->id)); + $viewhidden = has_capability('moodle/category:viewhiddencategories', $context, $USER, $isadmin); + + // Categories not visible will be still visible to site admins or users with viewhiddencourses capability. + $sql = 'SELECT cc.id, cc.sortorder, cc.name, cc.visible, cc.parent + FROM {course_categories} cc + WHERE cc.parent = 0' . (!$viewhidden ? ' AND cc.visible = 1' : '') . ' + ORDER BY cc.sortorder'; + $list = ''; + $categories = $DB->get_recordset_sql($sql, ['contextcoursecat' => CONTEXT_COURSECAT]); + foreach ($categories as $category) { + if (!$category->visible && !$viewhidden) { + // Skip if the category is not visible to the user. + continue; + } + $dimmed = $category->visible ? '' : ' class="dimmed"'; + $list .= '' . $category->name . '' . PHP_EOL; + } + $list = !empty($list) ? '
      ' . $list . '
    ' : ''; + $categories->close(); + $replace['/\{categories0\}/i'] = $list; + unset($list); + } + + // Tag: {categories0menu}. + // Description: A list of top level categories with links - for use in the custom menu as a top level menu. + // Parameters: None. + if (stripos($text, '{categories0menu}') !== false) { + // Display hidden categories if visibility user is siteadmin or role has moodle/category:viewhiddencategories. + $context = \context_system::instance(); + $isadmin = (is_siteadmin() && !is_role_switched($PAGE->course->id)); + $viewhidden = has_capability('moodle/category:viewhiddencategories', $context, $USER, $isadmin); + + // Categories not visible will be still visible to site admins or users with viewhiddencourses capability. + $sql = 'SELECT cc.id, cc.sortorder, cc.name, cc.visible, cc.parent + FROM {course_categories} cc + WHERE cc.parent = 0' . (!$viewhidden ? ' AND cc.visible = 1' : '') . ' + ORDER BY cc.sortorder'; + $list = ''; + $categories = $DB->get_recordset_sql($sql, ['contextcoursecat' => CONTEXT_COURSECAT]); + foreach ($categories as $category) { + if (!$category->visible && !$viewhidden) { + // Skip if the category is not visible to the user. + continue; + } + $list .= '-' . $category->name . '|/course/index.php?categoryid=' . $category->id . PHP_EOL; + } + $categories->close(); + $replace['/\{categories0menu\}/i'] = $list; + unset($list); + } + + // Tag: {categoriesx}. + // Description: An unordered list of links to categories in the same level as the current course. + // Parameters: None. + if (stripos($text, '{categoriesx}') !== false) { + $sql = "SELECT cc.id, cc.sortorder, cc.name, cc.visible, cc.parent + FROM {course_categories} cc + WHERE cc.parent = $catid AND cc.visible = 1 + ORDER BY cc.sortorder"; + $list = ''; + $categories = $DB->get_recordset_sql($sql, ['contextcoursecat' => CONTEXT_COURSECAT]); + foreach ($categories as $category) { + $list .= '
  • ' + . $category->name . '
  • ' . PHP_EOL; + } + $list = !empty($list) ? '
      ' . $list . '
    ' : ''; + $categories->close(); + $replace['/\{categoriesx\}/i'] = $list; + unset($list); + } + + // Tag: {categoriesxmenu}. + // Description: A list of links to categories in the same level as the current course - for use in the custom menu. + // Parameters: None. + if (stripos($text, '{categoriesxmenu}') !== false) { + $sql = "SELECT cc.id, cc.sortorder, cc.name, cc.visible, cc.parent + FROM {course_categories} cc + WHERE cc.parent = $catid AND cc.visible = 1 + ORDER BY cc.sortorder"; + $list = ''; + $categories = $DB->get_recordset_sql($sql, ['contextcoursecat' => CONTEXT_COURSECAT]); + foreach ($categories as $category) { + $list .= '-' . $category->name . '|/course/index.php?categoryid=' . $category->id . PHP_EOL; + } + $categories->close(); + $replace['/\{categoriesxmenu\}/i'] = $list; + unset($list); + } + + // Tag: {categorycards} and {categorycards categoryid}. + // Description: Course sub-categories of the current level presented as card tiles. + // Optional Parameter: You can specify a category id to display categories under that category. 0: Top level categories. + if (stripos($text, '{categorycards') !== false) { + $categoryids = []; + $thiscategorycard = null; + $categoryshowpic = get_config('filter_filtercodes', 'categorycardshowpic'); + + // If category ID is not specified in the tag, figure it out from context. + if (stripos($text, '{categorycards}') !== false) { + if (empty($PAGE->course->category)) { + // If we are not in a course, check if categoryid is part of URL (ex: course lists). + $thiscategorycard = optional_param('categoryid', 0, PARAM_INT); + } else { + // Retrieve the category id of the course we are in. + $thiscategorycard = $PAGE->course->category; + } + $categoryids[] = $thiscategorycard; + } + + // If category ID was specified in the tag, use it. + if (stripos($text, '{categorycards ') !== false) { + // Find all categorycards tags where category ID was specified. + preg_match_all('/\{categorycards ([0-9]+)\}/isuU', $text, $matches); + if (!empty($matches)) { + $categoryids = array_merge($categoryids, array_unique($matches[1])); + } + } + + // For each tag's category ID. + foreach ($categoryids as $catid) { + $sql = "SELECT cc.id, cc.sortorder, cc.name, cc.visible, cc.parent + FROM {course_categories} cc + WHERE cc.parent = $catid + ORDER BY cc.sortorder"; + $subcategories = $DB->get_recordset_sql($sql); + + $html = ''; + foreach ($subcategories as $category) { + // Skip if user does not have permissions to view. + if (!\core_course_category::can_view_category($category)) { + continue; + } + + // Render HTML category cards for the category. + $html .= $this->rendercategorycard($category, $categoryshowpic); + } + $subcategories->close(); + + if (!empty($html)) { + $html = '
      ' . $html . '
    '; + } + + // If this is the tag with no category ID. + if ($catid == $thiscategorycard) { + $replace['/\{categorycards\}/i'] = $html; + // If a tag with this category ID was also specified, replace it too. + if (stripos($text, '{categorycards ' . $catid . '}') !== false) { + $replace['/\{categorycards ' . $catid . '\}/isuU'] = $html; + } + } else { + $replace['/\{categorycards ' . $catid . '\}/isuU'] = $html; + } + } + } + unset($categories, $catid, $thiscategorycard, $catids, $categoryids, $matches, $html, $categoryshowpic); + } + + // Tag {mygroups}. + // Description: List of groups that the user is in. + // Parameters: None. + if (stripos($text, '{mygroups}') !== false) { + static $mygroups; + + if (!isset($mygroups)) { + // Fetch my groups. + $context = \context_course::instance($PAGE->course->id); + $groups = groups_get_all_groups($PAGE->course->id, $USER->id); + // Process group names through Moodle filters in case they are multi-language. + $mygroups = []; + foreach ($groups as $group) { + $mygroups[] = format_string($group->name, true, ['context' => $context]); + } + // Format groups into a language string. + $mygroups = $this->formatlist($mygroups); + } + $replace['/\{mygroups\}/i'] = $mygroups; + } + + // Tag {mygroupings}. + // Description: List of groupings that the user is in. + // Parameters: None. + if (stripos($text, '{mygroupings}') !== false) { + static $mygroupings; + + if (!isset($mygroupings)) { + // Fetch my groups. + $context = \context_course::instance($PAGE->course->id); + if (!isset($mygroupingslist)) { + $mygroupingslist = $this->getusergroupings($PAGE->course->id, $USER->id); + } + // Process group names through Moodle filters in case they are multi-language. + $mygroupings = []; + foreach ($mygroupingslist as $grouping) { + $mygroupings[] = format_string($grouping->name, true, ['context' => $context]); + } + // Format groups into a language string. + $mygroupings = $this->formatlist($mygroupings); + } + $replace['/\{mygroupings\}/i'] = $mygroupings; + } + + // Tag: {wwwcontactform}. + // Description: Action URL for ContactForm form submissions. + // Parameters: None. + if (stripos($text, '{wwwcontactform}') !== false) { + $replace['/\{wwwcontactform\}/i'] = $CFG->wwwroot . '/local/contact/index.php'; + } + + // Tag: {sectionname}. + // Description: The name of the section in which the current activity is located. Blank if not in a course. + // Parameters: None. + if (stripos($text, '{sectionname}') !== false) { + // If in a course and section name. + if ($PAGE->course->id != $SITE->id && isset($PAGE->cm->sectionnum)) { + $replace['/\{sectionname\}/i'] = get_section_name($PAGE->course->id, $PAGE->cm->sectionnum); + } else { + $replace['/\{sectionname\}/i'] = ''; + } + } + + // Tag: {recaptcha}. + // Description: Recaptcha. If used, you will need a way to process it as this just displays it. Used by ContactForms. + // Parameters: None. + if (stripos($text, '{recaptcha}') !== false) { + $replace['/\{recaptcha\}/i'] = $this->getrecaptcha(); + } + + // Tag: {readonly}. + // Description: For use in forms to make a field read-only when user is logged-in as non-guest. + // Parameters: None. + if (stripos($text, '{readonly}') !== false) { + if (isloggedin() && !isguestuser()) { + $replace['/\{readonly\}/i'] = 'readonly="readonly"'; + } else { + $replace['/\{readonly\}/i'] = ''; + } + } + + // Tag: {highlight}...{/highlight}. + // Description: Applies a yellow background to the text, like a yellow highlighter. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{/highlight}') !== false) { + $replace['/\{highlight\}/i'] = ''; + $replace['/\{\/highlight\}/i'] = ''; + } + + // Tag: {marktext}...{/marktext}. + // Description: Applies a custom style defined by the fc-marktext CSS class. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{/marktext}') !== false) { + $replace['/\{marktext\}/i'] = ''; + $replace['/\{\/marktext\}/i'] = ''; + } + + // Tag: {markborder}...{/markborder}. + // Description: Applies a red border around content. You can customize the style using the fc-markborder CSS class. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{/markborder}') !== false) { + $replace['/\{markborder\}/i'] = ''; + $replace['/\{\/markborder\}/i'] = ''; + } + + // Tag: {showmore}...{/showmore}. + // Description: Place part of your content in show more and it will initially appear collapsed with the words "show more". + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{/showmore}') !== false) { + $newtext = str_replace('{showmore}', ' ' . get_string('showmore', 'form') . '', $newtext); + if (stripos($newtext, 'fc-showmore-tmp') !== false) { + $newtext = preg_replace_callback('/fc-showmore-tmp/', function ($matches) { + static $count = 0; + return 'showmore-' . $count++; + }, $newtext); + $text = $newtext; + } + } + + // + // HTML tagging. + // + + // Tag: {nbsp}. + // Description: Will be replaced by an HTML non-breaking space ( ). + // Parameters: None. + if (stripos($text, '{nbsp}') !== false) { + $replace['/\{nbsp\}/i'] = ' '; + } + + // Tag: {hr}. + // Description: Will be replaced by an HTML horizontal rule (
    ). + // Parameters: None. + if (stripos($text, '{hr}') !== false) { + $replace['/\{hr\}/i'] = '
    '; + } + + // Tag: {-}. + // Description: Will be replaced by an HTML soft hyphen (­). + // Parameters: None. + if (stripos($text, '{-}') !== false) { + $replace['/\{-\}/i'] = '­'; + } + + // Tag: {langx xx}...{/langx}. + // Description: Tag text as being in a particular language. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{langx ') !== false) { + $replace['/\{langx\s+([a-z-]+)\}(.*)\{\/langx\}/isuU'] = '$2'; + } + + // Tag: {note}...{/note} + // Description: Used to add notes that will appear when editing but not when displayed. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{note}') !== false) { + // Remove the note tags and its content. + $replace['/\{note\}(.*)\{\/note\}/isuU'] = ''; + } + + // Tag: {details open|cssClass}{summary}...{/summary}...{/details}. + // Description: Used to create collapsable sections of content. See HTML details/summary for usage. + // Optional Parameter: 'open' if you want the content to be expanded by default. Alternatively, you can specify a CSS class. + // Requires content between tags. + if (stripos($text, '{/details}') !== false) { + $replace['/\{details\}/i'] = '
    '; + $replace['/\{details open\}/i'] = '
    '; + $replace['/\{\/details\}/i'] = '
    '; + $replace['/\{summary\}/i'] = ''; + $replace['/\{\/summary\}/i'] = ''; + if (preg_match_all('/\{details ([a-zA-Z0-9-_ ]+)\}/', $text, $matches) !== 0) { + foreach ($matches[1] as $cssclass) { + $replace['/\{details ' . $cssclass . '\}/i'] = '
    '; + } + } + } + + // Conditional block tags. + + if (strpos($text, '{if') !== false) { // If there are conditional tags. + require_once($CFG->libdir . '/completionlib.php'); + + // Tag: {ifinactivity}...{/ifinactivity}. + // Description: Will display content if the tag is in an activity. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{/ifinactivity}') !== false) { + if (substr($PAGE->pagetype, 0, 4) == 'mod-') { + $replace['/\{ifinactivity\}/isu'] = ''; + $replace['/\{\/ifinactivity\}/isu'] = ''; + } else { + $replace['/\{ifinactivity\}(.*){\/ifinactivity\}/isuU'] = ''; + } + } + + // Tag: {ifnotinactivity}...{/ifnotinactivity}. + // Description: Will display content if the tag is not in an activity. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{/ifnotinactivity}') !== false) { + if (substr($PAGE->pagetype, 0, 4) != 'mod-') { + $replace['/\{ifnotinactivity\}/isu'] = ''; + $replace['/\{\/ifnotinactivity\}/isu'] = ''; + } else { + $replace['/\{ifnotinactivity\}(.*){\/ifnotinactivity\}/isuU'] = ''; + } + } + + // Tag: {ifactivitycompleted coursemoduleid}...{/ifactivitycompleted}. + // Description: Will display content if the specified activity has been completed. + // Required Parameter: coursemoduleid is the id of the instance of the content module. + // Requires content between tags. + if (stripos($text, '{/ifactivitycompleted}') !== false) { + $completion = new completion_info($PAGE->course); + + if ($completion->is_enabled_for_site() && $completion->is_enabled() == COMPLETION_ENABLED) { + // Get a list of the the instances of this tag. + $re = '/{ifactivitycompleted\s+([0-9]+)\}(.*)\{\/ifactivitycompleted\}/isuU'; + $found = preg_match_all($re, $text, $matches); + + if ($found > 0) { + // Check if the activity is in the list. + foreach ($matches[1] as $cmid) { + $iscompleted = false; + + // Only process valid IDs. + if (($cm = get_coursemodule_from_id('', $cmid, 0)) !== false) { + // Get the completion data for this activity if it exists. + try { + $data = $completion->get_data($cm, true, $USER->id); + $iscompleted = ($data->completionstate == COMPLETION_COMPLETE); + } catch (Exception $e) { + unset($e); + continue; + } + } + + // If the activity has been completed, remove just the tags. Otherwise remove tags and content. + $key = '/{ifactivitycompleted\s+' . $cmid . '\}(.*)\{\/ifactivitycompleted\}/isuU'; + if ($iscompleted) { + // Completed. Keep the text and remove the tags. + $replace[$key] = "$1"; + } else { + // Activity not completed. Remove tags and content. + $replace[$key] = ''; + } + } + } + } + } + + // Tag: {ifnotactivitycompleted coursemoduleid}...{/ifnotactivitycompleted}. + // Description: Will display content if the specified activity has been completed. + // Required Parameter: coursemoduleid is the id of the instance of the content module. + // Requires content between tags. + if (stripos($text, '{/ifnotactivitycompleted}') !== false) { + $completion = new completion_info($PAGE->course); + + if ($completion->is_enabled_for_site() && $completion->is_enabled() == COMPLETION_ENABLED) { + // Get a list of the the instances of this tag. + $re = '/{ifnotactivitycompleted\s+([0-9]+)\}(.*)\{\/ifnotactivitycompleted\}/isuU'; + $found = preg_match_all($re, $text, $matches); + + if ($found > 0) { + // Check if the activity is in the list. + foreach ($matches[1] as $cmid) { + $iscompleted = false; + + // Only process valid IDs. + if (($cm = get_coursemodule_from_id('', $cmid, 0)) !== false) { + // Get the completion data for this activity. + try { + $data = $completion->get_data($cm, true, $USER->id); + $iscompleted = ($data->completionstate == COMPLETION_COMPLETE); + } catch (Exception $e) { + unset($e); + continue; + } + } + + // If the activity has been completed, remove just the tags. Otherwise remove tags and content. + $key = '/{ifnotactivitycompleted\s+' . $cmid . '\}(.*)\{\/ifnotactivitycompleted\}/isuU'; + if (!$iscompleted) { + // Completed. Keep the text and remove the tags. + $replace[$key] = "$1"; + } else { + // Activity not completed. Remove tags and content. + $replace[$key] = ''; + } + } + } + } + } + + // Tag: {ifprofile_field_shortname}...{ifprofile_field_shortname}. + // Description: Will display content if specified the Custom User Profile Fields is not empty. + // Required Parameter: Replace shortname with the shortname of the user profile field. Note that this is in both tags. + // Requires content between tags. + if (stripos($text, '{ifprofile_field_') !== false) { + $isuser = (isloggedin() && !isguestuser()); + + // Cached the defined custom profile fields and data. + if (!isset($profilefields)) { + $profilefields = $DB->get_records('user_info_field', null, '', 'id, datatype, shortname, visible, param3'); + if ($isuser && !empty($profilefields)) { + $profiledata = $DB->get_records_menu('user_info_data', ['userid' => $USER->id], '', 'fieldid, data'); + } + } + + // Determine if allowed to evaluate "Not visible" fields. + $allowall = empty(get_config('filter_filtercodes', 'ifprofilefiedonlyvisible')); + + // Process each custom of the available profile fields. + foreach ($profilefields as $field) { + $tag = 'ifprofile_field_' . $field->shortname; + + // If the tag exists, user is logged-in and we are allowed to evaluate this field. + if (isset($profiledata[$field->id]) && $isuser && ($field->visible != '0' || $allowall)) { + $data = trim($profiledata[$field->id]); + } else { + $data = ''; + } + // If the value is empty or zero, remove the all of the tags and their contents for that field shortname. + if (empty($data)) { + $replace['/\{' . $tag . '(.*)\}(.*)\{\/' . $tag . '\}/isuU'] = ''; + continue; + } + + // If no comparison value is specified. + if (stripos($text, '{' . $tag . '}') !== false) { + // Just remove the tags. + $replace['/\{' . $tag . '\}/isu'] = ''; + $replace['/\{\/' . $tag . '\}/isu'] = ''; + } + } + } + + // Tag: {ifprofile_shortname is|not|contains|in "value"}...{/ifprofile}. + // Description: Will display content if specified the User Profile Fields meets the specified condition. + // + // Parameter: shortname: Shortname of the custom user profile fields plus auth, idnumber, email, institution, + // department, city, country, timezone and lang. + // Parameter: contains|is|in: The comparison operator. Use: + // 'is' to check if the field is an exact match for the value. + // 'not' to check if the field does not contain the value. + // 'contains' to check if the field contains the text. + // 'in' to check if the value is in the fields content. + // Parameters: "value": The text to compare the field against. + // Requires content between tags. + if (stripos($text, '{/ifprofile}') !== false) { + // Retrieve all custom profile fields and specified core fields. + $corefields = ['id', 'username', 'auth', 'idnumber', 'email', 'institution', + 'department', 'city', 'country', 'timezone', 'lang']; + $profilefields = $this->getuserprofilefields($USER, $corefields); + + // Find all ifprofile tags. + $re = '/{ifprofile\s+(\w+)\s+(is|not|contains|in)\s+"([^}]*)"}(.*){\/ifprofile}/isuU'; + $found = preg_match_all($re, $text, $matches); + if ($found > 0) { + foreach ($matches[1] as $key => $match) { + $fieldname = $matches[1][$key]; + $string = $matches[0][$key]; // String found in $text. + $operator = $matches[2][$key]; + $value = $matches[3][$key]; + + // Do not process tag if the specified profile field name does not exist or user is not logged in. + if (!array_key_exists($fieldname, $profilefields) || !isloggedin() || isguestuser()) { + if ($operator == 'not') { + // It will always meet criteria of a "not" if the user doesn't have a profile. + $replace['/' . preg_quote($string, '/') . '/isuU'] = $matches[4][$key]; + } else { + // It will never match the criteria "is", "contains" or "in" if the user doesn't have a profile. + $replace['/' . preg_quote($string, '/') . '/isuU'] = ''; + } + continue; + } + + if (!empty($value)) { + $value = trim($value, '"'); // Trim quotation marks. + } + + $content = ''; + switch ($operator) { + case 'is': + // If the specified field is exactly the specified value. + // Example: {ifprofile country is "CA"}...{/ifprofile}. + // Example: {ifprofile city is ""}...{/ifprofile}. + if ($profilefields[$fieldname]->value === $value) { + $content = $matches[4][$key]; + } + break; + case 'not': + // Example: {ifprofile country not "CA"}...{/ifprofile}. + // Example: {ifprofile institution not ""}...{/ifprofile}. + if ($profilefields[$fieldname]->value !== $value) { + $content = $matches[4][$key]; + } + break; + case 'contains': + // If the specified field contains the specified value. + // Example:{ifprofile email contains "@example.com"}...{/ifprofile}. + if (strpos($profilefields[$fieldname]->value, $value) !== false) { + $content = $matches[4][$key]; + } + break; + case 'in': + // If the specified value contains the value specified in the field. + // Example: {ifprofile country in "CA,US,UK,AU,NZ"}...{/ifprofile}. + if (strpos($value, $profilefields[$fieldname]->value) !== false) { + $content = $matches[4][$key]; + } + break; + } + $replace['/' . preg_quote($string, '/') . '/isuU'] = $content; + } + } + } + + // Tag: {ifmobile}...{/ifmobile}. + // Description: Will display content if accessed from the mobile app. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{/ifmobile}') !== false) { + // If this is a web service or the Moodle mobile app... + if ($this->iswebservice()) { + // Yes, just remove the tags. + $replace['/\{ifmobile\}/i'] = ''; + $replace['/\{\/ifmobile\}/i'] = ''; + } else { + // Not from web services, remove tags and content. + $replace['/\{ifmobile\}(.*)\{\/ifmobile\}/isuU'] = ''; + } + } + + // Tag: {ifnotmobile}...{/ifnotmobile}. + // Description: Will display content if NOT accessed from the mobile app. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{/ifnotmobile}') !== false) { + // If this is a web service or the Moodle mobile app... + if (!$this->iswebservice()) { + // Yes, just remove the tags. + $replace['/\{ifnotmobile\}/i'] = ''; + $replace['/\{\/ifnotmobile\}/i'] = ''; + } else { + // Not from web services, remove tags and content. + $replace['/\{ifnotmobile\}(.*)\{\/ifnotmobile\}/isuU'] = ''; + } + } + + // Tag: {ifloggedinas}...{/ifloggedinas}. + // Description: Will display content if logged in as a different user. See https://docs.moodle.org/en/Log_in_as. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifloggedinas}') !== false) { + // If logged-in-as another user... + if (\core\session\manager::is_loggedinas()) { + // Just remove the tags. + $replace['/\{ifloggedinas\}/i'] = ''; + $replace['/\{\/ifloggedinas\}/i'] = ''; + } else { + // If logged in as another user, remove the ifloggedinas tags and contained content. + $replace['/\{ifloggedinas\}(.*)\{\/ifloggedinas\}/isuU'] = ''; + } + } + + // Tag: {ifnotloggedinas}...{/ifnotloggedinas}. + // Description: Will display content if NOT logged in as a different user. See https://docs.moodle.org/en/Log_in_as. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifnotloggedinas}') !== false) { + // If not logged-in-as another user... + if (!\core\session\manager::is_loggedinas()) { + // Just remove the tags. + $replace['/\{ifnotloggedinas\}/i'] = ''; + $replace['/\{\/ifnotloggedinas\}/i'] = ''; + } else { + // If logged in as another user, remove the if not loggedinas tags and contained content. + $replace['/\{ifnotloggedinas\}(.*)\{\/ifnotloggedinas\}/isuU'] = ''; + } + } + + // Tag: {ifvisible}...{/ifvisible}. + // Description: Will display content if the current course visibility is set to 'Show'. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifvisible}') !== false) { + global $COURSE; + // If the course visibility is set to Show... + if ($COURSE->id != 1 && !empty($COURSE->visible)) { + // Just remove the tags and leave the content. + $replace['/\{ifvisible\}/i'] = ''; + $replace['/\{\/ifvisible\}/i'] = ''; + } else { // Visibility set to Hide. + // Remove the if visible tags and their content. + $replace['/\{ifvisible\}(.*)\{\/ifvisible\}/isuU'] = ''; + } + } + + // Tag: {ifnotvisible}...{/ifnotvisible}. + // Description: Will display content if the current course visibility is set to 'Hide'. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifnotvisible}') !== false) { + global $COURSE; + // If the course visibility is set to hide... + if ($COURSE->id != 1 && empty($COURSE->visible)) { // Visibility set to Hide. + // Just remove the tags. + $replace['/\{ifnotvisible\}/i'] = ''; + $replace['/\{\/ifnotvisible\}/i'] = ''; + } else { // Visibility set to Show. + // Remove the if not visible tags and contained content. + $replace['/\{ifnotvisible\}(.*)\{\/ifnotvisible\}/isuU'] = ''; + } + } + + // Tag: {ifincohort idname|idnumber}...{/ifincohort}. + // Description: Will display content if the user is part of the specified cohort. + // Parameters: id name or id number of the cohort. + // Requires content between tags. + if (stripos($text, '{ifincohort ') !== false) { + static $mycohorts; + if (empty($mycohorts)) { // Cache list of cohorts. + require_once($CFG->dirroot . '/cohort/lib.php'); + $mycohorts = cohort_get_user_cohorts($USER->id); + } + $newtext = preg_replace_callback( + '/\{ifincohort ([\w\-]*)\}(.*)\{\/ifincohort\}/isuU', + function ($matches) use ($mycohorts) { + foreach ($mycohorts as $cohort) { + if ($cohort->idnumber == $matches[1] || $cohort->id == $matches[1]) { + return ($matches[2]); + }; + } + return ''; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Tag: {ifeditmode}...{/ifeditmode}. + // Description: Will display content if edit mode is turned on. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifeditmode}') !== false) { + // If editing mode is activated... + if ($PAGE->user_is_editing()) { + // Just remove the tags. + $replace['/\{ifeditmode\}/i'] = ''; + $replace['/\{\/ifeditmode\}/i'] = ''; + } else { + // If editing mode is not enabled, remove the ifeditmode tags and contained content. + $replace['/\{ifeditmode\}(.*)\{\/ifeditmode\}/isuU'] = ''; + } + } + + // Tag: {ifnoteditmode}...{/ifnoteditmode}. + // Description: Will display content if edit mode is turned off. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifnoteditmode}') !== false) { + // If editing mode is activated... + if ($PAGE->user_is_editing()) { + // If editing mode is enabled, remove the ifnoteditmode tags and contained content. + $replace['/\{ifnoteditmode\}(.*)\{\/ifnoteditmode\}/isuU'] = ''; + } else { + // Just remove the tags. + $replace['/\{ifnoteditmode\}/i'] = ''; + $replace['/\{\/ifnoteditmode\}/i'] = ''; + } + } + + // Tag: {ifcourserequests}...{/ifcourserequests}. + // Description: Will display content if the 'Request a course' feature is enabled. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifcourserequests}') !== false) { + // If Request a course is enabled... + $context = \context_system::instance(); + if (empty($CFG->enablecourserequests) || !has_capability('moodle/course:request', $context)) { + // Just remove the tags. + $replace['/\{ifcourserequests\}/i'] = ''; + $replace['/\{\/ifcourserequests\}/i'] = ''; + } else { + // If Request a Course is not enabled, remove the ifcourserequests tags and contained content. + $replace['/\{ifcourserequests\}(.*)\{\/ifcourserequests\}/isuU'] = ''; + } + } + + // Tags: {ifenrolpage}...{/ifenrolpage}. + // Description: Will display content if you are viewing the enrolment page of a course. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifenrolpage}') !== false) { + // If on a course enrolment page. + if ($PAGE->pagetype == 'enrol-index') { + // Remove the ifenrolpage tags. + $replace['/\{ifenrolpage\}/i'] = ''; + $replace['/\{\/ifenrolpage\}/i'] = ''; + } else { + // Remove the ifenrolpage strings. + $replace['/\{ifenrolpage\}(.*)\{\/ifenrolpage\}/isuU'] = ''; + } + } + + // Tags: {ifnotenrolpage}...{/ifnotenrolpage}. + // Description: Will display content if you are not viewing the enrolment page of a course. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifnotenrolpage}') !== false) { + // If on a course enrolment page. + if ($PAGE->pagetype == 'enrol-index') { + // Remove the ifnotenrolpage strings. + $replace['/\{ifnotenrolpage\}(.*)\{\/ifnotenrolpage\}/isuU'] = ''; + } else { + // Remove the ifenrolled tags. + $replace['/\{ifnotenrolpage\}/i'] = ''; + $replace['/\{\/ifnotenrolpage\}/i'] = ''; + } + } + + // Tag: {ifenrolled}..{/ifenrolled}. + // Description: Will display content if you are enrolled in the current course. + // Parameters: None. + // Requires content between tags. + + // Tag: {ifnotenrolled}...{/ifnotenrolled}. + // Description: Will display content if you are not enrolled in the current course. + // Parameters: None. + // Requires content between tags. + + // Tag: {ifincourse}...{/ifincourse}. + // Description: Will display content if you are anywhere in a course. + // Parameters: None. + // Requires content between tags. + + // Tag: {ifnotincourse}...{/ifnotincourse}. + // Description: Will display content if you are not anywhere in a course. + // Parameters: None. + // Requires content between tags. + + // Tag: {ifinsection}...{/ifinsection}. + // Description: Will display content if you are in a section of a course. + // Parameters: None. + // Requires content between tags. + + if ($PAGE->course->id == $SITE->id) { // If frontpage course. + // Everyone is automatically enrolled in the Front Page course. + // Remove the ifenrolled tags. + if (stripos($text, '{ifenrolled}') !== false) { + $replace['/\{ifenrolled\}/i'] = ''; + $replace['/\{\/ifenrolled\}/i'] = ''; + } + // Remove the ifnotenrolled strings. + if (stripos($text, '{ifnotenrolled}') !== false) { + $replace['/\{ifnotenrolled\}(.*)\{\/ifnotenrolled\}/isuU'] = ''; + } + // Remove the {ifincourse} strings if not in a course or on the Front Page. + if (stripos($text, '{ifincourse}') !== false) { + $replace['/\{ifincourse\}(.*)\{\/ifincourse\}/isuU'] = ''; + } + // If not in a course, remove the {ifnotincourse} tags. + if (stripos($text, '{ifnotincourse}') !== false) { + $replace['/\{ifnotincourse\}/i'] = ''; + $replace['/\{\/ifnotincourse\}/i'] = ''; + } + // Remove the {ifinsection} strings if not in a section of a course or are on the Front Page. + if (stripos($text, '{ifinsection}') !== false) { + $replace['/\{ifinsection\}(.*)\{\/ifinsection\}/isuU'] = ''; + } + } else { + if ($this->hasarchetype('student')) { // If user is enrolled in the course. + // Remove the {ifnotincourse} strings if in a course. + if (stripos($text, '{ifnotincourse}') !== false) { + $replace['/\{ifnotincourse\}(.*)\{\/ifnotincourse\}/isuU'] = ''; + } + // If enrolled, remove the {ifenrolled} tags. + if (stripos($text, '{ifenrolled}') !== false) { + $replace['/\{ifenrolled\}/i'] = ''; + $replace['/\{\/ifenrolled\}/i'] = ''; + } + // Remove the ifnotenrolled strings. + if (stripos($text, '{ifnotenrolled}') !== false) { + $replace['/\{ifnotenrolled\}(.*)\{\/ifnotenrolled\}/isuU'] = ''; + } + } else { + // Otherwise, remove the ifenrolled strings. + if (stripos($text, '{ifenrolled}') !== false) { + $replace['/\{ifenrolled\}(.*)\{\/ifenrolled\}/isuU'] = ''; + } + // And remove the ifnotenrolled tags. + if (stripos($text, '{ifnotenrolled}') !== false) { + $replace['/\{ifnotenrolled\}/i'] = ''; + $replace['/\{\/ifnotenrolled\}/i'] = ''; + } + } + // Tag: {ifincourse}...{/ifincourse}. // phpcs:ignore . + if (stripos($text, '{ifincourse}') !== false) { + $replace['/\{ifincourse\}/i'] = ''; + $replace['/\{\/ifincourse\}/i'] = ''; + } + // Tag: {ifinsection}...{/ifinsection}. // phpcs:ignore . + if (stripos($text, '{ifinsection}') !== false) { + if (!empty(@$PAGE->cm->sectionnum)) { + $replace['/\{ifinsection\}/i'] = ''; + $replace['/\{\/ifinsection\}/i'] = ''; + } else { + // Remove the ifinsection strings. + if (stripos($text, '{ifinsection}') !== false) { + $replace['/\{ifinsection\}(.*)\{\/ifinsection\}/isuU'] = ''; + } + } + } + } + + // Tag: {ifnotinsection}...{/ifnotinsection}. + // Description: Display content if not in a section of a course. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifnotinsection}') !== false) { + if (empty(@$PAGE->cm->sectionnum)) { + $replace['/\{ifnotinsection\}/i'] = ''; + $replace['/\{\/ifnotinsection\}/i'] = ''; + } else { + // Remove the ifnotinsection strings. + if (stripos($text, '{ifnotinsection}') !== false) { + $replace['/\{ifnotinsection\}(.*)\{\/ifnotinsection\}/isuU'] = ''; + } + } + } + + // Tag: {ifstudent}...{/ifstudent}. + // Description: This is similar to {ifenrolled} but only displays if user is enrolled as just a student in the course. + // Must be logged-in and must not have additional higher level roles. + // Example: Student but not Administrator, or Student but not Teacher. + // Parameters: None. + // Requires content between tags. + if ($this->hasonlyarchetype('student')) { + if (stripos($text, '{ifstudent}') !== false) { + // Just remove the tags. + $replace['/\{ifstudent\}/i'] = ''; + $replace['/\{\/ifstudent\}/i'] = ''; + } + } else { + // And remove the ifstudent strings. + if (stripos($text, '{ifstudent}') !== false) { + $replace['/\{ifstudent\}(.*)\{\/ifstudent\}/isuU'] = ''; + } + } + + // Tag: {ifminstudent}...{/ifminstudent}. + // Description: This is similar to {ifstudent} but will displays if user's list of roles includes student. + // Example: Student but may also be teacher or administrator. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifminstudent}') !== false) { + // If an assistant (non-editing teacher). + if ($this->hasarchetype('student')) { + // Just remove the tags. + $replace['/\{ifminstudent\}/i'] = ''; + $replace['/\{\/ifminstudent\}/i'] = ''; + } else { + // Remove the ifassistant strings. + $replace['/\{ifminstudent\}(.*)\{\/ifminstudent\}/isuU'] = ''; + } + } + + // Tag: {ifloggedin}...{/ifloggedin} + // Description: Display content if logged-in but not if logged-in as guest. + // Parameters: None. + // Requires content between tags. + + // Tag: {ifloggedout}...{/ifloggedout}. + // Description: Display content if NOT logged-in. Guest is not considered logged in. + // Parameters: None. + // Requires content between tags. + + if (isloggedin() && !isguestuser()) { // If logged-in but not just as guest. + // Just remove ifloggedin tags. + if (stripos($text, '{ifloggedin}') !== false) { + $replace['/\{ifloggedin\}/i'] = ''; + $replace['/\{\/ifloggedin\}/i'] = ''; + } + // Remove the ifloggedout strings. + if (stripos($text, '{ifloggedout}') !== false) { + $replace['/\{ifloggedout\}(.*)\{\/ifloggedout\}/isuU'] = ''; + } + } else { // If logged-out. + // Remove the ifloggedout tags. + if (stripos($text, '{ifloggedout}') !== false) { + $replace['/\{ifloggedout\}/i'] = ''; + $replace['/\{\/ifloggedout\}/i'] = ''; + } + // Remove ifloggedin strings. + if (stripos($text, '{ifloggedin}') !== false) { + $replace['/\{ifloggedin\}(.*)\{\/ifloggedin\}/isuU'] = ''; + } + } + + // Tag: {ifguest}...{/ifguest}. + // Description: Display content if logged-in as a guest user. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifguest}') !== false) { + if (isguestuser()) { // If logged-in as guest. + // Just remove the tags. + $replace['/\{ifguest\}/i'] = ''; + $replace['/\{\/ifguest\}/i'] = ''; + } else { + // If not logged-in as guest, remove the ifguest text. + $replace['/\{ifguest\}(.*)\{\/ifguest\}/isuU'] = ''; + } + } + + // Tag: {ifassistant}...{/ifassistant}. + // Description: Display content if a non-editing teacher in the course. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifassistant}') !== false) { + // If an assistant (non-editing teacher). + if ($this->hasarchetype('teacher') && stripos($text, '{ifassistant}') !== false) { + // Just remove the tags. + $replace['/\{ifassistant\}/i'] = ''; + $replace['/\{\/ifassistant\}/i'] = ''; + } else { + // Remove the ifassistant strings. + $replace['/\{ifassistant\}(.*)\{\/ifassistant\}/isuU'] = ''; + } + } + + // Tag: {ifteacher}...{/ifteacher}. + // Description: Display content if an editing teacher in the course. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifteacher}') !== false) { + if ($this->hasarchetype('editingteacher')) { // If a teacher. + // Just remove the tags. + $replace['/\{ifteacher\}/i'] = ''; + $replace['/\{\/ifteacher\}/i'] = ''; + } else { + // Remove the ifteacher strings. + $replace['/\{ifteacher\}(.*)\{\/ifteacher\}/isuU'] = ''; + } + } + + // Tag: {ifcreator}...{/ifcreator}. + // Description: Display content if the user has the role of course creator. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifcreator}') !== false) { + if ($this->hasarchetype('coursecreator')) { // If a course creator. + // Just remove the tags. + $replace['/\{ifcreator\}/i'] = ''; + $replace['/\{\/ifcreator\}/i'] = ''; + } else { + // Remove the iscreator strings. + $replace['/\{ifcreator\}(.*)\{\/ifcreator\}/isuU'] = ''; + } + } + + // Tag: {ifmanager}...{/ifmanager}. + // Description: Display content if the user has the role of a manager. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifmanager}') !== false) { + if ($this->hasarchetype('manager')) { // If a manager. + // Just remove the tags. + $replace['/\{ifmanager\}/i'] = ''; + $replace['/\{\/ifmanager\}/i'] = ''; + } else { + // Remove the ifmanager strings. + $replace['/\{ifmanager\}(.*)\{\/ifmanager\}/isuU'] = ''; + } + } + + // Tag: {ifadmin}...{/ifadmin}. + // Description: Display content if the user is a site administrator. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifadmin}') !== false) { + if (is_siteadmin() && !is_role_switched($PAGE->course->id)) { // If an administrator. + // Just remove the tags. + $replace['/\{ifadmin\}/i'] = ''; + $replace['/\{\/ifadmin\}/i'] = ''; + } else { + // Remove the ifadmin strings. + $replace['/\{ifadmin\}(.*)\{\/ifadmin\}/isuU'] = ''; + } + } + + // Tag: {ifdashboard}...{/ifdashboard}. + // Description: Display content if the user is viewing the dashboard. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifdashboard}') !== false) { + if ($PAGE->pagetype == 'my-index') { // If dashboard. + // Just remove the tags. + $replace['/\{ifdashboard\}/i'] = ''; + $replace['/\{\/ifdashboard\}/i'] = ''; + } else { + // If not on the dashboard page, remove the ifdashboard text. + $replace['/\{ifdashboard\}(.*)\{\/ifdashboard\}/isuU'] = ''; + } + } + + // Tag: {ifhome}...{/ifhome}. + // Description: Display content if the user is viewing the Front page. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifhome}') !== false) { + if ($PAGE->pagetype == 'site-index') { // If front page. + // Just remove the tags. + $replace['/\{ifhome\}/i'] = ''; + $replace['/\{\/ifhome\}/i'] = ''; + } else { + // If not on the front page, remove the ifhome text. + $replace['/\{ifhome\}(.*)\{\/ifhome\}/isuU'] = ''; + } + } + // Tag: {ifnothome}...{/ifnothome}. + // Description: Display content if the user is not viewing any other page than the Front page. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifnothome}') !== false) { + if ($PAGE->pagetype != 'site-index') { // If front page. + // Just remove the tags. + $replace['/\{ifnothome\}/i'] = ''; + $replace['/\{\/ifnothome\}/i'] = ''; + } else { + // If not on the front page, remove the ifhome text. + $replace['/\{ifnothome\}(.*)\{\/ifnothome\}/isuU'] = ''; + } + } + + // Tag: {ifdev}...{/ifdev}. + // Description: Display content if the user is a site admnistrator and has debugging set to DEVELOPER mode. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifdev}') !== false) { + // If an administrator with debugging is set to DEVELOPER mode... + if ($CFG->debugdisplay == 1 && is_siteadmin() && !is_role_switched($PAGE->course->id)) { + // Just remove the tags. + $replace['/\{ifdev\}/i'] = ''; + $replace['/\{\/ifdev\}/i'] = ''; + } else { + // If not a developer with debugging set to DEVELOPER mode, remove the ifdev tags and contained content. + $replace['/\{ifdev\}(.*)\{\/ifdev\}/isuU'] = ''; + } + } + + // Tag: {ifingroup id|idnumber}...{/ifingroup}. + // Description: Display content if the user is a member of the specified group. + // Required Parameters: group id or idnumber. + // Requires content between tags. + if (stripos($text, '{ifingroup') !== false) { + if (!isset($mygroupslist)) { // Fetch my groups. + $mygroupslist = groups_get_all_groups($PAGE->course->id, $USER->id); + } + $re = '/{ifingroup\s+(.*)\}(.*)\{\/ifingroup\}/isuU'; + $found = preg_match_all($re, $text, $matches); + if ($found > 0) { + foreach ($matches[1] as $groupid) { + $key = '/{ifingroup\s+' . $groupid . '\}(.*)\{\/ifingroup\}/isuU'; + $ismember = false; + foreach ($mygroupslist as $group) { + if ($groupid == $group->id || $groupid == $group->idnumber) { + $ismember = true; + break; + } + } + if ($ismember) { // Just remove the tags. + $replace[$key] = '$1'; + } else { // Remove the ifingroup tags and content. + $replace[$key] = ''; + } + } + } + } + + // Tag: {ifnotingroup id|idnumber}...{/ifnotingroup}. + // Description: Display content if the user is NOT a member of the specified group. + // Required Parameters: group id or idnumber. + // Requires content between tags. + if (stripos($text, '{ifnotingroup') !== false) { + if (!isset($mygroupslist)) { // Fetch my groups. + $mygroupslist = groups_get_all_groups($PAGE->course->id, $USER->id); + } + $re = '/{ifnotingroup\s+(.*)\}(.*)\{\/ifnotingroup\}/isuU'; + $found = preg_match_all($re, $text, $matches); + if ($found > 0) { + foreach ($matches[1] as $groupid) { + $key = '/{ifnotingroup\s+' . $groupid . '\}(.*)\{\/ifnotingroup\}/isuU'; + $ismember = false; + foreach ($mygroupslist as $group) { + if ($groupid == $group->id || $groupid == $group->idnumber) { + $ismember = true; + break; + } + } + if ($ismember) { // Remove the ifnotingroup tags and content. + $replace[$key] = ''; + } else { // Just remove the tags and keep the content. + $replace[$key] = '$1'; + } + } + } + } + + // Tag: {ifingrouping id|idnumber}...{/ifingrouping}. + // Description: Display content if the user is a member of the specified grouping. + // Required Parameters: group id or idnumber. + // Requires content between tags. + if (stripos($text, '{ifingrouping') !== false) { + if (!isset($mygroupingslist)) { + $mygroupingslist = $this->getusergroupings($PAGE->course->id, $USER->id); + } + $re = '/{ifingrouping\s+(.*)\}(.*)\{\/ifingrouping\}/isuU'; + $found = preg_match_all($re, $text, $matches); + if ($found > 0) { + foreach ($matches[1] as $groupingid) { + $key = '/{ifingrouping\s+' . $groupingid . '\}(.*)\{\/ifingrouping\}/isuU'; + $ismember = false; + foreach ($mygroupingslist as $grouping) { + if ($groupingid == $grouping->id || $groupingid == $grouping->idnumber) { + $ismember = true; + break; + } + } + if ($ismember) { // Just remove the tags. + $replace[$key] = '$1'; + } else { // Remove the ifingroup tags and content. + $replace[$key] = ''; + } + } + } + } + + // Tag: {ifnotingroup id|idnumber}...{/ifnotingroup}. + // Description: Display content if the user is NOT a member of the specified grouping. + // Required Parameters: group id or idnumber. + // Requires content between tags. + if (stripos($text, '{ifnotingrouping') !== false) { + if (!isset($mygroupingslist)) { + $mygroupingslist = $this->getusergroupings($PAGE->course->id, $USER->id); + } + $re = '/{ifnotingrouping\s+(.*)\}(.*)\{\/ifnotingrouping\}/isuU'; + $found = preg_match_all($re, $text, $matches); + if ($found > 0) { + foreach ($matches[1] as $groupingid) { + $key = '/{ifnotingrouping\s+' . $groupingid . '\}(.*)\{\/ifnotingrouping\}/isuU'; + $ismember = false; + foreach ($mygroupingslist as $grouping) { + if ($groupingid == $grouping->id || $groupingid == $grouping->idnumber) { + $ismember = true; + break; + } + } + if ($ismember) { // Remove the ifnotingroup tags and content. + $replace[$key] = ''; + } else { // Just remove the tags and keep the content. + $replace[$key] = '$1'; + } + } + } + } + + // Tag: {iftenant idnumber|tenantid}...{/iftenant}. + // Description: Display content only if the user is part of the specified tenant on Moodle Workplace. + // Required Parameter: tenant idnumber or tenantid. + // Requires content between tags. + if (stripos($text, '{iftenant') !== false) { + if (class_exists('tool_tenant\tenancy')) { + // Moodle Workplace. + $tenants = \tool_tenant\tenancy::get_tenants(); + // Get current tenantid. + $currenttenantid = \tool_tenant\tenancy::get_tenant_id(); + } else { + // Moodle Classic - Just simulate functionality as tenant 1. + // This allows a course to work in both Moodle Classic and Workplace. + $tenants[0] = new stdClass(); + $tenants[0]->idnumber = 1; + $tenants[0]->id = 1; + $currenttenantid = 1; + } + // We will use tenant's idnumber if it is set. If not, default to tenant id. + $currenttenantidnumber = 1; + foreach ($tenants as $tenant) { + if ($tenant->id == $currenttenantid) { + $currenttenantidnumber = $tenant->idnumber ? $tenant->idnumber : $tenant->id; + } + } + $re = '/{iftenant\s+(.*)\}(.*)\{\/iftenant\}/isuU'; + $found = preg_match_all($re, $text, $matches); + if ($found > 0) { + foreach ($matches[1] as $tenantid) { + $key = '/{iftenant\s+' . $tenantid . '\}(.*)\{\/iftenant\}/isuU'; + if ($tenantid == $currenttenantidnumber) { + // Just remove the tags. + $replace[$key] = '$1'; + } else { + // Remove the iftenant strings. + $replace[$key] = ''; + } + } + } + } + + // Tag: {ifworkplace}...{/ifworkplace}. + // Description: Display content only if using Moodle Workplace. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifworkplace}') !== false) { + if (class_exists('tool_tenant\tenancy')) { + // Moodle Workplace - Just remove the tags. + $replace['/\{ifworkplace\}/i'] = ''; + $replace['/\{\/ifworkplace\}/i'] = ''; + } else { + // If Moodle Classic, remove the ifworkplace tags and text. + $replace['/\{ifworkplace\}(.*)\{\/ifworkplace\}/isuU'] = ''; + } + } + + // Tag: {ifcustomrole shortrolename}...{/ifcustomrole}. + // Description: Display content only if user has the role specified by shortrolename in the current context. + // Parameters: Short role name. + // Requires content between tags. + if (stripos($text, '{ifcustomrole') !== false) { + $re = '/{ifcustomrole\s+(.*)\}(.*)\{\/ifcustomrole\}/isuU'; + $found = preg_match_all($re, $text, $matches); + if ($found > 0) { + $context = $PAGE->context; + if ($context->contextlevel == CONTEXT_COURSE) { + // We are in a course. + $context = \context_course::instance($context->instanceid); + } else if ($context->contextlevel == CONTEXT_MODULE) { + // We are in an activity. + $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST); + $context = \context_module::instance($cm->id); + unset($cm); + } + + // Get roles within this context. + $roles = get_user_roles($context, $USER->id, true); + $roles = array_column($roles, 'shortname'); + unset($context); + + // Replace all instances of a given ifcustomrole tag. + foreach ($matches[1] as $roleshortname) { + $key = '/{ifcustomrole\s+' . $roleshortname . '\}(.*)\{\/ifcustomrole\}/isuU'; + // We have a role that matches this tag. + if (in_array($roleshortname, $roles)) { + // Just remove the tags. + $replace[$key] = '$1'; + } else { + // Otherwise, remove the ifcustomrole tags and the string inside it. + $replace[$key] = ''; + } + unset($key); + } + } + unset($re); + unset($found); + } + + // Tag: {ifnotcustomrole shortrolename}...{/ifnotcustomrole}. + // Description: Display content only if user does NOT have the role specified by shortrolename in the current context. + // Required Parameters: Short role name. + // Requires content between tags. + if (stripos($text, '{ifnotcustomrole') !== false) { + $re = '/{ifnotcustomrole\s+(.*)\}(.*)\{\/ifnotcustomrole\}/isuU'; + $found = preg_match_all($re, $text, $matches); + if ($found > 0) { + $context = $PAGE->context; + if ($context->contextlevel == CONTEXT_COURSE) { + // We are in a course. + $context = \context_course::instance($context->instanceid); + } else if ($context->contextlevel == CONTEXT_MODULE) { + // We are in an activity. + $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST); + $context = \context_module::instance($cm->id); + unset($cm); + } + + // Get roles within this context. + $roles = get_user_roles($context, $USER->id, true); + $roles = array_column($roles, 'shortname'); + unset($context); + + // Replace all instances of a given ifnotcustomrole tag. + foreach ($matches[1] as $roleshortname) { + $key = '/{ifnotcustomrole\s+' . $roleshortname . '\}(.*)\{\/ifnotcustomrole\}/isuU'; + // We do not have a role that matches this tag. + if (!in_array($roleshortname, $roles)) { + // Just remove the tags. + $replace[$key] = '$1'; + } else { + // Otherwise, remove the ifnotcustomrole strings. + $replace[$key] = ''; + } + unset($key); + } + } + unset($re); + unset($found); + } + + // Tag: {ifhasarolename roleshortname}...{/ifhasarolename}. + // Description: Display content only if user has the role specified by shortrolename ANYWHERE on the site. + // Parameters: Short role name. + // Requires content between tags. + if (stripos($text, '{ifhasarolename') !== false) { + $re = '/{ifhasarolename\s+(.*)\}(.*)\{\/ifhasarolename\}/isuU'; + $found = preg_match_all($re, $text, $matches); + if ($found > 0) { + foreach ($matches[1] as $roleshortname) { + $key = '/{ifhasarolename\s+' . $roleshortname . '\}(.*)\{\/ifhasarolename\}/isuU'; + if ($this->hasarole($roleshortname, $USER->id)) { + // Just remove the tags. + $replace[$key] = '$1'; + } else { + // Remove the ifhasarolename strings. + $replace[$key] = ''; + } + } + } + } + + // Tag: {iftheme themename}...{/iftheme}. + // Description: Display content only if the current theme matches the one specified. + // Parameters: The name of the directory in which the theme is located. + // Requires content between tags. + if (stripos($text, '{/iftheme') !== false) { + $theme = strtolower($PAGE->theme->name); + $re = '/{iftheme\s+(.*)\}(.*)\{\/iftheme\}/isuU'; + $found = preg_match_all($re, $text, $matches); + if ($found > 0) { + foreach ($matches[1] as $themename) { + $key = '/{iftheme\s+' . $themename . '\}(.*)\{\/iftheme\}/isuU'; + if (strtolower($theme == strtolower($themename))) { + // Just remove the tags. + $replace[$key] = '$1'; + } else { + // Remove the iftheme strings. + $replace[$key] = ''; + } + } + } + } + + // Tag: {ifnottheme themename}...{/ifnottheme}. + // Description: Display content only if the current theme does not match the one specified. + // Parameters: The name of the directory in which the theme is located. + // Requires content between tags. + if (stripos($text, '{ifnottheme ') !== false) { + $theme = strtolower($PAGE->theme->name); + $re = '/{ifnottheme\s+(.*)\}(.*)\{\/ifnottheme\}/isuU'; + $found = preg_match_all($re, $text, $matches); + if ($found > 0) { + foreach ($matches[1] as $themename) { + $key = '/{ifnottheme\s+' . $themename . '\}(.*)\{\/ifnottheme\}/isuU'; + if (strtolower($theme) != strtolower($themename)) { + // Just remove the tags. + $replace[$key] = '$1'; + } else { + // Remove the ifnottheme strings. + $replace[$key] = ''; + } + } + } + } + + if (strpos($text, '{ifmin') !== false) { // If there are conditional ifmin tags. + // Tag: {ifminassistant}...{/ifminassistant}. + // Description: Display content only if user has the role of a non-editing teacher or higher. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifminassistant}') !== false) { + // If an assistant (non-editing teacher) or above. + if ($this->hasminarchetype('teacher') && stripos($text, '{ifminassistant}') !== false) { + // Just remove the tags. + $replace['/\{ifminassistant\}/i'] = ''; + $replace['/\{\/ifminassistant\}/i'] = ''; + } else { + // Remove the ifminassistant strings. + $replace['/\{ifminassistant\}(.*)\{\/ifminassistant\}/isuU'] = ''; + } + } + + // Tag: {ifminteacher}...{/ifminteacher}. + // Description: Display content only if user has the role of a editing teacher or higher. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifminteacher}') !== false) { + if ($this->hasminarchetype('editingteacher')) { // If a teacher or above. + // Just remove the tags. + $replace['/\{ifminteacher\}/i'] = ''; + $replace['/\{\/ifminteacher\}/i'] = ''; + } else { + // Remove the ifminteacher strings. + $replace['/\{ifminteacher\}(.*)\{\/ifminteacher\}/isuU'] = ''; + } + } + + // Tag: {ifmincreator}...{/ifmincreator}. + // Description: Display content only if user has the role of a course creator or higher. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifmincreator}') !== false) { + if ($this->hasminarchetype('coursecreator')) { // If a course creator or above. + // Just remove the tags. + $replace['/\{ifmincreator\}/i'] = ''; + $replace['/\{\/ifmincreator\}/i'] = ''; + } else { + // Remove the iscreator strings. + $replace['/\{ifmincreator\}(.*)\{\/ifmincreator\}/isuU'] = ''; + } + } + + // Tag: {ifminmanager}...{/ifminmanager}. + // Description: Display content only if user has the role of a manager or higher. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifminmanager}') !== false) { + if ($this->hasminarchetype('manager')) { // If a manager or above. + // Just remove the tags. + $replace['/\{ifminmanager\}/i'] = ''; + $replace['/\{\/ifminmanager\}/i'] = ''; + } else { + // Remove the ifminmanager strings. + $replace['/\{ifminmanager\}(.*)\{\/ifminmanager\}/isuU'] = ''; + } + } + + // Tag: {ifminsitemanager}...{/ifminsitemanager}. + // Description: Display content if user has the role of a site manager (not just course/category manager) or admin. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{ifminsitemanager}') !== false) { + static $issitemanager; + // If a manager or above. + if (!isset($issitemanager) && $issitemanager = $this->hasminarchetype('manager')) { + if (!is_siteadmin()) { + // Is at least a manager, but a site manager? Let's see. + $syscontext = \context_system::instance(); + $role = $DB->get_record('role', ['shortname' => 'manager'], '*', MUST_EXIST); + $userfields = 'u.id, u.username, u.firstname, u.lastname'; + $roleusers = get_role_users($role->id, $syscontext, false, $userfields); + $issitemanager = array_key_exists($USER->id, array_column($roleusers, null, 'id')); + } + } + if ($issitemanager) { + // Just remove the tags. + $replace['/\{ifminsitemanager\}/i'] = ''; + $replace['/\{\/ifminsitemanager\}/i'] = ''; + } else { + // Remove the ifminsitemanager strings. + $replace['/\{ifminsitemanager\}(.*)\{\/ifminsitemanager\}/isuU'] = ''; + } + } + } + } + + // Tag: {filtercodes}. + // Description: Show version of FilterCodes, but only if you have permission to add the tag. + // Parameters: None. + if (stripos($text, '{filtercodes}') !== false) { + // If you have the ability to edit the content. + if (has_capability('moodle/course:update', $PAGE->context)) { + // Show the version of the FilterCodes plugin. + $plugin = new stdClass(); + require($CFG->dirroot . '/filter/filtercodes/version.php'); + $replace['/\{filtercodes\}/i'] = "$plugin->release ($plugin->version)"; + } else { + $replace['/\{filtercodes\}/i'] = ''; + } + } + + // Tag: {chart } + // Description: Easily display a chart in one of several styles. + // Required Parameters: type=radial|pie|progressbar|progresspie, value=0-100, title=Title of the chart. + if ($CFG->branch >= 32 && version_compare(PHP_VERSION, '7.0.0') >= 0 && stripos($text, '{chart ') !== false) { + global $OUTPUT; + preg_match_all('/\{chart\s(\w+)\s([0-9]+)((?:\s)(.*))?\}/isuU', $text, $matches, PREG_SET_ORDER); + $matches = array_unique($matches, SORT_REGULAR); + foreach ($matches as $match) { + $type = $match[1]; // Chart type: radial, pie, progressbar or progresspie. + $value = $match[2]; // Value between 0 and 100. + $match[3] = $match[3] == null ? '' : $match[3]; + $title = trim($match[3]); // Optional text label. + $percent = get_string('percents', '', $value); + switch ($type) { // Type of chart. + case 'radial': // Tag: {chart radial 99 Label to be displayed} - Display a radial (circle) chart. + $chart = new \core\chart_pie(); + $chart->set_doughnut(true); // Calling set_doughnut(true) we display the chart as a doughnut. + if (!empty($title)) { + $chart->set_title($title); + } + $series = new \core\chart_series('Percentage', [min($value, 100), 100 - min($value, 100)]); + $chart->add_series($series); + $chart->set_labels(['Completed', 'Remaining']); + if ($CFG->branch >= 39) { + $chart->set_legend_options(['display' => false]); // Hide chart legend. + } + $html = '<div class="fc-chart-pie">' . $OUTPUT->render_chart($chart, false) . '</div>'; + break; + case 'pie': // Tag: {chart pie 99 Label to be displayed} - Display a pie chart. + $chart = new \core\chart_pie(); + $chart->set_doughnut(false); // Calling set_doughnut(true) we display the chart as a doughnut. + if (!empty($title)) { + $chart->set_title($title); + } + $series = new \core\chart_series('Percentage', [min($value, 100), 100 - min($value, 100)]); + $chart->add_series($series); + $chart->set_labels(['Completed', 'Remaining']); + if ($CFG->branch >= 39) { + $chart->set_legend_options(['display' => false]); // Hide chart legend. + } + $html = '<div class="fc-chart-pie">' . $OUTPUT->render_chart($chart, false) . '</div>'; + break; + case 'progressbar': // Tag: {chart progressbar 99 Label to be displayed} - Display a horizontal progress bar. + $html = ' + <div class="progress mb-0"> + <div class="fc-progress progress-bar bar" role="progressbar" aria-valuenow="' . $value + . '" style="width: ' . $value . '%" aria-valuemin="0" aria-valuemax="100"> + </div> + </div>'; + if (!empty($title)) { + $html .= '<div class="small">' . get_string( + 'chartprogressbarlabel', + 'filter_filtercodes', + ['label' => $title, 'value' => $percent] + ) . '</div>'; + } + break; + case 'progresspie': // Tag: {chart progresspie 99 Label to display} - Display a progress pie. + $styles = '--percent:' . $value . ';'; + $params = explode(' --', ' ' . $title); + $title = ''; + foreach ($params as $param) { + if (in_array(strtolower(strtok($param, ':')), ['color', 'size', 'border', 'bgcolor'])) { + $styles .= '--' . $param . ';'; + } else if (stripos($param, 'title:') === 0) { + $title = substr($param, 6); + } + } + $html = '<div class="fc-progress-pie" style="' . $styles . '">' . $percent . '</div>'; + if (!empty($title)) { + $html .= '<div class="small">' . $title . '</div>'; + } + break; + default: + $html = ''; + } + $replace['/\{chart ' . $type . ' ' . $value . preg_quote($match[3]) . '\}/isuU'] = $html; + $newtext = preg_replace(array_keys($replace), array_values($replace), $text); + if (!is_null($newtext)) { + $text = $newtext; + } + } + unset($chart, $matches, $html, $value, $title); + } + + // Tag: {alert stylename}...{/alert}. + // Description: Wraps content between the tags into a Bootstrap Alert box. + // Optional Parameters: Stylenames: primary|secondary|success|danger|warning|info|light|dark. Default is 'warning'. + // Requires content between tags. + // Note: Support of styles is theme dependant. Not all themes support these styles and some will support other styles. + if (stripos($text, '{/alert}') !== false) { + $newtext = preg_replace_callback( + '/\{alert(\s\w*)?\}(.*)\{\/alert\}/isuU', + function ($matches) { + // If alert <style> parameter is not included, default to alert-warning. + $matches[1] = trim($matches[1]); + $matches[1] = empty($matches[1]) || $matches[1] == 'border' ? 'border' : 'alert-' . $matches[1]; + return '<div class="alert ' . $matches[1] . '" role="alert"><p>' . $matches[2] . '</p></div>'; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Tag: {label stylename}{/label}. + // Description: Wraps content between the tags into a Bootstrap inline label box. + // Optional Parameters: Style names: default|primary|success|info|danger|warning. Default is 'info'. + // Requires content between tags. + // Note: Support of styles is theme dependant. Not all themes support these styles and some will support other styles. + if (stripos($text, '{/label}') !== false) { + $newtext = preg_replace_callback( + '/\{label(\s\w*)?\}(.*)\{\/label\}/isuU', + function ($matches) { + // If alert <style> parameter is not included, default to alert-info. + $matches[1] = trim($matches[1]); + $matches[1] = empty($matches[1]) ? 'info' : $matches[1]; + return '<span class="label label-' . $matches[1] . '">' . $matches[2] . '</span>'; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Tag: {help}...{/help}. + // Description: Creates a Moodle (?) icon that displays a help bubble when clicked. + // Requires content between tags. + // Parameters: None. + if (stripos($text, '{/help}') !== false) { + static $help; + static $helpwrapper = []; + if (!isset($help)) { + $help = get_string('help'); + $helpwrapper[0] = '<a class="btn btn-link p-0" role="button" data-container="body" data-toggle="popover"' + . ' data-placement="right" data-content="<div class="no-overflow"><p>'; + $helpwrapper[1] = '</p></div>" data-html="true" tabindex="0" data-trigger="focus"><i class="icon' + . ' fa fa-question-circle text-info fa-fw " title="' . $help . '" aria-label="' . $help . '"></i></a>'; + } + $newtext = preg_replace_callback( + '/\{help\}(.*)\{\/help\}/isuU', + function ($matches) use ($helpwrapper) { + return $helpwrapper[0] . htmlspecialchars($matches[1], ENT_COMPAT) . $helpwrapper[1]; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Tag: {info}...{/info}. + // Description: Creates a Moodle (!) icon that displays an information bubble when clicked. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{/info}') !== false) { + static $info; + static $infowrapper = []; + if (!isset($info)) { + $info = get_string('info'); + $infowrapper[0] = '<a class="btn btn-link p-0" role="button" data-container="body" data-toggle="popover"' + . ' data-placement="right" data-content="<div class="no-overflow"><p>'; + $infowrapper[1] = '</p></div>" data-html="true" tabindex="0" data-trigger="focus"><i class="icon' + . ' fa fa-info-circle text-info fa-fw " title="' . $info . '" aria-label="' . $info . '"></i></a>'; + } + $newtext = preg_replace_callback( + '/\{info\}(.*)\{\/info\}/isuU', + function ($matches) use ($infowrapper) { + return $infowrapper[0] . htmlspecialchars($matches[1], ENT_COMPAT) . $infowrapper[1]; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + /* ---------------- Apply all of the filtercodes so far. ---------------*/ + + if ($this->replacetags($text, $replace) == false) { + // No more tags? Put back the escaped tags, if any, and return the string. + $text = $this->escapedtags($text); + return $text; + } + + // Tag: {urlencode}...{/urlencode}. + // Description: URL Encodes the content between the tags for use as a parameter of a URL. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{urlencode}') !== false) { + // Replace {urlencode} tags and content with encoded content. + $newtext = preg_replace_callback( + '/\{urlencode\}(.*)\{\/urlencode\}/isuU', + function ($matches) { + return urlencode($matches[1]); + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Tag: {rawurlencode}...{/rawurlencode}. + // Description: URL Encodes the content between the tags for use as a parameter of a URL in RFC 3986. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{rawurlencode}') !== false) { + // Replace {urlencode} tags and content with encoded content. + $newtext = preg_replace_callback( + '/\{rawurlencode\}(.*)\{\/rawurlencode\}/isuU', + function ($matches) { + return rawurlencode($matches[1]); + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Tag: {qrcode}...{/qrcode}. + // Description: Encodes the content between the tags into a an HTML image tag containing a QR Code of the content. + // Parameters: None. + // Requires content between tags. + if (stripos($text, '{qrcode}') !== false) { + // Remove {qrcode}{/qrcode} tags and turn content between the tags into a QR code. + $newtext = preg_replace_callback( + '/\{qrcode\}(.*)\{\/qrcode\}/isuU', + function ($matches) { + $text = html_to_text($matches[1]); + $src = $this->qrcode($text); + $src = '<img src="' . $src . '" style="width:100%;max-width:480px;height:auto;" class="fc-qrcode" alt="' + . $text . '">'; + return $src; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + // Tag: {button URL}...{/button}. + // Description: Creates a button that displays the content and links to the specified URL. + // Required Parameter: URL. You also need to specify the content which will become the text in the button. + // Requires content between tags. + if (stripos($text, '{button ') !== false) { + $newtext = preg_replace_callback( + '/\{button\s+(.*)\}(.*)\{\/button\}/isuU', + function ($matches) { + // Remove HTML tags created by filters like Activity Name Auto-Linking and Convert URLs Into Links. + $url = strip_tags($matches[1]); + $label = $matches[2]; + return '<a href="' . $url . '" class="btn btn-primary">' . $label . '</a>'; + }, + $text + ); + if ($newtext !== false) { + $text = $newtext; + } + } + + /* ---------------- Apply the rest of the FilterCodes tags. ---------------*/ + + $this->replacetags($text, $replace); + // Put back the escaped tags. + $text = $this->escapedtags($text); + return $text; + } +} diff --git a/filter.php b/filter.php index a6072f6..b7e5d50 100644 --- a/filter.php +++ b/filter.php @@ -14,5342 +14,5 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <https://www.gnu.org/licenses/>. -/** - * Main filter code for FilterCodes. - * - * @package filter_filtercodes - * @copyright 2017-2024 TNG Consulting Inc. - www.tngconsulting.ca - * @author Michael Milette - * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -use block_online_users\fetcher; -use \core_table\local\filter\integer_filter; -use \core_user\table\participants_filterset; -use \core_user\table\participants_search; -use Endroid\QrCode\QrCode; - -require_once($CFG->dirroot . '/course/renderer.php'); - -/** - * Extends the moodle_text_filter class to provide plain text support for new tags. - * - * @copyright 2017-2024 TNG Consulting Inc. - www.tngconsulting.ca - * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class filter_filtercodes extends moodle_text_filter { - /** @var object $archetypes Object array of Moodle archetypes. */ - public $archetypes = []; - /** @var array $customroles array of Roles key is shortname and value is the id */ - private static $customroles = []; - /** - * @var array $customrolespermissions array of Roles key is shortname + context_id and the value is a boolean showing if - * user is allowed - */ - private static $customrolespermissions = []; - - /** - * Constructor: Get the role IDs associated with each of the archetypes. - */ - public function __construct() { - - // Note: This array must correspond to the one in function hasminarchetype. - $archetypelist = ['manager' => 1, 'coursecreator' => 2, 'editingteacher' => 3, 'teacher' => 4, 'student' => 5]; - foreach ($archetypelist as $archetype => $level) { - $roleids = []; - // Build array of roles. - foreach (get_archetype_roles($archetype) as $role) { - $roleids[] = $role->id; - } - $this->archetypes[$archetype] = (object) ['level' => $level, 'roleids' => $roleids]; - } - } - - /** - * Determine if any of the user's roles includes specified archetype. - * - * @param string $archetype Name of archetype. - * @return boolean Does: true, Does not: false. - */ - private function hasarchetype($archetype) { - // If not logged in or is just a guestuser, definitely doesn't have the archetype we want. - if (!isloggedin() || isguestuser()) { - return false; - } - - // Handle caching of results. - static $archetypes = []; - if (isset($archetypes[$archetype])) { - return $archetypes[$archetype]; - } - - global $USER, $PAGE; - $archetypes[$archetype] = false; - if (is_role_switched($PAGE->course->id)) { // Has switched roles. - $context = \context_course::instance($PAGE->course->id); - $id = $USER->access['rsw'][$context->path]; - $archetypes[$archetype] = in_array($id, $this->archetypes[$archetype]->roleids); - } else { - // For each of the roles associated with the archetype, check if the user has one of the roles. - foreach ($this->archetypes[$archetype]->roleids as $roleid) { - if (user_has_role_assignment($USER->id, $roleid, $PAGE->context->id)) { - $archetypes[$archetype] = true; - } - } - } - return $archetypes[$archetype]; - } - - /** - * Determine if the user only has a specified archetype amongst the user's role and no others. - * Example: Can be a student but not also be a teacher or manager. - * - * @param string $archetype Name of archetype. - * @return boolean Does: true, Does not: false. - */ - private function hasonlyarchetype($archetype) { - if ($this->hasarchetype($archetype)) { - $archetypes = array_keys($this->archetypes); - foreach ($archetypes as $archetypename) { - if ($archetypename != $archetype && $this->hasarchetype($archetypename)) { - return false; - } - } - global $PAGE; - if (is_role_switched($PAGE->course->id)) { - // Ignore site admin status if we have switched roles. - return true; - } else { - return is_siteadmin(); - } - } - return false; - } - - /** - * Determine if the user has the specified archetype or one with elevated capabilities. - * Example: Can be a teacher, course creator, manager or Administrator but not a student. - * - * @param string $minarchetype Name of archetype. - * @return boolean User meets minimum archetype requirement: true, does not: false. - */ - private function hasminarchetype($minarchetype) { - // Note: This array must start with one blank entry followed by the same list found in in __construct(). - $archetypelist = ['', 'manager', 'coursecreator', 'editingteacher', 'teacher', 'student']; - // For each archetype level between the one specified and 'manager'. - for ($level = $this->archetypes[$minarchetype]->level; $level >= 1; $level--) { - // Check to see if any of the user's roles correspond to the archetype. - if ($this->hasarchetype($archetypelist[$level])) { - return true; - } - } - // Return true regardless of the archetype if we are an administrator and not in a switched role. - global $PAGE; - return !is_role_switched($PAGE->course->id) && is_siteadmin(); - } - - /** - * Checks if a user has a custom role or not within the current context. - * - * @param string $roleshortname The role's shortname. - * @param integer $contextid The context where the tag appears. - * @return boolean True if user has custom role, otherwise, false. - */ - private function hascustomrole($roleshortname, $contextid = 0) { - $keytocheck = $roleshortname . '-' . $contextid; - if (!isset(self::$customrolespermissions[$keytocheck])) { - global $USER, $DB; - if (!isset(self::$customroles[$roleshortname])) { - self::$customroles[$roleshortname] = $DB->get_field('role', 'id', ['shortname' => $roleshortname]); - } - $hasrole = false; - if (self::$customroles[$roleshortname]) { - $hasrole = user_has_role_assignment($USER->id, self::$customroles[$roleshortname], $contextid); - } - self::$customrolespermissions[$keytocheck] = $hasrole; - } - - return self::$customrolespermissions[$keytocheck]; - } - - /** - * Determine if the specified user has the specified role anywhere in the system. - * - * @param string $roleshortname Role shortname. - * @param integer $userid The user's ID. - * @return boolean True if the user has the role, false if they do not. - */ - private function hasarole($roleshortname, $userid) { - // Cache list of user's roles. - static $list; - - if (!isset($list)) { - // Not cached yet? We can take care of that. - $list = []; - if (isloggedin() && !isguestuser()) { - // We only track logged-in roles. - global $DB; - // Retrieve list of role names. - $rolenames = $DB->get_records('role'); - // Retrieve list of my roles across all contexts. - $userroles = $DB->get_records('role_assignments', ['userid' => $userid]); - // For each of my roles, add the roll name to the list. - foreach ($userroles as $role) { - if (!empty($rolenames[$role->roleid]->shortname)) { - // There should always be a role name for each role id but you can't be too careful these days. - $list[] = $rolenames[$role->roleid]->shortname; - } - } - $list = array_unique($list); - if (is_siteadmin()) { - // Admin is not an actual role, but we can use our imagination for convenience. - $list[] = 'administrator'; - } - } - } - return in_array(strtolower($roleshortname), $list); - } - - /** - * Returns the URL of a blank Avatar as a square image. - * - * @param integer $size Width of desired image in pixels. - * @return MOODLE_URL URL to image of avatar image. - */ - private function getblankavatarurl($size) { - global $PAGE, $CFG; - $img = 'u/' . ($size > 100 ? 'f3' : ($size > 35 ? 'f1' : 'f2')); - $renderer = $PAGE->get_renderer('core'); - if ($CFG->branch >= 33) { - $url = $renderer->image_url($img); - } else { - $url = $renderer->pix_url($img); // Deprecated as of Moodle 3.3. - } - return (new \moodle_url($url))->out(); - } - - /** - * Retrieves the URL for the user's profile picture, if one is available. - * - * @param object $user The Moodle user object for which we want a photo. - * @param mixed $size Can be sm|md|lg or an integer 2|1|3 or an integer size in pixels > 3. - * @return string URL to the photo image file but with $1 for the size. - */ - private function getprofilepictureurl($user, $size = 'md') { - global $PAGE; - - $sizes = ['sm' => 35, '2' => 35, 'md' => 100, '1' => 100, 'lg' => 512, '3' => 512]; - if (empty($px = $sizes[$size])) { - $px = $size; // Size was specified in pixels. - } - $userpicture = new user_picture($user); - $userpicture->size = $px; // Size in pixels. - $url = $userpicture->get_url($PAGE); - return $url; - } - - /** - * Retrieves specified profile fields for a user. - * - * This method fetches and caches user profile fields from the database. If the fields have - * already been retrieved for the current request, the cached values are returned. This method - * supports fetching both core and custom user profile fields. - * - * @param object $user The user object whose profile fields are being retrieved. - * @param array $fields optional An array of field names to retrieve. If empty/not specified, only custom fields are retrieved. - * @return array An associative array of field names and their values for the specified user. - */ - private function getuserprofilefields($user, $fields = []) { - global $DB; - - static $profilefields; - static $lastfields; - - // If we have already cached the profile fields and data, return them. - if (isset($profilefields) && $lastfields == $fields) { - return $profilefields; - } - - $profilefields = []; - if (!isloggedin()) { - return $profilefields; - } - - // Get custom user profile fields, their value and visibilit. Only works for authenticated users. - $sql = "SELECT f.shortname, f.visible, f.datatype, COALESCE(d.data, '') AS value - FROM {user_info_field} f - LEFT JOIN {user_info_data} d ON f.id = d.fieldid AND d.userid = :userid - ORDER BY f.shortname;"; - $params = ['userid' => $user->id]; - // Determine if restricted to only visible fields. - if (!empty(get_config('filter_filtercodes', 'ifprofilefiedonlyvisible'))) { - $params['visible'] = 1; - } - $profilefields = $DB->get_records_sql($sql, $params); - - // Add core user profile fields. - foreach ($fields as $field) { - // Skip fields that don't exist (likely a typo). - if (isset($user->$field)) { - $profilefields[$field] = (object)['shortname' => $field, 'visible' => '1', - 'datatype' => 'text', 'value' => $user->$field]; - } - } - $lastfields = $fields; - - return $profilefields; - } - - /** - * Retrieves the user's groupings for a course. - * - * @param integer $courseid The course ID. - * @param integer $userid The user ID. - * @return array An array of groupings for the specified user in the specified course. - */ - private function getusergroupings($courseid, $userid) { - global $DB; - - return $DB->get_records_sql('SELECT gp.id, gp.name, gp.idnumber - FROM {user} u - INNER JOIN {groups_members} gm ON u.id = gm.userid - INNER JOIN {groups} g ON g.id = gm.groupid - INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid - INNER JOIN {groupings} gp ON gp.id = gg.groupingid - WHERE g.courseid = ? AND u.id = ? - GROUP BY gp.id - ORDER BY gp.name ASC', [$courseid, $userid]); - } - - /** - * Determine if running on http or https. Same as Moodle's is_https() except that it is backwards compatible to Moodle 2.7. - * - * @return boolean true if protocol is https, false if http. - */ - private function ishttps() { - global $CFG; - if ($CFG->branch >= 28) { - $ishttps = is_https(); // Available as of Moodle 2.8. - } else { - $ishttps = (filter_input(INPUT_SERVER, 'HTTPS') === 'on'); - } - return $ishttps; - } - - /** - * Determine if access is from a web service. - * - * @return boolean true if a web service, false if web browser. - */ - private function iswebservice() { - global $ME; - // If this is a web service or the Moodle mobile app... - $isws = (WS_SERVER || (strstr($ME, "webservice/") !== false && optional_param('token', '', PARAM_ALPHANUM))); - return $isws; - } - - /** - * Generates HTML code for a reCAPTCHA. - * - * @return string HTML Code for reCAPTCHA or blank if logged-in or Moodle reCAPTCHA is not configured. - */ - private function getrecaptcha() { - global $CFG; - // Is user not logged-in or logged-in as guest? - if (!isloggedin() || isguestuser()) { - // If Moodle reCAPTCHA configured. - if (!empty($CFG->recaptchaprivatekey) && !empty($CFG->recaptchapublickey)) { - // Yes? Generate reCAPTCHA. - if (file_exists($CFG->libdir . '/recaptchalib_v2.php')) { - // For reCAPTCHA 2.0. - require_once($CFG->libdir . '/recaptchalib_v2.php'); - return recaptcha_get_challenge_html(RECAPTCHA_API_URL, $CFG->recaptchapublickey); - } else { - // For reCAPTCHA 1.0. - require_once($CFG->libdir . '/recaptchalib.php'); - return recaptcha_get_html($CFG->recaptchapublickey, null, $this->ishttps()); - } - } else if ($CFG->debugdisplay == 1) { // If debugging is set to DEVELOPER... - // Show indicator that {reCAPTCHA} tag is not required. - return 'Warning: The reCAPTCHA tag is not required here.'; - } - } - // Logged-in as non-guest user (reCAPTCHA is not required) or Moodle reCAPTCHA not configured. - // Don't generate reCAPTCHA. - return ''; - } - - /** - * Scrape HTML (callback) - * - * Extract content from another web page. - * Example: Can be used to extract a shared privacy policy across your websites. - * - * @param string $url URL address of content source. - * @param string $tag HTML tag that contains the information we want to retrieve. - * @param string $class (optional) HTML tag class attribute we should match. - * @param string $id (optional) HTML tag id attribute we should match. - * @param string $code (optional) any URL encoded HTML code you want to insert after the retrieved content. - * @return string Extracted content+optional code. If content is unavailable, returns message to contact webmaster. - */ - private function scrapehtml($url, $tag = '', $class = '', $id = '', $code = '') { - // Retrieve content. If the URL fails, return a message. - $content = @file_get_contents($url); - if (empty($content)) { - return get_string('contentmissing', 'filter_filtercodes'); - } - - // Disable warnings. - $libxmlpreviousstate = libxml_use_internal_errors(true); - - // Load content into DOM object. - $dom = new DOMDocument(); - $dom->loadHTML($content); - - // Clear suppressed warnings. - libxml_clear_errors(); - libxml_use_internal_errors($libxmlpreviousstate); - - // Scrape out the content we want. If not found, return everything. - $xpath = new DOMXPath($dom); - - // If a tag was not specified. - if (empty($tag)) { - $tag .= '*'; // Match any tag. - } - $query = "//{$tag}"; - - // If a class was specified. - if (!empty($class)) { - $query .= "[@class=\"{$class}\"]"; - } - - // If an id was specified. - if (!empty($id)) { - $query .= "[@id=\"{$id}\"]"; - } - - $tag = $xpath->query($query); - $tag = $tag->item(0); - - return $dom->saveXML($tag) . urldecode($code); - } - - /** - * Convert a number of bytes (e.g. filesize) into human readable format. - * - * @param float $bytes Raw number of bytes. - * @return string Bytes in human readable format. - */ - private function humanbytes($bytes) { - if ($bytes === false || $bytes < 0 || is_null($bytes) || $bytes > 1.0E+26) { - // If invalid number of bytes, or value is more than about 84,703.29 Yottabyte (YB), assume it is infinite. - $str = '∞'; // Could not determine, assume infinite. - } else { - static $unit; - if (!isset($unit)) { - $units = ['sizeb', 'sizekb', 'sizemb', 'sizegb', 'sizetb', 'sizeeb', 'sizezb', 'sizeyb']; - $units = get_strings($units, 'filter_filtercodes'); - $units = array_values((array) $units); - } - $base = 1024; - $factor = min((int) log($bytes, $base), count($units) - 1); - $precision = [0, 2, 2, 1, 1, 1, 1, 0]; - $str = sprintf("%1.{$precision[$factor]}f", $bytes / pow($base, $factor)) . ' ' . $units[$factor]; - } - return $str; - } - - /** - * Correctly format a list as "A, B and C". - * - * @param array $list An array of numbers or strings. - * @return string The formatted string. - */ - private function formatlist($list) { - // Save and remove last item in list from array. - $last = array_pop($list); - if ($list) { - // Combine list using language list separator. - $list = implode(get_string('listsep', 'langconfig') . ' ', $list); - // Add last item separated by " and ". - $string = get_string('and', 'moodle', ['one' => $list, 'two' => $last]); - } else { - // Only one item in the list. No formatting required. - $string = $last; - } - return $string; - } - - /** - * Convert string containg one or more attribute="value" pairs into an associative array. - * - * @param string $attrs One or more attribute="value" pairs. - * @return array Associative array of attributes and values. - */ - private function attribstoarray($attrs) { - $arr = []; - - if (preg_match_all('/\s*(?:([a-z0-9-]+)\s*=\s*"([^"]*)")|(?:\s+([a-z0-9-]+)(?=\s*|>|\s+[a..z0-9]+))/i', $attrs, $matches)) { - // For each attribute in the string, add associated value to the array. - for ($i = 0; $i < count($matches[0]); $i++) { - if ($matches[3][$i]) { - $arr[$matches[3][$i]] = null; - } else { - $arr[$matches[1][$i]] = $matches[2][$i]; - } - } - } - return $arr; - } - - /** - * Render cards for provided category. - * - * @param object $category Category object. - * @param boolean $categoryshowpic Set to true to display a category image. False displays no image. - * @return string HTML rendering of category cars. - */ - private function rendercategorycard($category, $categoryshowpic) { - global $OUTPUT; - - if (!$category->visible) { - $dimmed = 'opacity: 0.5;'; - } else { - $dimmed = ''; - } - - $url = (new \moodle_url('/course/index.php', ['categoryid' => $category->id]))->out(); - if ($categoryshowpic) { - $imgurl = $OUTPUT->get_generated_image_for_id($category->id + 65535); - $html = '<li class="card shadow mr-4 mb-4 ml-0" style="min-width:290px;max-width:290px;' . $dimmed . '"> - <a href="' . $url . '" class="text-white h-100"> - <div class="card-img" style="background-image: url(' . $imgurl . ');height:100px;"></div> - <div class="card-img-overlay card-title pt-1 pr-3 pb-1 pl-3 m-0" ' - . 'style="height:fit-content;top:auto;background-color:rgba(0,0,0,.4);color:#ffffff;' - . 'text-shadow:-1px -1px 0 #767676, 1px -1px 0 #767676, -1px 1px 0 #767676, 1px 1px 0 #767676">' - . $category->name . '</div>'; - } else { - $html = '<li class="card shadow mr-4 mb-4 ml-0 fc-categorycard-' . $category->id . - '" style="min-width:350px;max-width:350px;' . $dimmed . '">' . - '<a href="' . $url . '" class="text-decoration-none h-100 p-4">' . $category->name; - } - $html .= '</a></li>' . PHP_EOL; - return $html; - } - - /** - * Render course cards for list of course ids. Not visible for hidden courses or if it has expired. - * - * @param array $rcourseids Array of course ids. - * @param string $format orientation/layout of course cards. - * @return string HTML of course cars. - */ - private function rendercoursecards($rcourseids, $format = 'vertical') { - global $CFG, $OUTPUT, $PAGE, $SITE; - - $content = ''; - $isadmin = (is_siteadmin() && !is_role_switched($PAGE->course->id)); - - foreach ($rcourseids as $courseid) { - if ($courseid == $SITE->id) { // Skip site. - continue; - } - $course = get_course($courseid); - $context = \context_course::instance($course->id); - // Course will be displayed if its visibility is set to Show AND (either has no end date OR a future end date). - $visible = ($course->visible && (empty($course->enddate) || time() < $course->enddate)); - // Courses not visible will be still visible to site admins or users with viewhiddencourses capability. - if (!$visible && !($isadmin || has_capability('moodle/course:viewhiddencourses', $context))) { - // Skip if the course is not visible to user or course is the "site". - continue; - } - - // Load image from course image. If none, generate a course image based on the course ID. - $context = \context_course::instance($courseid); - if ($course instanceof stdClass) { - $course = new \core_course_list_element($course); - } - $coursefiles = $course->get_course_overviewfiles(); - $imgurl = ''; - if ($CFG->branch >= 311) { - $imgurl = \core_course\external\course_summary_exporter::get_course_image($course); - } else { // Previous to Moodle 3.11. - foreach ($coursefiles as $file) { - if ($isimage = $file->is_valid_image()) { - // The file_encode_url() function is deprecated as per MDL-31071 but still in wide use. - $imgurl = file_encode_url("/pluginfile.php", '/' . $file->get_contextid() . '/' - . $file->get_component() . '/' . $file->get_filearea() . $file->get_filepath() - . $file->get_filename(), !$isimage); - $imgurl = (new \moodle_url($imgurl))->out(); - break; - } - } - } - if (empty($imgurl)) { - $imgurl = $OUTPUT->get_generated_image_for_id($courseid); - } - $courseurl = (new \moodle_url('/course/view.php', ['id' => $courseid]))->out(); - - switch ($format) { - case 'vertical': - $content .= ' - <div class="card shadow mr-4 mb-4 ml-1 fc-coursecard-card" style="min-width:300px;max-width:300px;"> - <a href="' . $courseurl . '" class="text-normal h-100"> - <div class="card-img-top" style="background-image:url(' . $imgurl - . ');height:100px;max-width:300px;padding-top:50%;background-size:cover;' - . 'background-repeat:no-repeat;background-position:center;"></div> - <div class="card-title pt-1 pr-3 pb-1 pl-3 m-0"><span class="sr-only">' . get_string('course') . ': </span>' - . $course->get_formatted_name() . '</div> - </a> - </div> - '; - break; - case 'horizontal': - global $DB; - $category = $DB->get_record('course_categories', ['id' => $course->category]); - $category = $category->name; - - $summary = $course->summary == null ? '' : $course->summary; - $summary = substr($summary, -4) == '<br>' ? substr($summary, 0, strlen($summary) - 4) : $summary; - - $content .= ' - <div class="card mb-3 fc-coursecard-list"> - <div class="row no-gutter"> - <div class="col-md-4"> - <a href="' . $courseurl . '" aria-hidden="true" tabindex="-1"> - <img src="' . $imgurl . '" class="card-img" alt=""> - </a> - </div> - <div class="col-md-8"> - <div class="card-body"> - <p class="card-text text-category" style="float:right"> - <small class="text-muted"><span class="sr-only">' - . get_string('category') . ': </span>' . $category . - '</small> - </p> - <h3 class="card-title"> - <a href="' . $courseurl . '" class="text-normal h-100"> - <span class="sr-only">' . get_string('course') . ': </span>' - . $course->get_formatted_name() . - '</a> - </h3> - <div class="card-text text-summary"><span class="sr-only">' - . get_string('summary') . ': </span>' . $summary . - '</div> - </div> - </div> - </div> - </div> - '; - break; - case 'table': - global $DB; - $category = $DB->get_record('course_categories', ['id' => $course->category]); - $category = $category->name; - - $course->summary == null ? '' : $course->summary; - $summary = substr($summary, -4) == '<br>' ? substr($summary, 0, strlen($summary) - 4) : $summary; - - $content .= ' - <tr class="fc-coursecard-table"> - <td class="text-coursename"><a href="' . $courseurl . '">' . $course->get_formatted_name() . '</a></td> - <td class="text-coursecategory">' . $category . '</td> - <td class="text-coursename" style="white-space:normal;">' . $summary . '</td> - </tr> - '; - break; - } - } - return $content; - } - - /** - * Get course card including format, header and footer. - * - * @param string $format card format. - * @return object $cards->format, $cards->header, $cards->footer - */ - private function getcoursecardinfo($format = null) { - static $cards; - if (is_object($cards)) { - return $cards; - } - $cards = new stdClass(); - if (empty($format)) { - $cards->format = get_config('filter_filtercodes', 'coursecardsformat'); - } else { - $cards->format = $format; - } - switch ($cards->format) { - case 'table': - $cards->header = ' - <table class="table table-hover table-responsive"> - <thead> - <tr> - <th scope="col">' . get_string('course') . '</th> - <th scope="col">' . get_string('category') . '</th> - <th scope="col">' . get_string('description') . '</th> - </tr> - </thead> - <tbody> - '; - $cards->footer = ' - </tbody> - </table>'; - break; - case 'horizontal': - $cards->header = '<div class="d-flex"><div>'; - $cards->footer = '</div></div>'; - break; - default: - $cards->format = 'vertical'; - $cards->header = '<div class="card-deck mr-0">'; - $cards->footer = '</div>'; - } - return $cards; - } - - /** - * Generate a user link of a specified type if logged-in. - * - * @param string $clinktype Type of link to generate. Options include: email, message, profile, phone1. - * @param object $user A user object. - * @param string $name The name to be displayed. - * - * @return string Generated link. - */ - private function userlink($clinktype, $user, $name) { - if (!isloggedin() || isguestuser()) { - $clinktype = ''; // No link, only name. - } - switch ($clinktype) { - case 'email': - $link = '<a href="mailto:' . $user->email . '">' . $name . '</a>'; - break; - case 'message': - $link = '<a href="' . (new \moodle_url('/message/index.php', ['id' => $user->id]))->out() . '">' . $name . '</a>'; - break; - case 'profile': - $link = '<a href="' . (new \moodle_url('/user/profile.php', ['id' => $user->id]))->out() . '">' . $name . '</a>'; - break; - case 'phone1': - if (!empty($user->phone1)) { - $link = '<a href="tel:' . $user->phone1 . '">' . $name . '</a>'; - } else { - $link = $name; - } - break; - default: - $link = $name; - } - return $link; - } - - /** - * Generate base64 encoded data img of QR Code. - * - * @param string $text Text to be encoded. - * @param string $label Label to display below QR code. - * @return string Base64 encoded data image. - */ - private function qrcode($text, $label = '') { - if (empty($text)) { - return ''; - } - global $CFG; - require_once($CFG->dirroot . '/filter/filtercodes/thirdparty/QrCode/src/QrCode.php'); - $code = new QrCode(); - $code->setText($text); - $code->setErrorCorrection('high'); - $code->setPadding(0); - $code->setSize(480); - $code->setLabelFontSize(16); - $code->setLabel($label); - $src = 'data:image/png;base64,' . base64_encode($code->get('png')); - return $src; - } - - /** - * Course completion progress percentage. - * - * @return int completion progress percentage - */ - private function completionprogress() { - static $progresspercent; - if (!isset($progresspercent)) { - global $PAGE; - $course = $PAGE->course; - $progresspercent = -1; // Disabled: -1. - if ( - $course->enablecompletion == 1 - && isloggedin() - && !isguestuser() - && \context_system::instance() != 'page-site-index' - ) { - $progresspercent = (int) \core_completion\progress::get_course_progress_percentage($course); - } - } - - return $progresspercent; - } - - /** - * Generator Tags - * - * This function processes tags that generate content that could potentially include additional tags. - * - * @param string $text The unprocessed text. Passed by refernce. - * @return boolean True of there are more tags to be processed, otherwise false. - */ - private function generatortags(&$text) { - global $CFG, $PAGE, $DB; - - $replace = []; // Array of key/value filterobjects. - - // If there are {menu...} tags. - if (stripos($text, '{menu') !== false) { - // Tag: {menuadmin}. - // Description: Displays a menu of useful links for site administrators when added to the custom menu. - // Parameters: None. - if (stripos($text, '{menuadmin}') !== false) { - $theme = $PAGE->theme->name; - $menu = ''; - if ($this->hasminarchetype('editingteacher')) { - $menu .= '{fa fa-wrench} {getstring}admin{/getstring}' . PHP_EOL; - } - if ($this->hasminarchetype('coursecreator')) { // If a course creator or above. - $menu .= '-{getstring}administrationsite{/getstring}|/admin/search.php' . PHP_EOL; - $menu .= '-{toggleeditingmenu}' . PHP_EOL; - $menu .= '-Moodle Academy|https://moodle.academy/' . PHP_EOL; - $menu .= '-###' . PHP_EOL; - } - if ($this->hasminarchetype('manager')) { // If a manager or above. - $menu .= '-{getstring}user{/getstring}: {getstring:admin}usermanagement{/getstring}|/admin/user.php' . PHP_EOL; - $menu .= '{ifminsitemanager}' . PHP_EOL; - $menu .= '-{getstring}user{/getstring}: {getstring:mnet}profilefields{/getstring}|/user/profile/index.php' . - PHP_EOL; - $menu .= '-###' . PHP_EOL; - $menu .= '{/ifminsitemanager}' . PHP_EOL; - $menu .= '-{getstring}course{/getstring}: {getstring:admin}coursemgmt{/getstring}|/course/management.php' . - '?categoryid={categoryid}' . PHP_EOL; - $menu .= '-{getstring}course{/getstring}: {getstring}new{/getstring}|/course/edit.php' . - '?category={categoryid}&returnto=topcat' . PHP_EOL; - $menu .= '-{getstring}course{/getstring}: {getstring}searchcourses{/getstring}|/course/search.php' . PHP_EOL; - } - if ($this->hasminarchetype('editingteacher')) { - $menu .= '-{getstring}course{/getstring}: {getstring}restore{/getstring}|/backup/restorefile.php' . - '?contextid={coursecontextid}' . PHP_EOL; - $menu .= '{ifincourse}' . PHP_EOL; - $menu .= '-{getstring}course{/getstring}: {getstring}backup{/getstring}|/backup/backup.php?id={courseid}' . - PHP_EOL; - if (stripos($text, '{menucoursemore}') === false) { - $menu .= '-{getstring}course{/getstring}: {getstring}participants{/getstring}|/user/index.php?id={courseid}' - . PHP_EOL; - $menu .= '-{getstring}course{/getstring}: {getstring:badges}badges{/getstring}|/badges/view.php' . - '?type=2&id={courseid}' . PHP_EOL; - $menu .= '-{getstring}course{/getstring}: {getstring}reports{/getstring}|/course/admin.php' . - '?courseid={courseid}#linkcoursereports' . PHP_EOL; - } - $menu .= '-{getstring}course{/getstring}: {getstring:enrol}enrolmentinstances{/getstring}|/enrol/instances.php' - . '?id={courseid}' . PHP_EOL; - $menu .= '-{getstring}course{/getstring}: {getstring}reset{/getstring}|/course/reset.php?id={courseid}' - . PHP_EOL; - $menu .= '-Course: Layoutit|https://www.layoutit.com/build" target="popup" ' . - 'onclick="window.open(\'https://www.layoutit.com/build\',\'popup\',\'width=1340,height=700\');' . - ' return false;|Bootstrap Page Builder' . PHP_EOL; - $menu .= '{/ifincourse}' . PHP_EOL; - $menu .= '-###' . PHP_EOL; - } - if ($this->hasminarchetype('manager')) { // If a manager or above. - $menu .= '-{getstring}site{/getstring}: {getstring}reports{/getstring}|/admin/category.php?category=reports' . - PHP_EOL; - } - if (is_siteadmin() && !is_role_switched($PAGE->course->id)) { // If an administrator. - $menu .= '-{getstring}site{/getstring}: {getstring:admin}additionalhtml{/getstring}|/admin/settings.php' . - '?section=additionalhtml' . PHP_EOL; - $menu .= '-{getstring}site{/getstring}: {getstring:admin}frontpage{/getstring}|/admin/settings.php' . - '?section=frontpagesettings|Including site name' . PHP_EOL; - $menu .= '-{getstring}site{/getstring}: {getstring:admin}plugins{/getstring}|/admin/search.php#linkmodules' . - PHP_EOL; - $menu .= '-{getstring}site{/getstring}: {getstring:admin}supportcontact{/getstring}|/admin/settings.php' . - '?section=supportcontact' . PHP_EOL; - - if ($CFG->branch >= 404) { - $label = 'themesettingsadvanced'; - $section = 'themesettingsadvanced'; - } else { - $label = 'themesettings'; - $section = 'themesettings'; - } - $menu .= '-{getstring}site{/getstring}: {getstring:admin}' . $label . '{/getstring}|/admin/settings.php' . - '?section=' . $section . '|Including custom menus, designer mode, theme in URL' . PHP_EOL; - - if (file_exists($CFG->dirroot . '/theme/' . $theme . '/settings.php')) { - require_once($CFG->libdir . '/adminlib.php'); - if (admin_get_root()->locate('theme_' . $theme)) { - // Settings use categories interface URL. - $url = '/admin/category.php?category=theme_' . $theme . PHP_EOL; - } else { - // Settings use tabs interface URL. - $url = '/admin/settings.php?section=themesetting' . $theme . PHP_EOL; - } - $menu .= '-{getstring}site{/getstring}: {getstring:admin}currenttheme{/getstring}|' . $url; - } - $menu .= '-{getstring}site{/getstring}: {getstring}notifications{/getstring} ({getstring}admin{/getstring})' . - '|/admin/index.php' . PHP_EOL; - } - $replace['/\{menuadmin\}/i'] = $menu; - } - - // Tag: {menucoursemore}. - // Description: Show a "More" menu containing most of 4.x secondary menu. Useful if theme with pre-4.x style navigation. - // Parameters: None. - if (stripos($text, '{menucoursemore}') !== false) { - $menu = ''; - $menu .= '{ifincourse}' . PHP_EOL; - if ($CFG->branch >= 400) { - $menu .= '{getstring}moremenu{/getstring}' . PHP_EOL; - } else { - $menu .= '{getstring:filter_filtercodes}moremenu{/getstring}' . PHP_EOL; - } - $menu .= '-{getstring}course{/getstring}|/course/view.php?id={courseid}' . PHP_EOL; - if ($this->hasminarchetype('editingteacher')) { - $menu .= '-{getstring}settings{/getstring}|/course/edit.php?id={courseid}' . PHP_EOL; - } - $menu .= '-{getstring}participants{/getstring}|/user/index.php?id={courseid}' . PHP_EOL; - $menu .= '-{getstring}grades{/getstring}|/grade/report/index.php?id={courseid}' . PHP_EOL; - if ($this->hasminarchetype('editingteacher')) { - $menu .= '-{getstring}reports{/getstring}|/report/view.php?courseid={courseid}' . PHP_EOL; - $menu .= '-###' . PHP_EOL; - $menu .= '-{getstring:question}questionbank{/getstring}|/question/edit.php?courseid={courseid}' . PHP_EOL; - if ($CFG->branch >= 39) { - $menu .= '-{getstring}contentbank{/getstring}|/contentbank/index.php?contextid={coursecontextid}' . PHP_EOL; - } - $menu .= '-{getstring:completion}coursecompletion{/getstring}|/course/completion.php?id={courseid}' . PHP_EOL; - $menu .= '-{getstring:badges}badges{/getstring}|/badges/view.php?type=2&id={courseid}' . PHP_EOL; - } - $pluginame = '{getstring:competency}competencies{/getstring}'; - $menu .= '-' . $pluginame . '|/admin/tool/lp/coursecompetencies.php?courseid={courseid}' . PHP_EOL; - if ($this->hasminarchetype('editingteacher')) { - $menu .= '-{getstring:admin}filters{/getstring}|/filter/manage.php?contextid={coursecontextid}' . PHP_EOL; - } - if ($CFG->branch >= 402) { - $menu .= '-{getstring:enrol}unenrolme{/getstring}|{courseunenrolurl}' . PHP_EOL; - } else { - $menu .= '-{getstring:filter_filtercodes}unenrolme{/getstring}|{courseunenrolurl}' . PHP_EOL; - } - if ($this->hasminarchetype('editingteacher')) { - $menu .= '-{getstring:mod_lti}courseexternaltools{/getstring}|/mod/lti/coursetools.php?id={courseid}' . PHP_EOL; - if ($CFG->branch >= 311) { - $pluginame = '{getstring:tool_brickfield}pluginname{/getstring}'; - $menu .= '-' . $pluginame . '|/admin/tool/brickfield/index.php?courseid={courseid}' . PHP_EOL; - } - $menu .= '-{getstring}coursereuse{/getstring}|/backup/import.php?id={courseid}' . PHP_EOL; - } - $menu .= '{/ifincourse}' . PHP_EOL; - $replace['/\{menucoursemore\}/i'] = $menu; - } - - // Tag: {menudev}. - // Description: Displays a menu of useful links for site administrators when added to the custom menu. - // Parameters: None. - if (stripos($text, '{menudev}') !== false) { - $menu = ''; - if (is_siteadmin() && !is_role_switched($PAGE->course->id)) { // If a site administrator. - $menu .= '-{getstring:tool_installaddon}installaddons{/getstring}|/admin/tool/installaddon' . PHP_EOL; - $menu .= '-###' . PHP_EOL; - $menu .= '-{getstring:admin}debugging{/getstring}|/admin/settings.php?section=debugging' . PHP_EOL; - $menu .= '-{getstring:admin}purgecachespage{/getstring}|/admin/purgecaches.php' . PHP_EOL; - $menu .= '-###' . PHP_EOL; - if (file_exists(dirname(__FILE__) . '/../../local/adminer/index.php')) { - $menu .= '-{getstring:local_adminer}pluginname{/getstring}|/local/adminer' . PHP_EOL; - } - if (file_exists(dirname(__FILE__) . '/../../local/codechecker/index.php')) { - $menu .= '-{getstring:local_codechecker}pluginname{/getstring}|/local/codechecker' . PHP_EOL; - } - if (file_exists(dirname(__FILE__) . '/../../local/moodlecheck/index.php')) { - $menu .= '-{getstring:local_moodlecheck}pluginname{/getstring}|/local/moodlecheck' . PHP_EOL; - } - if (file_exists(dirname(__FILE__) . '/../../admin/tool/pluginskel/index.php')) { - $menu .= '-{getstring:tool_pluginskel}pluginname{/getstring}|/admin/tool/pluginskel' . PHP_EOL; - } - if (file_exists(dirname(__FILE__) . '/../../local/tinyfilemanager/index.php')) { - $menu .= '-{getstring:local_tinyfilemanager}pluginname{/getstring}|/local/tinyfilemanager' . PHP_EOL; - } - $menu .= '-{getstring}phpinfo{/getstring}|/admin/phpinfo.php' . PHP_EOL; - $menu .= '-###' . PHP_EOL; - $menu .= '-{getstring:filter_filtercodes}pagebuilder{/getstring}|' - . '{getstring:filter_filtercodes}pagebuilderlink{/getstring}"' - . ' target="popup" onclick="window.open(\'{getstring:filter_filtercodes}pagebuilderlink{/getstring}\'' - . ',\'popup\',\'width=1340,height=700\'); return false;' . PHP_EOL; - $menu .= '-{getstring:filter_filtercodes}photoeditor{/getstring}|' - . '{getstring:filter_filtercodes}photoeditorlink{/getstring}"' - . ' target="popup" onclick="window.open(\'{getstring:filter_filtercodes}photoeditorlink{/getstring}\'' - . ',\'popup\',\'width=1340,height=700\'); return false;' . PHP_EOL; - $menu .= '-{getstring:filter_filtercodes}screenrec{/getstring}|' - . '{getstring:filter_filtercodes}screenreclink{/getstring}"' - . ' target="popup" onclick="window.open(\'{getstring:filter_filtercodes}screenreclink{/getstring}\'' - . ',\'popup\',\'width=1340,height=700\'); return false;' . PHP_EOL; - $menu .= '-###' . PHP_EOL; - $menu .= '-Dev docs|https://moodle.org/development|Moodle.org ({getstring}english{/getstring})' . PHP_EOL; - $menu .= '-Dev forum|https://moodle.org/mod/forum/view.php?id=55|Moodle.org ({getstring}english{/getstring})' . - PHP_EOL; - $menu .= '-Tracker|https://tracker.moodle.org/|Moodle.org ({getstring}english{/getstring})' . PHP_EOL; - $menu .= '-AMOS|https://lang.moodle.org/|Moodle.org ({getstring}english{/getstring})' . PHP_EOL; - $menu .= '-WCAG 2.1|https://www.w3.org/WAI/WCAG21/quickref/|W3C ({getstring}english{/getstring})' . PHP_EOL; - $menu .= '-###' . PHP_EOL; - $menu .= '-DevTuts|https://www.youtube.com/watch?v=UY_pcs4HdDM|{getstring}english{/getstring}' . PHP_EOL; - $menu .= '-Moodle Development School|https://moodledev.moodle.school/|{getstring}english{/getstring}' . PHP_EOL; - $menuurl = 'https://moodle.academy/course/index.php?categoryid=4'; - $menu .= '-Moodle Dev Academy|' . $menuurl . '|{getstring}english{/getstring}' . PHP_EOL; - } - $replace['/\{menudev\}/i'] = $menu; - } - - // Tag: {menuthemes}. - // Description: Theme switcher for custom menu. Only for administrators. Not available after POST. - // Parameters: None. - // Allow Theme Changes on URL must be enabled for this to have any effect. - if (stripos($text, '{menuthemes}') !== false) { - $menu = ''; - if (is_siteadmin() && empty($_POST)) { // If a site administrator. - if (get_config('core', 'allowthemechangeonurl')) { - $url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") - . "://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; - $url .= (strpos($url, '?') ? '&' : '?'); - $themeslist = \core_component::get_plugin_list('theme'); - $menu = ''; - foreach ($themeslist as $theme => $themedir) { - $themename = ucfirst(get_string('pluginname', 'theme_' . $theme)); - $menu .= '-' . $themename . '|' . $url . 'theme=' . $theme . PHP_EOL; - } - - // If an administrator, add links to Advanced Theme Settings and to Current theme settings. - if (is_siteadmin() && !is_role_switched($PAGE->course->id)) { - $theme = $PAGE->theme->name; - $menu = 'Themes' . PHP_EOL . $menu; - if ($CFG->branch >= 404) { - $label = 'themesettingsadvanced'; - $section = 'themesettingsadvanced'; - } else { - $label = 'themesettings'; - $section = 'themesettings'; - } - - $menu .= '-###' . PHP_EOL; - $menu .= '-{getstring:admin}' . $label . '{/getstring}|/admin/settings.php' . - '?section=' . $section . '|Including custom menus, designer mode, theme in URL' . PHP_EOL; - if (file_exists($CFG->dirroot . '/theme/' . $theme . '/settings.php')) { - require_once($CFG->libdir . '/adminlib.php'); - if (admin_get_root()->locate('theme_' . $theme)) { - // Settings using categories interface URL. - $url = '/admin/category.php?category=theme_' . $theme . PHP_EOL; - } else { - // Settings using tabs interface URL. - $url = '/admin/settings.php?section=themesetting' . $theme . PHP_EOL; - } - $menu .= '-{getstring:admin}currenttheme{/getstring}|' . $url; - } - } - } - } - $replace['/\{menuthemes\}/i'] = $menu; - } - - // Tag: {menuwishlist}. - // Description: Displays a list of wishlisted courses for the Primary (Custom) menu with an option to add or remove - // the current course from the wishlist. The list will be sorted alphabetically. If there are no courses in the - // wishlist or we are on a site page, a message will be displayed. - // Parameters: None. - if (stripos($text, '{menuwishlist}') !== false) { - // If not logged in, or guest user, do not display the Wishlist. - if (!isloggedin() || isguestuser()) { - $menu = ''; - } else { - global $USER, $DB, $PAGE; - - // Get the user's wishlist from the user_preference table. - $wishlist = $DB->get_record('user_preferences', [ - 'userid' => $USER->id, - 'name' => 'filter_filtercodes_wishlist', - ]); - $wishlist = $wishlist ? explode(',', $wishlist->value) : []; - - // Generate the list of wishlisted courses. - $menu = ''; - foreach ($wishlist as $courseid) { - $course = $DB->get_record('course', ['id' => $courseid]); - if ($course) { - $courseurl = (new \moodle_url('/course/view.php', ['id' => $course->id]))->out(); - $menu .= '-' . format_string($course->fullname) . '|' . $courseurl . "\n"; - } - } - if (!empty($menu)) { - // Sort course names. - $menu = explode("\n", $menu); - $menu = array_filter($menu, 'strlen'); - usort($menu, 'strnatcasecmp'); - $menu = trim(implode("\n", $menu)); - } - - // Check if the current course is in the wishlist. - if ($PAGE->course->id === SITEID) { - if (empty($menu)) { - $menu = '-' . get_string('wishlist_nocourses', 'filter_filtercodes') . "\n"; - } - } else { - if (!empty($menu)) { - $menu .= "\n-###\n"; - } - $action = in_array($PAGE->course->id, $wishlist) ? 'remove' : 'add'; - $url = (new \moodle_url('/filter/filtercodes/wishlist.php', [ - 'courseid' => $PAGE->course->id, - 'action' => $action, - ]))->out(); - $menu .= '-' . get_string('wishlist_' . $action, 'filter_filtercodes') . '|' . $url . "\n"; - } - $menu = get_string('wishlist', 'filter_filtercodes') . "\n" . $menu; - } - - // Replace the {menuwishlist} tag with the generated wishlist output, if any. - $replace['/\{menuwishlist\}/i'] = $menu; - } - } - - // Check if any {course*} or %7Bcourse*%7D tags. Note: There is another course tags section further down. - $coursetagsexist = (stripos($text, '{course') !== false || stripos($text, '%7Bcourse') !== false); - if ($coursetagsexist) { - // Tag: {coursesummary} or {coursesummary courseid}. - // Description: Course summary as defined in the course settings. - // Optional parameters: Course id. Default is to use the current course, or site summary if not in a course. - if (stripos($text, '{coursesummary') !== false) { - if (stripos($text, '{coursesummary}') !== false) { - // No course ID specified. - $coursecontext = \context_course::instance($PAGE->course->id); - $PAGE->course->summary == null ? '' : $PAGE->course->summary; - $replace['/\{coursesummary\}/i'] = format_text( - $PAGE->course->summary, - FORMAT_HTML, - ['context' => $coursecontext] - ); - } - if (stripos($text, '{coursesummary ') !== false) { - // Course ID was specified. - preg_match_all('/\{coursesummary ([0-9]+)\}/', $text, $matches); - // Eliminate course IDs. - $courseids = array_unique($matches[1]); - $coursecontext = \context_course::instance($PAGE->course->id); - foreach ($courseids as $id) { - $course = $DB->get_record('course', ['id' => $id]); - if (!empty($course)) { - $course->summary == null ? '' : $course->summary; - $replace['/\{coursesummary ' . $course->id . '\}/isuU'] = format_text( - $course->summary, - FORMAT_HTML, - ['context' => $coursecontext] - ); - } - } - unset($matches, $course, $courseids, $id); - } - } - } - - // Tag: {formquickquestion} - // Tag: {formcheckin} - // Tag: {formcontactus} - // Tag: {formcourserequest} - // Tag: {formsupport} - // Tag: {formsesskey} - // - // Description: Tags used to generate pre-define forms for use with ContactForm plugin. - // Parameters: None. - if (stripos($text, '{form') !== false) { - $pre = '<form action="{wwwcontactform}" method="post" class="cf '; - $post = '</form>'; - $options = ['noclean' => true, 'para' => false, 'newlines' => false]; - // These require that you already be logged-in. - foreach (['formquickquestion', 'formcheckin'] as $form) { - if (stripos($text, '{' . $form . '}') !== false) { - if (isloggedin() && !isguestuser()) { - $formcode = get_string($form, 'filter_filtercodes'); - $replace['/\{' . $form . '\}/i'] = $pre . $form . '">' . get_string($form, 'filter_filtercodes') . $post; - } else { - $replace['/\{' . $form . '\}/i'] = ''; - } - } - } - // These work regardless of whether you are logged-in or not. - foreach (['formcontactus', 'formcourserequest', 'formsupport'] as $form) { - if (stripos($text, '{' . $form . '}') !== false) { - $formcode = get_string($form, 'filter_filtercodes'); - $replace['/\{' . $form . '\}/i'] = $pre . $form . '">' . $formcode . $post; - } else { - $replace['/\{' . $form . '\}/i'] = ''; - } - } - - // Tag: {formsesskey}. - if (stripos($text, '{formsesskey}') !== false) { - $replace['/\{formsesskey\}/i'] = '<input type="hidden" id="sesskey" name="sesskey" value="">'; - $replace['/\{formsesskey\}/i'] .= '<script>document.getElementById(\'sesskey\').value = M.cfg.sesskey;</script>'; - } - } - - // Tag: {global_[custom]}. - // Description: Global Custom tags as defined in plugin settings. - // Parameters: custom: Name of custom global tag from FilterCodes settings. - if (stripos($text, '{global_') !== false) { - // Get total number of defined global block tags. - $globaltagcount = get_config('filter_filtercodes', 'globaltagcount'); - for ($i = 1; $i <= $globaltagcount; $i++) { - // Get name of tag. - $tag = get_config('filter_filtercodes', 'globalname' . $i); - // If defined and tag exists in the content. - if (!empty($tag) && stripos($text, '{global_' . $tag . '}') !== false) { - // Replace the tag with new content. - $content = get_config('filter_filtercodes', 'globalcontent' . $i); - $replace['/\{global_' . $tag . '\}/i'] = $content; - } - } - unset($i); - unset($globaltagcount); - unset($tag); - unset($content); - } - - // Tag: {teamcards}. - // Description: Displays a series of card for each contact on the site. Configurable in FilterCodes settings. - // Note: Included selected roles in Site Administration > Appearance > Course > Course Contacts. - // Parameters: None. - if (stripos($text, '{teamcards}') !== false) { - global $OUTPUT, $DB; - - $sql = 'SELECT DISTINCT u.id, u.username, u.firstname, u.lastname, u.email, u.picture, u.imagealt, u.firstnamephonetic, - u.lastnamephonetic, u.middlename, u.alternatename, u.description, u.phone1 - FROM {course} c, {role_assignments} ra, {user} u, {context} ct - WHERE c.id = ct.instanceid AND ra.roleid in (?) AND ra.userid = u.id AND ct.id = ra.contextid - AND u.suspended = 0 AND u.deleted = 0 - ORDER BY u.lastname desc, u.firstname'; - $users = $DB->get_records_sql($sql, [$CFG->coursecontact]); - - $cards = ''; - if (count($users)) { - $clinktype = get_config('filter_filtercodes', 'teamcardslinktype'); - $cardformat = get_config('filter_filtercodes', 'teamcardsformat'); - $narrowpage = get_config('filter_filtercodes', 'narrowpage'); - - switch ($cardformat) { // Show as info icon. - case 'infoicon': - $info = get_string('info'); - $prewrap = '<a class="btn btn-link p-0 m-0 align-baseline" role="button" data-container="body"' - . ' data-toggle="popover" data-placement="right" data-content="<div class="no-overflow">' - . '<p>'; - $postwrap = '</p></div>" data-html="true" tabindex="0" data-trigger="focus"><i class="icon' - . ' fa fa-info-circle text-info fa-fw " title="' . $info . '" aria-label="' . $info . '"></i></a>'; - break; - case 'brief': // Show as text. - $prewrap = '<br><p class="smaller">'; - $postwrap = '</p>'; - break; - case 'verbose': // Show as text. - break; - default: // Don't show user description. - $cardformat = ''; - } - - // Prepare some strings. - $linksr = [ - '' => '', - 'email' => get_string('issueremail', 'badges'), - 'message' => get_string('message', 'message'), - 'profile' => get_string('profile'), - 'phone' => get_string('phone'), - ]; - if ($cardformat == 'verbose') { - if (empty($CFG->enablegravatar)) { - $blankavatarurl = $this->getblankavatarurl(150); - } - foreach ($users as $user) { - $cards .= '<div class="clearfix mb-4">'; - $name = '<h3 class="h4">' . get_string('fullnamedisplay', null, $user) . '</h3>'; - $cards .= $this->userlink($clinktype, $user, $name); - if (empty($user->picture) && empty($CFG->enablegravatar)) { - $cards .= '<img src="' . $blankavatarurl . '" class="img-fluid" width="150" height="150" alt="">'; - } else { - $cards .= $OUTPUT->user_picture($user, [ - 'size' => '150', - 'class' => 'img-fluid pull-left p-1 border mr-4', - 'link' => false, 'visibletoscreenreaders' => false, - ]); - } - $cards .= format_string($user->description); - $cards .= '</div><hr>'; - } - } else { - if (empty($CFG->enablegravatar)) { - $blankavatarurl = $this->getblankavatarurl(250); - } - $cards .= '<div class="row" id="fc_teamcards" style="width:99%;">'; - foreach ($users as $user) { - $cards .= '<div class="col-sm-6 col-md-4 col-lg-3 col-xl-' . (empty($narrowpage) ? 4 : 3) . ' mt-3">'; - if (empty($user->picture) && empty($CFG->enablegravatar)) { - $cards .= '<img src="' . $blankavatarurl . '" class="img-fluid" width="250" height="250" alt="">'; - } else { - $cards .= $OUTPUT->user_picture($user, [ - 'size' => '250', - 'class' => 'img-fluid', - 'link' => false, - 'visibletoscreenreaders' => false, - ]); - } - $name = '<br><h3 class="h5 font-weight-bold d-inline">' . get_string('fullnamedisplay', null, $user) . - '</h3>'; - $cards .= $this->userlink($clinktype, $user, $name); - if (!empty($user->description) && !empty($cardformat)) { - $cards .= $prewrap . format_string($user->description) . $postwrap; - } - $cards .= '</div>'; - } - $cards .= '</div>'; - } - } - $replace['/\{teamcards\}/i'] = $cards; - unset($cards, $users, $sql, $info, $prewrap, $postwrap, $cardformat); - } - - // Custom Course Fields - First implemented in Moodle 3.7. - if ($CFG->branch >= 37) { - // Tag: {course_field_shortname}. - // Description: Content from the custom course field specified by its shortname. - // Required Parameters: shortname of a custom course field. - if (stripos($text, '{course_field_') !== false) { - // Cached the custom course field data. - static $coursefields; - if (!isset($coursefields)) { - $handler = \core_course\customfield\course_handler::create(); - $coursefields = $handler->export_instance_data_object($PAGE->course->id, true); - $fieldsvisible = $handler->export_instance_data_object($PAGE->course->id); - // Blank out the fields that should not be displayed. - foreach ($coursefields as $field => $value) { - if (empty($fieldsvisible->$field)) { - $coursefields->$field = ''; - } - } - } - $coursecontext = \context_course::instance($PAGE->course->id); - foreach ($coursefields as $field => $value) { - $shortname = strtolower($field); - // If the tag exists and it is not hidden in the custom course field's settings. - if (stripos($text, '{course_field_' . $shortname . '}') !== false) { - $replace['/\{course_field_' . $shortname . '\}/i'] = format_text( - $value, - FORMAT_HTML, - ['context' => $coursecontext] - ); - } - } - } - - // Tag: {course_fields}. - // Description: All content from the custom user profile fields specified by shortname as set in the user's profile. - // Parameters: None. - if (stripos($text, '{course_fields}') !== false) { - // Display all custom course fields. - $customfields = ''; - if ($PAGE->course instanceof stdClass) { - $thiscourse = new \core_course_list_element($PAGE->course); - } - if ($thiscourse->has_custom_fields()) { - $handler = \core_course\customfield\course_handler::create(); - $customfields = $handler->display_custom_fields_data($thiscourse->get_custom_fields()); - } - $coursecontext = \context_course::instance($PAGE->course->id); - $replace['/\{course_fields\}/i'] = format_text($customfields, FORMAT_HTML, ['context' => $coursecontext]); - } - } - - if (stripos($text, '{dashboard_siteinfo}') !== false) { - if (is_siteadmin() && !is_role_switched($PAGE->course->id)) { // If an administrator. - $appbytes = @disk_free_space('.'); - $databytes = @disk_free_space($CFG->dataroot); - $disktxt = $this->humanbytes($databytes); - if ($appbytes != $databytes) { - $disktxt = 'app: ' . $disktxt . ' | data: ' . $this->humanbytes($databytes); - } - - $cards = []; - $cards[] = [ - 'icon' => 'fa-database', - 'label' => 'Available disk space', - 'info' => $disktxt, - ]; - $cards[] = [ - 'icon' => 'fa-graduation-cap', - 'label' => get_string('courses'), - 'info' => get_string('total') . ' {coursecount}', - ]; - $cards[] = [ - 'icon' => 'fa-users', - 'label' => get_string('users'), - 'info' => get_string('active') . ' {usersonline} | ' . get_string('total') . ' {usersactive}', - ]; - $totalcards = count($cards); - - $content = ' - <div class="fcdashboard-siteinfo container-fluid"> - <h3 class="sr-only">Site info dashboard</h2> - <div class="row"> - '; - for ($card = 0; $card < $totalcards; $card++) { - $content .= ' - <div class="col-12 col-md-6 col-lg-3"> - <div class="card-body"> - <i class="fa fa-3x ' . $cards[$card]['icon'] . ' float-left pr-3"></i> - <h3 class="h5 pt-1">' . $cards[$card]['label'] . '</h3> - <p>' . $cards[$card]['info'] . '</p> - </div> - </div> - '; - } - $content .= ' - </div> - </div> - '; - $coursecontext = \context_course::instance($PAGE->course->id); - $replace['/\{dashboard_siteinfo\}/i'] = format_text($content, FORMAT_HTML, ['context' => $coursecontext]); - } else { - $replace['/\{dashboard_siteinfo\}/i'] = ''; - } - } - - /* ---------------- Apply all of the filtercodes so far. ---------------*/ - - return $this->replacetags($text, $replace); - } - - /** - * Handle escaped tags. - * - * @param string $text Content to be processed. - * @return string Processed text. - * - * Note: First time this function is called, it will escape all tags that should not be processed. - * The second time it is called, it will turn escaped tags back into unprocessed plain text tags. - */ - private function escapedtags($text) { - static $escapedtags; - static $escapedtagsenc; - static $escapebraces; - - // Don't process if this feature is disabled. - if (!isset($escapebraces)) { - $escapebraces = !empty(get_config('filter_filtercodes', 'escapebraces')); - if (!$escapebraces) { - return $text; - } - } - - if (!isset($escapedtags) || !isset($escapedtagsenc)) { - // First time called, temporarily replace the escaped tags so they will not be processed by FilterCodes. - - // Regular tags. - $escapedtags = (strpos($text, '[{') !== false && strpos($text, '}]') !== false); - if ($escapedtags) { - $text = str_replace('[{', chr(2), $text); - $text = str_replace('}]', chr(3), $text); - } - - // Encoded tags. - $escapedtagsenc = (strpos($text, '[%7B') !== false && strpos($text, '%7D]') !== false); - if ($escapedtagsenc) { - $text = str_replace('[%7B', chr(4), $text); - $text = str_replace('%7D]', chr(5), $text); - } - } else { - // Second time called, complete the process of putting back the tags, but not escaped. - - // Regular tags. - if ($escapedtags) { - $text = str_replace(chr(2), '{', $text); - $text = str_replace(chr(3), '}', $text); - } - $escapedtags = null; - - // Encoded tags. - if ($escapedtagsenc) { - $text = str_replace(chr(4), '%7B', $text); - $text = str_replace(chr(5), '%7D', $text); - } - $escapedtagsenc = null; - } - - return $text; - } - - /** - * Applies all filters defined in $replace to the $text. - * - * @param string $text Content to be processed. Passed by reference. - * @param array $replace Array in the format Key=Regex, Value=To be applied. Passed by reference. - * @return boolean True of there are more changes, otherwise false. - */ - private function replacetags(&$text, &$replace) { - $newtext = null; - $moretags = true; - if (count($replace) > 0) { - $newtext = preg_replace(array_keys($replace), array_values($replace), $text); - if (!is_null($newtext)) { - $text = $newtext; - if (strpos($text, '{') === false && strpos($text, '%7B') === false) { - $moretags = false; - } - } - $replace = []; - } - return $moretags; - } - - /** - * Main filter function called by Moodle. - * - * @param string $text Content to be filtered. - * @param array $options Moodle filter options. None are implemented in this plugin. - * @return string Content with filters applied. - */ - public function filter($text, array $options = []) { - global $CFG, $SITE, $PAGE, $USER, $DB; - - if (strpos($text, '{') === false && strpos($text, '%7B') === false) { - return $text; - } - - // Declare some of the static variables. - static $profilefields; - static $profiledata; - static $mygroupslist; - static $mygroupingslist; - - $replace = []; // Array of key/value filterobjects. - - // Handle escaped tags to be ignored. Remove them so they don't get processed if the option to [{escape braces}] is enabled. - $text = $this->escapedtags($text); - - // START: Process tags that may end up containing other tags first. - - // ...===================================================================================================================. - // Tags that may create more content which could possibly include tags. These need to be processed first. - // ...===================================================================================================================. - - // Loop through the tags that may have embedded tags until these generator tags have all been proceseed. - - $loop = 0; // We only support tags nested up to 3 deep - to handle circular references. - do { - $moretags = $this->generatortags($text); - } while ($loop++ < 3 && $moretags); - - // We can now process all other tags including ones added by the code above. - - // ...===================================================================================================================. - // Tags that may be used as parameters by other tags should be processed before the tags that may include them. - // ...===================================================================================================================. - - // Tag: {lang}. - // Description: First 2-letters, in lowercase, of current language of user interface. - // Parameters: None. - if (stripos($text, '{lang}') !== false) { - // Replace with 2-letter current primary language. - $replace['/\{lang\}/i'] = substr(current_language(), 0, 2); - } - - // Tag: {preferredlanguage}. - // Description: First 2-letters, in lowercase, of the user's preferred language as set in their profile. - // Parameters: None. - if (stripos($text, '{preferredlanguage}') !== false) { - if (isloggedin() && !isguestuser()) { - // If user does not have a preferred language, default to the system default language. - $preflang = empty($USER->lang) ? $CFG->lang : $USER->lang; - if ($preflang == 'en') { - $langconfig = $CFG->dirroot . '/lang/en/langconfig.php'; - } else { - $langconfig = $CFG->dataroot . '/lang/' . $preflang . '/langconfig.php'; - } - // Ignore parents here for now. - $string = []; - include($langconfig); - if (!empty($string['thislanguage'])) { - $replace['/\{preferredlanguage\}/i'] = '<span lang="' . $preflang . '">' . $string['thislanguage'] . '</span>'; - } else { // This should never happen since the known user already exists. - $replace['/\{preferredlanguage\}/i'] = get_string('unknown', 'notes'); - } - } else { - $replace['/\{preferredlanguage\}/i'] = ''; - } - unset($preflang, $langconfig, $string); - } - - // Tag: %7Buserid%7D. - // Description: Alias for {userid}. Useful for encoded urls. - // Parameters: None. - if (stripos($text, '%7Buserid%7D') !== false) { - $text = str_replace('%7Buserid%7D', '{userid}', $text); - } - - // Tag: {userid}. - // Description: User's user ID. - // Parameters: None. - if (stripos($text, '{userid}') !== false) { - $replace['/\{userid\}/i'] = $USER->id; - } - - // Tags: {courseid... - if (stripos($text, '{course') !== false || stripos($text, '%7Bcourseid') !== false) { - $courseid = 1; // Default to site. - if ($PAGE->pagetype == 'enrol-index') { - // Make it work, even when we are on the enrolment page. - $courseid = optional_param('id', $courseid, PARAM_INT); - } else { - $courseid = $PAGE->course->id; - } - - // Tag: %7Bcourseid%7D. - // Description: An alias for {courseid}. Useful for encoded URLs. - // Parameters: None. - if (stripos($text, '%7Bcourseid%7D') !== false) { - $text = str_replace('%7Bcourseid%7D', '{courseid}', $text); - } - - // Tag: {courseid}. - // Description: Course ID. Will be 1 (SITE) if not in a course. - // Parameters: None. - if (stripos($text, '{courseid}') !== false) { - $replace['/\{courseid\}/i'] = $courseid; - } - - // Tag: {coursegradepercent}. - // Description: Current overall course grade as a percentage. - // Parameters: None. - if (version_compare(PHP_VERSION, '7.0.0') >= 0 && stripos($text, '{coursegradepercent}') !== false) { - require_once($CFG->libdir . '/gradelib.php'); - require_once($CFG->dirroot . '/grade/querylib.php'); - $gradeobj = grade_get_course_grade($USER->id, $PAGE->course->id); - if (!empty($grademax = floatval($gradeobj->item->grademax))) { - // Avoid divide by 0 error if no grades have been defined. - $grade = floatval($grademax) > 0 ? (int) ($gradeobj->grade / floatval($grademax) * 100) : 0; - } else { - $grade = 0; - } - $replace['/\{coursegradepercent\}/i'] = $grade; - } - - // Tag: {courseprogresspercent}. - // Description: Course completion progress percentage as a number. - // Parameters: None. - if (stripos($text, '{courseprogresspercent}') !== false) { - $progress = $this->completionprogress(); - if ($progress != -1) { // Is enabled. - $replace['/\{courseprogresspercent\}/i'] = $progress; - } else { - $replace['/\{courseprogresspercent\}/i'] = ''; - } - unset($progress); - } - - // Tag: %7Bcoursecontextid%7D. - // Description: Alias for {coursecontextid}. Useful for encoded URLs. - // Parameters: None. - if (stripos($text, '%7Bcoursecontextid%7D') !== false) { - $text = str_replace('%7Bcoursecontextid%7D', '{coursecontextid}', $text); - } - - // Tag: {coursecontextid}. - // Description: Course context id. - // Parameters: None. - if (stripos($text, '{coursecontextid}') !== false) { - $context = \context_course::instance($PAGE->course->id); - $coursecontextid = isset($PAGE->course->id) ? $context->id : 1; - $replace['/\{coursecontextid\}/i'] = $coursecontextid; - } - - // Tag: %7Bcoursemoduleid%7D. - // Description: Alias for {coursemoduleid}. Useful for encoded URLs. - // Parameters: None. - if (stripos($text, '%7Bcoursemoduleid%7D') !== false) { - $text = str_replace('7Bcoursemoduleid%7D', '{coursemoduleid}', $text); - } - - // Tag: {coursemoduleid}. - // Description: Course module id. - // Parameters: None. - // Note: %7Bcoursemoduleid%7D is an alias for {coursemoduleid}. Useful for encoded URLs. - if (stripos($text, '{coursemoduleid}') !== false) { - if (isset($PAGE->cm->id)) { - $replace['/\{coursemoduleid\}/isu'] = $PAGE->cm->id; - } - } - - // Tag: {courseshortname}. - // Description: The short name of this course. If not in a course, will use the site's shortname. - // Parameters: None. - if (stripos($text, '{courseshortname}') !== false) { - $course = $PAGE->course; - if ($course->id == $SITE->id) { // Front page - use site name. - $replace['/\{courseshortname\}/i'] = format_string($SITE->shortname); - } else { // In a course - use course full name. - $coursecontext = \context_course::instance($course->id); - $replace['/\{courseshortname\}/i'] = format_string($course->shortname, true, ['context' => $coursecontext]); - } - } - } - - // Tag: {categoryid}. - // Description: Category ID in which the current course is located. - // Parameters: None. - if (stripos($text, '{categoryid}') !== false) { - if (empty($PAGE->course->category)) { - // If we are not in a course, check if categoryid is part of URL (ex: course lists). - $catid = optional_param('categoryid', 0, PARAM_INT); - } else { - // Retrieve the category id of the course we are in. - $catid = $PAGE->course->category; - } - $replace['/\{categoryid\}/i'] = $catid; - } - - if (stripos($text, '{refer') !== false) { - // Tag: {referer}. - // Description: Alias for {referrer} tag. For backwards compatibility with original incorrect spelling of the tag. - // Parameters: None. - if (stripos($text, '{referer}') !== false) { - $text = str_replace('{referer}', '{referrer}', $text); - } - - // Tag: {referrer}. - // Description: URL that brought the user to the current page. - // Parameters: None. - if (stripos($text, '{referrer}') !== false) { - if ($CFG->branch >= 28) { - $replace['/\{referrer\}/i'] = get_local_referer(false); - } else { - $replace['/\{referrer\}/i'] = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''; - } - } - } - - // Tag: %7Bwwwroot%7D. - // Description: Alias for {wwwroot}. - // Parameters: None. - if (stripos($text, '%7Bwwwroot%7D') !== false) { - $text = str_replace('%7Bwwwroot%7D', '{wwwroot}', $text); - } - - // Tag: {wwwroot}. - // Description: URL of the site's webroot. - // Parameters: None. - if (stripos($text, '{wwwroot}') !== false) { - $replace['/\{wwwroot\}/i'] = $CFG->wwwroot; - } - - // Tag: {pagepath}. - // Description: Path of the current page without wwwroot. - // Parameters: None. - if (stripos($text, '{pagepath}') !== false) { - $url = (is_object($PAGE->url) ? $PAGE->url->out_as_local_url() : ''); - if (strpos($url, '?') === false && strpos($url, '#') === false) { - $url .= '?'; - } - $replace['/\{pagepath\}/i'] = $url; - } - - if (stripos($text, '{thisurl') !== false) { - $url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . - "://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; - - // Tag: {thisurl}. - // Description: Complete URL of the current page. - // Parameters: None. - if (stripos($text, '{thisurl}') !== false) { - $replace['/\{thisurl\}/i'] = $url; - } - // Tag: {thisurl_enc}. - // Description: Complete URL of the current page - URL encoded for use as a parameter of a URL. - // Parameters: None. - if (stripos($text, '{thisurl_enc}') !== false) { - $replace['/\{thisurl_enc\}/i'] = rawurlencode($url); - } - } - - // Tag: {protocol}. - // Description: Protocol used to access the website (http or https). - // Parameters: None. - if (stripos($text, '{protocol}') !== false) { - $replace['/\{protocol\}/i'] = 'http' . ($this->ishttps() ? 's' : ''); - } - - // Tag: {ipaddress}. - // Description: IP Address of the web client accessing the page. - // Parameters: None. - if (stripos($text, '{ipaddress}') !== false) { - $replace['/\{ipaddress\}/i'] = getremoteaddr(); - } - - // Tag: {sesskey}. - // Description: Moodle Session key. Does not work in forums. May be disabled in FilterCodes settings. - // Parameters: None. - if (get_config('filter_filtercodes', 'enable_sesskey')) { - if ((!isset($PAGE->cm->modname) || $PAGE->cm->modname != 'forum') && $PAGE->pagetype != 'admin-cron') { - if (stripos($text, '{sesskey}') !== false) { - // Tag: {sesskey}. - $replace['/\{sesskey\}/i'] = sesskey(); - } - // Tag: %7Bsesskey%7D (for encoded URLs). - if (stripos($text, '%7Bsesskey%7D') !== false) { - $replace['/%7Bsesskey%7D/i'] = sesskey(); - } - } - } - - // Tag: %7Bsectionid%7D. - // Description: Alias of {sectionid}. - // Parameters: None. - if (stripos($text, '%7Bsectionid%7D') !== false) { - $text = str_replace('%7Bsectionid%7D', '{sectionid}', $text); - } - - // Tag: {sectionid}. - // Description: The course section id in which the current activity is located. - // Parameters: None. - if (stripos($text, '{sectionid}') !== false) { - $replace['/\{sectionid\}/i'] = @$PAGE->cm->sectionnum; - } - - // Tag: {getstring:component_name}stringidentifier{/getstring} or {getstring}stringidentifier{/getstring}. - // Description: Retrieves Moodle string. - // Optional Parameter: Component name. If component_name (plugin) is not specified, will default to "moodle". - // Required Content: The string identifier. - if (stripos($text, '{/getstring}') !== false) { - // Replace {getstring:} tag and parameters with retrieved content. - $newtext = preg_replace_callback( - '/\{getstring:?(\w*)\}(\w+)\{\/getstring\}/isuU', - function ($matches) use ($CFG) { - if ($strexists = get_string_manager()->string_exists($matches[2], $matches[1]) && $CFG->branch >= 28) { - $strexists = !get_string_manager()->string_deprecated($matches[2], $matches[1]); - } - if ($strexists) { - return get_string($matches[2], $matches[1]); - } else { - return "{getstring" . (!empty($matches[1]) ? ":$matches[1]" : '') . "}$matches[2]{/getstring}"; - } - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Tag: {courseunenrolurl}. - // Description: URL to unenrol from a course. - // Parameters: None. - if (stripos($text, '{courseunenrolurl}') !== false) { - require_once($CFG->libdir . '/enrollib.php'); - $course = $PAGE->course; - $coursecontext = \context_course::instance($course->id); - $replace['/\{courseunenrolurl\}/i'] = ''; - if ($course->id != SITEID && isloggedin() && !isguestuser() && is_enrolled($coursecontext)) { - $plugins = enrol_get_plugins(true); - $instances = enrol_get_instances($course->id, true); - foreach ($instances as $instance) { - if (!isset($plugins[$instance->enrol])) { - continue; - } - $plugin = $plugins[$instance->enrol]; - if ($unenrollink = $plugin->get_unenrolself_link($instance)) { - $replace['/\{courseunenrolurl\}/i'] = $unenrollink->out(); - break; - } - } - } - } - - // Tag: {fa fa-icon-name} - // Description: FontAwesome 4.7 and 6.0 icons. - // Required Parameter: 'fa' can be fa|fas|fa-solid|fab|fa-brands. Additional options available if using FontAwesome Pro. - // Required Parameter: icon-name. See full list at https://fontawesome.com/v4/icons/ or https://fontawesome.com/v6/icons/. - // Note that FontAwesome 6.x icons are only included with Moodle 4.2+. - if (stripos($text, '{fa') !== false) { - // Replace {fa...} tag and parameters with FontAwesome HTML. - $regex = '/\{fa('; - $regex .= 's|-solid|'; // Solid - included with Moodle. - $regex .= 'b|-brands|'; // Brands - included with Moodle. - // The rest require the FontAwesome Pro. - $regex .= 'r|-regular|'; - $regex .= 'l|-light|'; - $regex .= 't|-thin|'; - $regex .= 'd|-duotone|'; - $regex .= 'ss|-sharp\s+fa-solid|'; - $regex .= 'sr|-sharp\s+fa-regular|'; - $regex .= 'sl|-sharp\s+fa-light|'; - $regex .= 'st|-sharp\s+fa-thin|'; - $regex .= 'sd|-sharp\s+fa-duotone'; - $regex .= '){0,1}\s+fa-([a-z0-9 -]+)\}/isuU'; - $newtext = preg_replace_callback( - $regex, - function ($matches) { - $matches[0] = $matches[0] == null ? '' : $matches[0]; - return '<span class="' . substr($matches[0], 1, -1) . '" aria-hidden="true"></span>'; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Tag: {glyphicon glyphion-name}. - // Description: Glyphicon icons. - // Required Parameter: name. - // Note: Glyphicons Font/CSS must be loaded as part of your theme. - if (stripos($text, '{glyphicon ') !== false) { - // Replace {glyphicon glyphicon-...} tag and parameters with Glyphicons HTML. - $newtext = preg_replace_callback( - '/\{glyphicon\sglyphicon-([a-z0-9 -]+)\}/isuU', - function ($matches) { - $matches[0] = $matches[0] == null ? '' : $matches[0]; - return '<span class="' . substr($matches[0], 1, -1) . '" aria-hidden="true"></span>'; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Tag: {multilang xx}...{/multilang}. - // Description: Works just like the Moodle's Multi-Language filter except it's a plain text tag. No more HTML editing! - // Required Parameter: xx: The language. - // Requires content between tags. - // Note: This tag has a dependency on Moodle's Multi-Language Content filter being enabled. That filter - // must be below FilterCodes in Site Administration > Plugins > Manage Filters. This does not do any filtering of its own. - // For more information on the Multi-Language Content filter see https://docs.moodle.org/en/Multi-language_content_filter. - if (stripos($text, '{/multilang}') !== false) { - // This is specifically to make it easier to use Moodle's own multi-language filter. - $replace['/\{multilang\s+([a-z-]+)\}(.*)\{\/multilang\}/isuU'] = '<span lang="$1" class="multilang">$2</span>'; - } - - // Tag: {firstaccessdate} or {firstaccessdate dateTimeFormat}. - // Description: Date that the user first accessed the site. - // Optional parameters: dateTimeFormat - either one of Moodle's built-in data/time formats or php's strftime. - if (stripos($text, '{firstaccessdate') !== false) { - if (isloggedin() && !isguestuser() && !empty($USER->firstaccess)) { - // Replace {firstaccessdate} tag with formatted date. - if (stripos($text, '{firstaccessdate}') !== false) { - $replace['/\{firstaccessdate\}/i'] = userdate($USER->firstaccess, get_string('strftimedatefullshort')); - } - // Replace {firstaccessdate dateTimeFormat} tag and parameters with formatted date. - if (stripos($text, '{firstaccessdate ') !== false) { - $newtext = preg_replace_callback( - '/\{firstaccessdate\s+(.+)\}/isuU', - function ($matches) use ($USER) { - // Check if this is a built-in Moodle date/time format. - if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { - // It is! Get the strftime string. - $matches[1] = get_string($matches[1], 'langconfig'); - } - return userdate($USER->firstaccess, $matches[1]); - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - } else { - $replace['/\{firstaccessdate(.*)\}/i'] = get_string('never'); - } - } - - // Tag: {lastlogin} or {lastlogin dateTimeFormat}. - // Description: Date that the user last logged in to the site. - // Optional parameters: dateTimeFormat - either one of Moodle's built-in data/time formats or php's strftime. - if (stripos($text, '{lastlogin') !== false) { - if (isloggedin() && !isguestuser() && !empty($USER->lastlogin)) { - // Replace {lastlogin} tag with formatted date. - if (stripos($text, '{lastlogin}') !== false) { - $replace['/\{lastlogin\}/i'] = userdate($USER->lastlogin, get_string('strftimedatetimeshort')); - } - // Replace {lastlogin dateTimeFormat} tag and parameters with formatted date. - if (stripos($text, '{lastlogin ') !== false) { - $newtext = preg_replace_callback( - '/\{lastlogin\s+(.+)\}/isuU', - function ($matches) use ($USER) { - // Check if this is a built-in Moodle date/time format. - if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { - // It is! Get the strftime string. - $matches[1] = get_string($matches[1], 'langconfig'); - } - return userdate($USER->lastlogin, $matches[1]); - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - } else { - $replace['/\{lastlogin(.*)\}/i'] = get_string('never'); - } - } - - // Tag: {coursestartdate} or {coursestartdate dateTimeFormat courseid}. - // Description: The course start date. - // Optional Parameters: dateTimeFormat - either in a Moodle datetime format or a PHP strftime format. - // Optional Parameters: id - id of a course. - if (stripos($text, '{coursestartdate') !== false) { - // Replace {coursestartdate} tag with formatted date. - if (stripos($text, '{coursestartdate}') !== false) { - if (!empty($PAGE->course->startdate)) { - $startdate = $PAGE->course->startdate; - } else { - $startdate = $DB->get_field_select('course', 'startdate', 'id = :id', ['id' => $PAGE->course->id]); - } - if (!empty($startdate)) { - $replace['/\{coursestartdate\}/i'] = userdate($startdate, get_string('strftimedatefullshort')); - } else { - $replace['/\{coursestartdate(.*)\}/isuU'] = get_string('notyetstarted', 'completion'); - } - } - - // Replace {coursestartdate dateTimeFormat} tag and parameters with formatted date. - if (stripos($text, '{coursestartdate ') !== false) { - $newtext = preg_replace_callback( - '/\{coursestartdate\s(.*)(\s\d+)?\}/isuU', - function ($matches) use ($PAGE, $DB) { - - // Optional date/time format. - if (is_numeric($matches[1])) { - // Only the course ID was specified. - $matches[2] = trim($matches[1]); // Course ID. - $matches[1] = ''; // Date/time format. - } else { - $matches[2] = empty($matches[2]) ? $PAGE->course->id : trim($matches[2]); // Course ID. - $matches[1] = trim($matches[1]); - } - - // Optional course ID. - if (empty($matches[2])) { // No course ID, use current course. - if (!empty($PAGE->course->startdate)) { - $startdate = $PAGE->course->startdate; - } else { - $startdate = $DB->get_field_select( - 'course', - 'startdate', - 'id = :id', - ['id' => $PAGE->course->id] - ); - } - } else { // Course ID was specifed. - $course = $DB->get_record('course', ['id' => $matches[2]]); - if (!empty($course)) { - $startdate = $course->startdate; - if (!empty($course->startdate)) { - $startdate = $course->startdate; - } else { - $startdate = $DB->get_field_select( - 'course', - 'startdate', - 'id = :id', - ['id' => $course->id] - ); - } - } else { - // Should only happen if course does not exist. - $startdate = 1; // December 31, 1969. - } - } - - // Check if this is a built-in Moodle date/time format. - if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { - // It is! Get the strftime string. - $matches[1] = get_string($matches[1], 'langconfig'); - } - - // Format the date. - if (!empty($startdate)) { - $startdate = userdate($startdate, $matches[1]); - } else { - $startdate = get_string('notyetstarted', 'completion'); - } - - return $startdate; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } else { - $replace['/\{coursestartdate(.*)\}/isuU'] = get_string('notyetstarted', 'completion'); - } - } - - // Tag: {courseenddate} or {coursesenddate dateTimeFormat courseid}. - // Description: The course end date. - // Optional Parameters: dateTimeFormat - either in a Moodle datetime format or a PHP strftime format. - // Optional Parameters: id - id of a course. - if (stripos($text, '{courseenddate') !== false) { - // Replace {courseenddate} tag with formatted date. - if (stripos($text, '{courseenddate}') !== false) { - if (empty($PAGE->course->enddate)) { - $enddate = $PAGE->course->enddate; - } else { - $enddate = $DB->get_field_select('course', 'enddate', 'id = :id', ['id' => $PAGE->course->id]); - } - if (!empty($enddate)) { - $replace['/\{courseenddate\}/i'] = userdate($enddate, get_string('strftimedatefullshort')); - } else { - $replace['/\{courseenddate(.*)\}/isuU'] = get_string('none'); - } - } - - // Replace {courseenddate dateTimeFormat} tag and parameters with formatted date. - if (stripos($text, '{courseenddate ') !== false) { - $newtext = preg_replace_callback( - '/\{courseenddate\s(.*)(\s\d+)?\}/isuU', - function ($matches) use ($PAGE, $DB) { - - // Optional date/time format. - if (is_numeric($matches[1])) { - // Only the course ID was specified. - $matches[2] = trim($matches[1]); // Course ID. - $matches[1] = ''; // Date/time format. - } else { - $matches[2] = empty($matches[2]) ? $PAGE->course->id : trim($matches[2]); // Course ID. - $matches[1] = trim($matches[1]); - } - - // Optional course ID. - if (empty($matches[2])) { // No course ID, use current course. - if (!empty($PAGE->course->enddate)) { - $enddate = $PAGE->course->enddate; - } else { - $enddate = $DB->get_field_select( - 'course', - 'enddate', - 'id = :id', - ['id' => $PAGE->course->id] - ); - } - } else { // Course ID was specifed. - $course = $DB->get_record('course', ['id' => $matches[2]]); - if (!empty($course)) { - $enddate = $course->enddate; - if (!empty($course->enddate)) { - $enddate = $course->enddate; - } else { - $enddate = $DB->get_field_select( - 'course', - 'enddate', - 'id = :id', - ['id' => $course->id] - ); - } - } else { - // Should only happen if course does not exist. - $enddate = 1; // December 31, 1969. - } - } - - // Check if this is a built-in Moodle date/time format. - if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { - // It is! Get the strftime string. - $matches[1] = get_string($matches[1], 'langconfig'); - } - - // Format the date. - if (!empty($enddate)) { - $enddate = userdate($enddate, $matches[1]); - } else { - $enddate = get_string('none'); - } - - return $enddate; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } else { - $replace['/\{courseenddate(.*)\}/isuU'] = get_string('none'); - } - } - - // Tag: {coursecompletiondate} or {coursecompletiondate dateTimeFormat}. - // Description: The course completion date. - // Optional Parameters: dateTimeFormat - either in a Moodle datetime format or a PHP strftime format. - if (stripos($text, '{coursecompletiondate') !== false) { - if ( - $PAGE->course - && isset($CFG->enablecompletion) - && $CFG->enablecompletion == 1 // COMPLETION_ENABLED. - && $PAGE->course->enablecompletion - ) { - $ccompletion = new completion_completion(['userid' => $USER->id, 'course' => $PAGE->course->id]); - $incomplete = get_string('notcompleted', 'completion'); - } else { // Completion not enabled. - $incomplete = get_string('completionnotenabled', 'completion'); - } - if (!empty($ccompletion->timecompleted)) { - // Replace {coursecompletiondate} tag with formatted date. - if (stripos($text, '{coursecompletiondate}') !== false) { - $replace['/\{coursecompletiondate\}/i'] = userdate( - $ccompletion->timecompleted, - get_string('strftimedatefullshort') - ); - } - // Replace {coursecompletiondate dateTimeFormat} tag and parameters with formatted date. - if (stripos($text, '{coursecompletiondate ') !== false) { - $newtext = preg_replace_callback( - '/\{coursecompletiondate\s+(.+)\}/isuU', - function ($matches) use ($ccompletion) { - // Check if this is a built-in Moodle date/time format. - if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { - // It is! Get the strftime string. - $matches[1] = get_string($matches[1], 'langconfig'); - } - return userdate($ccompletion->timecompleted, $matches[1]); - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - } else { - $replace['/\{coursecompletiondate(.*)\}/isuU'] = $incomplete; - } - } - - // Tag: {courseenrolmentdate} or {courseenrolmentdate dateTimeFormat}. - // Description: The course enrolment date. - // Optional Parameters: dateTimeFormat - either in a Moodle datetime format or a PHP strftime format. - if (stripos($text, '{courseenrolmentdate') !== false) { - $sql = ' - SELECT ue.timecreated - FROM {user} u - JOIN {user_enrolments} ue ON ue.userid = u.id - JOIN {enrol} e ON ue.enrolid = e.id - WHERE ue.userid = :userid AND e.courseid = :courseid - '; - $thisuser = $DB->get_records_sql($sql, ['userid' => $USER->id, 'courseid' => $PAGE->course->id]); - if (count($thisuser)) { - // Gets the first key of the array. - reset($thisuser); - $datecreated = key($thisuser); - // Replace {courseenrolmentdate} tag with formatted date. - if (stripos($text, '{courseenrolmentdate}') !== false) { - $replace['/\{courseenrolmentdate\}/i'] = userdate($datecreated, get_string('strftimedatefullshort')); - } - // Replace {courseenrolmentdate dateTimeFormat} tag and parameters with formatted date. - if (stripos($text, '{courseenrolmentdate ') !== false) { - $newtext = preg_replace_callback( - '/\{courseenrolmentdate\s+(.+)\}/isuU', - function ($matches) use ($datecreated) { - // Check if this is a built-in Moodle date/time format. - if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { - // It is! Get the strftime string. - $matches[1] = get_string($matches[1], 'langconfig'); - } - return userdate($datecreated, $matches[1]); - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - } else { - $replace['/\{courseenrolmentdate(.*)\}/isuU'] = ''; - } - } - - // Tag: {now} or {now dateTimeFormat}. - // Description: Current year, 4 digits. - // Optional parameter: dateTimeFormat - either one of Moodle's built-in data/time formats or php's strftime. - if (stripos($text, '{now') !== false) { - // Replace {now} tag with formatted date. - $now = time(); - if (stripos($text, '{now}') !== false) { - $replace['/\{now\}/i'] = userdate($now, get_string('strftimedatefullshort')); - } - // Replace {now dateTimeFormat} tag and parameters with formatted date. - if (stripos($text, '{now ') !== false) { - $newtext = preg_replace_callback( - '/\{now\s+(.+)\}/isuU', - function ($matches) use ($now) { - // Check if this is a built-in Moodle date/time format. - if (!empty($matches[1]) && get_string_manager()->string_exists($matches[1], 'langconfig')) { - // It is! Get the strftime string. - $matches[1] = get_string($matches[1], 'langconfig'); - } - return userdate($now, $matches[1]); - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - unset($now); - } - - // Tag: {keyboard}text{/keyboard}. - // Description: Wraps the text inside a set of HTML <keyb> tags. - // Parameters: Any text. - if (stripos($text, '{/keyboard}') !== false) { - $replace['/\{keyboard\}(.*)\{\/keyboard\}/isuU'] = '<kbd>$1</kbd>'; - } - - /* ---------------- Apply all of the filtercodes so far. ---------------*/ - - if ($this->replacetags($text, $replace) == false) { - // No more tags? Put back the escaped tags, if any, and return the string. - $text = $this->escapedtags($text); - return $text; - } - - // ...===================================================================================================================. - // The rest of the tags below. Put tags above if they generate more tags or will be used as parameters for other tags. - // ...===================================================================================================================. - - // Simple tags that don't ever have parameters. - - // Substitutions. - - $u = $USER; - if (!isloggedin() || isguestuser()) { - $u->firstname = get_string('defaultfirstname', 'filter_filtercodes'); - $u->lastname = get_string('defaultsurname', 'filter_filtercodes'); - } - $u->fullname = trim(get_string('fullnamedisplay', null, $u)); - - // Tag: {firstname}. - // Description: User's first name as set in their profile. - // Parameters: None. - if (stripos($text, '{firstname}') !== false) { - $replace['/\{firstname\}/i'] = $u->firstname; - } - - // Tag: {surname}. - // Description: Alias for {lastname}. - // Parameters: None. - if (stripos($text, '{surname}') !== false) { - $text = str_replace('{surname}', '{lastname}', $text); - } - - // Tag: {lastname}. - // Description: User's last name as set in their profile. - // Parameters: None. - if (stripos($text, '{lastname}') !== false) { - $replace['/\{lastname\}/i'] = $u->lastname; - } - - // Tag: {fullname}. - // Description: User's full name as set in their profile. - // Parameters: None. - if (stripos($text, '{fullname}') !== false) { - $replace['/\{fullname\}/i'] = $u->fullname; - } - - // Tag: {alternatename}. - // Description: User's alternate name as set in their profile. - // Parameters: None. - if (stripos($text, '{alternatename}') !== false) { - // If alternate name is empty, use firstname instead. - if (isloggedin() && !isguestuser() && (!is_null($USER->alternatename) && !empty(trim($USER->alternatename)))) { - $replace['/\{alternatename\}/i'] = $USER->alternatename; - } else { - $replace['/\{alternatename\}/i'] = $u->firstname; - } - } - - // Tag: {email}. - // Description: User's email address as set in their profile. - // Parameters: None. - if (stripos($text, '{email}') !== false) { - $replace['/\{email\}/i'] = isloggedin() && !isguestuser() ? $USER->email : ''; - } - - // Tag: {city}. - // Description: User's city as set in their profile. - // Parameters: None. - if (stripos($text, '{city}') !== false) { - $replace['/\{city\}/i'] = isloggedin() && !isguestuser() ? $USER->city : ''; - } - - // Tag: {country}. - // Description: User's country as set in their profile. - // Parameters: None. - if (stripos($text, '{country}') !== false) { - if (isloggedin() && !isguestuser() && !empty($USER->country)) { - $replace['/\{country\}/i'] = get_string($USER->country, 'countries'); - } else { - $replace['/\{country\}/i'] = ''; - } - } - // Tag: {timezone}. - // Description: User's time zone as set in their profile. - // Parameters: None. - if (stripos($text, '{timezone}') !== false) { - if (isloggedin() && !isguestuser() && !empty($USER->timezone)) { - if ($USER->timezone == '99') { // Default is system timezone. - $replace['/\{timezone\}/i'] = \core_date::get_default_php_timezone(); - } else { - $replace['/\{timezone\}/i'] = \core_date::get_localised_timezone($USER->timezone); - } - } - } - - // Tag: {institution}. - // Description: User's institution as set in their profile. - // Parameters: None. - if (stripos($text, '{institution}') !== false) { - $replace['/\{institution\}/i'] = isloggedin() && !isguestuser() ? $USER->institution : ''; - } - - // Tag: {department}. - // Description: User's department as set in their profile. - // Parameters: None. - if (stripos($text, '{department}') !== false) { - $replace['/\{department\}/i'] = isloggedin() && !isguestuser() ? $USER->department : ''; - } - - // Tag: {idnumber}. - // Description: idnumber as specified in the user's profile. - // Parameters: None. - if (stripos($text, '{idnumber}') !== false) { - $replace['/\{idnumber\}/i'] = isloggedin() && !isguestuser() ? $USER->idnumber : ''; - } - - // Tag: {webpage} - // Description: Social field in user's profile. This migrates from pre-Moodle 3.11 - for backwards compatibility. - // Parameters: None. - if (stripos($text, '{webpage}') !== false) { - if ($CFG->branch >= 311) { - $text = str_replace('{webpage}', '{profile_field_webpage}', $text); - } else { - $replace['/\{webpage\}/i'] = isloggedin() && !isguestuser() ? $USER->url : ''; - } - } - - // Tag: {diskfreespace}. - // Description: Free space of Moodle application volume. - // Parameters: None. - if (stripos($text, '{diskfreespace}') !== false) { - $bytes = @disk_free_space('.'); - $replace['/\{diskfreespace\}/i'] = $this->humanbytes($bytes); - } - - // Tag: {diskfreespacedata}. - // Description: Free space of Moodle data volume. - // Parameters: None. - if (stripos($text, '{diskfreespacedata}') !== false) { - $bytes = @disk_free_space($CFG->dataroot); - $replace['/\{diskfreespacedata\}/i'] = $this->humanbytes($bytes); - } - - // Tags starting with: {support...}. - if (stripos($text, '{support') !== false) { - // Tag: {supportname}. - // Description: Support name for the site from Moodle settings. - // None. - if (stripos($text, '{supportname}') !== false) { - if (empty($CFG->supportname)) { - $replace['/\{supportname\}/i'] = get_string('notavailable', 'filter_filtercodes'); - } else { - $replace['/\{supportname\}/i'] = $CFG->supportname; - } - } - - // Tag: {supportemail}. - // Description: Support email address for the site from Moodle settings. - // None. - if (stripos($text, '{supportemail}') !== false) { - if (empty($CFG->supportname)) { - $replace['/\{supportemail\}/i'] = get_string('notavailable', 'filter_filtercodes'); - } else { - $replace['/\{supportemail\}/i'] = $CFG->supportemail; - } - } - - // Tag: {supportpage}. - // Description: URL of Support for the site from Moodle settings. - // None. - if (stripos($text, '{supportpage}') !== false) { - if (empty($CFG->supportname)) { - $replace['/\{supportpage\}/i'] = ''; - } else { - $replace['/\{supportpage\}/i'] = $CFG->supportpage; - } - } - - // Tag: {supportservicespage}. - // Description: URL of support services page from Moodle settings. - // None. - if ($CFG->branch >= 402 && stripos($text, '{supportservicespage}') !== false) { - $replace['/\{supportservicespage\}/i'] = $CFG->servicespage; - } - } - - if (stripos($text, '{site') !== false) { - // Tag: {sitename}. - // Description: The full name of the site name. - // Parameters: None. - if (stripos($text, '{sitename') !== false) { - $sitecontext = \context_system::instance(); - $replace['/\{sitename\}/i'] = format_string($SITE->fullname, true, ['context' => $sitecontext]); - } - - // Tag: {sitesummary}. - // Description: Site summary as defined in the Front Page/Site Home Settings. - // Parameters: None. - if (stripos($text, '{sitesummary}') !== false) { - $replace['/\{sitesummary\}/i'] = $SITE->fullname; - } - - // Tag: {siteyear}. - // Description: Current year, 4 digits. - // Parameters: None. - if (stripos($text, '{siteyear}') !== false) { - $replace['/\{siteyear\}/i'] = date('Y'); - } - // Tag: {sitelogourl} or %7Bsitelogourl%7D. - // Description: URL of site logo. - // Parameters: None. - if (stripos($text, '{sitelogourl}') !== false) { - global $OUTPUT; - $replace['/\{sitelogourl\}/i'] = '' . $OUTPUT->get_logo_url(); - } - if (stripos($text, '%7Bsitelogourl%7D') !== false) { - global $OUTPUT; - $replace['/\%7Bsitelogourl\%7D/i'] = '' . $OUTPUT->get_logo_url(); - } - } - - /* ---------------- Apply all of the filtercodes so far. ---------------*/ - - if ($this->replacetags($text, $replace) == false) { - // No more tags? Put back the escaped tags, if any, and return the string. - $text = $this->escapedtags($text); - return $text; - } - - if (stripos($text, '{profile') !== false) { - // Tag: {profile_field_shortname}. - // Description: Contents of the custom user profile field. Will apply formating to datetime and checkbox type fields. - // Required Parameters: shortname of a custom profile field. - if (stripos($text, '{profile_field') !== false) { - $isuser = (isloggedin() && !isguestuser()); - // Cached the defined custom profile fields and data. - if (!isset($profilefields)) { - $profilefields = $DB->get_records('user_info_field', null, '', 'id, datatype, shortname, visible, param3'); - if ($isuser && !empty($profilefields)) { - $profiledata = $DB->get_records_menu('user_info_data', ['userid' => $USER->id], '', 'fieldid, data'); - } - } - $showhidden = get_config('filter_filtercodes', 'showhiddenprofilefields'); - foreach ($profilefields as $field) { - // If the tag exists and is not set to "Not visible" in the custom profile field's settings. - if ( - $isuser - && stripos($text, '{profile_field_' . $field->shortname . '}') !== false - && ($field->visible != '0' || !empty($showhidden)) - ) { - $data = isset($profiledata[$field->id]) ? trim($profiledata[$field->id]) : '' . PHP_EOL; - switch ($field->datatype) { // Format data for some field types. - case 'datetime': - // Include date and time or just date? - $datetimeformat = !empty($field->param3) ? 'strftimedaydatetime' : 'strftimedate'; - $data = empty($data) ? '' : userdate($data, get_string($datetimeformat, 'langconfig')); - break; - case 'checkbox': - // 1 = Yes, 0 = No - $data = empty($data) ? get_string('no') : get_string('yes'); - break; - } - $replace['/\{profile_field_' . $field->shortname . '\}/i'] = $data; - } else { - $replace['/\{profile_field_' . $field->shortname . '\}/i'] = ''; - } - } - } - - // Tag: {profilefullname}. - // Description: Full name of current user. - // Parameters: None. - if (stripos($text, '{profilefullname}') !== false) { - $fullname = ''; - if (isloggedin() && !isguestuser()) { - $fullname = get_string('fullnamedisplay', null, $USER); - if ($PAGE->pagelayout == 'mypublic' && $PAGE->pagetype == 'user-profile') { - $userid = optional_param('userid', optional_param( - 'user', - optional_param('id', $USER->id, PARAM_INT), - PARAM_INT - ), PARAM_INT); - if ($user = $DB->get_record('user', ['id' => $userid, 'deleted' => 0])) { - $fullname = get_string('fullnamedisplay', null, $user); - } - } - } - $replace['/\{profilefullname\}/i'] = $fullname; - unset($fullname); - } - } - - // Tag: {scrape url="" <optional parameters>}. - // Description: Scrapes content from an external HTML page. Cannot scrape secure pages from sites that requires login. - // Optional parameters: You may use any combination of the following: tag="..." class="..." id="..." code="...". - if (get_config('filter_filtercodes', 'enable_scrape') && stripos($text, '{scrape ') !== false) { - // Replace {scrape} tag and its attributes with retrieved content. - $newtext = preg_replace_callback( - '/\{scrape\s+(.*)\}/isuU', - function ($matches) { - // Parse the scrape tag's atributes. - $matches[0] = $matches[0] == null ? '' : strip_tags($matches[0]); - $attribs = substr($matches[0], 1, -1); - $scrape = $this->attribstoarray($attribs); - $url = isset($scrape['url']) ? $scrape['url'] : ''; - $tag = isset($scrape['tag']) ? $scrape['tag'] : ''; - $class = isset($scrape['class']) ? $scrape['class'] : ''; - $id = isset($scrape['id']) ? $scrape['id'] : ''; - $code = isset($scrape['code']) ? $scrape['code'] : ''; - // If nothing else, we must have a URL parameter. - if (empty($url)) { - return "SCRAPE error: Missing or invalid required URL parameter."; - } - // Replace {scrape} tag and its attributes with retrieved content. - return $this->scrapehtml($url, $tag, $class, $id, $code); - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Any {user*} tags. - if (stripos($text, '{user') !== false || stripos($text, '%7Buser') !== false) { - // Tag: {username}. - // Description: User's username as defined in their profile. When not logged in, uses predefined name in language file. - // Parameters: None. - if (stripos($text, '{username}') !== false) { - $replace['/\{username\}/i'] = isloggedin() - && !isguestuser() ? $USER->username : get_string('defaultusername', 'filter_filtercodes'); - } - - // These tags: {userpictureurl} and {userpictureimg}. - if (stripos($text, '{userpicture') !== false) { - // Tag: {userpictureurl size}. - // Description: URL of user's picture as set in their profile. - // Parameters: Sizes: sm|md|lg or an integer 2|1|3 or an integer size in pixels > 3. - if (stripos($text, '{userpictureurl ') !== false) { - $newtext = preg_replace_callback( - '/\{userpictureurl\s+(\w+)\}/isuU', - function ($matches) use ($USER) { - return $this->getprofilepictureurl($USER, $matches[1]); - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Tag: {userpictureimg size}. - // Description: URL of user's picture as set in their profile, wrapped in an HTML img tag. - // Parameters: Sizes: sm|md|lg or an integer 2|1|3 or an integer size in pixels > 3. - if (stripos($text, '{userpictureimg ') !== false) { - $newtext = preg_replace_callback( - '/\{userpictureimg\s+(\w+)\}/isuU', - function ($matches) use ($USER) { - $url = $this->getprofilepictureurl($USER, $matches[1]); - $tag = '<img src="' . $url . '" alt="' . $USER->fullname . '" class="userpicture">'; - return $tag; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - } - - // Tag: {userdescription}. - // Description: Description as set in user's profile. - // Parameters: None. - if (stripos($text, '{userdescription}') !== false) { - if (isloggedin() && !isguestuser()) { - $user = $DB->get_record('user', ['id' => $USER->id], 'description', MUST_EXIST); - $replace['/\{userdescription\}/i'] = format_text($user->description, $USER->descriptionformat); - unset($user); - } else { - $replace['/\{userdescription\}/i'] = ''; - } - } - - // Tag: {usercount}. - // Description: A count of the total number of users on the site. Includes suspended and unconfirmed users. - // Parameters: None. - if (stripos($text, '{usercount}') !== false) { - // Count total number of current users on the site. - // Exclude deleted users, admin and guest. - $cnt = $DB->count_records('user', ['deleted' => 0]) - 2; - $replace['/\{usercount\}/i'] = $cnt; - } - - // Tag: {usersactive}. - // Description: A count of the total number of active users on the site. - // Parameters: None. - if (stripos($text, '{usersactive}') !== false) { - // Count total number of current users on the site. - // Exclude deleted, suspended and unconfirmed users, admin and guest. - $cnt = $DB->count_records('user', ['deleted' => 0, 'suspended' => 0, 'confirmed' => 1]) - 2; - $replace['/\{usersactive\}/i'] = $cnt; - } - - // Tag: {usersonline}. - // Description: A count of the total number of users currently online on the site within the last 5 minutes. - // Parameters: None. - if (stripos($text, '{usersonline}') !== false) { - $timetosee = 300; // Within last number of seconds (300 = 5 minutes). - if (isset($CFG->block_online_users_timetosee)) { - $timetosee = $CFG->block_online_users_timetosee * 60; - } - $now = time(); - - // Calculate if we are in separate groups. - $isseparategroups = ($PAGE->course->groupmode == SEPARATEGROUPS - && $PAGE->course->groupmodeforce - && !has_capability('moodle/site:accessallgroups', $PAGE->context)); - - // Get the user current group. - $thisgroup = $isseparategroups ? groups_get_course_group($PAGE->course) : null; - - $onlineusers = new fetcher( - $thisgroup, - $now, - $timetosee, - $PAGE->context, - $PAGE->context->contextlevel, - $PAGE->course->id - ); - - // Count online users. - $usersonline = $onlineusers->count_users(); - $replace['/\{usersonline\}/i'] = $usersonline; - } - - // Tag: {userscountrycount}. - // Description: A count of the total number countries that users are in as set in their user profile. - // Parameters: None. - if (stripos($text, '{userscountrycount}') !== false) { - $count = $DB->count_records_sql('SELECT COUNT(DISTINCT country) FROM {user} WHERE id > 2'); - $replace['/\{userscountrycount\}/i'] = $count; - } - } - - // Check if any {course*} or %7Bcourse*%7D tags. Note: There is another course tags section further up. - $coursetagsexist = (stripos($text, '{course') !== false || stripos($text, '%7Bcourse') !== false); - if ($coursetagsexist) { - // Tag: {coursecontacts}. - // Description: Get list of course contacts based on settings in Site Administration > Appearances > Courses. - // Parameters: None. - if (stripos($text, '{coursecontacts}') !== false) { - $contacts = ''; - // If course (not site pages) with contacts. - if ($PAGE->course->id) { - $course = new \core_course_list_element($PAGE->course); - if ($course->has_course_contacts()) { - // Get tag settings. - $cshowpic = get_config('filter_filtercodes', 'coursecontactshowpic'); - $cshowdesc = get_config('filter_filtercodes', 'coursecontactshowdesc'); - $clinktype = get_config('filter_filtercodes', 'coursecontactlinktype'); - - // Prepare some strings. - $linksr = ['' => '', - 'email' => get_string('issueremail', 'badges'), - 'message' => get_string('message', 'message'), - 'profile' => get_string('profile'), - 'phone' => get_string('phone'), - ]; - $iconclass = ['' => '', - 'email' => 'fa fa-envelope-o', - 'message' => 'fa fa-comment-o', - 'profile' => 'fa fa-user-o', - 'phone' => 'fa fa-mobile', - ]; - - $cnt = 0; - foreach ($course->get_course_contacts() as $coursecontact) { - $icon = '<i class="' . $iconclass[$clinktype] . '" aria-hidden="true"></i> '; - - $contacts .= '<li>'; - - // Get list of course contacts based on settings in Site Administration > Appearances > Courses. - // Get list of user's roles in the course. - $rolenames = array_map(function ($role) { - return $role->displayname; - }, $coursecontact['roles']); - - // Retrieve contact's profile information. - $user = $DB->get_record( - 'user', - ['id' => $coursecontact['user']->id], - $fields = '*', - $strictness = IGNORE_MULTIPLE - ); - $fullname = get_string('fullnamedisplay', null, $user); - if ($cshowpic) { - $imgurl = $this->getprofilepictureurl($user, 3); - $contacts .= '<img src="' . $imgurl . '" alt="' . $fullname - . '" class="img-fluid img-thumbnail' . (!empty($cnt) ? ' mt-4' : '') . '">'; - $cnt++; - } - - $contactsclose = '<span class="sr-only">' . $linksr[$clinktype] . ': </span>'; - $contactsclose .= $fullname . '</a>'; - - $contacts .= '<span class="fc-coursecontactroles">' . implode(", ", $rolenames) . ': </span>'; - - switch ($clinktype) { - case 'email': - $contacts .= $icon . '<a href="mailto:' . $user->email . '">'; - $contacts .= $contactsclose; - break; - case 'message': - $contacts .= $icon . '<a href="' . (new \moodle_url( - '/message/index.php', - ['id' => $coursecontact['user']->id] - ))->out() . '">'; - $contacts .= $contactsclose; - break; - case 'profile': - $contacts .= $icon . '<a href="' . (new \moodle_url( - '/user/profile.php', - ['id' => $coursecontact['user']->id, 'course' => $PAGE->course->id] - ))->out() . '">'; - $contacts .= $contactsclose; - break; - case 'phone1' && !empty($user->phone1): - $contacts .= $icon . '<a href="tel:' . $user->phone1 . '">'; - $contacts .= $contactsclose; - break; - default: // Default is no-link. - $contacts .= $fullname; - break; - } - if ($cshowdesc && !empty($user->description)) { - $contacts .= '<div' . (empty($cshowpic) ? ' class="mb-4"' : '') . '>' . - $user->description . '</div>'; - } - $contacts .= '</li>'; - } - } - } - - if (empty($contacts)) { - $replace['/\{coursecontacts\}/i'] = get_string('nocontacts', 'message'); - } else { - $replace['/\{coursecontacts\}/i'] = '<ul class="fc-coursecontacts list-unstyled ml-0 pl-0">' . - $contacts . '</ul>'; - } - unset($contacts, $contactsclose, $fullname, $url, $user, $rolenames, $icon, $iconclass); - unset($linksr, $clinktype, $cshowpic); - } - - // Tag: {courseparticipantcount}. - // Description: Get a the number of participants in the course. This includes anyone registered in the course. - // Parameters: None. - if (stripos($text, '{courseparticipantcount}') !== false) { - static $courseparticipantcount; - require_once($CFG->dirroot . '/user/lib.php'); - if (!isset($courseparticipants)) { - $sql = "SELECT COUNT(1) - FROM {user_enrolments} ue - JOIN {enrol} e ON e.id = ue.enrolid - WHERE e.courseid = :courseid"; - $params = ['courseid' => $PAGE->course->id]; - $courseparticipantcount = $DB->count_records_sql($sql, $params); - } - $replace['/\{courseparticipantcount\}/i'] = $courseparticipantcount; - } - - // Tag: {coursecount students|students:active}. - // Requires one of two parameters: - // Optional Parameters: "students" - Filter limiting to just users with the role of student; or - // Optional Parameters: "students:active" - Filter limiting to student who have not been suspended. - // Description: Get just the number of "students" in the course. - if (stripos($text, '{coursecount students}') !== false) { - if ($CFG->branch >= 32) { - $coursecontext = \context_course::instance($PAGE->course->id); - $role = $DB->get_record('role', ['shortname' => 'student']); - $students = get_role_users($role->id, $coursecontext); - $cnt = count($students); - unset($students); - } else { - $cnt = ''; - } - $replace['/\{coursecount students\}/i'] = $cnt; - } - if (stripos($text, '{coursecount students:active}') !== false) { - $sql = "SELECT COUNT(DISTINCT ue.userid) - FROM {user_enrolments} ue - JOIN {enrol} e ON e.id = ue.enrolid - JOIN {course} c ON c.id = e.courseid - JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = 50 - JOIN {role_assignments} ra ON ra.contextid = ctx.id AND ra.userid = ue.userid - JOIN {role} r ON r.id = ra.roleid AND r.shortname = 'student' - WHERE ue.status = 0 AND e.courseid = :courseid"; - $cnt = $DB->count_records_sql($sql, ['courseid' => $PAGE->course->id]); - $replace['/\{coursecount students:active\}/i'] = $cnt; - } - - // Tag: {coursecount}. - // Description: The total number of courses. - // Parameters: None. - // Note that there are parametered vesions above giving it a completely different purpose. - if (stripos($text, '{coursecount}') !== false) { - // Count courses excluding front page. - $cnt = $DB->count_records('course', []) - 1; - $replace['/\{coursecount\}/i'] = $cnt; - } - - // Tag: {courseidnumber}. - // Description: The course idnumber as set in the course settings. - // Parameters: None. - if (stripos($text, '{courseidnumber}') !== false) { - $replace['/\{courseidnumber\}/i'] = $PAGE->course->idnumber; - } - - // Tag: {coursename}. - // Description: The full name of a course, or the site name if not in a course. - // Parameters: None. - if (stripos($text, '{coursename') !== false) { - if (stripos($text, '{coursename}') !== false) { - // No course ID was specified. - $course = $PAGE->course; - if ($course->id == $SITE->id) { // If not in a course, use the site name. - $coursecontext = \context_system::instance(); - $replace['/\{coursename\}/i'] = format_string( - $SITE->fullname, - true, - ['context' => $coursecontext] - ); - } else { // If in a course - use course full name. - $coursecontext = \context_course::instance($course->id); - $replace['/\{coursename\}/i'] = format_string( - $course->fullname, - true, - ['context' => $coursecontext] - ); - } - } - if (stripos($text, '{coursename ') !== false) { - // Course ID was specified. - preg_match_all('/\{coursename ([0-9]+)\}/', $text, $matches); - // Eliminate course IDs. - $courseids = array_unique($matches[1]); - $coursecontext = \context_system::instance(); - foreach ($courseids as $id) { - $course = $DB->get_record('course', ['id' => $id]); - if (!empty($course)) { - $replace['/\{coursename ' . $course->id . '\}/isuU'] = format_string( - $course->fullname, - true, - ['context' => $coursecontext] - ); - } - } - unset($matches, $course, $courseids, $id); - } - } - - if (stripos($text, '{courseimage') !== false) { - $course = $PAGE->course; - if ($CFG->branch >= 33) { - $imgurl = \core_course\external\course_summary_exporter::get_course_image($course); - } else { // Previous to Moodle 3.3. - $imgurl = ''; - $context = \context_course::instance($course->id); - if ($course instanceof stdClass) { - $course = new \core_course_list_element($course); - } - $coursefiles = $course->get_course_overviewfiles(); - foreach ($coursefiles as $file) { - if ($isimage = $file->is_valid_image()) { - $filename = '/' . $file->get_contextid() . '/' . $file->get_component() - . '/' . $file->get_filearea() . $file->get_filepath() . $file->get_filename(); - $imgurl = file_encode_url("/pluginfile.php", $filename, !$isimage); - break; - } - } - } - if (empty($imgurl)) { - global $OUTPUT; - $imgurl = $OUTPUT->get_generated_image_for_id($course->id); - } - - // Tag: {courseimage}. - // Description: Course image as rendeable HTML img tag. - // Parameters: None. - if (stripos($text, '{courseimage}') !== false) { - $replace['/\{courseimage\}/i'] = '<img src="' . $imgurl . '" class="img-responsive">'; - } - - // Tag: {courseimage-url}. - // Description: Course image URL. - // Parameters: none. - if (stripos($text, '{courseimage-url}') !== false) { - $replace['/\{courseimage-url\}/i'] = $imgurl; - } - } - - // Tag: {coursecount}. - // Description: The total number of courses. - // Parameters: None. - if (stripos($text, '{coursecount}') !== false) { - // Count courses excluding front page. - $cnt = $DB->count_records('course', []) - 1; - $replace['/\{coursecount\}/i'] = $cnt; - } - - // Tag: {coursesactive}. - // Description: The total number of active visible courses: visibility set to Show, started, not ended. - // Parameters: None. - if (stripos($text, '{coursesactive}') !== false) { - // Count current courses (between start and end date, if any) set to Show - excluding front page. - $today = time(); - $sql = "SELECT COUNT(id) - FROM {course} - WHERE visible = 1 - AND startdate <= :today - AND (enddate > :today2 OR enddate = 0);"; - // Subtract one for site course, where id = 1. - $cnt = $DB->count_records_sql($sql, ['today' => $today, 'today2' => $today]) - 1; - $replace['/\{coursesactive\}/i'] = $cnt; - } - - // Tag: {coursegrade}. - // Description: Overall grade in a courses, with percentage symbol. - // Parameters: None. - if (version_compare(PHP_VERSION, '7.0.0') >= 0 && stripos($text, '{coursegrade}') !== false) { - require_once($CFG->libdir . '/gradelib.php'); - require_once($CFG->dirroot . '/grade/querylib.php'); - $gradeobj = grade_get_course_grade($USER->id, $PAGE->course->id); - $grade = 0; - if (!empty($grademax = floatval($gradeobj->item->grademax))) { - // Avoid divide by 0 error if no grades have been defined. - $grade = floatval($grademax) > 0 ? (int) ($gradeobj->grade / floatval($grademax) * 100) : 0; - } - $replace['/\{coursegrade\}/i'] = get_string('percents', '', $grade); - } - - if (stripos($text, '{courseprogress') !== false) { - $progress = $this->completionprogress(); - - // Tag: {courseprogress}. - // Description: Course completion progress percentage as formatted text. - // Parameters: None. - if (stripos($text, '{courseprogress}') !== false) { - if ($progress != -1) { // Is enabled. - $replace['/\{courseprogress\}/i'] = '<span class="sr-only">' - . get_string('aria:courseprogress', 'block_myoverview') . '</span> ' - . get_string('completepercent', 'block_myoverview', $progress); - } else { - $replace['/\{courseprogress\}/i'] = ''; - } - } - - // Tag: {courseprogressbar}. - // Description: Course completion progress bar. - // Parameters: None. - if (stripos($text, '{courseprogressbar}') !== false) { - if ($progress != -1) { // Is enabled. - $replace['/\{courseprogressbar\}/i'] = ' - <div class="progress"> - <div class="progress-bar bar" role="progressbar" aria-valuenow="' . $progress - . '" style="width: ' . $progress . '%" aria-valuemin="0" aria-valuemax="100"> - </div> - </div>'; - } else { - $replace['/\{courseprogressbar\}/i'] = ''; - } - } - unset($progress); - } - - // Tag: {coursecards} and {coursecards <categoryid>}. - // Description: Courses in a category branch as cards - // Optional Parameters: The category ID number. - if (stripos($text, '{coursecards') !== false) { - global $OUTPUT; - - $chelper = new coursecat_helper(); - $chelper->set_show_courses(20)->set_courses_display_options([ - 'recursive' => true, - 'limit' => $CFG->frontpagecourselimit, - 'viewmoreurl' => (new \moodle_url('/course/index.php'))->out(), - 'viewmoretext' => new lang_string('fulllistofcourses'), - ]); - - $chelper->set_attributes(['class' => 'frontpage-course-list-all']); - // Find all coursecards tags where category ID was specified. - preg_match_all('/\{coursecards ([0-9]+)\}/', $text, $matches); - // Check if tag with no category. - $nocat = (stripos($text, '{coursecards}') !== false); - if ($nocat) { - $matches[1][] = 0; - } - // Eliminate duplicate categories. - $categories = array_unique($matches[1]); - - $card = $this->getcoursecardinfo(); - - foreach ($categories as $catid) { - try { - $coursecat = \core_course_category::get($catid); - // Get list of courses in this category. - $courses = $coursecat->get_courses($chelper->get_courses_display_options()); - } catch (Exception $e) { - // Course category not found or not accessible. - // No courses available. - $courses = []; - } - - $rcourseids = array_keys($courses); - if (count($rcourseids) > 0) { - $content = $this->rendercoursecards($rcourseids, $card->format); - } else { - $content = ''; - } - if ($catid == 0 && $nocat) { - $replace['/\{coursecards\}/i'] = !empty($content) ? $card->header . $content . $card->footer : ''; - } - $replace['/\{coursecards ' . $catid . '\}/isuU'] = - !empty($content) ? $card->header . $content . $card->footer : ''; - } - } - - // Tag: {coursecard courseid}. - // Description: Display a course card for the specified course id. - // Optional Parameters: a courseid number. If not specified, will use the current course's id or the site id (1). - if (stripos($text, '{coursecard ') !== false) { - $re = '/\{coursecard\s([\s\d]+)\}/isuU'; - $found = preg_match_all($re, $text, $matches); - $matches = array_combine(array_values($matches[0]), array_values($matches[1])); - $card = $this->getcoursecardinfo(); - foreach ($matches as $key => $match) { - $courseids = explode(' ', $match); - - // Only keep valid course ids. - $courseids = array_map('trim', $courseids); // Remove extra spaces. - $courseids = array_filter($courseids); // Remove empty elements. - $courseids = array_unique($courseids); // Remove duplicates. - foreach ($courseids as $key => $courseid) { - $course = $DB->get_record('course', ['id' => $courseid]); - if ($course === false) { - // Course not found. Remove it from the list. - unset($courseids[$key]); - } - } - // Create cards for existing courses that are visible to user. - $content = $this->rendercoursecards($courseids, $card->format); - $replace['/\{coursecard ' . $match . '\}/isuU'] = - !empty($content) ? $card->header . $content . $card->footer : ''; - } - } - - // Tag: {coursecardsbyenrol}. - // Description: Display list of 10 most popular courses by enrolment count (tested with MySQL and PostgreSQL). - // Parameters: None. - if (stripos($text, '{coursecardsbyenrol}') !== false) { - $sql = "SELECT c.id, c.fullname, COUNT(*) AS enrolments - FROM {course} c - JOIN (SELECT DISTINCT e.courseid, ue.id AS userid - FROM {user_enrolments} ue - JOIN {enrol} e ON e.id = ue.enrolid) ue ON ue.courseid = c.id - GROUP BY c.id, c.fullname - ORDER BY 3 DESC, c.fullname"; - $courses = $DB->get_records_sql($sql, [], 0, get_config('filter_filtercodes', 'coursecardsbyenrol')); - $rcourseids = array_keys($courses); - if (count($rcourseids) > 0) { - $card = $this->getcoursecardinfo(); - $content = $this->rendercoursecards($rcourseids, $card->format); - } else { - $card = new stdClass(); - $card->header = ''; - $card->footer = ''; - $content = ''; - } - $replace['/\{coursecardsbyenrol\}/i'] = !empty($content) ? $card->header . $content . $card->footer : ''; - } - - // Tag: {courserequest}. - // Description: Link to Request a Course form. - // Parameters: None. - if (stripos($text, '{courserequest}') !== false) { - // Add request a course link. - $context = \context_system::instance(); - if (!empty($CFG->enablecourserequests) && has_capability('moodle/course:request', $context)) { - $link = '<a href="' . (new \moodle_url('/course/request.php'))->out() . '">' . get_string('requestcourse') . '</a>'; - } else { - $link = ''; - } - $replace['/\{courserequest\}/i'] = $link; - } - - if (stripos($text, '{courserequestmenu') !== false) { - // Add request a course link. - $context = \context_system::instance(); - if (!empty($CFG->enablecourserequests) && has_capability('moodle/course:request', $context)) { - // Tag: {courserequestmenu0}. - // Description: Link to Request a Course form formatted for use as a top level custom menu item. - // Parameters: None. - if (stripos($text, '{courserequestmenu0}') !== false) { - // Top level menu. - $link = get_string('requestcourse') . '|' . (new \moodle_url('/course/request.php'))->out(); - $replace['/\{courserequestmenu0\}/i'] = $link; - } - - // Tag: {courserequestmenu}. - // Description: Link to Request a Course form formatted for use as a second level custom menu item. - // Parameters: None. - if (stripos($text, '{courserequestmenu}') !== false) { - // Not top level menu. - $link = '-###' . PHP_EOL; - $link .= '-' . get_string('requestcourse') . '|' . (new \moodle_url('/course/request.php'))->out(); - $replace['/\{courserequestmenu\}/i'] = $link; - } - } else { - $replace['/\{courserequestmenu\}/i'] = ''; - } - } - } - - // These tags: {mycourses} and {mycoursesmenu} and {mycoursescards}. - if (stripos($text, '{mycourse') !== false || stripos($text, '{myccourse') !== false) { - if (isloggedin() && !isguestuser()) { - // Retrieve list of user's enrolled courses. - $sortorder = 'visible DESC'; - // Prevent undefined $CFG->navsortmycoursessort errors. - if (empty($CFG->navsortmycoursessort)) { - $CFG->navsortmycoursessort = 'sortorder'; - } - // Append the chosen sortorder. - $sortorder = $sortorder . ',' . $CFG->navsortmycoursessort . ' ASC'; - $mycourses = enrol_get_my_courses('fullname,id', $sortorder); - $myccourses = []; - - // Save and remove completed courses from the list. - if ( - isset($CFG->enablecompletion) && $CFG->enablecompletion == 1 // COMPLETION_ENABLED. - && get_config('filter_filtercodes', 'hidecompletedcourses') - ) { - foreach ($mycourses as $key => $mycourse) { - $ccompletion = new completion_completion(['userid' => $USER->id, 'course' => $mycourse->id]); - if (!empty($ccompletion->timecompleted)) { - // Save course to list of completed courses. - $myccourses[] = $mycourses[$key]; - // Remove completed course from the list. - unset($mycourses[$key]); - } - } - } - - // Messages to display if not enrolled in any courses or have not yet completed some courses. - // Start by assuming that we are not enrolled in any courses. - $emptylist = get_string(($CFG->branch >= 29 ? 'notenrolled' : 'nocourses'), 'grades'); - $emptycclist = $emptylist; - if (!empty($mycourses)) { // Enrolled in some courses. - $emptylist = ''; - } - if (empty($myccourses)) { // Not completed any courses. - $emptycclist = get_string('nocompletedcourses', 'filter_filtercodes'); - } - - // Tag: {mycourses}. - // Description: An unordered list of links to enrolled courses. - // Parameters: None. - if (stripos($text, '{mycourses}') !== false) { - $list = ''; - foreach ($mycourses as $mycourse) { - $list .= '<li><a href="' . (new \moodle_url('/course/view.php', ['id' => $mycourse->id]))->out() . '">' . - $mycourse->fullname . '</a></li>'; - } - $replace['/\{mycourses\}/i'] = '<ul>' . (empty($list) ? "<li>$emptylist</li>" : $list) . '</ul>'; - unset($list); - } - - // Tag: {myccourses}. - // Description: An unordered list of links to completed courses. - // Parameters: None. - if (stripos($text, '{myccourses}') !== false) { - $list = ''; - foreach ($myccourses as $myccourse) { - $list .= '<li><a href="' . (new \moodle_url('/course/view.php', ['id' => $myccourse->id]))->out() . '">' . - $myccourse->fullname . '</a></li>'; - } - $replace['/\{myccourses\}/i'] = '<ul>' . (empty($list) ? "<li>$emptycclist</li>" : $list) . '</ul>'; - unset($list); - } - - // Tag: {mycoursesmenu}. - // Description: A custom menu list of enrolled course names with links. - // Parameters: None. - if (stripos($text, '{mycoursesmenu}') !== false) { - $list = ''; - foreach ($mycourses as $mycourse) { - $list .= '-' . $mycourse->fullname . '|' . - (new \moodle_url('/course/view.php', ['id' => $mycourse->id]))->out() . PHP_EOL; - } - $replace['/\{mycoursesmenu\}/i'] = '-' . (empty($list) ? $emptylist : $list); - unset($list); - } - - // Tag: {mycoursescards}. - // Description: Generates a course card for each enrolled course. - // Parameters: None. - if (stripos($text, '{mycoursescards}') !== false) { - $list = ''; - $courseids = []; - foreach ($mycourses as $mycourse) { - $courseids[] = $mycourse->id; - } - // If enrolled in at least one course, generate cards. - if (!empty($courseids)) { - $card = $this->getcoursecardinfo(); - $list = $card->header . $this->rendercoursecards($courseids, $card->format) . $card->footer; - } - $replace['/\{mycoursescards\}/i'] = (empty($list) ? $emptylist : $list); - unset($list); - } - - // Tag: {mycoursescards <categoryid(s)>}. - // Description: Generates a course card for each enrolled course in the specified category. - // Optional Parameters: One or more category ids separated by a space. - if (stripos($text, '{mycoursescards ') !== false) { - // Get the card format. - $card = $this->getcoursecardinfo(); - // Find all of the mycoursescards tags where category ID was specified. - preg_match_all('/{mycoursescards ([^}]*)}/', $text, $matches); - // For each tag. - foreach ($matches[0] as $key => $tag) { - $catids = array_map('intval', array_filter(explode(' ', $matches[1][$key]), 'is_numeric')); - // For each category in each tag. - $content = ''; - foreach ($catids as $catid) { - // Get all the enrolled courses in the specified category for the user. - $courses = $DB->get_records_sql( - "SELECT c.* - FROM {course} c - JOIN {enrol} e ON e.courseid = c.id - JOIN {user_enrolments} ue ON ue.enrolid = e.id - WHERE ue.userid = ? AND c.category = ? - ORDER BY c.shortname", - [$USER->id, $catid] - ); - // Make an array of the course ids and render the course cards. - $courseids = array_column($courses, 'id'); - $content .= $this->rendercoursecards($courseids, $card->format); - } - if (!empty($content)) { - $replace['/' . $tag . '/isuU'] = $card->header . $content . $card->footer; - } - } - unset($card); - unset($matches); - unset($catids); - unset($catid); - unset($content); - unset($courses); - unset($courseids); - } - } else { // Not logged in. - // Replace tags with message indicating that you need to be logged in. - $replace['/\{mycourses\}/i'] = '<ul class="mycourseslist"><li>' . get_string('loggedinnot') . '</li></ul>'; - $replace['/\{myccourses\}/i'] = '<ul class="mycourseslist"><li>' . get_string('loggedinnot') . '</li></ul>'; - $replace['/\{mycoursesmenu\}/i'] = '-' . get_string('loggedinnot') . PHP_EOL; - $replace['/\{mycoursescards[^}]*\}/i'] = '<p>' . get_string('loggedinnot') . '</p>'; - } - } - - // Tag: {editingtoggle}. - // Description: Is "off" if in edit page mode. Otherwise "on". Useful for creating Turn Editing On/Off links. - // Parameters: None. - if (stripos($text, '{editingtoggle}') !== false) { - $replace['/\{editingtoggle\}/i'] = ($PAGE->user_is_editing() ? 'off' : 'on'); - } - - // Tag: {toggleeditingmenu}. - // Description: Creates menu link to toggle editing on and off. - // Parameters: None. - if (stripos($text, '{toggleeditingmenu}') !== false) { - $editmode = ($PAGE->user_is_editing() ? 'off' : 'on'); - $edittext = get_string('turnediting' . $editmode); - if ($PAGE->bodyid == 'page-site-index' && $PAGE->pagetype == 'site-index') { // Front page. - $replace['/\{toggleeditingmenu\}/i'] = $edittext . '|' . (new \moodle_url( - '/course/view.php', - ['id' => $PAGE->course->id, 'sesskey' => sesskey(), 'edit' => $editmode] - ))->out(); - } else { // All other pages. - $replace['/\{toggleeditingmenu\}/i'] = $edittext . '|' . (new \moodle_url( - $PAGE->url, - ['edit' => $editmode, 'adminedit' => $editmode, 'sesskey' => sesskey()] - ))->out() . PHP_EOL; - } - } - - // Tags starting with: {categor...}. - if (stripos($text, '{categor') !== false) { - if (empty($PAGE->course->category)) { - // If we are not in a course, check if categoryid is part of URL (ex: course lists). - $catid = optional_param('categoryid', 0, PARAM_INT); - } else { - // Retrieve the category id of the course we are in. - $catid = $PAGE->course->category; - } - - if (!empty($catid)) { - $category = $DB->get_record('course_categories', ['id' => $catid]); - } - - // Tag: {categoryname}. - // Description: Name of category in which the current course is located. - // Parameters: None. - if (stripos($text, '{categoryname}') !== false) { - if (!empty($catid)) { - // If category is not 0, get category name. - $replace['/\{categoryname\}/i'] = $category->name; - } else { - // Otherwise, category has no name. - $replace['/\{categoryname\}/i'] = ''; - } - } - - // Tag: {categorynumber}. - // Description: categorynumber of the category in which the current course is located, as set in the category settings. - // Parameters: None. - if (stripos($text, '{categorynumber}') !== false) { - if (!empty($catid)) { - // If category is not 0, get category number. - $replace['/\{categorynumber\}/i'] = $category->idnumber; - } else { - // Otherwise, category has no number. - $replace['/\{categorynumber\}/i'] = ''; - } - } - - // Tag: {categorydescription}. - // Description: Description of the category in which the current course is located, as set in the category settings. - // Parameters: None. - if (stripos($text, '{categorydescription}') !== false) { - if (!empty($catid)) { - // If category is not 0, get category description. - $catcontext = \context_coursecat::instance($category->id); - // Resolve embedded URLs that might be in the description. - $description = file_rewrite_pluginfile_urls( - $category->description, - 'pluginfile.php', - $catcontext->id, - 'coursecat', - 'description', - 0 - ); - $replace['/\{categorydescription\}/i'] = $description; - } else { - // Otherwise, category has no description. - $replace['/\{categorydescription\}/i'] = ''; - } - } - - // Tag: {categories}. - // Description: An unordered list of links to categories. - // Parameters: None. - if (stripos($text, '{categories}') !== false) { - // Retrieve list of all categories. - if ($CFG->branch >= 36) { // Moodle 3.6+. - $categories = \core_course_category::make_categories_list(); - } else { - require_once($CFG->libdir . '/coursecatlib.php'); - $categories = coursecat::make_categories_list(); - } - $list = ''; - foreach ($categories as $id => $name) { - $list .= '<li><a href="' . - (new \moodle_url('/course/index.php', ['categoryid' => $id]))->out() . '">' . $name . '</a></li>'; - } - $list = !empty($list) ? '<ul class="categorylist">' . $list . '</ul>' : ''; - $replace['/\{categories\}/i'] = $list; - unset($tag); - unset($list); - } - - // Tag: {categoriesmenu}. - // Description: A list of categories with links - for use in the custom menu as a submenu. - // Parameters: None. - if (stripos($text, '{categoriesmenu}') !== false) { - // Retrieve list of all categories. - if ($CFG->branch >= 36) { // Moodle 3.6+. - $categories = \core_course_category::make_categories_list(); - } else { - require_once($CFG->libdir . '/coursecatlib.php'); - $categories = coursecat::make_categories_list(); - } - $list = ''; - foreach ($categories as $id => $name) { - $list .= '-' . $name . '|/course/index.php?categoryid=' . $id . PHP_EOL; - } - $replace['/\{categoriesmenu\}/i'] = $list; - unset($tag); - unset($list); - } - - // Tag: {categories0}. - // Description: An unordered list of links to top level categories. - // Parameters: None. - if (stripos($text, '{categories0}') !== false) { - // Display hidden categories if visibility user is siteadmin or role has moodle/category:viewhiddencategories. - $context = \context_system::instance(); - $isadmin = (is_siteadmin() && !is_role_switched($PAGE->course->id)); - $viewhidden = has_capability('moodle/category:viewhiddencategories', $context, $USER, $isadmin); - - // Categories not visible will be still visible to site admins or users with viewhiddencourses capability. - $sql = 'SELECT cc.id, cc.sortorder, cc.name, cc.visible, cc.parent - FROM {course_categories} cc - WHERE cc.parent = 0' . (!$viewhidden ? ' AND cc.visible = 1' : '') . ' - ORDER BY cc.sortorder'; - $list = ''; - $categories = $DB->get_recordset_sql($sql, ['contextcoursecat' => CONTEXT_COURSECAT]); - foreach ($categories as $category) { - if (!$category->visible && !$viewhidden) { - // Skip if the category is not visible to the user. - continue; - } - $dimmed = $category->visible ? '' : ' class="dimmed"'; - $list .= '<li' . $dimmed . '><a href="' . (new \moodle_url( - '/course/index.php', - ['categoryid' => $category->id]))->out() - . '">' . $category->name . '</a></li>' . PHP_EOL; - } - $list = !empty($list) ? '<ul>' . $list . '</ul>' : ''; - $categories->close(); - $replace['/\{categories0\}/i'] = $list; - unset($list); - } - - // Tag: {categories0menu}. - // Description: A list of top level categories with links - for use in the custom menu as a top level menu. - // Parameters: None. - if (stripos($text, '{categories0menu}') !== false) { - // Display hidden categories if visibility user is siteadmin or role has moodle/category:viewhiddencategories. - $context = \context_system::instance(); - $isadmin = (is_siteadmin() && !is_role_switched($PAGE->course->id)); - $viewhidden = has_capability('moodle/category:viewhiddencategories', $context, $USER, $isadmin); - - // Categories not visible will be still visible to site admins or users with viewhiddencourses capability. - $sql = 'SELECT cc.id, cc.sortorder, cc.name, cc.visible, cc.parent - FROM {course_categories} cc - WHERE cc.parent = 0' . (!$viewhidden ? ' AND cc.visible = 1' : '') . ' - ORDER BY cc.sortorder'; - $list = ''; - $categories = $DB->get_recordset_sql($sql, ['contextcoursecat' => CONTEXT_COURSECAT]); - foreach ($categories as $category) { - if (!$category->visible && !$viewhidden) { - // Skip if the category is not visible to the user. - continue; - } - $list .= '-' . $category->name . '|/course/index.php?categoryid=' . $category->id . PHP_EOL; - } - $categories->close(); - $replace['/\{categories0menu\}/i'] = $list; - unset($list); - } - - // Tag: {categoriesx}. - // Description: An unordered list of links to categories in the same level as the current course. - // Parameters: None. - if (stripos($text, '{categoriesx}') !== false) { - $sql = "SELECT cc.id, cc.sortorder, cc.name, cc.visible, cc.parent - FROM {course_categories} cc - WHERE cc.parent = $catid AND cc.visible = 1 - ORDER BY cc.sortorder"; - $list = ''; - $categories = $DB->get_recordset_sql($sql, ['contextcoursecat' => CONTEXT_COURSECAT]); - foreach ($categories as $category) { - $list .= '<li><a href="' . (new \moodle_url('/course/index.php', ['categoryid' => $category->id]))->out() . '">' - . $category->name . '</a></li>' . PHP_EOL; - } - $list = !empty($list) ? '<ul>' . $list . '</ul>' : ''; - $categories->close(); - $replace['/\{categoriesx\}/i'] = $list; - unset($list); - } - - // Tag: {categoriesxmenu}. - // Description: A list of links to categories in the same level as the current course - for use in the custom menu. - // Parameters: None. - if (stripos($text, '{categoriesxmenu}') !== false) { - $sql = "SELECT cc.id, cc.sortorder, cc.name, cc.visible, cc.parent - FROM {course_categories} cc - WHERE cc.parent = $catid AND cc.visible = 1 - ORDER BY cc.sortorder"; - $list = ''; - $categories = $DB->get_recordset_sql($sql, ['contextcoursecat' => CONTEXT_COURSECAT]); - foreach ($categories as $category) { - $list .= '-' . $category->name . '|/course/index.php?categoryid=' . $category->id . PHP_EOL; - } - $categories->close(); - $replace['/\{categoriesxmenu\}/i'] = $list; - unset($list); - } - - // Tag: {categorycards} and {categorycards categoryid}. - // Description: Course sub-categories of the current level presented as card tiles. - // Optional Parameter: You can specify a category id to display categories under that category. 0: Top level categories. - if (stripos($text, '{categorycards') !== false) { - $categoryids = []; - $thiscategorycard = null; - $categoryshowpic = get_config('filter_filtercodes', 'categorycardshowpic'); - - // If category ID is not specified in the tag, figure it out from context. - if (stripos($text, '{categorycards}') !== false) { - if (empty($PAGE->course->category)) { - // If we are not in a course, check if categoryid is part of URL (ex: course lists). - $thiscategorycard = optional_param('categoryid', 0, PARAM_INT); - } else { - // Retrieve the category id of the course we are in. - $thiscategorycard = $PAGE->course->category; - } - $categoryids[] = $thiscategorycard; - } - - // If category ID was specified in the tag, use it. - if (stripos($text, '{categorycards ') !== false) { - // Find all categorycards tags where category ID was specified. - preg_match_all('/\{categorycards ([0-9]+)\}/isuU', $text, $matches); - if (!empty($matches)) { - $categoryids = array_merge($categoryids, array_unique($matches[1])); - } - } - - // For each tag's category ID. - foreach ($categoryids as $catid) { - $sql = "SELECT cc.id, cc.sortorder, cc.name, cc.visible, cc.parent - FROM {course_categories} cc - WHERE cc.parent = $catid - ORDER BY cc.sortorder"; - $subcategories = $DB->get_recordset_sql($sql); - - $html = ''; - foreach ($subcategories as $category) { - // Skip if user does not have permissions to view. - if (!\core_course_category::can_view_category($category)) { - continue; - } - - // Render HTML category cards for the category. - $html .= $this->rendercategorycard($category, $categoryshowpic); - } - $subcategories->close(); - - if (!empty($html)) { - $html = '<ul class="fc-categorycards card-deck mr-0">' . $html . '</ul>'; - } - - // If this is the tag with no category ID. - if ($catid == $thiscategorycard) { - $replace['/\{categorycards\}/i'] = $html; - // If a tag with this category ID was also specified, replace it too. - if (stripos($text, '{categorycards ' . $catid . '}') !== false) { - $replace['/\{categorycards ' . $catid . '\}/isuU'] = $html; - } - } else { - $replace['/\{categorycards ' . $catid . '\}/isuU'] = $html; - } - } - } - unset($categories, $catid, $thiscategorycard, $catids, $categoryids, $matches, $html, $categoryshowpic); - } - - // Tag {mygroups}. - // Description: List of groups that the user is in. - // Parameters: None. - if (stripos($text, '{mygroups}') !== false) { - static $mygroups; - - if (!isset($mygroups)) { - // Fetch my groups. - $context = \context_course::instance($PAGE->course->id); - $groups = groups_get_all_groups($PAGE->course->id, $USER->id); - // Process group names through Moodle filters in case they are multi-language. - $mygroups = []; - foreach ($groups as $group) { - $mygroups[] = format_string($group->name, true, ['context' => $context]); - } - // Format groups into a language string. - $mygroups = $this->formatlist($mygroups); - } - $replace['/\{mygroups\}/i'] = $mygroups; - } - - // Tag {mygroupings}. - // Description: List of groupings that the user is in. - // Parameters: None. - if (stripos($text, '{mygroupings}') !== false) { - static $mygroupings; - - if (!isset($mygroupings)) { - // Fetch my groups. - $context = \context_course::instance($PAGE->course->id); - if (!isset($mygroupingslist)) { - $mygroupingslist = $this->getusergroupings($PAGE->course->id, $USER->id); - } - // Process group names through Moodle filters in case they are multi-language. - $mygroupings = []; - foreach ($mygroupingslist as $grouping) { - $mygroupings[] = format_string($grouping->name, true, ['context' => $context]); - } - // Format groups into a language string. - $mygroupings = $this->formatlist($mygroupings); - } - $replace['/\{mygroupings\}/i'] = $mygroupings; - } - - // Tag: {wwwcontactform}. - // Description: Action URL for ContactForm form submissions. - // Parameters: None. - if (stripos($text, '{wwwcontactform}') !== false) { - $replace['/\{wwwcontactform\}/i'] = $CFG->wwwroot . '/local/contact/index.php'; - } - - // Tag: {sectionname}. - // Description: The name of the section in which the current activity is located. Blank if not in a course. - // Parameters: None. - if (stripos($text, '{sectionname}') !== false) { - // If in a course and section name. - if ($PAGE->course->id != $SITE->id && isset($PAGE->cm->sectionnum)) { - $replace['/\{sectionname\}/i'] = get_section_name($PAGE->course->id, $PAGE->cm->sectionnum); - } else { - $replace['/\{sectionname\}/i'] = ''; - } - } - - // Tag: {recaptcha}. - // Description: Recaptcha. If used, you will need a way to process it as this just displays it. Used by ContactForms. - // Parameters: None. - if (stripos($text, '{recaptcha}') !== false) { - $replace['/\{recaptcha\}/i'] = $this->getrecaptcha(); - } - - // Tag: {readonly}. - // Description: For use in forms to make a field read-only when user is logged-in as non-guest. - // Parameters: None. - if (stripos($text, '{readonly}') !== false) { - if (isloggedin() && !isguestuser()) { - $replace['/\{readonly\}/i'] = 'readonly="readonly"'; - } else { - $replace['/\{readonly\}/i'] = ''; - } - } - - // Tag: {highlight}...{/highlight}. - // Description: Applies a yellow background to the text, like a yellow highlighter. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{/highlight}') !== false) { - $replace['/\{highlight\}/i'] = '<mark style="background-color:#FFFF00;">'; - $replace['/\{\/highlight\}/i'] = '</mark>'; - } - - // Tag: {marktext}...{/marktext}. - // Description: Applies a custom style defined by the fc-marktext CSS class. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{/marktext}') !== false) { - $replace['/\{marktext\}/i'] = '<mark class="fc-marktext">'; - $replace['/\{\/marktext\}/i'] = '</mark>'; - } - - // Tag: {markborder}...{/markborder}. - // Description: Applies a red border around content. You can customize the style using the fc-markborder CSS class. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{/markborder}') !== false) { - $replace['/\{markborder\}/i'] = '<mark class="fc-markborder" style="border:2px dashed red;padding:0.03em 0.25em;">'; - $replace['/\{\/markborder\}/i'] = '</mark>'; - } - - // Tag: {showmore}...{/showmore}. - // Description: Place part of your content in show more and it will initially appear collapsed with the words "show more". - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{/showmore}') !== false) { - $newtext = str_replace('{showmore}', '<span id="fc-showmore-tmp" class="fc-showmore hidden">', $text); - if (stripos($newtext, 'fc-showmore-tmp') !== false) { - $newtext = preg_replace_callback('/fc-showmore-tmp/', function ($matches) { - static $count = 0; - return 'showmore-' . $count++; - }, $newtext); - $text = $newtext; - } - $newtext = str_replace('{/showmore}', '</span> <a href="#" class="fc-showmore" style="white-space: nowrap;" ' . - 'onclick="m=document.getElementById(\'fc-showmore-tmp\').classList;m.toggle(\'hidden\');' . - 'this.text=(m.contains(\'hidden\')?\'' . get_string('showmore', 'form') . '\':\'' . - get_string('showless', 'form') . '\');return false;">' . get_string('showmore', 'form') . '</a>', $newtext); - if (stripos($newtext, 'fc-showmore-tmp') !== false) { - $newtext = preg_replace_callback('/fc-showmore-tmp/', function ($matches) { - static $count = 0; - return 'showmore-' . $count++; - }, $newtext); - $text = $newtext; - } - } - - // - // HTML tagging. - // - - // Tag: {nbsp}. - // Description: Will be replaced by an HTML non-breaking space ( ). - // Parameters: None. - if (stripos($text, '{nbsp}') !== false) { - $replace['/\{nbsp\}/i'] = ' '; - } - - // Tag: {hr}. - // Description: Will be replaced by an HTML horizontal rule (<hr>). - // Parameters: None. - if (stripos($text, '{hr}') !== false) { - $replace['/\{hr\}/i'] = '<hr>'; - } - - // Tag: {-}. - // Description: Will be replaced by an HTML soft hyphen (­). - // Parameters: None. - if (stripos($text, '{-}') !== false) { - $replace['/\{-\}/i'] = '­'; - } - - // Tag: {langx xx}...{/langx}. - // Description: Tag text as being in a particular language. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{langx ') !== false) { - $replace['/\{langx\s+([a-z-]+)\}(.*)\{\/langx\}/isuU'] = '<span lang="$1">$2</span>'; - } - - // Tag: {note}...{/note} - // Description: Used to add notes that will appear when editing but not when displayed. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{note}') !== false) { - // Remove the note tags and its content. - $replace['/\{note\}(.*)\{\/note\}/isuU'] = ''; - } - - // Tag: {details open|cssClass}{summary}...{/summary}...{/details}. - // Description: Used to create collapsable sections of content. See HTML details/summary for usage. - // Optional Parameter: 'open' if you want the content to be expanded by default. Alternatively, you can specify a CSS class. - // Requires content between tags. - if (stripos($text, '{/details}') !== false) { - $replace['/\{details\}/i'] = '<details>'; - $replace['/\{details open\}/i'] = '<details open>'; - $replace['/\{\/details\}/i'] = '</details>'; - $replace['/\{summary\}/i'] = '<summary>'; - $replace['/\{\/summary\}/i'] = '</summary>'; - if (preg_match_all('/\{details ([a-zA-Z0-9-_ ]+)\}/', $text, $matches) !== 0) { - foreach ($matches[1] as $cssclass) { - $replace['/\{details ' . $cssclass . '\}/i'] = '<details class="' . $cssclass . '">'; - } - } - } - - // Conditional block tags. - - if (strpos($text, '{if') !== false) { // If there are conditional tags. - require_once($CFG->libdir . '/completionlib.php'); - - // Tag: {ifinactivity}...{/ifinactivity}. - // Description: Will display content if the tag is in an activity. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{/ifinactivity}') !== false) { - if (substr($PAGE->pagetype, 0, 4) == 'mod-') { - $replace['/\{ifinactivity\}/isu'] = ''; - $replace['/\{\/ifinactivity\}/isu'] = ''; - } else { - $replace['/\{ifinactivity\}(.*){\/ifinactivity\}/isuU'] = ''; - } - } - - // Tag: {ifnotinactivity}...{/ifnotinactivity}. - // Description: Will display content if the tag is not in an activity. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{/ifnotinactivity}') !== false) { - if (substr($PAGE->pagetype, 0, 4) != 'mod-') { - $replace['/\{ifnotinactivity\}/isu'] = ''; - $replace['/\{\/ifnotinactivity\}/isu'] = ''; - } else { - $replace['/\{ifnotinactivity\}(.*){\/ifnotinactivity\}/isuU'] = ''; - } - } - - // Tag: {ifactivitycompleted coursemoduleid}...{/ifactivitycompleted}. - // Description: Will display content if the specified activity has been completed. - // Required Parameter: coursemoduleid is the id of the instance of the content module. - // Requires content between tags. - if (stripos($text, '{/ifactivitycompleted}') !== false) { - $completion = new completion_info($PAGE->course); - - if ($completion->is_enabled_for_site() && $completion->is_enabled() == COMPLETION_ENABLED) { - // Get a list of the the instances of this tag. - $re = '/{ifactivitycompleted\s+([0-9]+)\}(.*)\{\/ifactivitycompleted\}/isuU'; - $found = preg_match_all($re, $text, $matches); - - if ($found > 0) { - // Check if the activity is in the list. - foreach ($matches[1] as $cmid) { - $iscompleted = false; - - // Only process valid IDs. - if (($cm = get_coursemodule_from_id('', $cmid, 0)) !== false) { - // Get the completion data for this activity if it exists. - try { - $data = $completion->get_data($cm, true, $USER->id); - $iscompleted = ($data->completionstate == COMPLETION_COMPLETE); - } catch (Exception $e) { - unset($e); - continue; - } - } - - // If the activity has been completed, remove just the tags. Otherwise remove tags and content. - $key = '/{ifactivitycompleted\s+' . $cmid . '\}(.*)\{\/ifactivitycompleted\}/isuU'; - if ($iscompleted) { - // Completed. Keep the text and remove the tags. - $replace[$key] = "$1"; - } else { - // Activity not completed. Remove tags and content. - $replace[$key] = ''; - } - } - } - } - } - - // Tag: {ifnotactivitycompleted coursemoduleid}...{/ifnotactivitycompleted}. - // Description: Will display content if the specified activity has been completed. - // Required Parameter: coursemoduleid is the id of the instance of the content module. - // Requires content between tags. - if (stripos($text, '{/ifnotactivitycompleted}') !== false) { - $completion = new completion_info($PAGE->course); - - if ($completion->is_enabled_for_site() && $completion->is_enabled() == COMPLETION_ENABLED) { - // Get a list of the the instances of this tag. - $re = '/{ifnotactivitycompleted\s+([0-9]+)\}(.*)\{\/ifnotactivitycompleted\}/isuU'; - $found = preg_match_all($re, $text, $matches); - - if ($found > 0) { - // Check if the activity is in the list. - foreach ($matches[1] as $cmid) { - $iscompleted = false; - - // Only process valid IDs. - if (($cm = get_coursemodule_from_id('', $cmid, 0)) !== false) { - // Get the completion data for this activity. - try { - $data = $completion->get_data($cm, true, $USER->id); - $iscompleted = ($data->completionstate == COMPLETION_COMPLETE); - } catch (Exception $e) { - unset($e); - continue; - } - } - - // If the activity has been completed, remove just the tags. Otherwise remove tags and content. - $key = '/{ifnotactivitycompleted\s+' . $cmid . '\}(.*)\{\/ifnotactivitycompleted\}/isuU'; - if (!$iscompleted) { - // Completed. Keep the text and remove the tags. - $replace[$key] = "$1"; - } else { - // Activity not completed. Remove tags and content. - $replace[$key] = ''; - } - } - } - } - } - - // Tag: {ifprofile_field_shortname}...{ifprofile_field_shortname}. - // Description: Will display content if specified the Custom User Profile Fields is not empty. - // Required Parameter: Replace shortname with the shortname of the user profile field. Note that this is in both tags. - // Requires content between tags. - if (stripos($text, '{ifprofile_field_') !== false) { - $isuser = (isloggedin() && !isguestuser()); - - // Cached the defined custom profile fields and data. - if (!isset($profilefields)) { - $profilefields = $DB->get_records('user_info_field', null, '', 'id, datatype, shortname, visible, param3'); - if ($isuser && !empty($profilefields)) { - $profiledata = $DB->get_records_menu('user_info_data', ['userid' => $USER->id], '', 'fieldid, data'); - } - } - - // Determine if allowed to evaluate "Not visible" fields. - $allowall = empty(get_config('filter_filtercodes', 'ifprofilefiedonlyvisible')); - - // Process each custom of the available profile fields. - foreach ($profilefields as $field) { - $tag = 'ifprofile_field_' . $field->shortname; - - // If the tag exists, user is logged-in and we are allowed to evaluate this field. - if (isset($profiledata[$field->id]) && $isuser && ($field->visible != '0' || $allowall)) { - $data = trim($profiledata[$field->id]); - } else { - $data = ''; - } - // If the value is empty or zero, remove the all of the tags and their contents for that field shortname. - if (empty($data)) { - $replace['/\{' . $tag . '(.*)\}(.*)\{\/' . $tag . '\}/isuU'] = ''; - continue; - } - - // If no comparison value is specified. - if (stripos($text, '{' . $tag . '}') !== false) { - // Just remove the tags. - $replace['/\{' . $tag . '\}/isu'] = ''; - $replace['/\{\/' . $tag . '\}/isu'] = ''; - } - } - } - - // Tag: {ifprofile_shortname is|not|contains|in "value"}...{/ifprofile}. - // Description: Will display content if specified the User Profile Fields meets the specified condition. - // - // Parameter: shortname: Shortname of the custom user profile fields plus auth, idnumber, email, institution, - // department, city, country, timezone and lang. - // Parameter: contains|is|in: The comparison operator. Use: - // 'is' to check if the field is an exact match for the value. - // 'not' to check if the field does not contain the value. - // 'contains' to check if the field contains the text. - // 'in' to check if the value is in the fields content. - // Parameters: "value": The text to compare the field against. - // Requires content between tags. - if (stripos($text, '{/ifprofile}') !== false) { - // Retrieve all custom profile fields and specified core fields. - $corefields = ['id', 'username', 'auth', 'idnumber', 'email', 'institution', - 'department', 'city', 'country', 'timezone', 'lang']; - $profilefields = $this->getuserprofilefields($USER, $corefields); - - // Find all ifprofile tags. - $re = '/{ifprofile\s+(\w+)\s+(is|not|contains|in)\s+"([^}]*)"}(.*){\/ifprofile}/isuU'; - $found = preg_match_all($re, $text, $matches); - if ($found > 0) { - foreach ($matches[1] as $key => $match) { - $fieldname = $matches[1][$key]; - $string = $matches[0][$key]; // String found in $text. - $operator = $matches[2][$key]; - $value = $matches[3][$key]; - - // Do not process tag if the specified profile field name does not exist or user is not logged in. - if (!array_key_exists($fieldname, $profilefields) || !isloggedin() || isguestuser()) { - if ($operator == 'not') { - // It will always meet criteria of a "not" if the user doesn't have a profile. - $replace['/' . preg_quote($string, '/') . '/isuU'] = $matches[4][$key]; - } else { - // It will never match the criteria "is", "contains" or "in" if the user doesn't have a profile. - $replace['/' . preg_quote($string, '/') . '/isuU'] = ''; - } - continue; - } - - if (!empty($value)) { - $value = trim($value, '"'); // Trim quotation marks. - } - - $content = ''; - switch ($operator) { - case 'is': - // If the specified field is exactly the specified value. - // Example: {ifprofile country is "CA"}...{/ifprofile}. - // Example: {ifprofile city is ""}...{/ifprofile}. - if ($profilefields[$fieldname]->value === $value) { - $content = $matches[4][$key]; - } - break; - case 'not': - // Example: {ifprofile country not "CA"}...{/ifprofile}. - // Example: {ifprofile institution not ""}...{/ifprofile}. - if ($profilefields[$fieldname]->value !== $value) { - $content = $matches[4][$key]; - } - break; - case 'contains': - // If the specified field contains the specified value. - // Example:{ifprofile email contains "@example.com"}...{/ifprofile}. - if (strpos($profilefields[$fieldname]->value, $value) !== false) { - $content = $matches[4][$key]; - } - break; - case 'in': - // If the specified value contains the value specified in the field. - // Example: {ifprofile country in "CA,US,UK,AU,NZ"}...{/ifprofile}. - if (strpos($value, $profilefields[$fieldname]->value) !== false) { - $content = $matches[4][$key]; - } - break; - } - $replace['/' . preg_quote($string, '/') . '/isuU'] = $content; - } - } - } - - // Tag: {ifmobile}...{/ifmobile}. - // Description: Will display content if accessed from the mobile app. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{/ifmobile}') !== false) { - // If this is a web service or the Moodle mobile app... - if ($this->iswebservice()) { - // Yes, just remove the tags. - $replace['/\{ifmobile\}/i'] = ''; - $replace['/\{\/ifmobile\}/i'] = ''; - } else { - // Not from web services, remove tags and content. - $replace['/\{ifmobile\}(.*)\{\/ifmobile\}/isuU'] = ''; - } - } - - // Tag: {ifnotmobile}...{/ifnotmobile}. - // Description: Will display content if NOT accessed from the mobile app. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{/ifnotmobile}') !== false) { - // If this is a web service or the Moodle mobile app... - if (!$this->iswebservice()) { - // Yes, just remove the tags. - $replace['/\{ifnotmobile\}/i'] = ''; - $replace['/\{\/ifnotmobile\}/i'] = ''; - } else { - // Not from web services, remove tags and content. - $replace['/\{ifnotmobile\}(.*)\{\/ifnotmobile\}/isuU'] = ''; - } - } - - // Tag: {ifloggedinas}...{/ifloggedinas}. - // Description: Will display content if logged in as a different user. See https://docs.moodle.org/en/Log_in_as. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifloggedinas}') !== false) { - // If logged-in-as another user... - if (\core\session\manager::is_loggedinas()) { - // Just remove the tags. - $replace['/\{ifloggedinas\}/i'] = ''; - $replace['/\{\/ifloggedinas\}/i'] = ''; - } else { - // If logged in as another user, remove the ifloggedinas tags and contained content. - $replace['/\{ifloggedinas\}(.*)\{\/ifloggedinas\}/isuU'] = ''; - } - } - - // Tag: {ifnotloggedinas}...{/ifnotloggedinas}. - // Description: Will display content if NOT logged in as a different user. See https://docs.moodle.org/en/Log_in_as. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifnotloggedinas}') !== false) { - // If not logged-in-as another user... - if (!\core\session\manager::is_loggedinas()) { - // Just remove the tags. - $replace['/\{ifnotloggedinas\}/i'] = ''; - $replace['/\{\/ifnotloggedinas\}/i'] = ''; - } else { - // If logged in as another user, remove the if not loggedinas tags and contained content. - $replace['/\{ifnotloggedinas\}(.*)\{\/ifnotloggedinas\}/isuU'] = ''; - } - } - - // Tag: {ifvisible}...{/ifvisible}. - // Description: Will display content if the current course visibility is set to 'Show'. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifvisible}') !== false) { - global $COURSE; - // If the course visibility is set to Show... - if ($COURSE->id != 1 && !empty($COURSE->visible)) { - // Just remove the tags and leave the content. - $replace['/\{ifvisible\}/i'] = ''; - $replace['/\{\/ifvisible\}/i'] = ''; - } else { // Visibility set to Hide. - // Remove the if visible tags and their content. - $replace['/\{ifvisible\}(.*)\{\/ifvisible\}/isuU'] = ''; - } - } - - // Tag: {ifnotvisible}...{/ifnotvisible}. - // Description: Will display content if the current course visibility is set to 'Hide'. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifnotvisible}') !== false) { - global $COURSE; - // If the course visibility is set to hide... - if ($COURSE->id != 1 && empty($COURSE->visible)) { // Visibility set to Hide. - // Just remove the tags. - $replace['/\{ifnotvisible\}/i'] = ''; - $replace['/\{\/ifnotvisible\}/i'] = ''; - } else { // Visibility set to Show. - // Remove the if not visible tags and contained content. - $replace['/\{ifnotvisible\}(.*)\{\/ifnotvisible\}/isuU'] = ''; - } - } - - // Tag: {ifincohort idname|idnumber}...{/ifincohort}. - // Description: Will display content if the user is part of the specified cohort. - // Parameters: id name or id number of the cohort. - // Requires content between tags. - if (stripos($text, '{ifincohort ') !== false) { - static $mycohorts; - if (empty($mycohorts)) { // Cache list of cohorts. - require_once($CFG->dirroot . '/cohort/lib.php'); - $mycohorts = cohort_get_user_cohorts($USER->id); - } - $newtext = preg_replace_callback( - '/\{ifincohort ([\w\-]*)\}(.*)\{\/ifincohort\}/isuU', - function ($matches) use ($mycohorts) { - foreach ($mycohorts as $cohort) { - if ($cohort->idnumber == $matches[1] || $cohort->id == $matches[1]) { - return ($matches[2]); - }; - } - return ''; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Tag: {ifeditmode}...{/ifeditmode}. - // Description: Will display content if edit mode is turned on. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifeditmode}') !== false) { - // If editing mode is activated... - if ($PAGE->user_is_editing()) { - // Just remove the tags. - $replace['/\{ifeditmode\}/i'] = ''; - $replace['/\{\/ifeditmode\}/i'] = ''; - } else { - // If editing mode is not enabled, remove the ifeditmode tags and contained content. - $replace['/\{ifeditmode\}(.*)\{\/ifeditmode\}/isuU'] = ''; - } - } - - // Tag: {ifnoteditmode}...{/ifnoteditmode}. - // Description: Will display content if edit mode is turned off. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifnoteditmode}') !== false) { - // If editing mode is activated... - if ($PAGE->user_is_editing()) { - // If editing mode is enabled, remove the ifnoteditmode tags and contained content. - $replace['/\{ifnoteditmode\}(.*)\{\/ifnoteditmode\}/isuU'] = ''; - } else { - // Just remove the tags. - $replace['/\{ifnoteditmode\}/i'] = ''; - $replace['/\{\/ifnoteditmode\}/i'] = ''; - } - } - - // Tag: {ifcourserequests}...{/ifcourserequests}. - // Description: Will display content if the 'Request a course' feature is enabled. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifcourserequests}') !== false) { - // If Request a course is enabled... - $context = \context_system::instance(); - if (empty($CFG->enablecourserequests) || !has_capability('moodle/course:request', $context)) { - // Just remove the tags. - $replace['/\{ifcourserequests\}/i'] = ''; - $replace['/\{\/ifcourserequests\}/i'] = ''; - } else { - // If Request a Course is not enabled, remove the ifcourserequests tags and contained content. - $replace['/\{ifcourserequests\}(.*)\{\/ifcourserequests\}/isuU'] = ''; - } - } - - // Tags: {ifenrolpage}...{/ifenrolpage}. - // Description: Will display content if you are viewing the enrolment page of a course. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifenrolpage}') !== false) { - // If on a course enrolment page. - if ($PAGE->pagetype == 'enrol-index') { - // Remove the ifenrolpage tags. - $replace['/\{ifenrolpage\}/i'] = ''; - $replace['/\{\/ifenrolpage\}/i'] = ''; - } else { - // Remove the ifenrolpage strings. - $replace['/\{ifenrolpage\}(.*)\{\/ifenrolpage\}/isuU'] = ''; - } - } - - // Tags: {ifnotenrolpage}...{/ifnotenrolpage}. - // Description: Will display content if you are not viewing the enrolment page of a course. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifnotenrolpage}') !== false) { - // If on a course enrolment page. - if ($PAGE->pagetype == 'enrol-index') { - // Remove the ifnotenrolpage strings. - $replace['/\{ifnotenrolpage\}(.*)\{\/ifnotenrolpage\}/isuU'] = ''; - } else { - // Remove the ifenrolled tags. - $replace['/\{ifnotenrolpage\}/i'] = ''; - $replace['/\{\/ifnotenrolpage\}/i'] = ''; - } - } - - // Tag: {ifenrolled}..{/ifenrolled}. - // Description: Will display content if you are enrolled in the current course. - // Parameters: None. - // Requires content between tags. - - // Tag: {ifnotenrolled}...{/ifnotenrolled}. - // Description: Will display content if you are not enrolled in the current course. - // Parameters: None. - // Requires content between tags. - - // Tag: {ifincourse}...{/ifincourse}. - // Description: Will display content if you are anywhere in a course. - // Parameters: None. - // Requires content between tags. - - // Tag: {ifnotincourse}...{/ifnotincourse}. - // Description: Will display content if you are not anywhere in a course. - // Parameters: None. - // Requires content between tags. - - // Tag: {ifinsection}...{/ifinsection}. - // Description: Will display content if you are in a section of a course. - // Parameters: None. - // Requires content between tags. - - if ($PAGE->course->id == $SITE->id) { // If frontpage course. - // Everyone is automatically enrolled in the Front Page course. - // Remove the ifenrolled tags. - if (stripos($text, '{ifenrolled}') !== false) { - $replace['/\{ifenrolled\}/i'] = ''; - $replace['/\{\/ifenrolled\}/i'] = ''; - } - // Remove the ifnotenrolled strings. - if (stripos($text, '{ifnotenrolled}') !== false) { - $replace['/\{ifnotenrolled\}(.*)\{\/ifnotenrolled\}/isuU'] = ''; - } - // Remove the {ifincourse} strings if not in a course or on the Front Page. - if (stripos($text, '{ifincourse}') !== false) { - $replace['/\{ifincourse\}(.*)\{\/ifincourse\}/isuU'] = ''; - } - // If not in a course, remove the {ifnotincourse} tags. - if (stripos($text, '{ifnotincourse}') !== false) { - $replace['/\{ifnotincourse\}/i'] = ''; - $replace['/\{\/ifnotincourse\}/i'] = ''; - } - // Remove the {ifinsection} strings if not in a section of a course or are on the Front Page. - if (stripos($text, '{ifinsection}') !== false) { - $replace['/\{ifinsection\}(.*)\{\/ifinsection\}/isuU'] = ''; - } - } else { - if ($this->hasarchetype('student')) { // If user is enrolled in the course. - // Remove the {ifnotincourse} strings if in a course. - if (stripos($text, '{ifnotincourse}') !== false) { - $replace['/\{ifnotincourse\}(.*)\{\/ifnotincourse\}/isuU'] = ''; - } - // If enrolled, remove the {ifenrolled} tags. - if (stripos($text, '{ifenrolled}') !== false) { - $replace['/\{ifenrolled\}/i'] = ''; - $replace['/\{\/ifenrolled\}/i'] = ''; - } - // Remove the ifnotenrolled strings. - if (stripos($text, '{ifnotenrolled}') !== false) { - $replace['/\{ifnotenrolled\}(.*)\{\/ifnotenrolled\}/isuU'] = ''; - } - } else { - // Otherwise, remove the ifenrolled strings. - if (stripos($text, '{ifenrolled}') !== false) { - $replace['/\{ifenrolled\}(.*)\{\/ifenrolled\}/isuU'] = ''; - } - // And remove the ifnotenrolled tags. - if (stripos($text, '{ifnotenrolled}') !== false) { - $replace['/\{ifnotenrolled\}/i'] = ''; - $replace['/\{\/ifnotenrolled\}/i'] = ''; - } - } - // Tag: {ifincourse}...{/ifincourse}. // phpcs:ignore . - if (stripos($text, '{ifincourse}') !== false) { - $replace['/\{ifincourse\}/i'] = ''; - $replace['/\{\/ifincourse\}/i'] = ''; - } - // Tag: {ifinsection}...{/ifinsection}. // phpcs:ignore . - if (stripos($text, '{ifinsection}') !== false) { - if (!empty(@$PAGE->cm->sectionnum)) { - $replace['/\{ifinsection\}/i'] = ''; - $replace['/\{\/ifinsection\}/i'] = ''; - } else { - // Remove the ifinsection strings. - if (stripos($text, '{ifinsection}') !== false) { - $replace['/\{ifinsection\}(.*)\{\/ifinsection\}/isuU'] = ''; - } - } - } - } - - // Tag: {ifnotinsection}...{/ifnotinsection}. - // Description: Display content if not in a section of a course. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifnotinsection}') !== false) { - if (empty(@$PAGE->cm->sectionnum)) { - $replace['/\{ifnotinsection\}/i'] = ''; - $replace['/\{\/ifnotinsection\}/i'] = ''; - } else { - // Remove the ifnotinsection strings. - if (stripos($text, '{ifnotinsection}') !== false) { - $replace['/\{ifnotinsection\}(.*)\{\/ifnotinsection\}/isuU'] = ''; - } - } - } - - // Tag: {ifstudent}...{/ifstudent}. - // Description: This is similar to {ifenrolled} but only displays if user is enrolled as just a student in the course. - // Must be logged-in and must not have additional higher level roles. - // Example: Student but not Administrator, or Student but not Teacher. - // Parameters: None. - // Requires content between tags. - if ($this->hasonlyarchetype('student')) { - if (stripos($text, '{ifstudent}') !== false) { - // Just remove the tags. - $replace['/\{ifstudent\}/i'] = ''; - $replace['/\{\/ifstudent\}/i'] = ''; - } - } else { - // And remove the ifstudent strings. - if (stripos($text, '{ifstudent}') !== false) { - $replace['/\{ifstudent\}(.*)\{\/ifstudent\}/isuU'] = ''; - } - } - - // Tag: {ifminstudent}...{/ifminstudent}. - // Description: This is similar to {ifstudent} but will displays if user's list of roles includes student. - // Example: Student but may also be teacher or administrator. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifminstudent}') !== false) { - // If an assistant (non-editing teacher). - if ($this->hasarchetype('student')) { - // Just remove the tags. - $replace['/\{ifminstudent\}/i'] = ''; - $replace['/\{\/ifminstudent\}/i'] = ''; - } else { - // Remove the ifassistant strings. - $replace['/\{ifminstudent\}(.*)\{\/ifminstudent\}/isuU'] = ''; - } - } - - // Tag: {ifloggedin}...{/ifloggedin} - // Description: Display content if logged-in but not if logged-in as guest. - // Parameters: None. - // Requires content between tags. - - // Tag: {ifloggedout}...{/ifloggedout}. - // Description: Display content if NOT logged-in. Guest is not considered logged in. - // Parameters: None. - // Requires content between tags. - - if (isloggedin() && !isguestuser()) { // If logged-in but not just as guest. - // Just remove ifloggedin tags. - if (stripos($text, '{ifloggedin}') !== false) { - $replace['/\{ifloggedin\}/i'] = ''; - $replace['/\{\/ifloggedin\}/i'] = ''; - } - // Remove the ifloggedout strings. - if (stripos($text, '{ifloggedout}') !== false) { - $replace['/\{ifloggedout\}(.*)\{\/ifloggedout\}/isuU'] = ''; - } - } else { // If logged-out. - // Remove the ifloggedout tags. - if (stripos($text, '{ifloggedout}') !== false) { - $replace['/\{ifloggedout\}/i'] = ''; - $replace['/\{\/ifloggedout\}/i'] = ''; - } - // Remove ifloggedin strings. - if (stripos($text, '{ifloggedin}') !== false) { - $replace['/\{ifloggedin\}(.*)\{\/ifloggedin\}/isuU'] = ''; - } - } - - // Tag: {ifguest}...{/ifguest}. - // Description: Display content if logged-in as a guest user. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifguest}') !== false) { - if (isguestuser()) { // If logged-in as guest. - // Just remove the tags. - $replace['/\{ifguest\}/i'] = ''; - $replace['/\{\/ifguest\}/i'] = ''; - } else { - // If not logged-in as guest, remove the ifguest text. - $replace['/\{ifguest\}(.*)\{\/ifguest\}/isuU'] = ''; - } - } - - // Tag: {ifassistant}...{/ifassistant}. - // Description: Display content if a non-editing teacher in the course. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifassistant}') !== false) { - // If an assistant (non-editing teacher). - if ($this->hasarchetype('teacher') && stripos($text, '{ifassistant}') !== false) { - // Just remove the tags. - $replace['/\{ifassistant\}/i'] = ''; - $replace['/\{\/ifassistant\}/i'] = ''; - } else { - // Remove the ifassistant strings. - $replace['/\{ifassistant\}(.*)\{\/ifassistant\}/isuU'] = ''; - } - } - - // Tag: {ifteacher}...{/ifteacher}. - // Description: Display content if an editing teacher in the course. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifteacher}') !== false) { - if ($this->hasarchetype('editingteacher')) { // If a teacher. - // Just remove the tags. - $replace['/\{ifteacher\}/i'] = ''; - $replace['/\{\/ifteacher\}/i'] = ''; - } else { - // Remove the ifteacher strings. - $replace['/\{ifteacher\}(.*)\{\/ifteacher\}/isuU'] = ''; - } - } - - // Tag: {ifcreator}...{/ifcreator}. - // Description: Display content if the user has the role of course creator. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifcreator}') !== false) { - if ($this->hasarchetype('coursecreator')) { // If a course creator. - // Just remove the tags. - $replace['/\{ifcreator\}/i'] = ''; - $replace['/\{\/ifcreator\}/i'] = ''; - } else { - // Remove the iscreator strings. - $replace['/\{ifcreator\}(.*)\{\/ifcreator\}/isuU'] = ''; - } - } - - // Tag: {ifmanager}...{/ifmanager}. - // Description: Display content if the user has the role of a manager. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifmanager}') !== false) { - if ($this->hasarchetype('manager')) { // If a manager. - // Just remove the tags. - $replace['/\{ifmanager\}/i'] = ''; - $replace['/\{\/ifmanager\}/i'] = ''; - } else { - // Remove the ifmanager strings. - $replace['/\{ifmanager\}(.*)\{\/ifmanager\}/isuU'] = ''; - } - } - - // Tag: {ifadmin}...{/ifadmin}. - // Description: Display content if the user is a site administrator. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifadmin}') !== false) { - if (is_siteadmin() && !is_role_switched($PAGE->course->id)) { // If an administrator. - // Just remove the tags. - $replace['/\{ifadmin\}/i'] = ''; - $replace['/\{\/ifadmin\}/i'] = ''; - } else { - // Remove the ifadmin strings. - $replace['/\{ifadmin\}(.*)\{\/ifadmin\}/isuU'] = ''; - } - } - - // Tag: {ifdashboard}...{/ifdashboard}. - // Description: Display content if the user is viewing the dashboard. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifdashboard}') !== false) { - if ($PAGE->pagetype == 'my-index') { // If dashboard. - // Just remove the tags. - $replace['/\{ifdashboard\}/i'] = ''; - $replace['/\{\/ifdashboard\}/i'] = ''; - } else { - // If not on the dashboard page, remove the ifdashboard text. - $replace['/\{ifdashboard\}(.*)\{\/ifdashboard\}/isuU'] = ''; - } - } - - // Tag: {ifhome}...{/ifhome}. - // Description: Display content if the user is viewing the Front page. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifhome}') !== false) { - if ($PAGE->pagetype == 'site-index') { // If front page. - // Just remove the tags. - $replace['/\{ifhome\}/i'] = ''; - $replace['/\{\/ifhome\}/i'] = ''; - } else { - // If not on the front page, remove the ifhome text. - $replace['/\{ifhome\}(.*)\{\/ifhome\}/isuU'] = ''; - } - } - // Tag: {ifnothome}...{/ifnothome}. - // Description: Display content if the user is not viewing any other page than the Front page. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifnothome}') !== false) { - if ($PAGE->pagetype != 'site-index') { // If front page. - // Just remove the tags. - $replace['/\{ifnothome\}/i'] = ''; - $replace['/\{\/ifnothome\}/i'] = ''; - } else { - // If not on the front page, remove the ifhome text. - $replace['/\{ifnothome\}(.*)\{\/ifnothome\}/isuU'] = ''; - } - } - - // Tag: {ifdev}...{/ifdev}. - // Description: Display content if the user is a site admnistrator and has debugging set to DEVELOPER mode. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifdev}') !== false) { - // If an administrator with debugging is set to DEVELOPER mode... - if ($CFG->debugdisplay == 1 && is_siteadmin() && !is_role_switched($PAGE->course->id)) { - // Just remove the tags. - $replace['/\{ifdev\}/i'] = ''; - $replace['/\{\/ifdev\}/i'] = ''; - } else { - // If not a developer with debugging set to DEVELOPER mode, remove the ifdev tags and contained content. - $replace['/\{ifdev\}(.*)\{\/ifdev\}/isuU'] = ''; - } - } - - // Tag: {ifingroup id|idnumber}...{/ifingroup}. - // Description: Display content if the user is a member of the specified group. - // Required Parameters: group id or idnumber. - // Requires content between tags. - if (stripos($text, '{ifingroup') !== false) { - if (!isset($mygroupslist)) { // Fetch my groups. - $mygroupslist = groups_get_all_groups($PAGE->course->id, $USER->id); - } - $re = '/{ifingroup\s+(.*)\}(.*)\{\/ifingroup\}/isuU'; - $found = preg_match_all($re, $text, $matches); - if ($found > 0) { - foreach ($matches[1] as $groupid) { - $key = '/{ifingroup\s+' . $groupid . '\}(.*)\{\/ifingroup\}/isuU'; - $ismember = false; - foreach ($mygroupslist as $group) { - if ($groupid == $group->id || $groupid == $group->idnumber) { - $ismember = true; - break; - } - } - if ($ismember) { // Just remove the tags. - $replace[$key] = '$1'; - } else { // Remove the ifingroup tags and content. - $replace[$key] = ''; - } - } - } - } - - // Tag: {ifnotingroup id|idnumber}...{/ifnotingroup}. - // Description: Display content if the user is NOT a member of the specified group. - // Required Parameters: group id or idnumber. - // Requires content between tags. - if (stripos($text, '{ifnotingroup') !== false) { - if (!isset($mygroupslist)) { // Fetch my groups. - $mygroupslist = groups_get_all_groups($PAGE->course->id, $USER->id); - } - $re = '/{ifnotingroup\s+(.*)\}(.*)\{\/ifnotingroup\}/isuU'; - $found = preg_match_all($re, $text, $matches); - if ($found > 0) { - foreach ($matches[1] as $groupid) { - $key = '/{ifnotingroup\s+' . $groupid . '\}(.*)\{\/ifnotingroup\}/isuU'; - $ismember = false; - foreach ($mygroupslist as $group) { - if ($groupid == $group->id || $groupid == $group->idnumber) { - $ismember = true; - break; - } - } - if ($ismember) { // Remove the ifnotingroup tags and content. - $replace[$key] = ''; - } else { // Just remove the tags and keep the content. - $replace[$key] = '$1'; - } - } - } - } - - // Tag: {ifingrouping id|idnumber}...{/ifingrouping}. - // Description: Display content if the user is a member of the specified grouping. - // Required Parameters: group id or idnumber. - // Requires content between tags. - if (stripos($text, '{ifingrouping') !== false) { - if (!isset($mygroupingslist)) { - $mygroupingslist = $this->getusergroupings($PAGE->course->id, $USER->id); - } - $re = '/{ifingrouping\s+(.*)\}(.*)\{\/ifingrouping\}/isuU'; - $found = preg_match_all($re, $text, $matches); - if ($found > 0) { - foreach ($matches[1] as $groupingid) { - $key = '/{ifingrouping\s+' . $groupingid . '\}(.*)\{\/ifingrouping\}/isuU'; - $ismember = false; - foreach ($mygroupingslist as $grouping) { - if ($groupingid == $grouping->id || $groupingid == $grouping->idnumber) { - $ismember = true; - break; - } - } - if ($ismember) { // Just remove the tags. - $replace[$key] = '$1'; - } else { // Remove the ifingroup tags and content. - $replace[$key] = ''; - } - } - } - } - - // Tag: {ifnotingroup id|idnumber}...{/ifnotingroup}. - // Description: Display content if the user is NOT a member of the specified grouping. - // Required Parameters: group id or idnumber. - // Requires content between tags. - if (stripos($text, '{ifnotingrouping') !== false) { - if (!isset($mygroupingslist)) { - $mygroupingslist = $this->getusergroupings($PAGE->course->id, $USER->id); - } - $re = '/{ifnotingrouping\s+(.*)\}(.*)\{\/ifnotingrouping\}/isuU'; - $found = preg_match_all($re, $text, $matches); - if ($found > 0) { - foreach ($matches[1] as $groupingid) { - $key = '/{ifnotingrouping\s+' . $groupingid . '\}(.*)\{\/ifnotingrouping\}/isuU'; - $ismember = false; - foreach ($mygroupingslist as $grouping) { - if ($groupingid == $grouping->id || $groupingid == $grouping->idnumber) { - $ismember = true; - break; - } - } - if ($ismember) { // Remove the ifnotingroup tags and content. - $replace[$key] = ''; - } else { // Just remove the tags and keep the content. - $replace[$key] = '$1'; - } - } - } - } - - // Tag: {iftenant idnumber|tenantid}...{/iftenant}. - // Description: Display content only if the user is part of the specified tenant on Moodle Workplace. - // Required Parameter: tenant idnumber or tenantid. - // Requires content between tags. - if (stripos($text, '{iftenant') !== false) { - if (class_exists('tool_tenant\tenancy')) { - // Moodle Workplace. - $tenants = \tool_tenant\tenancy::get_tenants(); - // Get current tenantid. - $currenttenantid = \tool_tenant\tenancy::get_tenant_id(); - } else { - // Moodle Classic - Just simulate functionality as tenant 1. - // This allows a course to work in both Moodle Classic and Workplace. - $tenants[0] = new stdClass(); - $tenants[0]->idnumber = 1; - $tenants[0]->id = 1; - $currenttenantid = 1; - } - // We will use tenant's idnumber if it is set. If not, default to tenant id. - $currenttenantidnumber = 1; - foreach ($tenants as $tenant) { - if ($tenant->id == $currenttenantid) { - $currenttenantidnumber = $tenant->idnumber ? $tenant->idnumber : $tenant->id; - } - } - $re = '/{iftenant\s+(.*)\}(.*)\{\/iftenant\}/isuU'; - $found = preg_match_all($re, $text, $matches); - if ($found > 0) { - foreach ($matches[1] as $tenantid) { - $key = '/{iftenant\s+' . $tenantid . '\}(.*)\{\/iftenant\}/isuU'; - if ($tenantid == $currenttenantidnumber) { - // Just remove the tags. - $replace[$key] = '$1'; - } else { - // Remove the iftenant strings. - $replace[$key] = ''; - } - } - } - } - - // Tag: {ifworkplace}...{/ifworkplace}. - // Description: Display content only if using Moodle Workplace. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifworkplace}') !== false) { - if (class_exists('tool_tenant\tenancy')) { - // Moodle Workplace - Just remove the tags. - $replace['/\{ifworkplace\}/i'] = ''; - $replace['/\{\/ifworkplace\}/i'] = ''; - } else { - // If Moodle Classic, remove the ifworkplace tags and text. - $replace['/\{ifworkplace\}(.*)\{\/ifworkplace\}/isuU'] = ''; - } - } - - // Tag: {ifcustomrole shortrolename}...{/ifcustomrole}. - // Description: Display content only if user has the role specified by shortrolename in the current context. - // Parameters: Short role name. - // Requires content between tags. - if (stripos($text, '{ifcustomrole') !== false) { - $re = '/{ifcustomrole\s+(.*)\}(.*)\{\/ifcustomrole\}/isuU'; - $found = preg_match_all($re, $text, $matches); - if ($found > 0) { - $context = $PAGE->context; - if ($context->contextlevel == CONTEXT_COURSE) { - // We are in a course. - $context = \context_course::instance($context->instanceid); - } else if ($context->contextlevel == CONTEXT_MODULE) { - // We are in an activity. - $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST); - $context = \context_module::instance($cm->id); - unset($cm); - } - - // Get roles within this context. - $roles = get_user_roles($context, $USER->id, true); - $roles = array_column($roles, 'shortname'); - unset($context); - - // Replace all instances of a given ifcustomrole tag. - foreach ($matches[1] as $roleshortname) { - $key = '/{ifcustomrole\s+' . $roleshortname . '\}(.*)\{\/ifcustomrole\}/isuU'; - // We have a role that matches this tag. - if (in_array($roleshortname, $roles)) { - // Just remove the tags. - $replace[$key] = '$1'; - } else { - // Otherwise, remove the ifcustomrole tags and the string inside it. - $replace[$key] = ''; - } - unset($key); - } - } - unset($re); - unset($found); - } - - // Tag: {ifnotcustomrole shortrolename}...{/ifnotcustomrole}. - // Description: Display content only if user does NOT have the role specified by shortrolename in the current context. - // Required Parameters: Short role name. - // Requires content between tags. - if (stripos($text, '{ifnotcustomrole') !== false) { - $re = '/{ifnotcustomrole\s+(.*)\}(.*)\{\/ifnotcustomrole\}/isuU'; - $found = preg_match_all($re, $text, $matches); - if ($found > 0) { - $context = $PAGE->context; - if ($context->contextlevel == CONTEXT_COURSE) { - // We are in a course. - $context = \context_course::instance($context->instanceid); - } else if ($context->contextlevel == CONTEXT_MODULE) { - // We are in an activity. - $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST); - $context = \context_module::instance($cm->id); - unset($cm); - } - - // Get roles within this context. - $roles = get_user_roles($context, $USER->id, true); - $roles = array_column($roles, 'shortname'); - unset($context); - - // Replace all instances of a given ifnotcustomrole tag. - foreach ($matches[1] as $roleshortname) { - $key = '/{ifnotcustomrole\s+' . $roleshortname . '\}(.*)\{\/ifnotcustomrole\}/isuU'; - // We do not have a role that matches this tag. - if (!in_array($roleshortname, $roles)) { - // Just remove the tags. - $replace[$key] = '$1'; - } else { - // Otherwise, remove the ifnotcustomrole strings. - $replace[$key] = ''; - } - unset($key); - } - } - unset($re); - unset($found); - } - - // Tag: {ifhasarolename roleshortname}...{/ifhasarolename}. - // Description: Display content only if user has the role specified by shortrolename ANYWHERE on the site. - // Parameters: Short role name. - // Requires content between tags. - if (stripos($text, '{ifhasarolename') !== false) { - $re = '/{ifhasarolename\s+(.*)\}(.*)\{\/ifhasarolename\}/isuU'; - $found = preg_match_all($re, $text, $matches); - if ($found > 0) { - foreach ($matches[1] as $roleshortname) { - $key = '/{ifhasarolename\s+' . $roleshortname . '\}(.*)\{\/ifhasarolename\}/isuU'; - if ($this->hasarole($roleshortname, $USER->id)) { - // Just remove the tags. - $replace[$key] = '$1'; - } else { - // Remove the ifhasarolename strings. - $replace[$key] = ''; - } - } - } - } - - // Tag: {iftheme themename}...{/iftheme}. - // Description: Display content only if the current theme matches the one specified. - // Parameters: The name of the directory in which the theme is located. - // Requires content between tags. - if (stripos($text, '{/iftheme') !== false) { - $theme = strtolower($PAGE->theme->name); - $re = '/{iftheme\s+(.*)\}(.*)\{\/iftheme\}/isuU'; - $found = preg_match_all($re, $text, $matches); - if ($found > 0) { - foreach ($matches[1] as $themename) { - $key = '/{iftheme\s+' . $themename . '\}(.*)\{\/iftheme\}/isuU'; - if (strtolower($theme == strtolower($themename))) { - // Just remove the tags. - $replace[$key] = '$1'; - } else { - // Remove the iftheme strings. - $replace[$key] = ''; - } - } - } - } - - // Tag: {ifnottheme themename}...{/ifnottheme}. - // Description: Display content only if the current theme does not match the one specified. - // Parameters: The name of the directory in which the theme is located. - // Requires content between tags. - if (stripos($text, '{ifnottheme ') !== false) { - $theme = strtolower($PAGE->theme->name); - $re = '/{ifnottheme\s+(.*)\}(.*)\{\/ifnottheme\}/isuU'; - $found = preg_match_all($re, $text, $matches); - if ($found > 0) { - foreach ($matches[1] as $themename) { - $key = '/{ifnottheme\s+' . $themename . '\}(.*)\{\/ifnottheme\}/isuU'; - if (strtolower($theme) != strtolower($themename)) { - // Just remove the tags. - $replace[$key] = '$1'; - } else { - // Remove the ifnottheme strings. - $replace[$key] = ''; - } - } - } - } - - if (strpos($text, '{ifmin') !== false) { // If there are conditional ifmin tags. - // Tag: {ifminassistant}...{/ifminassistant}. - // Description: Display content only if user has the role of a non-editing teacher or higher. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifminassistant}') !== false) { - // If an assistant (non-editing teacher) or above. - if ($this->hasminarchetype('teacher') && stripos($text, '{ifminassistant}') !== false) { - // Just remove the tags. - $replace['/\{ifminassistant\}/i'] = ''; - $replace['/\{\/ifminassistant\}/i'] = ''; - } else { - // Remove the ifminassistant strings. - $replace['/\{ifminassistant\}(.*)\{\/ifminassistant\}/isuU'] = ''; - } - } - - // Tag: {ifminteacher}...{/ifminteacher}. - // Description: Display content only if user has the role of a editing teacher or higher. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifminteacher}') !== false) { - if ($this->hasminarchetype('editingteacher')) { // If a teacher or above. - // Just remove the tags. - $replace['/\{ifminteacher\}/i'] = ''; - $replace['/\{\/ifminteacher\}/i'] = ''; - } else { - // Remove the ifminteacher strings. - $replace['/\{ifminteacher\}(.*)\{\/ifminteacher\}/isuU'] = ''; - } - } - - // Tag: {ifmincreator}...{/ifmincreator}. - // Description: Display content only if user has the role of a course creator or higher. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifmincreator}') !== false) { - if ($this->hasminarchetype('coursecreator')) { // If a course creator or above. - // Just remove the tags. - $replace['/\{ifmincreator\}/i'] = ''; - $replace['/\{\/ifmincreator\}/i'] = ''; - } else { - // Remove the iscreator strings. - $replace['/\{ifmincreator\}(.*)\{\/ifmincreator\}/isuU'] = ''; - } - } - - // Tag: {ifminmanager}...{/ifminmanager}. - // Description: Display content only if user has the role of a manager or higher. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifminmanager}') !== false) { - if ($this->hasminarchetype('manager')) { // If a manager or above. - // Just remove the tags. - $replace['/\{ifminmanager\}/i'] = ''; - $replace['/\{\/ifminmanager\}/i'] = ''; - } else { - // Remove the ifminmanager strings. - $replace['/\{ifminmanager\}(.*)\{\/ifminmanager\}/isuU'] = ''; - } - } - - // Tag: {ifminsitemanager}...{/ifminsitemanager}. - // Description: Display content if user has the role of a site manager (not just course/category manager) or admin. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{ifminsitemanager}') !== false) { - static $issitemanager; - // If a manager or above. - if (!isset($issitemanager) && $issitemanager = $this->hasminarchetype('manager')) { - if (!is_siteadmin()) { - // Is at least a manager, but a site manager? Let's see. - $syscontext = \context_system::instance(); - $role = $DB->get_record('role', ['shortname' => 'manager'], '*', MUST_EXIST); - $userfields = 'u.id, u.username, u.firstname, u.lastname'; - $roleusers = get_role_users($role->id, $syscontext, false, $userfields); - $issitemanager = array_key_exists($USER->id, array_column($roleusers, null, 'id')); - } - } - if ($issitemanager) { - // Just remove the tags. - $replace['/\{ifminsitemanager\}/i'] = ''; - $replace['/\{\/ifminsitemanager\}/i'] = ''; - } else { - // Remove the ifminsitemanager strings. - $replace['/\{ifminsitemanager\}(.*)\{\/ifminsitemanager\}/isuU'] = ''; - } - } - } - } - - // Tag: {filtercodes}. - // Description: Show version of FilterCodes, but only if you have permission to add the tag. - // Parameters: None. - if (stripos($text, '{filtercodes}') !== false) { - // If you have the ability to edit the content. - if (has_capability('moodle/course:update', $PAGE->context)) { - // Show the version of the FilterCodes plugin. - $plugin = new stdClass(); - require($CFG->dirroot . '/filter/filtercodes/version.php'); - $replace['/\{filtercodes\}/i'] = "$plugin->release ($plugin->version)"; - } else { - $replace['/\{filtercodes\}/i'] = ''; - } - } - - // Tag: {chart <type> <value> <title>} - // Description: Easily display a chart in one of several styles. - // Required Parameters: type=radial|pie|progressbar|progresspie, value=0-100, title=Title of the chart. - if ($CFG->branch >= 32 && version_compare(PHP_VERSION, '7.0.0') >= 0 && stripos($text, '{chart ') !== false) { - global $OUTPUT; - preg_match_all('/\{chart\s(\w+)\s([0-9]+)((?:\s)(.*))?\}/isuU', $text, $matches, PREG_SET_ORDER); - $matches = array_unique($matches, SORT_REGULAR); - foreach ($matches as $match) { - $type = $match[1]; // Chart type: radial, pie, progressbar or progresspie. - $value = $match[2]; // Value between 0 and 100. - $match[3] = $match[3] == null ? '' : $match[3]; - $title = trim($match[3]); // Optional text label. - $percent = get_string('percents', '', $value); - switch ($type) { // Type of chart. - case 'radial': // Tag: {chart radial 99 Label to be displayed} - Display a radial (circle) chart. - $chart = new \core\chart_pie(); - $chart->set_doughnut(true); // Calling set_doughnut(true) we display the chart as a doughnut. - if (!empty($title)) { - $chart->set_title($title); - } - $series = new \core\chart_series('Percentage', [min($value, 100), 100 - min($value, 100)]); - $chart->add_series($series); - $chart->set_labels(['Completed', 'Remaining']); - if ($CFG->branch >= 39) { - $chart->set_legend_options(['display' => false]); // Hide chart legend. - } - $html = '<div class="fc-chart-pie">' . $OUTPUT->render_chart($chart, false) . '</div>'; - break; - case 'pie': // Tag: {chart pie 99 Label to be displayed} - Display a pie chart. - $chart = new \core\chart_pie(); - $chart->set_doughnut(false); // Calling set_doughnut(true) we display the chart as a doughnut. - if (!empty($title)) { - $chart->set_title($title); - } - $series = new \core\chart_series('Percentage', [min($value, 100), 100 - min($value, 100)]); - $chart->add_series($series); - $chart->set_labels(['Completed', 'Remaining']); - if ($CFG->branch >= 39) { - $chart->set_legend_options(['display' => false]); // Hide chart legend. - } - $html = '<div class="fc-chart-pie">' . $OUTPUT->render_chart($chart, false) . '</div>'; - break; - case 'progressbar': // Tag: {chart progressbar 99 Label to be displayed} - Display a horizontal progress bar. - $html = ' - <div class="progress mb-0"> - <div class="fc-progress progress-bar bar" role="progressbar" aria-valuenow="' . $value - . '" style="width: ' . $value . '%" aria-valuemin="0" aria-valuemax="100"> - </div> - </div>'; - if (!empty($title)) { - $html .= '<div class="small">' . get_string( - 'chartprogressbarlabel', - 'filter_filtercodes', - ['label' => $title, 'value' => $percent] - ) . '</div>'; - } - break; - case 'progresspie': // Tag: {chart progresspie 99 Label to display} - Display a progress pie. - $styles = '--percent:' . $value . ';'; - $params = explode(' --', ' ' . $title); - $title = ''; - foreach ($params as $param) { - if (in_array(strtolower(strtok($param, ':')), ['color', 'size', 'border', 'bgcolor'])) { - $styles .= '--' . $param . ';'; - } else if (stripos($param, 'title:') === 0) { - $title = substr($param, 6); - } - } - $html = '<div class="fc-progress-pie" style="' . $styles . '">' . $percent . '</div>'; - if (!empty($title)) { - $html .= '<div class="small">' . $title . '</div>'; - } - break; - default: - $html = ''; - } - $replace['/\{chart ' . $type . ' ' . $value . preg_quote($match[3]) . '\}/isuU'] = $html; - $newtext = preg_replace(array_keys($replace), array_values($replace), $text); - if (!is_null($newtext)) { - $text = $newtext; - } - } - unset($chart, $matches, $html, $value, $title); - } - - // Tag: {alert stylename}...{/alert}. - // Description: Wraps content between the tags into a Bootstrap Alert box. - // Optional Parameters: Stylenames: primary|secondary|success|danger|warning|info|light|dark. Default is 'warning'. - // Requires content between tags. - // Note: Support of styles is theme dependant. Not all themes support these styles and some will support other styles. - if (stripos($text, '{/alert}') !== false) { - $newtext = preg_replace_callback( - '/\{alert(\s\w*)?\}(.*)\{\/alert\}/isuU', - function ($matches) { - // If alert <style> parameter is not included, default to alert-warning. - $matches[1] = trim($matches[1]); - $matches[1] = empty($matches[1]) || $matches[1] == 'border' ? 'border' : 'alert-' . $matches[1]; - return '<div class="alert ' . $matches[1] . '" role="alert"><p>' . $matches[2] . '</p></div>'; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Tag: {label stylename}{/label}. - // Description: Wraps content between the tags into a Bootstrap inline label box. - // Optional Parameters: Style names: default|primary|success|info|danger|warning. Default is 'info'. - // Requires content between tags. - // Note: Support of styles is theme dependant. Not all themes support these styles and some will support other styles. - if (stripos($text, '{/label}') !== false) { - $newtext = preg_replace_callback( - '/\{label(\s\w*)?\}(.*)\{\/label\}/isuU', - function ($matches) { - // If alert <style> parameter is not included, default to alert-info. - $matches[1] = trim($matches[1]); - $matches[1] = empty($matches[1]) ? 'info' : $matches[1]; - return '<span class="label label-' . $matches[1] . '">' . $matches[2] . '</span>'; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Tag: {help}...{/help}. - // Description: Creates a Moodle (?) icon that displays a help bubble when clicked. - // Requires content between tags. - // Parameters: None. - if (stripos($text, '{/help}') !== false) { - static $help; - static $helpwrapper = []; - if (!isset($help)) { - $help = get_string('help'); - $helpwrapper[0] = '<a class="btn btn-link p-0" role="button" data-container="body" data-toggle="popover"' - . ' data-placement="right" data-content="<div class="no-overflow"><p>'; - $helpwrapper[1] = '</p></div>" data-html="true" tabindex="0" data-trigger="focus"><i class="icon' - . ' fa fa-question-circle text-info fa-fw " title="' . $help . '" aria-label="' . $help . '"></i></a>'; - } - $newtext = preg_replace_callback( - '/\{help\}(.*)\{\/help\}/isuU', - function ($matches) use ($helpwrapper) { - return $helpwrapper[0] . htmlspecialchars($matches[1], ENT_COMPAT) . $helpwrapper[1]; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Tag: {info}...{/info}. - // Description: Creates a Moodle (!) icon that displays an information bubble when clicked. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{/info}') !== false) { - static $info; - static $infowrapper = []; - if (!isset($info)) { - $info = get_string('info'); - $infowrapper[0] = '<a class="btn btn-link p-0" role="button" data-container="body" data-toggle="popover"' - . ' data-placement="right" data-content="<div class="no-overflow"><p>'; - $infowrapper[1] = '</p></div>" data-html="true" tabindex="0" data-trigger="focus"><i class="icon' - . ' fa fa-info-circle text-info fa-fw " title="' . $info . '" aria-label="' . $info . '"></i></a>'; - } - $newtext = preg_replace_callback( - '/\{info\}(.*)\{\/info\}/isuU', - function ($matches) use ($infowrapper) { - return $infowrapper[0] . htmlspecialchars($matches[1], ENT_COMPAT) . $infowrapper[1]; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - /* ---------------- Apply all of the filtercodes so far. ---------------*/ - - if ($this->replacetags($text, $replace) == false) { - // No more tags? Put back the escaped tags, if any, and return the string. - $text = $this->escapedtags($text); - return $text; - } - - // Tag: {urlencode}...{/urlencode}. - // Description: URL Encodes the content between the tags for use as a parameter of a URL. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{urlencode}') !== false) { - // Replace {urlencode} tags and content with encoded content. - $newtext = preg_replace_callback( - '/\{urlencode\}(.*)\{\/urlencode\}/isuU', - function ($matches) { - return urlencode($matches[1]); - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Tag: {rawurlencode}...{/rawurlencode}. - // Description: URL Encodes the content between the tags for use as a parameter of a URL in RFC 3986. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{rawurlencode}') !== false) { - // Replace {urlencode} tags and content with encoded content. - $newtext = preg_replace_callback( - '/\{rawurlencode\}(.*)\{\/rawurlencode\}/isuU', - function ($matches) { - return rawurlencode($matches[1]); - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Tag: {qrcode}...{/qrcode}. - // Description: Encodes the content between the tags into a an HTML image tag containing a QR Code of the content. - // Parameters: None. - // Requires content between tags. - if (stripos($text, '{qrcode}') !== false) { - // Remove {qrcode}{/qrcode} tags and turn content between the tags into a QR code. - $newtext = preg_replace_callback( - '/\{qrcode\}(.*)\{\/qrcode\}/isuU', - function ($matches) { - $text = html_to_text($matches[1]); - $src = $this->qrcode($text); - $src = '<img src="' . $src . '" style="width:100%;max-width:480px;height:auto;" class="fc-qrcode" alt="' - . $text . '">'; - return $src; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - // Tag: {button URL}...{/button}. - // Description: Creates a button that displays the content and links to the specified URL. - // Required Parameter: URL. You also need to specify the content which will become the text in the button. - // Requires content between tags. - if (stripos($text, '{button ') !== false) { - $newtext = preg_replace_callback( - '/\{button\s+(.*)\}(.*)\{\/button\}/isuU', - function ($matches) { - // Remove HTML tags created by filters like Activity Name Auto-Linking and Convert URLs Into Links. - $url = strip_tags($matches[1]); - $label = $matches[2]; - return '<a href="' . $url . '" class="btn btn-primary">' . $label . '</a>'; - }, - $text - ); - if ($newtext !== false) { - $text = $newtext; - } - } - - /* ---------------- Apply the rest of the FilterCodes tags. ---------------*/ - - $this->replacetags($text, $replace); - // Put back the escaped tags. - $text = $this->escapedtags($text); - return $text; - } -} +// For backwards compatibility with Moodle 4.4 and below. +class_alias(\core_filters\text_filter::class, \filter_filtercodes::class);