Merge branch 'MDL-48778-master' of git://github.com/lameze/moodle
authorDan Poltawski <dan@moodle.com>
Mon, 8 Feb 2016 17:13:02 +0000 (17:13 +0000)
committerDan Poltawski <dan@moodle.com>
Mon, 8 Feb 2016 17:13:02 +0000 (17:13 +0000)
22 files changed:
Gruntfile.js
auth/ldap/auth.php
enrol/ldap/lib.php
enrol/ldap/tests/ldap_test.php
lib/excellib.class.php
lib/ldaplib.php
lib/phpmailer/moodle_phpmailer.php
lib/tests/ldaplib_test.php
mod/folder/db/install.xml
mod/folder/db/upgrade.php
mod/folder/download_folder.php [new file with mode: 0644]
mod/folder/lang/en/folder.php
mod/folder/lib.php
mod/folder/mod_form.php
mod/folder/renderer.php
mod/folder/settings.php
mod/folder/version.php
mod/forum/db/messages.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/tests/maildigest_test.php
mod/forum/version.php

index 07d56c9..7412970 100644 (file)
 module.exports = function(grunt) {
     var path = require('path'),
         tasks = {},
-        cwd = process.env.PWD || process.cwd(),
-        inAMD = path.basename(cwd) == 'amd';
+        cwd = process.env.PWD || process.cwd();
+
+    // Windows users can't run grunt in a subdirectory, so allow them to set
+    // the root by passing --root=path/to/dir.
+    if (grunt.option('root')) {
+        var root = grunt.option('root');
+        if (grunt.file.exists(__dirname, root)) {
+            cwd = path.join(__dirname, root);
+            grunt.log.ok('Setting root to '+cwd);
+        } else {
+            grunt.fail.fatal('Setting root to '+root+' failed - path does not exist');
+        }
+    }
+
+    var inAMD = path.basename(cwd) == 'amd';
 
     // Globbing pattern for matching all AMD JS source files.
     var amdSrc = [inAMD ? cwd + '/src/*.js' : '**/amd/src/*.js'];
index 9d3a4ad..0739c59 100644 (file)
@@ -113,31 +113,7 @@ class auth_plugin_ldap extends auth_plugin_base {
         }
 
         // Hack prefix to objectclass
-        if (empty($this->config->objectclass)) {
-            // Can't send empty filter
-            $this->config->objectclass = '(objectClass=*)';
-        } else if (stripos($this->config->objectclass, 'objectClass=') === 0) {
-            // Value is 'objectClass=some-string-here', so just add ()
-            // around the value (filter _must_ have them).
-            $this->config->objectclass = '('.$this->config->objectclass.')';
-        } else if (strpos($this->config->objectclass, '(') !== 0) {
-            // Value is 'some-string-not-starting-with-left-parentheses',
-            // which is assumed to be the objectClass matching value.
-            // So build a valid filter with it.
-            $this->config->objectclass = '(objectClass='.$this->config->objectclass.')';
-        } else {
-            // There is an additional possible value
-            // '(some-string-here)', that can be used to specify any
-            // valid filter string, to select subsets of users based
-            // on any criteria. For example, we could select the users
-            // whose objectClass is 'user' and have the
-            // 'enabledMoodleUser' attribute, with something like:
-            //
-            //   (&(objectClass=user)(enabledMoodleUser=1))
-            //
-            // In this particular case we don't need to do anything,
-            // so leave $this->config->objectclass as is.
-        }
+        $this->config->objectclass = ldap_normalise_objectclass($this->config->objectclass);
     }
 
     /**
index ae1a9e4..8fa81d6 100644 (file)
@@ -33,6 +33,13 @@ class enrol_ldap_plugin extends enrol_plugin {
     protected $enroltype = 'enrol_ldap';
     protected $errorlogtag = '[ENROL LDAP] ';
 
+    /**
+     * The object class to use when finding users.
+     *
+     * @var string $userobjectclass
+     */
+    protected $userobjectclass;
+
     /**
      * Constructor for the plugin. In addition to calling the parent
      * constructor, we define and 'fix' some settings depending on the
@@ -59,8 +66,13 @@ class enrol_ldap_plugin extends enrol_plugin {
         unset($ldap_usertypes);
 
         $default = ldap_getdefaults();
-        // Remove the objectclass default, as the values specified there are for
-        // users, and we are dealing with groups here.
+
+        // The objectclass in the defaults is for a user.
+        // This will be required later, but enrol_ldap uses 'objectclass' for its group objectclass.
+        // Save the normalised user objectclass for later.
+        $this->userobjectclass = ldap_normalise_objectclass($default['objectclass'][$this->get_config('user_type')]);
+
+        // Remove the objectclass default, as the values specified there are for users, and we are dealing with groups here.
         unset($default['objectclass']);
 
         // Use defaults if values not given. Dont use this->get_config()
@@ -72,31 +84,19 @@ class enrol_ldap_plugin extends enrol_plugin {
             }
         }
 
+        // Normalise the objectclass used for groups.
         if (empty($this->config->objectclass)) {
-            // Can't send empty filter. Fix it for now and future occasions
-            $this->set_config('objectclass', '(objectClass=*)');
-        } else if (stripos($this->config->objectclass, 'objectClass=') === 0) {
-            // Value is 'objectClass=some-string-here', so just add ()
-            // around the value (filter _must_ have them).
-            // Fix it for now and future occasions
-            $this->set_config('objectclass', '('.$this->config->objectclass.')');
-        } else if (stripos($this->config->objectclass, '(') !== 0) {
-            // Value is 'some-string-not-starting-with-left-parentheses',
-            // which is assumed to be the objectClass matching value.
-            // So build a valid filter with it.
-            $this->set_config('objectclass', '(objectClass='.$this->config->objectclass.')');
+            // No objectclass set yet - set a default class.
+            $this->config->objectclass = ldap_normalise_objectclass(null, '*');
+            $this->set_config('objectclass', $this->config->objectclass);
         } else {
-            // There is an additional possible value
-            // '(some-string-here)', that can be used to specify any
-            // valid filter string, to select subsets of users based
-            // on any criteria. For example, we could select the users
-            // whose objectClass is 'user' and have the
-            // 'enabledMoodleUser' attribute, with something like:
-            //
-            //   (&(objectClass=user)(enabledMoodleUser=1))
-            //
-            // In this particular case we don't need to do anything,
-            // so leave $this->config->objectclass as is.
+            $objectclass = ldap_normalise_objectclass($this->config->objectclass);
+            if ($objectclass !== $this->config->objectclass) {
+                // The objectclass was changed during normalisation.
+                // Save it in config, and update the local copy of config.
+                $this->set_config('objectclass', $objectclass);
+                $this->config->objectclass = $objectclass;
+            }
         }
     }
 
@@ -490,7 +490,7 @@ class enrol_ldap_plugin extends enrol_plugin {
                                 // as the idnumber does not match their dn and we get dn's from membership.
                                 $memberidnumbers = array();
                                 foreach ($ldapmembers as $ldapmember) {
-                                    $result = ldap_read($this->ldapconnection, $ldapmember, '(objectClass=*)',
+                                    $result = ldap_read($this->ldapconnection, $ldapmember, $this->userobjectclass,
                                                         array($this->config->idnumber_attribute));
                                     $entry = ldap_first_entry($this->ldapconnection, $result);
                                     $values = ldap_get_values($this->ldapconnection, $entry, $this->config->idnumber_attribute);
@@ -838,10 +838,9 @@ class enrol_ldap_plugin extends enrol_plugin {
         require_once($CFG->libdir.'/ldaplib.php');
 
         $ldap_contexts = explode(';', $this->get_config('user_contexts'));
-        $ldap_defaults = ldap_getdefaults();
 
         return ldap_find_userdn($this->ldapconnection, $userid, $ldap_contexts,
-                                '(objectClass='.$ldap_defaults['objectclass'][$this->get_config('user_type')].')',
+                                $this->userobjectclass,
                                 $this->get_config('idnumber_attribute'), $this->get_config('user_search_sub'));
     }
 
index be471bd..9ac6277 100644 (file)
@@ -469,4 +469,64 @@ class enrol_ldap_testcase extends advanced_testcase {
             }
         }
     }
+
+    /**
+     * Test that normalisation of the use objectclass is completed successfully.
+     *
+     * @dataProvider objectclass_fetch_provider
+     * @param string $usertype The supported user type
+     * @param string $expected The expected filter value
+     */
+    public function test_objectclass_fetch($usertype, $expected) {
+        $this->resetAfterTest();
+        // Set the user type - this must be performed before the plugin is instantiated.
+        set_config('user_type', $usertype, 'enrol_ldap');
+
+        // Fetch the plugin.
+        $instance = enrol_get_plugin('ldap');
+
+        // Use reflection to sneak a look at the plugin.
+        $rc = new ReflectionClass('enrol_ldap_plugin');
+        $rcp = $rc->getProperty('userobjectclass');
+        $rcp->setAccessible(true);
+
+        // Fetch the current userobjectclass value.
+        $value = $rcp->getValue($instance);
+        $this->assertEquals($expected, $value);
+    }
+
+    /**
+     * Data provider for the test_objectclass_fetch testcase.
+     *
+     * @return array of testcases.
+     */
+    public function objectclass_fetch_provider() {
+        return array(
+            // This is the list of values from ldap_getdefaults() normalised.
+            'edir' => array(
+                'edir',
+                '(objectClass=user)'
+            ),
+            'rfc2307' => array(
+                'rfc2307',
+                '(objectClass=posixaccount)'
+            ),
+            'rfc2307bis' => array(
+                'rfc2307bis',
+                '(objectClass=posixaccount)'
+            ),
+            'samba' => array(
+                'samba',
+                '(objectClass=sambasamaccount)'
+            ),
+            'ad' => array(
+                'ad',
+                '(samaccounttype=805306368)'
+            ),
+            'default' => array(
+                'default',
+                '(objectClass=*)'
+            ),
+        );
+    }
 }
