From b2f52efffe2b84bd2fe9084b1a28baf84fa07ab3 Mon Sep 17 00:00:00 2001
From: John Okely <john@moodle.com>
Date: Fri, 1 Aug 2014 10:25:52 +0800
Subject: [PATCH] MDL-46269 Filters: Add externalprotocol filter to convert
 external content protocols

---
 filter/externalprotocol/filter.php                 | 129 +++++++++++++++++++++
 .../lang/en/filter_externalprotocol.php            |  33 ++++++
 filter/externalprotocol/settings.php               |  38 ++++++
 filter/externalprotocol/tests/filter_test.php      |  73 ++++++++++++
 filter/externalprotocol/tests/fixtures/sample.txt  |  16 +++
 filter/externalprotocol/version.php                |  30 +++++
 6 files changed, 319 insertions(+)
 create mode 100644 filter/externalprotocol/filter.php
 create mode 100644 filter/externalprotocol/lang/en/filter_externalprotocol.php
 create mode 100644 filter/externalprotocol/settings.php
 create mode 100644 filter/externalprotocol/tests/filter_test.php
 create mode 100644 filter/externalprotocol/tests/fixtures/sample.txt
 create mode 100644 filter/externalprotocol/version.php

diff --git a/filter/externalprotocol/filter.php b/filter/externalprotocol/filter.php
new file mode 100644
index 0000000..42b2091
--- /dev/null
+++ b/filter/externalprotocol/filter.php
@@ -0,0 +1,129 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Filter converting URLs in the text to HTML links
+ *
+ * @package    filter
+ * @subpackage externalprotocol
+ * @copyright  2014 John Okely <john@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+class filter_externalprotocol extends moodle_text_filter {
+
+    /**
+     * @var array global configuration for this filter
+     *
+     * This might be eventually moved into parent class if we found it
+     * useful for other filters, too.
+     */
+    protected static $globalconfig;
+
+    /**
+     * Apply the filter to the text
+     *
+     * @see filter_manager::apply_filter_chain()
+     * @param string $text to be processed by the text
+     * @param array $options filter options
+     * @return string text after processing
+     */
+    public function filter($text, array $options = array()) {
+        if (!isset($options['originalformat'])) {
+            // if the format is not specified, we are probably called by {@see format_string()}
+        }
+        if (in_array($options['originalformat'], explode(',', $this->get_global_config('formats')))) {
+            $this->convert_protocols($text);
+        }
+        return $text;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    // internal implementation starts here
+    ////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Returns the global filter setting
+     *
+     * If the $name is provided, returns single value. Otherwise returns all
+     * global settings in object. Returns null if the named setting is not
+     * found.
+     *
+     * @param mixed $name optional config variable name, defaults to null for all
+     * @return string|object|null
+     */
+    protected function get_global_config($name=null) {
+        $this->load_global_config();
+        if (is_null($name)) {
+            return self::$globalconfig;
+
+        } elseif (array_key_exists($name, self::$globalconfig)) {
+            return self::$globalconfig->{$name};
+
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Makes sure that the global config is loaded in self::$globalconfig
+     *
+     * @return void
+     */
+    protected function load_global_config() {
+        if (is_null(self::$globalconfig)) {
+            self::$globalconfig = get_config('filter_externalprotocol');
+        }
+    }
+
+    /**
+     * Given some text this function converts any external embedded content URLs it finds from one protocol to another
+     *
+     * @param string $text Passed in by reference. The string to be searched for external content.
+     */
+    protected function convert_protocols(&$text) {
+        global $CFG;
+
+        # TODO: Convert this function to be passed an array, then it generates a regex
+        $text = $this->convert_tags(array(array('[A-Za-z]*', 'src')), 'http', 'https', $text);
+    }
+
+    protected function convert_tags($pairs, $old_protocol, $new_protocol, $text) {
+
+        $domains = implode(explode(',', self::$globalconfig->blacklist), '|'); # TODO replace dots with \.
+
+        foreach ($pairs as $pair) {
+            $text = $this->convert_tag_protocol($pair[0], $pair[1], $old_protocol, $new_protocol, $text, $domains);
+        }
+
+        return $text;
+    }
+
+    /**
+     * Converts the protocol of urls in a given attribute of all instances of a given tag
+     *
+     * @param string $text The string to be searched for the tags.
+     * @return string The string, with protocols replaced
+     */
+    protected function convert_tag_protocol($tag, $attribute, $old_protocol, $new_protocol, $text, $domains) {
+
+        return preg_replace('/(<' . $tag . '\s[^>]*' . $attribute . '\s*=\s*["\'])' . $old_protocol .
+                            '(:\/\/)([^"\'\/]*)(' . $domains . ')([^"\'])/', '$1' . $new_protocol . '$2' . '$3$4$5', $text);
+    }
+}
diff --git a/filter/externalprotocol/lang/en/filter_externalprotocol.php b/filter/externalprotocol/lang/en/filter_externalprotocol.php
new file mode 100644
index 0000000..7d6a4ee
--- /dev/null
+++ b/filter/externalprotocol/lang/en/filter_externalprotocol.php
@@ -0,0 +1,33 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Strings for filter_externalprotocol
+ *
+ * @package    filter
+ * @subpackage externalprotocol
+ * @copyright  2014 John Okely <john@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['filtername'] = 'Embedded content protocol corrector';
+$string['settingformats'] = 'Apply to formats';
+$string['settingformats_desc'] = 'The filter will be applied only if the text was inserted in one of the selected formats.';
+$string['settingblacklist'] = 'Blacklist';
+$string['settingblacklist_desc'] = 'These sites will not have their protocols converted.';
diff --git a/filter/externalprotocol/settings.php b/filter/externalprotocol/settings.php
new file mode 100644
index 0000000..ff3a30a
--- /dev/null
+++ b/filter/externalprotocol/settings.php
@@ -0,0 +1,38 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package    filter
+ * @subpackage externalprotocol
+ * @copyright  2014 John Okely <john@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if ($ADMIN->fulltree) {
+
+    $settings->add(new admin_setting_configmulticheckbox('filter_externalprotocol/formats',
+            get_string('settingformats', 'filter_externalprotocol'),
+            get_string('settingformats_desc', 'filter_externalprotocol'),
+            array(FORMAT_MOODLE => 1, FORMAT_HTML => 1, FORMAT_PLAIN => 1, FORMAT_MARKDOWN => 1), format_text_menu()));
+
+    $settings->add(new admin_setting_configtextarea('filter_externalprotocol/blacklist',
+            get_string('settingblacklist', 'filter_externalprotocol'),
+            get_string('settingblacklist_desc', 'filter_externalprotocol'),
+            ''));
+}
diff --git a/filter/externalprotocol/tests/filter_test.php b/filter/externalprotocol/tests/filter_test.php
new file mode 100644
index 0000000..cccaa9b
--- /dev/null
+++ b/filter/externalprotocol/tests/filter_test.php
@@ -0,0 +1,73 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit test for the filter_externalprotocol
+ *
+ * @package    filter_externalprotocol
+ * @category   phpunit
+ * @copyright  2014 John Okely <john@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/filter/externalprotocol/filter.php'); // Include the code to test
+
+
+class filter_externalprotocol_testcase extends basic_testcase {
+
+    function get_convert_protocols_test_cases() {
+        $texts = array (
+            //an http image
+            'Image: <img src="http://www.google.com.au/images/srpr/logo11w.png" alt="http" width="538" height="190" style="vertical-align:text-bottom; margin: 0 .5em;" class="img-responsive">' => 'Image: <img src="https://www.google.com.au/images/srpr/logo11w.png" alt="http" width="538" height="190" style="vertical-align:text-bottom; margin: 0 .5em;" class="img-responsive">',
+            //an http image with the source attribute last
+            'Image: <img alt="http" width="538" height="190" style="vertical-align:text-bottom; margin: 0 .5em;" class="img-responsive" src="http://www.google.com.au/images/srpr/logo11w.png">' => 'Image: <img alt="http" width="538" height="190" style="vertical-align:text-bottom; margin: 0 .5em;" class="img-responsive" src="https://www.google.com.au/images/srpr/logo11w.png">',
+            //a https image
+        );
+
+        $data = array();
+        foreach ($texts as $text => $correctresult) {
+            $data[] = array($text, $correctresult);
+        }
+        return $data;
+    }
+
+    /**
+     * @dataProvider get_convert_protocols_test_cases
+     */
+    function test_convert_protocols($text, $correctresult) {
+        $testablefilter = new testable_filter_externalprotocol();
+        $testablefilter->convert_protocols($text);
+        $this->assertEquals($correctresult, $text);
+    }
+
+}
+
+
+/**
+ * Test subclass that makes all the protected methods we want to test public.
+ */
+class testable_filter_externalprotocol extends filter_externalprotocol {
+    public function __construct() {
+        $this->get_global_config();
+        self::$globalconfig->blacklist = 'google.com';
+    }
+    public function convert_protocols(&$text) {
+        parent::convert_protocols($text);
+    }
+}
diff --git a/filter/externalprotocol/tests/fixtures/sample.txt b/filter/externalprotocol/tests/fixtures/sample.txt
new file mode 100644
index 0000000..23a45fe
--- /dev/null
+++ b/filter/externalprotocol/tests/fixtures/sample.txt
@@ -0,0 +1,16 @@
+http://www.lipsum.com
+Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
+Why do we use it?<a href="dummylink.htm">dummy</a>
+
+It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).
+
+Where does it come from?
+
+Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
+
+The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.
+Where can I get some?
+
+There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.
+<a href="http://en.wikipedia.org/wiki/Lorem_ipsum">Wikipedia</a>
+http://www.lorem-ipsum.info/
diff --git a/filter/externalprotocol/version.php b/filter/externalprotocol/version.php
new file mode 100644
index 0000000..6a522fb
--- /dev/null
+++ b/filter/externalprotocol/version.php
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Version details
+ *
+ * @package    filter
+ * @subpackage externalprotocol
+ * @copyright  2014 John Okely <john@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version   = 2014073100;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2014050800;        // Requires this Moodle version
+$plugin->component = 'filter_externalprotocol'; // Full name of the plugin (used for diagnostics)
-- 
1.8.3.2

