diff --git a/lang/en/moodle.php b/lang/en/moodle.php
index 1dfcb74b4f8..5caa7836f08 100644
--- a/lang/en/moodle.php
+++ b/lang/en/moodle.php
@@ -698,6 +698,8 @@ $string['emailresetconfirmationsubject'] = '{$a}: Password reset request';
$string['emailresetconfirmsent'] = 'An email has been sent to your address at {$a}.
It contains easy instructions to confirm and complete this password change.
If you continue to have difficulty, contact the site administrator.';
+$string['emailtoprivatefiles'] = 'You can also e-mail files as attachments straight to your private files space. Simply attach your files to an e-mail and send it to {$a}';
+$string['emailtoprivatefilesdenied'] = 'Your administrator has disabled the option to upload your own private files.';
$string['emptydragdropregion'] = 'empty region';
$string['enable'] = 'Enable';
$string['encryptedcode'] = 'Encrypted code';
@@ -1445,6 +1447,8 @@ $string['previous'] = 'Previous';
$string['previouslyselectedusers'] = 'Previously selected users not matching \'{$a}\'';
$string['previoussection'] = 'Previous section';
$string['primaryadminsetup'] = 'Setup administrator account';
+$string['private_files_handler'] = 'Store attachments to an e-mail in the user\'s private files storage space.';
+$string['private_files_handler_name'] = 'Email to Private files';
$string['profile'] = 'Profile';
$string['profilenotshown'] = 'This profile description will not be shown until this person is enrolled in at least one course.';
$string['publicprofile'] = 'Public profile';
diff --git a/lib/classes/message/inbound/private_files_handler.php b/lib/classes/message/inbound/private_files_handler.php
new file mode 100644
index 00000000000..07c63ca1d79
--- /dev/null
+++ b/lib/classes/message/inbound/private_files_handler.php
@@ -0,0 +1,138 @@
+.
+
+/**
+ * A Handler to store attachments sent in e-mails as private files.
+ *
+ * @package core_message
+ * @copyright 2014 Andrew Nicols
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\message\inbound;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * A Handler to store attachments sent in e-mails as private files.
+ *
+ * @package core
+ * @copyright 2014 Andrew Nicols
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class private_files_handler extends handler {
+
+ /**
+ * Return a description for the current handler.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return get_string('private_files_handler', 'moodle');
+ }
+
+ /**
+ * Return a short name for the current handler.
+ * This appears in the admin pages as a human-readable name.
+ *
+ * @return string
+ */
+ public function get_name() {
+ return get_string('private_files_handler_name', 'moodle');
+ }
+
+ /**
+ * Process a message received and validated by the Inbound Message processor.
+ *
+ * @throws \core\message\inbound\processing_failed_exception
+ * @param \stdClass $record The Inbound Message record
+ * @param \stdClass $data The message data packet
+ * @return bool Whether the message was successfully processed.
+ */
+ public function process_message(\stdClass $record, \stdClass $data) {
+ global $USER, $CFG;
+
+ $context = \context_user::instance($USER->id);
+
+ if (!has_capability('moodle/user:manageownfiles', $context)) {
+ throw new \core\message\inbound\processing_failed_exception('emailtoprivatefilesdenied', 'moodle', $data);
+ }
+
+ // Initial setup.
+ $component = 'user';
+ $filearea = 'private';
+ $itemid = 0;
+ $license = $CFG->sitedefaultlicense;
+ $author = fullname($USER);
+
+ // Determine the quota space for this user.
+ $maxbytes = $CFG->userquota;
+ if (has_capability('moodle/user:ignoreuserquota', $context)) {
+ $maxbytes = USER_CAN_IGNORE_FILE_SIZE_LIMITS;
+ }
+
+ // Keep track of files which were uploaded, and which were skipped.
+ $skippedfiles = array();
+ $uploadedfiles = array();
+ $failedfiles = array();
+
+ $fs = get_file_storage();
+ foreach ($data->attachments as $attachmenttype => $attachments) {
+ foreach ($attachments as $attachment) {
+ mtrace("--- Processing attachment '{$attachment->filename}'");
+
+ if (file_is_draft_area_limit_reached($itemid, $maxbytes, $attachment->filesize)) {
+ // The user quota will be exceeded if this file is included.
+ $skippedfiles[] = $attachment;
+ mtrace("---- Skipping attacment. User will be over quota.");
+ continue;
+ }
+
+ // Create a new record for this file.
+ $record = new \stdClass();
+ $record->filearea = $filearea;
+ $record->component = $component;
+ $record->filepath = '/';
+ $record->itemid = $itemid;
+ $record->license = $license;
+ $record->author = $author;
+ $record->contextid = $context->id;
+ $record->userid = $USER->id;
+
+ $record->filename = $fs->get_unused_filename($context->id, $record->component, $record->filearea,
+ $record->itemid, $record->filepath, $attachment->filename);
+
+ mtrace("--> Attaching {$record->filename} to " .
+ "/{$record->contextid}/{$record->component}/{$record->filearea}/" .
+ "{$record->itemid}{$record->filepath}{$record->filename}");
+
+ if ($fs->create_file_from_string($record, $attachment->content)) {
+ // File created successfully.
+ mtrace("---- File uploaded successfully as {$record->filename}.");
+ $uploadedfiles[] = $attachment;
+ } else {
+ mtrace("---- Skipping attacment. Unknown failure during creation.");
+ $failedfiles[] = $attachment;
+ }
+ }
+ }
+
+ // TODO send the user a confirmation e-mail.
+ // Note, some files may have failed because the user has been pushed over quota. This does not constitute a failure.
+
+ return true;
+ }
+}
diff --git a/lib/db/messageinbound_handlers.php b/lib/db/messageinbound_handlers.php
new file mode 100644
index 00000000000..05e1ae8b2a6
--- /dev/null
+++ b/lib/db/messageinbound_handlers.php
@@ -0,0 +1,32 @@
+.
+
+/**
+ * Inbound Message Handlers for core.
+ *
+ * @package core_message
+ * @copyright 2014 Andrew NIcols
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$handlers = array(
+ array(
+ 'classname' => '\core\message\inbound\private_files_handler',
+ 'defaultexpiration' => null,
+ ),
+);
diff --git a/user/files.php b/user/files.php
index f1f03b77f66..00d1ebada6f 100644
--- a/user/files.php
+++ b/user/files.php
@@ -64,6 +64,12 @@ $options = array('subdirs' => 1, 'maxbytes' => $maxbytes, 'maxfiles' => -1, 'acc
'areamaxbytes' => $maxareabytes);
file_prepare_standard_filemanager($data, 'files', $options, $context, 'user', 'private', 0);
+// Attempt to generate an inbound message address to support e-mail to private files.
+$generator = new \core\message\inbound\address_manager();
+$generator->set_handler('\core\message\inbound\private_files_handler');
+$generator->set_data(-1);
+$data->emaillink = $generator->generate($USER->id);
+
$mform = new user_files_form(null, array('data' => $data, 'options' => $options));
if ($mform->is_cancelled()) {
@@ -75,6 +81,8 @@ if ($mform->is_cancelled()) {
echo $OUTPUT->header();
echo $OUTPUT->box_start('generalbox');
+
+
$mform->display();
echo $OUTPUT->box_end();
echo $OUTPUT->footer();
diff --git a/user/files_form.php b/user/files_form.php
index 9c0947ef3d2..709b3c9a86d 100644
--- a/user/files_form.php
+++ b/user/files_form.php
@@ -45,6 +45,11 @@ class user_files_form extends moodleform {
$mform->addElement('filemanager', 'files_filemanager', get_string('files'), null, $options);
$mform->addElement('hidden', 'returnurl', $data->returnurl);
+ if (isset($data->emaillink)) {
+ $emaillink = html_writer::link(new moodle_url('mailto:' . $data->emaillink), $data->emaillink);
+ $mform->addElement('static', 'emailaddress', '',
+ get_string('emailtoprivatefiles', 'moodle', $emaillink));
+ }
$mform->setType('returnurl', PARAM_LOCALURL);
$this->add_action_buttons(true, get_string('savechanges'));
diff --git a/version.php b/version.php
index da46cfee28f..67c83fbaa1c 100644
--- a/version.php
+++ b/version.php
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
-$version = 2014100800.00; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2014100800.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.