index b75f173..ff38252 100644 (file)
@@ -163,6 +163,8 @@ class MoodleExcelWorksheet {
         $name = strtr(trim($name, "'"), '[]*/\?:', '       ');
         // Shorten the title if necessary.
         $name = core_text::substr($name, 0, 31);
+        // After the substr, we might now have a single quote on the end.
+        $name = trim($name, "'");
 
         if ($name === '') {
             // Name is required!
index a1769fb..ccda806 100644 (file)
@@ -270,6 +270,42 @@ function ldap_find_userdn($ldapconnection, $username, $contexts, $objectclass, $
     return $ldap_user_dn;
 }
 
+/**
+ * Normalise the supplied objectclass filter.
+ *
+ * This normalisation is a rudimentary attempt to format the objectclass filter correctly.
+ *
+ * @param string $objectclass The objectclass to normalise
+ * @param string $default The default objectclass value to use if no objectclass was supplied
+ * @return string The normalised objectclass.
+ */
+function ldap_normalise_objectclass($objectclass, $default = '*') {
+    if (empty($objectclass)) {
+        // Can't send empty filter.
+        $return = sprintf('(objectClass=%s)', $default);
+    } else if (stripos($objectclass, 'objectClass=') === 0) {
+        // Value is 'objectClass=some-string-here', so just add () around the value (filter _must_ have them).
+        $return = sprintf('(%s)', $objectclass);
+    } else if (stripos($objectclass, '(') !== 0) {
+        // Value is 'some-string-not-starting-with-left-parentheses', which is assumed to be the objectClass matching value.
+        // Build a valid filter using the value it.
+        $return = sprintf('(objectClass=%s)', $objectclass);
+    } else {
+        // There is an additional possible value '(some-string-here)', that can be used to specify any valid filter
+        // string, to select subsets of users based on any criteria.
+        //
+        // For example, we could select the users whose objectClass is 'user' and have the 'enabledMoodleUser'
+        // attribute, with something like:
+        //
+        // (&(objectClass=user)(enabledMoodleUser=1))
+        //
+        // In this particular case we don't need to do anything, so leave $this->config->objectclass as is.
+        $return = $objectclass;
+    }
+
+    return $return;
+}
+
 /**
  * Returns values like ldap_get_entries but is binary compatible and
  * returns all attributes as array.
index b502d4a..b3955ae 100644 (file)
@@ -52,6 +52,8 @@ class moodle_phpmailer extends PHPMailer {
         global $CFG;
         $this->Version   = 'Moodle '.$CFG->version;         // mailer version
         $this->CharSet   = 'UTF-8';
+        // MDL-52637: Disable the automatic TLS encryption added in v5.2.10 (9da56fc1328a72aa124b35b738966315c41ef5c6).
+        $this->SMTPAutoTLS = false;
 
         if (!empty($CFG->smtpauthtype)) {
             $this->AuthType = $CFG->smtpauthtype;
index 0864333..909c3f7 100644 (file)
@@ -166,4 +166,45 @@ class core_ldaplib_testcase extends advanced_testcase {
             $this->assertSame($test['expected'], ldap_stripslashes($test['test']));
         }
     }
+
+    /**
+     * Tests for ldap_normalise_objectclass.
+     *
+     * @dataProvider ldap_normalise_objectclass_provider
+     * @param array $args Arguments passed to ldap_normalise_objectclass
+     * @param string $expected The expected objectclass filter
+     */
+    public function test_ldap_normalise_objectclass($args, $expected) {
+        $this->assertEquals($expected, call_user_func_array('ldap_normalise_objectclass', $args));
+    }
+
+    /**
+     * Data provider for the test_ldap_normalise_objectclass testcase.
+     *
+     * @return array of testcases.
+     */
+    public function ldap_normalise_objectclass_provider() {
+        return array(
+            'Empty value' => array(
+                array(null),
+                '(objectClass=*)',
+            ),
+            'Empty value with different default' => array(
+                array(null, 'lion'),
+                '(objectClass=lion)',
+            ),
+            'Supplied unwrapped objectClass' => array(
+                array('objectClass=tiger'),
+                '(objectClass=tiger)',
+            ),
+            'Supplied string value' => array(
+                array('leopard'),
+                '(objectClass=leopard)',
+            ),
+            'Supplied complex' => array(
+                array('(&(objectClass=cheetah)(enabledMoodleUser=1))'),
+                '(&(objectClass=cheetah)(enabledMoodleUser=1))',
+            ),
+        );
+    }
 }
index 1c63071..9fc25e1 100644 (file)
@@ -15,6 +15,7 @@
         <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="display" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Display type of folder contents - on a separate page or inline"/>
         <FIELD NAME="showexpanded" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="false" DEFAULT="1" SEQUENCE="false" COMMENT="1 = expanded, 0 = collapsed for sub-folders"/>
+        <FIELD NAME="showdownloadfolder" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="false" DEFAULT="1" SEQUENCE="false" COMMENT="1 = show download folder button"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
index 05d297a..85ef365 100644 (file)
@@ -128,5 +128,17 @@ function xmldb_folder_upgrade($oldversion) {
     // Moodle v3.0.0 release upgrade line.
     // Put any upgrade step following this.
 
+
+    // Add showdownloadfolder option.
+    if ($oldversion < 2016020201) {
+        $table = new xmldb_table('folder');
+        $field = new xmldb_field('showdownloadfolder', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'showexpanded');
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field, 'showdownloadfolder');
+        }
+
+        upgrade_mod_savepoint(true, 2016020201, 'folder');
+    }
+
     return true;
 }
