diff --git a/classes/text_filter.php b/classes/text_filter.php new file mode 100644 index 0000000..5c20ebe --- /dev/null +++ b/classes/text_filter.php @@ -0,0 +1,189 @@ +. + +namespace filter_embedquestion; + +use filter_embedquestion\embed_id; +use filter_embedquestion\embed_location; +use filter_embedquestion\output\embed_iframe; +use filter_embedquestion\output\error_message; +use filter_embedquestion\question_options; +use filter_embedquestion\token; +use filter_embedquestion\utils; + +/** + * A Moodle text filter to embed questions from the bank in content. + * + * @package filter_embedquestion + * @copyright 2018 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class text_filter extends \moodle_text_filter { + /** + * @var string Closing part of the embed token wrapper. + */ + const STRING_PREFIX = '{Q{'; + /** + * @var string Closing part of the embed token wrapper. + */ + const STRING_SUFFIX = '}Q}'; + + /** + * @var \filter_embedquestion\output\renderer the renderer. + */ + protected $renderer; + + /** + * @var int the course id, derived from $this->context. + */ + protected $courseid; + + /** + * @var moodle_page page object + */ + protected $page; + + public function setup($page, $context) { + $this->page = $page; + } + + /** + * Get the regexp needed to extract embed codes from within some text. + * + * @return string the regular expression. + */ + public static function get_filter_regexp(): string { + return '~' . preg_quote(self::STRING_PREFIX, '~') . + '((?:(?!' . preg_quote(self::STRING_SUFFIX, '~') . ').)*)' . + preg_quote(self::STRING_SUFFIX, '~') . '~'; + } + + public function filter($text, array $options = []) { + return preg_replace_callback(self::get_filter_regexp(), + [$this, 'embed_question_callback'], $text); + } + + /** + * For use by the preg_replace_callback call above. + * + * @param array $matches the parts matched by the regular expression. + * + * @return string the replacement string. + */ + public function embed_question_callback(array $matches): string { + return $this->embed_question($matches[1]); + } + + /** + * Process the bit of the input for embedding one question. + * + * @param string $embedcode the contents of the {Q{...}Q} delimiters. + * + * @return string HTML code for the iframe to display the question. + */ + public function embed_question(string $embedcode): string { + if ($this->renderer === null) { + $this->renderer = $this->page->get_renderer('filter_embedquestion'); + } + if ($this->courseid === null) { + $this->courseid = utils::get_relevant_courseid($this->context); + } + if (isguestuser()) { + return $this->display_error('noguests'); + } + + list($embedid, $params) = self::parse_embed_code($embedcode); + if ($embedid === null) { + return $this->display_error('invalidtoken'); + } + + $options = new question_options(); + $options->set_from_filter_options($params); + + if (!$options->iframedescription) { + $options->iframedescription = utils::make_unique_iframe_description(); + } + + $embedlocation = embed_location::make_from_page($this->page); + + $showquestionurl = utils::get_show_url($embedid, $embedlocation, $options); + return $this->renderer->render(new embed_iframe($showquestionurl, $options->iframedescription)); + } + + /** + * Display an error since the question cannot be displayed. + * + * @param string $string the string to use for the message. + * @param array|null $a any values needed by the strings. + * + * @return string HTML for the error. + */ + protected function display_error(string $string, array $a = null): string { + return $this->renderer->render(new error_message( + get_string($string, 'filter_embedquestion', $a))); + } + + /** + * Parse an embed code, validate the token, and return the idnumbers and any options. + * @param string $embedcode the embed code. + * @return array an array with two elements: $embedid and $params. + * If the code is invalid, all elements are null. + */ + public static function parse_embed_code(string $embedcode): array { + $parts = explode('|', htmlspecialchars_decode($embedcode)); + + if (count($parts) < 2) { + return [null, null]; + } + + $questioninfo = array_shift($parts); + $token = array_pop($parts); + + $embedid = embed_id::create_from_string($questioninfo); + if ($embedid === null) { + return [null, null]; + } + + if (!token::is_authorized_secret_token($token, $embedid)) { + return [null, null]; + } + + $params = self::parse_options($parts); + if (!is_array($params)) { + return [null, null]; + } + + return [$embedid, $params]; + } + + /** + * Process the options, verifying that they are all of the form name=value. + * + * @param array $parts the individual 'name=options' strings. + * @return array|null the parsed options, or false if they were malformed. + */ + public static function parse_options(array $parts): ?array { + $params = []; + foreach ($parts as $part) { + if (strpos($part, '=') === false) { + return null; + } + list($name, $value) = explode('=', $part); + $params[$name] = $value; + } + return $params; + } +} diff --git a/filter.php b/filter.php index 7bd1ea6..26fb12c 100644 --- a/filter.php +++ b/filter.php @@ -14,174 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -use filter_embedquestion\embed_id; -use filter_embedquestion\embed_location; -use filter_embedquestion\output\embed_iframe; -use filter_embedquestion\output\error_message; -use filter_embedquestion\question_options; -use filter_embedquestion\token; -use filter_embedquestion\utils; - /** * A Moodle text filter to embed questions from the bank in content. * - * @package filter_embedquestion - * @copyright 2018 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package filter_embedquestion + * @copyright 2024 onwards University College London {@link https://www.ucl.ac.uk/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Leon Stringer */ -class filter_embedquestion extends moodle_text_filter { - /** - * @var string Closing part of the embed token wrapper. - */ - const STRING_PREFIX = '{Q{'; - /** - * @var string Closing part of the embed token wrapper. - */ - const STRING_SUFFIX = '}Q}'; - - /** - * @var \filter_embedquestion\output\renderer the renderer. - */ - protected $renderer; - - /** - * @var int the course id, derived from $this->context. - */ - protected $courseid; - - /** - * @var moodle_page page object - */ - protected $page; - - public function setup($page, $context) { - $this->page = $page; - } - - /** - * Get the regexp needed to extract embed codes from within some text. - * - * @return string the regular expression. - */ - public static function get_filter_regexp(): string { - return '~' . preg_quote(self::STRING_PREFIX, '~') . - '((?:(?!' . preg_quote(self::STRING_SUFFIX, '~') . ').)*)' . - preg_quote(self::STRING_SUFFIX, '~') . '~'; - } - - public function filter($text, array $options = []) { - return preg_replace_callback(self::get_filter_regexp(), - [$this, 'embed_question_callback'], $text); - } - - /** - * For use by the preg_replace_callback call above. - * - * @param array $matches the parts matched by the regular expression. - * - * @return string the replacement string. - */ - public function embed_question_callback(array $matches): string { - return $this->embed_question($matches[1]); - } - - /** - * Process the bit of the input for embedding one question. - * - * @param string $embedcode the contents of the {Q{...}Q} delimiters. - * - * @return string HTML code for the iframe to display the question. - */ - public function embed_question(string $embedcode): string { - if ($this->renderer === null) { - $this->renderer = $this->page->get_renderer('filter_embedquestion'); - } - if ($this->courseid === null) { - $this->courseid = utils::get_relevant_courseid($this->context); - } - if (isguestuser()) { - return $this->display_error('noguests'); - } - - list($embedid, $params) = self::parse_embed_code($embedcode); - if ($embedid === null) { - return $this->display_error('invalidtoken'); - } - - $options = new question_options(); - $options->set_from_filter_options($params); - - if (!$options->iframedescription) { - $options->iframedescription = utils::make_unique_iframe_description(); - } - - $embedlocation = embed_location::make_from_page($this->page); - - $showquestionurl = utils::get_show_url($embedid, $embedlocation, $options); - return $this->renderer->render(new embed_iframe($showquestionurl, $options->iframedescription)); - } - - /** - * Display an error since the question cannot be displayed. - * - * @param string $string the string to use for the message. - * @param array|null $a any values needed by the strings. - * - * @return string HTML for the error. - */ - protected function display_error(string $string, array $a = null): string { - return $this->renderer->render(new error_message( - get_string($string, 'filter_embedquestion', $a))); - } - - /** - * Parse an embed code, validate the token, and return the idnumbers and any options. - * @param string $embedcode the embed code. - * @return array an array with two elements: $embedid and $params. - * If the code is invalid, all elements are null. - */ - public static function parse_embed_code(string $embedcode): array { - $parts = explode('|', htmlspecialchars_decode($embedcode)); - - if (count($parts) < 2) { - return [null, null]; - } - - $questioninfo = array_shift($parts); - $token = array_pop($parts); - - $embedid = embed_id::create_from_string($questioninfo); - if ($embedid === null) { - return [null, null]; - } - - if (!token::is_authorized_secret_token($token, $embedid)) { - return [null, null]; - } - - $params = self::parse_options($parts); - if (!is_array($params)) { - return [null, null]; - } - return [$embedid, $params]; - } +defined('MOODLE_INTERNAL') || die(); - /** - * Process the options, verifying that they are all of the form name=value. - * - * @param array $parts the individual 'name=options' strings. - * @return array|null the parsed options, or false if they were malformed. - */ - public static function parse_options(array $parts): ?array { - $params = []; - foreach ($parts as $part) { - if (strpos($part, '=') === false) { - return null; - } - list($name, $value) = explode('=', $part); - $params[$name] = $value; - } - return $params; - } -} +class_alias(\filter_embedquestion\text_filter::class, \filter_embedquestion::class); diff --git a/tests/attempt_test.php b/tests/attempt_test.php index de2fe8c..6412b8d 100644 --- a/tests/attempt_test.php +++ b/tests/attempt_test.php @@ -275,7 +275,7 @@ public function test_question_rendering(): void { $expectedregex = '~

Question [^<]+' . '

Not complete
Marked out of 1.00
' . '' . + ']*>Edit question
' . '(v1 \(latest\))?' . '
' . '' .