diff --git a/mod/folder/download_folder.php b/mod/folder/download_folder.php
new file mode 100644 (file)
index 0000000..178e795
--- /dev/null
@@ -0,0 +1,62 @@
+<?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/>.
+
+/**
+ * Folder download
+ *
+ * @package   mod_folder
+ * @copyright 2015 Andrew Hancox <andrewdchancox@googlemail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . "/../../config.php");
+
+$id = required_param('id', PARAM_INT);  // Course module ID.
+$cm = get_coursemodule_from_id('folder', $id, 0, true, MUST_EXIST);
+
+$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+require_course_login($course, true, $cm);
+$context = context_module::instance($cm->id);
+require_capability('mod/folder:view', $context);
+
+$folder = $DB->get_record('folder', array('id' => $cm->instance), '*', MUST_EXIST);
+
+$foldertree = new folder_tree($folder, $cm);
+$downloadable = folder_archive_available($folder, $foldertree);
+if (!$downloadable) {
+    print_error('cannotdownloaddir', 'repository');
+}
+
+// Completion.
+$completion = new completion_info($course);
+$completion->set_module_viewed($cm);
+
+$fs = get_file_storage();
+$file = $fs->get_file($context->id, 'mod_folder', 'content', 0, '/', '.');
+if (!$file) {
+    print_error('cannotdownloaddir', 'repository');
+}
+
+$zipper   = get_file_packer('application/zip');
+$filename = clean_filename($folder->name . "-" . date("Ymd")) . ".zip";
+$temppath = make_request_directory() . $filename;
+
+if ($zipper->archive_to_pathname(array('/' => $file), $temppath)) {
+    send_temp_file($temppath, $filename);
+} else {
+    print_error('cannotdownloaddir', 'repository');
+}
index f17e18a..e37f9a1 100644 (file)
@@ -25,6 +25,7 @@
 
 $string['contentheader'] = 'Content';
 $string['dnduploadmakefolder'] = 'Unzip files and create folder';
+$string['downloadfolder'] = 'Download folder';
 $string['eventfolderupdated'] = 'Folder updated';
 $string['folder:addinstance'] = 'Add a new folder';
 $string['folder:managefiles'] = 'Manage files in folder module';
@@ -50,5 +51,12 @@ Also note that participants view actions can not be logged in this case.';
 $string['displaypage'] = 'On a separate page';
 $string['displayinline'] = 'Inline on a course page';
 $string['noautocompletioninline'] = 'Automatic completion on viewing of activity can not be selected together with "Display inline" option';
+$string['showdownloadfolder'] = 'Show download folder button';
+$string['showdownloadfolder_help'] = 'If set to \'yes\', a button will be shown to allow users to download a zip archive containing all files.';
 $string['showexpanded'] = 'Show subfolders expanded';
 $string['showexpanded_help'] = 'If set to \'yes\', subfolders are shown expanded by default; otherwise they are shown collapsed.';
+$string['maxsizetodownload'] = 'Maximum folder download size (MB)';
+$string['maxsizetodownload_help'] = 'If set then users will not be able to downlod zip archives of folder where the total size is larger than this value.';
+
+
+
index aa96de4..fe7dcd7 100644 (file)
@@ -482,3 +482,48 @@ function folder_view($folder, $course, $cm, $context) {
     $completion = new completion_info($course);
     $completion->set_module_viewed($cm);
 }
+
+/**
+ * Check if the folder can be zipped and downloaded.
+ * @param stdClass $folder
+ * @param folder_tree $foldertree
+ * @return bool True if the folder can be zipped and downloaded.
+ * @throws \dml_exception
+ */
+function folder_archive_available($folder, $foldertree) {
+    if (!$folder->showdownloadfolder) {
+        return false;
+    }
+
+    $size = folder_get_directory_size($foldertree->dir);
+    $maxsize = get_config('folder', 'maxsizetodownload') * 1024 * 1024;
+
+    if ($size == 0) {
+        return false;
+    }
+
+    if (!empty($maxsize) && $size > $maxsize) {
+        return false;
+    }
+
+    return true;
+}
+
+/**
+ * Recursively measure the size of the files in a directory.
+ * @param array $directory
+ * @return int size of directory contents in bytes
+ */
+function folder_get_directory_size($directory) {
+    $size = 0;
+
+    foreach ($directory['files'] as $file) {
+        $size += $file->get_filesize();
+    }
+
+    foreach ($directory['subdirs'] as $subdirectory) {
+        $size += folder_get_directory_size($subdirectory);
+    }
+
+    return $size;
+}
\ No newline at end of file
index 7c6764c..ec57d86 100644 (file)
@@ -63,6 +63,11 @@ class mod_folder_mod_form extends moodleform_mod {
         $mform->addElement('advcheckbox', 'showexpanded', get_string('showexpanded', 'folder'));
         $mform->addHelpButton('showexpanded', 'showexpanded', 'mod_folder');
         $mform->setDefault('showexpanded', $config->showexpanded);
+
+        // Adding option to enable downloading archive of folder.
+        $mform->addElement('advcheckbox', 'showdownloadfolder', get_string('showdownloadfolder', 'folder'));
+        $mform->addHelpButton('showdownloadfolder', 'showdownloadfolder', 'mod_folder');
+        $mform->setDefault('showdownloadfolder', true);
         //-------------------------------------------------------
         $this->standard_coursemodule_elements();
 
index cf31a3d..1731ed5 100644 (file)
@@ -65,11 +65,26 @@ class mod_folder_renderer extends plugin_renderer_base {
                 'generalbox foldertree');
 
         // Do not append the edit button on the course page.
-        if ($folder->display != FOLDER_DISPLAY_INLINE && has_capability('mod/folder:managefiles', $context)) {
+        if ($folder->display != FOLDER_DISPLAY_INLINE) {
+            $containercontents = '';
+            $downloadable = folder_archive_available($folder, $foldertree);
+
+            if ($downloadable) {
+                $containercontents .= $this->output->single_button(
+                    new moodle_url('/mod/folder/download_folder.php', array('id' => $cm->id)),
+                    get_string('downloadfolder', 'folder')
+                );
+            }
+
+            if (has_capability('mod/folder:managefiles', $context)) {
+                $containercontents .= $this->output->single_button(
+                    new moodle_url('/mod/folder/edit.php', array('id' => $cm->id)),
+                    get_string('edit')
+                );
+            }
             $output .= $this->output->container(
-                    $this->output->single_button(new moodle_url('/mod/folder/edit.php',
-                    array('id' => $cm->id)), get_string('edit')),
-                    'mdl-align folder-edit-button');
+                $containercontents,
+                'mdl-align folder-edit-button');
         }
         return $output;
     }
index f07e58d..e65c4e4 100644 (file)
@@ -28,6 +28,10 @@ defined('MOODLE_INTERNAL') || die;
 if ($ADMIN->fulltree) {
     //--- general settings -----------------------------------------------------------------------------------
     $settings->add(new admin_setting_configcheckbox('folder/showexpanded',
-            get_string('showexpanded', 'folder'),
-            get_string('showexpanded_help', 'folder'), 1));
+        get_string('showexpanded', 'folder'),
+        get_string('showexpanded_help', 'folder'), 1));
+
+    $settings->add(new admin_setting_configtext('folder/maxsizetodownload',
+        get_string('maxsizetodownload', 'folder'),
+        get_string('maxsizetodownload_help', 'folder'), '', PARAM_INT));
 }
index cd48911..5bfa7f7 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2015111600;       // The current module version (Date: YYYYMMDDXX)
+$plugin->version   = 2016020201;       // The current module version (Date: YYYYMMDDXX)
 $plugin->requires  = 2015111000;    // Requires this Moodle version
 $plugin->component = 'mod_folder';     // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 0;
index f195b01..1733de0 100644 (file)
  */
 
 $messageproviders = array (
+    // Ordinary single forum posts.
+    'posts' => array(
+    ),
 
-/// Ordinary single forum posts
-    'posts' => array (
-    )
-
+    // Forum digest messages.
+    'digests' => array(
+    ),
 );
-
-
-
index ae32f1e..f00cc11 100644 (file)
@@ -461,6 +461,7 @@ $string['shortpost'] = 'Short post';
 $string['showsubscribers'] = 'Show/edit current subscribers';
 $string['singleforum'] = 'A single simple discussion';
 $string['smallmessage'] = '{$a->user} posted in {$a->forumname}';
+$string['smallmessagedigest'] = 'Forum digest containing {$a} messages';
 $string['startedby'] = 'Started by';
 $string['subject'] = 'Subject';
 $string['subscribe'] = 'Subscribe to this forum';
index d993c41..121b2e6 100644 (file)
@@ -1063,6 +1063,7 @@ function forum_cron() {
 
                     $postsarray = $discussionposts[$discussionid];
                     sort($postsarray);
+                    $sentcount = 0;
 
                     foreach ($postsarray as $postid) {
                         $post = $posts[$postid];
@@ -1144,6 +1145,7 @@ function forum_cron() {
                                 $userto->markposts[$post->id] = $post->id;
                             }
                         }
+                        $sentcount++;
                     }
                     $footerlinks = array();
                     if ($canunsubscribe) {
@@ -1162,10 +1164,18 @@ function forum_cron() {
                     $posthtml = '';
                 }
 
-                $attachment = $attachname='';
-                // Directly email forum digests rather than sending them via messaging, use the
-                // site shortname as 'from name', the noreply address will be used by email_to_user.
-                $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
+                $eventdata = new \core\message\message();
+                $eventdata->component           = 'mod_forum';
+                $eventdata->name                = 'digests';
+                $eventdata->userfrom            = core_user::get_noreply_user();
+                $eventdata->userto              = $userto;
+                $eventdata->subject             = $postsubject;
+                $eventdata->fullmessage         = $posttext;
+                $eventdata->fullmessageformat   = FORMAT_PLAIN;
+                $eventdata->fullmessagehtml     = $posthtml;
+                $eventdata->notification        = 1;
+                $eventdata->smallmessage        = get_string('smallmessagedigest', 'forum', $sentcount);
+                $mailresult = message_send($eventdata);
 
                 if (!$mailresult) {
                     mtrace("ERROR: mod/forum/cron.php: Could not send out digest mail to user $userto->id ".
index 12d24bb..b694007 100644 (file)
@@ -150,9 +150,10 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
      * specified number of times.
      *
      * @param integer $expected The number of times that the post should have been sent
-     * @return array An array of the messages caught by the message sink
+     * @param integer $individualcount The number of individual messages sent
+     * @param integer $digestcount The number of digest messages sent
      */
-    protected function helper_run_cron_check_count($expected, $messagecount, $mailcount) {
+    protected function helper_run_cron_check_count($expected, $individualcount, $digestcount) {
         if ($expected === 0) {
             $this->expectOutputRegex('/(Email digests successfully sent to .* users.){0}/');
         } else {
@@ -162,15 +163,18 @@ class mod_forum_maildigest_testcase extends advanced_testcase {
 
         // Now check the results in the message sink.
         $messages = $this->helper->messagesink->get_messages();
-        // There should be the expected number of messages.
-        $this->assertEquals($messagecount, count($messages));
 
-        // Now check the results in the mail sink.
-        $messages = $this->helper->mailsink->get_messages();
-        // There should be the expected number of messages.
-        $this->assertEquals($mailcount, count($messages));
+        $counts = (object) array('digest' => 0, 'individual' => 0);
+        foreach ($messages as $message) {
+            if (strpos($message->subject, 'forum digest') !== false) {
+                $counts->digest++;
+            } else {
+                $counts->individual++;
+            }
+        }
 
-        return $messages;
+        $this->assertEquals($digestcount, $counts->digest);
+        $this->assertEquals($individualcount, $counts->individual);
     }
 
     public function test_set_maildigest() {
index 50277d1..cd4eaf1 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2015120800;       // The current module version (Date: YYYYMMDDXX)
+$plugin->version   = 2015120801;       // The current module version (Date: YYYYMMDDXX)
 $plugin->requires  = 2015111000;       // Requires this Moodle version
 $plugin->component = 'mod_forum';      // Full name of the plugin (used for diagnostics)