From e6bc768a5d9f6d237c1a2d253bbc33b7b494ed2a Mon Sep 17 00:00:00 2001 From: Jason Platts Date: Mon, 27 Jan 2014 12:26:06 +0000 Subject: [PATCH] OUBlog: Copy posts between individual and personal blog instances #8871 #9031 --- backup/moodle2/backup_oublog_stepslib.php | 2 +- db/install.xml | 3 +- db/services.php | 69 ++++ db/upgrade.php | 15 + externallib.php | 302 ++++++++++++++ import.php | 481 ++++++++++++++++++++++ internaldoc/testcase.importpages.txt | 297 +++++++++++++ lang/en/oublog.php | 35 ++ lib.php | 5 +- locallib.php | 343 +++++++++++++++ mod_form.php | 19 +- module.js | 80 ++++ settings.php | 7 + styles.css | 43 ++ version.php | 2 +- view.php | 7 + 16 files changed, 1704 insertions(+), 6 deletions(-) create mode 100644 db/services.php create mode 100644 externallib.php create mode 100644 import.php create mode 100644 internaldoc/testcase.importpages.txt diff --git a/backup/moodle2/backup_oublog_stepslib.php b/backup/moodle2/backup_oublog_stepslib.php index e16e815..163349e 100644 --- a/backup/moodle2/backup_oublog_stepslib.php +++ b/backup/moodle2/backup_oublog_stepslib.php @@ -40,7 +40,7 @@ protected function define_structure() { 'accesstoken', 'intro', 'introformat', 'allowcomments', 'individual', 'maxbytes', 'maxattachments', 'maxvisibility', 'global', 'views', 'completionposts', 'completioncomments', 'reportingemail', 'displayname', - 'statblockon')); + 'statblockon', 'allowimport')); $instances = new backup_nested_element('instances'); diff --git a/db/install.xml b/db/install.xml index 313deb7..607a4e4 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ - @@ -25,6 +25,7 @@ + diff --git a/db/services.php b/db/services.php new file mode 100644 index 0000000..21db646 --- /dev/null +++ b/db/services.php @@ -0,0 +1,69 @@ +. + +/** + * Web service definition. + * + * @package mod_oublog + * @copyright 2013 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$functions = array( + 'mod_oublog_get_user_blogs' => array( + 'classname' => 'mod_oublog_external', + 'methodname' => 'get_user_blogs', + 'classpath' => 'mod/oublog/externallib.php', + 'description' => 'Get all user\'s blogs on system', + 'type' => 'read', + ), + 'mod_oublog_get_blog_info' => array( + 'classname' => 'mod_oublog_external', + 'methodname' => 'get_blog_info', + 'classpath' => 'mod/oublog/externallib.php', + 'description' => 'Get info on blog, inc access check', + 'type' => 'read', + ), + 'mod_oublog_get_blog_allposts' => array( + 'classname' => 'mod_oublog_external', + 'methodname' => 'get_blog_allposts', + 'classpath' => 'mod/oublog/externallib.php', + 'description' => 'Get importable user posts from blog', + 'type' => 'read', + ), + 'mod_oublog_get_blog_posts' => array( + 'classname' => 'mod_oublog_external', + 'methodname' => 'get_blog_posts', + 'classpath' => 'mod/oublog/externallib.php', + 'description' => 'Get selected user posts from blog', + 'type' => 'read', + ), + +); + +$services = array( + 'OUBlog import' => array( + 'shortname' => 'oublogimport', + 'functions' => array ('mod_oublog_get_user_blogs', 'mod_oublog_get_blog_info', + 'mod_oublog_get_blog_allposts', 'mod_oublog_get_blog_posts'), + 'requiredcapability' => '', + 'restrictedusers' => 1, + 'enabled' => 1, + 'downloadfiles' => 1 + ) +); diff --git a/db/upgrade.php b/db/upgrade.php index 9528df1..65aba4f 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -223,5 +223,20 @@ function xmldb_oublog_upgrade($oldversion=0) { upgrade_mod_savepoint(true, 2013121100, 'oublog'); } + if ($oldversion < 2014012702) { + + // Define field allowimport to be added to oublog. + $table = new xmldb_table('oublog'); + $field = new xmldb_field('allowimport', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'statblockon'); + + // Conditionally launch add field allowimport. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Oublog savepoint reached. + upgrade_mod_savepoint(true, 2014012702, 'oublog'); + } + return true; } diff --git a/externallib.php b/externallib.php new file mode 100644 index 0000000..f5fa8d7 --- /dev/null +++ b/externallib.php @@ -0,0 +1,302 @@ +. + +/** + * External oublog API. + * Used for importing posts from another server. + * + * @package mod + * @subpackage oublog + * @copyright 2013 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once("$CFG->libdir/externallib.php"); +require_once("$CFG->dirroot/mod/oublog/locallib.php"); + +class mod_oublog_external extends external_api { + + public static function get_user_blogs_parameters() { + return new external_function_parameters(array( + 'username' => new external_value(PARAM_USERNAME, 'User username') + )); + } + + /** + * Return all blogs on the system for the user + * @param string $username + */ + public static function get_user_blogs($username) { + global $DB; + $username = self::validate_parameters(self::get_user_blogs_parameters(), + array('username' => $username)); + $user = $DB->get_field('user', 'id', array('username' => $username['username']), IGNORE_MISSING); + if (!$user) { + return; + } + $result = oublog_import_getblogs($user); + // Add remote property to each blog to identify that it came from web service. + foreach ($result as &$blog) { + $blog->remote = true; + } + return $result; + } + + public static function get_user_blogs_returns() { + return new external_multiple_structure( + new external_single_structure( + array( + 'cmid' => new external_value(PARAM_INT, 'blog course module id'), + 'coursename' => new external_value(PARAM_TEXT, 'course name text'), + 'numposts' => new external_value(PARAM_INT, 'number of posts in blog'), + 'name' => new external_value(PARAM_TEXT, 'activity name text'), + 'remote' => new external_value(PARAM_BOOL, 'identifies activity is remote'), + ) + ) + ); + } + + public static function get_blog_info_parameters() { + return new external_function_parameters( + array( + 'cmid' => new external_value(PARAM_INT, 'Blog cm id'), + 'username' => new external_value(PARAM_USERNAME, 'User username'), + ) + ); + } + + public static function get_blog_info($cmid, $username) { + global $DB; + $params = self::validate_parameters(self::get_blog_info_parameters(), + array('cmid' => $cmid, 'username' => $username)); + $user = $DB->get_field('user', 'id', array('username' => $params['username']), IGNORE_MISSING); + if (!$user) { + return; + } + $result = oublog_import_getbloginfo($params['cmid'], $user); + return array( + 'bcmid' => $result[0], + 'boublogid' => $result[1], + 'bcontextid' => $result[2], + 'boublogname' => $result[3], + 'bcoursename' => $result[4], + ); + } + + public static function get_blog_info_returns() { + return new external_single_structure( + array( + 'bcmid' => new external_value(PARAM_INT, 'Blog cm id'), + 'boublogid' => new external_value(PARAM_INT, 'Blog id'), + 'bcontextid' => new external_value(PARAM_INT, 'Blog context id'), + 'boublogname' => new external_value(PARAM_TEXT, 'Blog name'), + 'bcoursename' => new external_value(PARAM_TEXT, 'Course short name'), + )); + } + + public static function get_blog_allposts_parameters() { + return new external_function_parameters( + array( + 'blogid' => new external_value(PARAM_INT, 'Blog id'), + 'sort' => new external_value(PARAM_TEXT, 'sort sql'), + 'username' => new external_value(PARAM_USERNAME, 'User username'), + 'page' => new external_value(PARAM_INT, 'results page', VALUE_OPTIONAL, 0), + 'tags' => new external_value(PARAM_SEQUENCE, 'tags to filter by', VALUE_OPTIONAL, null), + ) + ); + } + + /** + * Gets all user posts from blog, filtered by page and tags + * @param int $blogid + * @param string $sort + * @param string $username + * @param int $page + * @param string $tags comma separated sequence of selected tag ids to filter by + * @return array + */ + public static function get_blog_allposts($blogid, $sort, $username, $page = 0, $tags = null) { + global $DB; + $params = self::validate_parameters(self::get_blog_allposts_parameters(), + array('blogid' => $blogid, 'sort' => $sort, 'username' => $username, + 'page' => $page, 'tags' => $tags)); + $user = $DB->get_field('user', 'id', array('username' => $params['username']), IGNORE_MISSING); + if (!$user) { + return; + } + $result = oublog_import_getallposts($params['blogid'], $params['sort'], $user, + $params['page'], $params['tags']); + if (!is_array($result[2])) { + $result[2] = array(); + } + foreach ($result[0] as &$post) { + if (isset($post->tags)) { + $tagupdate = array(); + // Update post tags into a format that Moodle WS can work with. + foreach ($post->tags as $id => $tag) { + $tagupdate[] = (object) array('id' => $id, 'tag' => $tag); + } + $post->tags = $tagupdate; + } + } + return array('posts' => $result[0], 'total' => $result[1], 'tagnames' => $result[2]); + } + + public static function get_blog_allposts_returns() { + return new external_single_structure(array( + 'posts' => new external_multiple_structure(new external_single_structure(array( + 'id' => new external_value(PARAM_INT, 'post id'), + 'title' => new external_value(PARAM_TEXT, 'title'), + 'timeposted' => new external_value(PARAM_INT, 'created'), + 'tags' => new external_multiple_structure(new external_single_structure(array( + 'id' => new external_value(PARAM_INT, 'tag id'), + 'tag' => new external_value(PARAM_TEXT, 'tag value')) + ), 'tags', VALUE_OPTIONAL), + ))), + 'total' => new external_value(PARAM_INT, 'total user posts in blog'), + 'tagnames' => new external_multiple_structure( + new external_single_structure(array( + 'id' => new external_value(PARAM_INT, 'tag id'), + 'tag' => new external_value(PARAM_TEXT, 'tag value'), + )), 'tags', VALUE_OPTIONAL) + )); + } + + public static function get_blog_posts_parameters() { + return new external_function_parameters( + array( + 'blogid' => new external_value(PARAM_INT, 'Blog id'), + 'bcontextid' => new external_value(PARAM_INT, 'Blog module context id'), + 'selected' => new external_value(PARAM_SEQUENCE, 'post ids'), + 'inccomments' => new external_value(PARAM_BOOL, 'include comments', + VALUE_OPTIONAL, false), + 'username' => new external_value(PARAM_USERNAME, 'User username'), + ) + ); + } + + /** + * Get selected blog posts from blog + * @param int $blogid + * @param string $selected comma separated sequence of selected post ids to filter by + * @param bool $inccomments - blog uses comments or not + * @param string $username - used to ensure user posts only + * @return array of posts + */ + public static function get_blog_posts($blogid, $bcontextid, $selected, $inccomments = false, $username) { + global $DB; + $params = self::validate_parameters(self::get_blog_posts_parameters(), + array('blogid' => $blogid, 'bcontextid' => $bcontextid, 'selected' => $selected, + 'inccomments' => $inccomments, 'username' => $username)); + $user = $DB->get_field('user', 'id', array('username' => $username), IGNORE_MISSING); + if (!$user) { + return; + } + $selected = explode(',', $params['selected']); + $return = oublog_import_getposts($params['blogid'], $params['bcontextid'], + $selected, $params['inccomments'], $user); + // Convert file objects into a custom known object to send. + foreach ($return as &$post) { + foreach ($post->images as &$file) { + $file = (object) array( + 'contextid' => $file->get_contextid(), + 'filearea' => $file->get_filearea(), + 'filepath' => $file->get_filepath(), + 'filename' => $file->get_filename(), + 'itemid' => $file->get_itemid() + ); + } + foreach ($post->attachments as &$file) { + $file = (object) array( + 'contextid' => $file->get_contextid(), + 'filearea' => $file->get_filearea(), + 'filepath' => $file->get_filepath(), + 'filename' => $file->get_filename(), + 'itemid' => $file->get_itemid() + ); + } + foreach ($post->comments as &$comment) { + foreach ($comment->images as &$file) { + $file = (object) array( + 'contextid' => $file->get_contextid(), + 'filearea' => $file->get_filearea(), + 'filepath' => $file->get_filepath(), + 'filename' => $file->get_filename(), + 'itemid' => $file->get_itemid() + ); + } + } + } + return $return; + } + + public static function get_blog_posts_returns() { + return new external_multiple_structure(new external_single_structure(array( + 'id' => new external_value(PARAM_INT, 'post id'), + 'oubloginstancesid' => new external_value(PARAM_INT, 'instance id'), + 'groupid' => new external_value(PARAM_INT, 'group id'), + 'title' => new external_value(PARAM_TEXT, 'title'), + 'message' => new external_value(PARAM_RAW, 'message'), + 'timeposted' => new external_value(PARAM_INT, 'created'), + 'allowcomments' => new external_value(PARAM_INT, 'comments allowed'), + 'timeupdated' => new external_value(PARAM_INT, 'updated'), + 'deletedby' => new external_value(PARAM_INT, 'deleted by'), + 'timedeleted' => new external_value(PARAM_INT, 'deleted'), + 'visibility' => new external_value(PARAM_INT, 'visibility'), + 'lasteditedby' => new external_value(PARAM_INT, 'edited by'), + 'tags' => new external_multiple_structure(new external_single_structure(array( + 'id' => new external_value(PARAM_INT, 'tag id'), + 'tag' => new external_value(PARAM_TEXT, 'tag value'), + 'postid' => new external_value(PARAM_INT, 'tag post id'), + )), 'tags', VALUE_OPTIONAL), + 'images' => new external_multiple_structure(new external_single_structure(array( + 'contextid' => new external_value(PARAM_INT, 'context id'), + 'filearea' => new external_value(PARAM_AREA, 'filearea'), + 'filepath' => new external_value(PARAM_PATH, 'path'), + 'filename' => new external_value(PARAM_FILE, 'filename'), + 'itemid' => new external_value(PARAM_INT, 'item id'), + )), 'images', VALUE_OPTIONAL), + 'attachments' => new external_multiple_structure(new external_single_structure(array( + 'contextid' => new external_value(PARAM_INT, 'context id'), + 'filearea' => new external_value(PARAM_AREA, 'filearea'), + 'filepath' => new external_value(PARAM_PATH, 'filepath'), + 'filename' => new external_value(PARAM_FILE, 'filename'), + 'itemid' => new external_value(PARAM_INT, 'item id'), + )), 'attachments', VALUE_OPTIONAL), + 'comments' => new external_multiple_structure( + new external_single_structure(array( + 'id' => new external_value(PARAM_INT, 'id'), + 'postid' => new external_value(PARAM_INT, 'post id'), + 'userid' => new external_value(PARAM_INT, 'user id'), + 'title' => new external_value(PARAM_TEXT, 'title'), + 'message' => new external_value(PARAM_RAW, 'message'), + 'timeposted' => new external_value(PARAM_INT, 'posted'), + 'deletedby' => new external_value(PARAM_INT, 'deleted by'), + 'timedeleted' => new external_value(PARAM_INT, 'deleted'), + 'authorname' => new external_value(PARAM_INT, 'ex author'), + 'authorip' => new external_value(PARAM_INT, 'ex author ip'), + 'timeapproved' => new external_value(PARAM_INT, 'approved'), + 'images' => new external_multiple_structure(new external_single_structure(array( + 'contextid' => new external_value(PARAM_INT, 'context id'), + 'filearea' => new external_value(PARAM_AREA, 'filearea'), + 'filepath' => new external_value(PARAM_PATH, 'path'), + 'filename' => new external_value(PARAM_FILE, 'filename'), + 'itemid' => new external_value(PARAM_INT, 'item id'), + )), 'images', VALUE_OPTIONAL), + )), 'comments', VALUE_OPTIONAL) + ))); + } +} diff --git a/import.php b/import.php new file mode 100644 index 0000000..375e2e2 --- /dev/null +++ b/import.php @@ -0,0 +1,481 @@ +. + +/** + * Import pages into the current blog + * Supports imports from Individual blog to Individual blog (same user) + * + * @package mod + * @subpackage oublog + * @copyright 2013 The open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->dirroot . '/mod/oublog/locallib.php'); + +$id = required_param('id', PARAM_INT);// Blog cm ID. + +// Load efficiently (and with full $cm data) using get_fast_modinfo. +$course = $DB->get_record_select('course', + 'id = (SELECT course FROM {course_modules} WHERE id = ?)', array($id), + '*', MUST_EXIST); +$modinfo = get_fast_modinfo($course); +$cm = $modinfo->get_cm($id); +if ($cm->modname !== 'oublog') { + print_error('invalidcoursemodule'); +} + +if (!$oublog = $DB->get_record('oublog', array('id' => $cm->instance))) { + print_error('invalidcoursemodule'); +} + +$context = get_context_instance(CONTEXT_MODULE, $cm->id); +$tempoublog = clone $oublog; +if ($tempoublog->global) { + $tempoublog->maxvisibility = OUBLOG_VISIBILITY_LOGGEDINUSER;// Force login regardless of setting. +} else { + $tempoublog->maxvisibility = OUBLOG_VISIBILITY_COURSEUSER;// Force login regardless of setting. +} +oublog_check_view_permissions($tempoublog, $context, $cm); +$blogname = oublog_get_displayname($oublog); + +// Is able to import check for current blog. +if (!$oublog->allowimport || + (!$oublog->global && $oublog->individual == OUBLOG_NO_INDIVIDUAL_BLOGS)) { + // Must have import enabled. Individual blog mode only. + print_error('import_notallowed', 'oublog', null, $blogname); +} +// Check if group mode set - need to check user is in selected group etc. +$groupmode = oublog_get_activity_groupmode($cm, $course); +$currentgroup = 0; +if ($groupmode != NOGROUPS) { + $currentgroup = oublog_get_activity_group($cm); + $ingroup = groups_is_member($currentgroup); + if ($oublog->individual != OUBLOG_NO_INDIVIDUAL_BLOGS && ($currentgroup && !$ingroup)) { + // Must be group memeber for individual blog with group mode on. + print_error('import_notallowed', 'oublog', null, $blogname); + } +} + +$step = optional_param('step', 0, PARAM_INT); +if (optional_param('cancel', '', PARAM_ALPHA) == get_string('cancel')) { + $step -= 2;// Go back 2 steps if cancel. +} +$oublogoutput = $PAGE->get_renderer('mod_oublog'); + +// Page header. +$params = array('id' => $id); +$PAGE->set_url('/mod/oublog/import.php', $params); +$errlink = new moodle_url('/mod/oublog/import.php', $params); +$PAGE->set_title(get_string('import', 'oublog')); +$PAGE->navbar->add(get_string('import', 'oublog')); +echo $OUTPUT->header(); +echo $OUTPUT->heading(get_string('import', 'oublog')); + +echo html_writer::start_div('oublog_import_step oublog_import_step' . $step); + +if ($step == 0) { + // Show list of oublog activities user has access to import from. + echo html_writer::tag('p', get_string('import_step0_inst', 'oublog')); + $curcourse = -1; + $blogs = oublog_import_getblogs($USER->id, $cm->id); + try { + if ($remoteblogs = oublog_import_remote_call('mod_oublog_get_user_blogs', + array('username' => $USER->username))) { + $blogs = array_merge($blogs, $remoteblogs); + } + } catch (moodle_exception $e) { + // Ignore fail when contacting external server, keep message for debugging. + debugging($e->getMessage()); + } + $personalblogout = ''; + $blogout = ''; + foreach ($blogs as $bloginfo) { + if ($bloginfo->coursename != '' && $curcourse != $bloginfo->coursename) { + if ($curcourse != -1) { + $blogout .= html_writer::end_tag('ul'); + } + $blogout .= $OUTPUT->heading($bloginfo->coursename, 3); + $blogout .= html_writer::start_tag('ul'); + $curcourse = $bloginfo->coursename; + } + // Use this activity icon for all blogs (in case from another server). + $img = html_writer::empty_tag('img', array('src' => $cm->get_icon_url($oublogoutput), + 'alt' => '')); + $bloglink = ''; + if ($bloginfo->numposts) { + $url = new moodle_url('/mod/oublog/import.php', $params + + array('step' => 1, 'bid' => $bloginfo->cmid)); + if (isset($bloginfo->remote)) { + $url->param('remote', true); + } + $link = html_writer::link($url, $bloginfo->name); + $bloglink = html_writer::tag('li', $img . ' ' . $link . ' ' . + get_string('import_step0_numposts', 'oublog', $bloginfo->numposts)); + } else { + $bloglink = html_writer::tag('li', $img . ' ' . $bloginfo->name . ' ' . + get_string('import_step0_numposts', 'oublog', 0)); + } + if ($bloginfo->coursename != '') { + $blogout .= $bloglink; + } else { + $personalblogout .= $bloglink; + } + } + if ($personalblogout != '') { + echo html_writer::tag('ul', $personalblogout); + } + echo $blogout; + if ($curcourse != -1) { + echo html_writer::end_tag('ul'); + } + if (empty($blogs)) { + echo $OUTPUT->error_text(get_string('import_step0_nonefound', 'oublog')); + } +} else if ($step == 1) { + // Get available posts, first get selected blog info + check access. + $bid = required_param('bid', PARAM_INT); + $stepinfo = array('step' => 1, 'bid' => $bid); + if ($remote = optional_param('remote', false, PARAM_BOOL)) { + $stepinfo['remote'] = true; + // Blog on remote server, use WS to get info. + if (!$result = oublog_import_remote_call('mod_oublog_get_blog_info', + array('username' => $USER->username, 'cmid' => $bid))) { + throw new moodle_exception('invalidcoursemodule', 'error'); + } + $boublogid = $result->boublogid; + $bcontextid = $result->bcontextid; + $boublogname = $result->boublogname; + $bcoursename = $result->bcoursename; + } else { + list($bid, $boublogid, $bcontextid, $boublogname, $bcoursename) = oublog_import_getbloginfo($bid); + } + echo html_writer::start_tag('p', array('class' => 'oublog_import_step1_from')); + echo get_string('import_step1_from', 'oublog') . '
' . html_writer::tag('span', $boublogname); + echo html_writer::end_tag('p'); + // Setup table early so sort can be determined (needs setup to be called first). + $table = new flexible_table($cm->id * $bid); + $url = new moodle_url('/mod/oublog/import.php', $params + $stepinfo); + $table->define_baseurl($url); + $table->define_columns(array('title', 'timeposted', 'tags', 'include')); + $table->column_style('include', 'text-align', 'center'); + $table->sortable(true, 'timeposted', SORT_DESC); + $table->maxsortkeys = 1; + $table->no_sorting('tags'); + $table->no_sorting('include'); + $table->setup(); + $sort = flexible_table::get_sort_for_table($cm->id * $bid); + if (empty($sort)) { + $sort = 'timeposted DESC'; + } + if ($tags = optional_param('tags', null, PARAM_SEQUENCE)) { + // Filter by joining tag instances. + $stepinfo['tags'] = $tags; + } + $perpage = 100;// Must match value in oublog_import_getallposts. + $page = optional_param('page', 0, PARAM_INT); + $stepinfo['page'] = $page; + $preselected = optional_param('preselected', '', PARAM_SEQUENCE); + $stepinfo['preselected'] = $preselected; + $preselected = array_filter(array_unique(explode(',', $preselected))); + if ($remote) { + $result = oublog_import_remote_call('mod_oublog_get_blog_allposts', array( + 'blogid' => $boublogid, 'username' => $USER->username, 'sort' => $sort, + 'page' => $page, 'tags' => $tags)); + $posts = $result->posts; + $total = $result->total; + $tagnames = $result->tagnames; + // Fix up post tags to required format as passed differently from WS. + foreach ($posts as &$post) { + if (isset($post->tags)) { + $newtagarr = array(); + foreach ($post->tags as $tag) { + $newtagarr[$tag->id] = $tag->tag; + } + $post->tags = $newtagarr; + } + } + } else { + list($posts, $total, $tagnames) = oublog_import_getallposts($boublogid, $sort, $USER->id, + $page, $tags); + } + if ($posts) { + // Finish seting up table vars. + $url = new moodle_url('/mod/oublog/import.php', $params + $stepinfo); + $table->define_baseurl($url); + $perpage = $total < $perpage ? $total : $perpage; + $table->pagesize($perpage, $total); + $taghead = get_string('import_step1_table_tags', 'oublog'); + if (!empty($tagnames)) { + // Add tag filter removal links. + $taghead .= '
'; + foreach ($tagnames as $tagid => $tag) { + $tagaarcopy = explode(',', $tags); + unset($tagaarcopy[array_search($tagid, $tagaarcopy)]); + $turl = new moodle_url('/mod/oublog/import.php', + array_merge($params, $stepinfo, array('tags' => implode(',', $tagaarcopy)))); + $taghead .= ' ' . html_writer::link($turl, $OUTPUT->pix_icon('t/delete', + get_string('import_step1_removetag', 'oublog', $tag->tag))) . + ' ' . format_text($tag->tag, FORMAT_HTML); + } + } + $table->define_headers(array(get_string('import_step1_table_title', 'oublog'), + get_string('import_step1_table_posted', 'oublog'), + $taghead, + get_string('import_step1_table_include', 'oublog'))); + echo html_writer::start_tag('form', array('method' => 'post', 'action' => qualified_me())); + $untitledcount = 1; + foreach ($posts as &$post) { + $tagcol = ''; + if (isset($post->tags)) { + // Create tag column for post. + foreach ($post->tags as $tagid => $tag) { + $newtagval = empty($tags) ? $tagid : $tags . ",$tagid"; + $turl = new moodle_url('/mod/oublog/import.php', + array_merge($params, $stepinfo, array('tags' => $newtagval))); + $tagcol .= html_writer::link($turl, format_text($tag, FORMAT_HTML), + array('class' => 'oublog_import_tag')); + } + } + if (empty($post->title)) { + $post->title = get_string('untitledpost', 'oublog') . ' ' . $untitledcount; + $untitledcount++; + } + $importcol = html_writer::checkbox('post_' . $post->id, $post->id, in_array($post->id, $preselected), + get_string('import_step1_include_label', 'oublog', format_string($post->title))); + $table->add_data(array( + format_string($post->title), + oublog_date($post->timeposted), + $tagcol, $importcol)); + } + $module = array ('name' => 'mod_oublog'); + $module['fullpath'] = '/mod/oublog/module.js'; + $module['requires'] = array('node', 'node-event-delegate', 'querystring'); + $PAGE->requires->strings_for_js(array('import_step1_all', 'import_step1_none'), 'oublog'); + $PAGE->requires->js_init_call('M.mod_oublog.init_posttable', null, false, $module); + $table->finish_output(); + echo html_writer::start_div(); + foreach (array_merge($params, $stepinfo, array('step' => 2, 'sesskey' => sesskey())) as $param => $value) { + echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $param, 'value' => $value)); + } + echo html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'submit', + 'value' => get_string('import_step1_submit', 'oublog'))); + echo html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'cancel', + 'value' => get_string('cancel'))); + echo html_writer::end_div(); + echo html_writer::end_tag('form'); + } +} else if ($step == 2) { + // Do the import, show feedback. First check access. + echo html_writer::tag('p', get_string('import_step2_inst', 'oublog')); + flush(); + $bid = required_param('bid', PARAM_INT); + if ($remote = optional_param('remote', false, PARAM_BOOL)) { + // Blog on remote server, use WS to get info. + if (!$result = oublog_import_remote_call('mod_oublog_get_blog_info', + array('username' => $USER->username, 'cmid' => $bid))) { + throw new moodle_exception('invalidcoursemodule', 'error'); + } + $boublogid = $result->boublogid; + $bcontextid = $result->bcontextid; + $boublogname = $result->boublogname; + $bcoursename = $result->bcoursename; + } else { + list($bid, $boublogid, $bcontextid, $boublogname, $bcoursename) = oublog_import_getbloginfo($bid); + } + require_sesskey(); + // Get selected and pre-selected posts. + $preselected = explode(',', optional_param('preselected', '', PARAM_SEQUENCE)); + $selected = array(); + foreach ($_POST as $name => $val) { + if (strpos($name, 'post_') === 0) { + $selected[] = $val; + } + } + $selected = array_filter(array_unique(array_merge($selected, $preselected), SORT_NUMERIC)); + $stepinfo = array('step' => 2, 'bid' => $bid, 'preselected' => implode(',', $selected), 'remote' => $remote); + if (empty($selected)) { + echo html_writer::tag('p', get_string('import_step2_none', 'oublog')); + echo $OUTPUT->continue_button(new moodle_url('/mod/oublog/import.php', + array_merge($params, $stepinfo, array('step' => 1)))); + echo $OUTPUT->footer(); + exit; + } + + if ($remote) { + $posts = oublog_import_remote_call('mod_oublog_get_blog_posts', + array('username' => $USER->username, 'blogid' => $boublogid, 'selected' => implode(',', $selected), + 'inccomments' => $oublog->allowcomments != OUBLOG_COMMENTS_PREVENT, 'bcontextid' => $bcontextid)); + } else { + $posts = oublog_import_getposts($boublogid, $bcontextid, $selected, + $oublog->allowcomments != OUBLOG_COMMENTS_PREVENT, $USER->id); + } + + if (empty($posts)) { + print_error('import_step2_none', 'oublog'); + } + + // Get/create user blog instance for this activity. + if ($oublog->global) { + list($notused, $oubloginstance) = oublog_get_personal_blog($USER->id); + } else { + if (!$oubloginstance = $DB->get_record('oublog_instances', array('oublogid' => $oublog->id, 'userid' => $USER->id))) { + if (!$oubloginstance = oublog_add_bloginstance($oublog->id, $USER->id)) { + print_error('Failed to create blog instance'); + } + $oubloginstance = (object) array('id' => $oubloginstance); + } + } + // Copy all posts (updating group), checking for conflicts first. + $bar = new progress_bar('oublog_import_step2_prog', 500, true); + $conflicts = array(); + $ignoreconflicts = optional_param('ignoreconflicts', false, PARAM_BOOL); + $cur = 0; + $files = get_file_storage(); + foreach ($posts as $post) { + $cur++; + // Is there a conflict, if so add to our list so we can re-do these later. + if (!$ignoreconflicts && $DB->get_records('oublog_posts', array('title' => $post->title, + 'timeposted' => $post->timeposted, 'oubloginstancesid' => $oubloginstance->id))) { + $conflicts[] = $post->id; + $bar->update($cur, count($posts), get_string('import_step2_prog', 'oublog')); + continue; + } + $trans = $DB->start_delegated_transaction(); + $newpost = new stdClass(); + $newpost->oubloginstancesid = $oubloginstance->id; + $newpost->groupid = 0;// Force 0 as individual mode. + $newpost->title = $post->title; + $newpost->message = $post->message; + $newpost->timeposted = $post->timeposted; + $newpost->allowcomments = $post->allowcomments; + $newpost->timeupdated = time(); + $newpost->visibility = $post->visibility; + if ($oublog->maxvisibility < $newpost->visibility) { + $newpost->visibility = $oublog->maxvisibility; + } + if ($oublog->allowcomments == OUBLOG_COMMENTS_PREVENT) { + $newpost->allowcomments = OUBLOG_COMMENTS_PREVENT; + } + $newid = $DB->insert_record('oublog_posts', $newpost); + // Add tags copied from original + new short code tag. + if ($bcoursename) { + $tagname = textlib::strtolower($bcoursename); + if (!$bctag = $DB->get_field('oublog_tags', 'id', + array('tag' => $tagname))) { + $bctag = $DB->insert_record('oublog_tags', + (object) array('tag' => $tagname)); + } + if (!isset($post->tags)) { + $post->tags = array((object) array('id' => $bctag, 'tag' => $tagname)); + } else { + $post->tags[] = (object) array('id' => $bctag, 'tag' => $tagname); + } + } + if (isset($post->tags)) { + foreach ($post->tags as $tagval) { + if (!$remote || ($bcoursename && $tagval == $tagname)) { + $DB->insert_record('oublog_taginstances', (object) array( + 'oubloginstancesid' => $oubloginstance->id, 'postid' => $newid, 'tagid' => $tagval->id)); + } else { + // Find/create tag. + if (!$tagid = $DB->get_field('oublog_tags', 'id', array('tag' => $tagval->tag))) { + $tagid = $DB->insert_record('oublog_tags', (object) array('tag' => $tagval->tag)); + } + $DB->insert_record('oublog_taginstances', (object) array( + 'oubloginstancesid' => $oubloginstance->id, 'postid' => $newid, 'tagid' => $tagid)); + } + } + } + // Copy across images/attachments (no maximum check). + if ($remote) { + // Download remote files and add to new post. + oublog_import_remotefiles($post->images, $context->id, $newid); + oublog_import_remotefiles($post->attachments, $context->id, $newid); + } else { + foreach ($post->images as $image) { + $files->create_file_from_storedfile(array('itemid' => $newid, 'contextid' => $context->id), $image); + } + foreach ($post->attachments as $attach) { + $files->create_file_from_storedfile(array('itemid' => $newid, 'contextid' => $context->id), $attach); + } + } + // Copy own comments (if enabled on this blog). + if (!empty($post->comments)) { + foreach ($post->comments as $comment) { + $oldcid = $comment->id; + unset($comment->id); + $comment->postid = $newid; + $comment->userid = $USER->id; + $newcid = $DB->insert_record('oublog_comments', $comment); + // Copy comment images. + if (!$remote) { + foreach ($comment->images as $image) { + $files->create_file_from_storedfile(array('itemid' => $newcid, + 'contextid' => $context->id), $image); + } + } else { + oublog_import_remotefiles($comment->images, $context->id, $newcid); + } + } + // Inform completion system, if available. + $completion = new completion_info($course); + if ($completion->is_enabled($cm) && ($oublog->completioncomments)) { + $completion->update_state($cm, COMPLETION_COMPLETE); + } + } + // Update search (add required properties to newpost). + $newpost->id = $newid; + $newpost->userid = $USER->id; + $newpost->tags = array(); + if (isset($post->tags)) { + foreach ($post->tags as $tag) { + $newpost->tags[$tag->id] = $tag->tag; + } + } + oublog_search_update($newpost, $cm); + $trans->allow_commit(); + $bar->update($cur, count($posts), get_string('import_step2_prog', 'oublog')); + } + if (count($conflicts) != count($posts)) { + // Inform completion system, if available. + $completion = new completion_info($course); + if ($completion->is_enabled($cm) && ($oublog->completionposts)) { + $completion->update_state($cm, COMPLETION_COMPLETE); + } + } + echo html_writer::tag('p', get_string('import_step2_total', 'oublog', + (count($posts) - count($conflicts)))); + $continueurl = '/mod/oublog/view.php?id=' . $cm->id; + if ($oublog->global) { + $continueurl = '/mod/oublog/view.php?user=' . $USER->id; + } + if (count($conflicts)) { + // Enable conflicts to be ignored by resending only these. + $stepinfo['ignoreconflicts'] = true; + $stepinfo['preselected'] = implode(',', $conflicts); + $url = new moodle_url('/mod/oublog/import.php', array_merge($params, $stepinfo)); + $conflictimport = new single_button($url, get_string('import_step2_conflicts_submit', 'oublog')); + echo $OUTPUT->confirm(get_string('import_step2_conflicts', 'oublog', count($conflicts)), + $conflictimport, $continueurl); + } else { + echo $OUTPUT->continue_button($continueurl); + } +} + +echo html_writer::end_div(); +echo $OUTPUT->footer(); diff --git a/internaldoc/testcase.importpages.txt b/internaldoc/testcase.importpages.txt new file mode 100644 index 0000000..2a7c130 --- /dev/null +++ b/internaldoc/testcase.importpages.txt @@ -0,0 +1,297 @@ +This script describes steps to test the OU Blog facility for importing posts from a user +perspective. It is intended to cover most of the UI and features. + +NOTE: In this test case, the word 'blog' always refers to the OU blog. + +The test steps in this script follow on from each other and aren't independent. + + +Initial setup +============= + +This test case requires: + +- 2 users. S1 - a student user and an admin user (both system-wide, not 'view as' or course roles). +- Two test courses, referred to as TC1 and TC2, that are visible to students. +- User S1 should be enrolled on TC1 and TC2. + +The test server must have debugging set to DEVELOPER level and to display +errors; during all parts of the test script, there should be no debugging +warnings. + + +CRE Creating blog and data +=========================== + +CRE01 / admin. + Enter TC1. + Create a blog called WCB01 - a whole-course blog (blog together, no group mode). + Create a blog called IB01 - set to Separate individual blogs, enable comments from logged-in users. + Create a blog called IB02 - set to Separate individual blogs. + Create a blog called IB03 - set to Separate individual blogs - make this hidden from students. + +CRE02 / admin + Enter TC2. + Create a blog called IB04 - set to Separate individual blogs, enable comments from logged-in users. + +CRE03 / admin + Enter TC1 and enter blog IB01. + Create a new post, titled "CRE03 - admin", any message text, other options at default. + +CRE04 / S1 (change) + Enter TC1 and enter blog IB01. + Create a new post: + Leave title as blank + Add any text to message, upload any image into the text + Ensure comments are allowed + Add the same image as an attachment + Add tags "a,b,c" + Save + -- Verify new post is displayed in blog view page. + +CRE05 / S1 + Repeat CRE04, using tags value "a,b". + +CRE06 / S1 + Repeat CRE04, making title "CRE06" and tags value "a". + +CRE07 / S1 + Against CRE06 post, add a new comment by selecting 'Add your comment' + Add any text to the comment message, include an uploaded image + Save comment + -- Verify comment text with image shows. + +CRE08 / admin (change) + Enter TC1 and enter blog IB01. + Against CRE06 post, add a new comment by selecting 'Add your comment' + Add any text to the comment message + Save comment + -- Verify comment text with image shows. + +AVL Import pages availability +============================= + +AVL01 / admin + Enter TC2 and select blog IB04 + -- Verify 'Import posts' button is not shown next to New blog post button. + +AVL02 / admin + Select Edit settings under OU blog administration (Administration block) + Check 'Enable post import' checkbox + Select Save and display + -- Verify 'Import posts' button is now shown next to 'New blog post' button. + +AVL03 / admin + Enter TC1 and select blog WCB01 + -- Verify 'Import posts' button is not shown next to New blog post button. + +AVL04 / admin + Select Edit settings under OU blog administration (Administration block) + Check 'Enable post import' checkbox + Select Save and display + -- Verify warning is shown advising post import cannot be enabled and form is not saved. + Exit edit settings page. + +SEL Import page selection +========================= + +SEL01 / S1 (change) + Enter TC2 and select blog IB04 + Select the 'Import posts' button + -- Verify that the 'Import pages' screen is shown. This should only list the following: + -- TC1 + -- IB01 (3 posts) [Where IB01 is a link] + -- IB02 (0 posts) [Where IB02 is not a link] + -- [Note that other courses/blogs not setup in this test may appear depending on system content] + +SEL02 / S1 + Select the IB01 link + -- Verify page changes, and shows: + -- Import from: [TC1 shortname and full name] : IB01 + -- A table with 'Title', 'Date posted', 'Tags' and 'Include in import' columns + -- Table should contain 3 rows: + -- CRE06, [Date post was created], a + -- Untitled post 1, [Date post was created], a,b + -- Untitled post 2, [Date post was created], a,b,c. + +SEL03 / S1 + Select Date posted column heading link + -- Verify that posts re-order into reverse order, Date posted should now have an 'up' symbol + Select Title column heading link + -- Verify that post re-order alphabetically, Title should now have an 'up' symbol + +SEL04 / S1 + Select link on any tag 'a' in the Tag column of the table + -- Verify page reloads - information should remain the same. A "[cross] a" link should appear in Tags column header + Select link on any tag 'b' in the Tag column of the table + -- Verify page reloads - only the two posts with tag b should now show in the table, + -- "[cross] a" and "[cross] b" links should appear in Tags column header + Select link on tag 'c' in the tag column of the table + -- Verify page reloads - only the post with tag c should now show in the table, + -- "[cross] a", "[cross] b" and "[cross] c" links should appear in Tags column header + Select the icon next to 'c' in Tags column header + -- Verify page reloads - the two posts with tag b should now show in the table, + -- "[cross] a" and "[cross] b" links should appear in Tags column header. + +SEL05 / S1 (JavaScript enabled only) + Select 'Select all' link in 'Include in import' column header + -- Verify all posts shown have 'Include in import' checked + Select 'Select none' link in 'Include in import' column header + -- Verify all posts shown have 'Include in import' not checked. + +SEL06 / S1 + Ensure no posts have 'Include in import' checkbox checked + Select 'Import posts' button + -- Verify page changes and 'No posts selected for import' message displayed + Select 'Continue' button + -- Verify returned to post selection table. + +SEL07 / S1 + Select 'Cancel' button + -- Verify return to blog selection screen. + +IMP Import blog pages +===================== + +IMP01/ S1 + Enter TC2 and select blog IB04, then select 'Import pages' button + Select link to IB01 (3 posts) + Select 'Include in import' checkbox for all three posts shown + Select 'Import posts' + -- Verify page changes and 'Importing posts:' is displayed along with a progress bar + -- Verify progress bar reaches 100% and message 'Imported 3 posts' is displayed. + +IMP02 / S1 + Select the 'Continue' button + -- Verify IB04 main blog view page displayed + -- Verify 3 posts now display, check content, tags and attachments against original + Select 'Permalink' for post CRE06 + -- Verify 1 comment is displayed (by user s1), check content. + +IMP03 / S1 + Repeat IMP01 + -- Verify 'Imported 0 posts' displayed + -- Verify message '3 posts to import were identified as conflicts with existing posts.' displayed + Select 'Cancel' + -- Verify IB04 main blog view page displayed, with 3 posts listed. + +IMP04 / S1 + Repeat IMP01 + Select 'Import conflicting posts' button + -- Verify page changes and 'Importing posts:' is displayed along with a progress bar + -- Verify progress bar reaches 100% and message 'Imported 3 posts' is displayed. + Repeat IMP02 + -- Verify as per IMP02, but there are now exact duplicates of all three posts + +LPB Import using local Personal blog +==================================== + +LPB01 / S1 + Enter TC2 and select blog IB04, then select 'Import pages' button + -- Verify user's personal blog is displayed in blog list according to following rules: + -- User has not visited their personal blog - Not displayed + -- User has no posts in their personal blog - Displayed, but with no link + -- User has posts in their personal blog - Displayed, with link + If no personal blog posts available visit the personal blog (/mod/oublog/view.php) + Add 1 new post (any content, private visibility) to the personal blog + Repeat this test step. + +LPB02 / S1 + Select user S1's personal blog from the Import pages blog list + Select any post available to import and start the importing process + -- Verify the post imported process completed correctly + Select Continue button + -- Verify you are returned to user S1's personal blog. + +LPB03 / S1 + Enter the personal blog for user S1 (/mod/oublog/view.php) + -- Verify 'Import posts' button is not available. + +LPB04 / admin (change) + Enter the personal blog for user admin (/mod/oublog/view.php) + Select Edit settings under OU blog administration (Administration block) + Check 'Enable post import' checkbox + Select Save and display + -- Verify 'Import posts' button is now shown next to 'New blog post' button. + +LPB05/ S1 (change) + Enter the personal blog for user S1 (/mod/oublog/view.php), then select 'Import pages' button + Select link to IB01 + Select 'Include in import' checkbox for one post - CRE06 + Select 'Import posts' + -- Verify page changes and 'Importing posts:' is displayed along with a progress bar + -- Verify progress bar reaches 100% and message 'Imported 1 posts' is displayed. + +LPB06 / S1 + Select the 'Continue' button + -- Verify personal blog view page displayed for user S1 + -- Verify CRE06 post now displays as a private post, check content, tags and attachments against original + Select 'Permalink' for post CRE06 + -- Verify 1 comment is displayed (by user s1), check content. + +RPB Import using remote personal blog +===================================== + +Note that this test requires a separate test server with personal blog enabled(referred to as remote in this test). +Both servers will need to be configured to enable page importing between them prior to testing. +The S1 test user must have identical usernames on both servers. + +RPB01 / admin + Access the remote server + Enter the personal blog for user admin (/mod/oublog/view.php) + Select Edit settings under OU blog administration (Administration block) + Check 'Enable post import' checkbox + Select Save and display + -- Verify 'Import posts' button is now shown next to 'New blog post' button. + +RPB02 / S1 (change) + Access the remote server + Enter the personal blog for user S1 (/mod/oublog/view.php) + Select the 'Blog options' link + Alter the blog name to identify that the blog is on the remote server, Save changes + Select 'Import posts' button + -- Verify a list of courses and blogs is displayed that correspond to the original test server + -- Verify course TC1, blog IB01 is displayed. + +RPB03 / S1 + Select blog IB01 from the import posts page + -- Verify page changes, and shows: + -- Import from: [TC1 shortname and full name] : IB01 + -- A table with 'Title', 'Date posted', 'Tags' and 'Include in import' columns + -- Table should contain 3 rows: + -- CRE06, [Date post was created], a + -- Untitled post 1, [Date post was created], a,b + -- Untitled post 2, [Date post was created], a,b,c. + +RPB04 / S1 + Select 'Include in import' checkbox for all three posts shown + Select 'Import posts' + -- Verify page changes and 'Importing posts:' is displayed along with a progress bar + -- Verify progress bar reaches 100% and message 'Imported 3 posts' is displayed + Select the 'Continue' button + -- Verify S1 user personal blog view page displayed + -- Verify 3 posts now display, check content, tags and attachments against original + Select 'Permalink' for post CRE06 + -- Verify 1 comment is displayed (by user s1), check content. + +RPB04 / S1 + Access original server used in earlier tests + Enter TC2 and select blog IB04, then select 'Import pages' button + -- Very user S1's personal blog is displayed twice, one name should match name set in RPB02 + +RPB05 / S1 + Select link for personal blog on remote server from the import posts page + -- Verify a table of posts is displayed; this should include the 3 posts imported in RPB04 + Select 'Include in import' checkbox for all three posts imported from IB01 in RPB04 + Select 'Import posts' + -- Verify 'Imported 0 posts' displayed + -- Verify message '3 posts to import were identified as conflicts with existing posts.' displayed + Select 'Import conflicting posts' button + -- Verify page changes and 'Importing posts:' is displayed along with a progress bar + -- Verify progress bar reaches 100% and message 'Imported 3 posts' is displayed + Select the 'Continue' button + -- Verify IB04 blog view page displayed + -- Verify the 3 posts now display (these have highest post number when looking at permalink url), + check content, tags and attachments against original + Select 'Permalink' for post CRE06 + -- Verify 1 comment is displayed (by user s1), check content. diff --git a/lang/en/oublog.php b/lang/en/oublog.php index 09a53d5..a3267c6 100644 --- a/lang/en/oublog.php +++ b/lang/en/oublog.php @@ -426,6 +426,12 @@ $string['maxattachmentsize_help'] = 'This setting specifies the largest size of image/file that can be used in a blog post.'; $string['attachments_help'] = 'You can optionally attach one or more files to a blog post. If you attach an image, it will be displayed after the message.'; +$string['remoteserver'] = 'Import from remote server'; +$string['configremoteserver'] = 'Root address (wwwroot) of remote server to be used for post imports. +Blogs on this server will be shown in addition to those on local site when importing posts.'; +$string['remotetoken'] = 'Import remote server token'; +$string['configremotetoken'] = 'Web service user token for oublog webservices on import remote server.'; + $string['reportingemail'] = 'Reporting email addresses'; $string['reportingemail_help'] = 'This setting specifies the email addresses of those who will be informed about issues with posts or comments within the OUBlog. @@ -465,6 +471,7 @@ $string['commentposts_info_thisyear'] = 'Posts with the most number of comments added in the past year'; $string['commentposts_info_thismonth'] = 'Posts with the most number of comments added in the past month'; +// Delete and Email. $string['emailcontenthtml'] = 'This is a notification to advise you that your {$a->activityname} post with the following details has been deleted by \'{$a->firstname} {$a->lastname}\':

@@ -485,3 +492,31 @@ $string['sendanddelete'] = 'Send and delete'; $string['extra_emails'] = 'Email address of other recipients'; $string['extra_emails_help'] = 'Enter one or more email address(es) separated by spaces or semicolons.'; + +// Import pages. +$string['allowimport'] = 'Enable post import'; +$string['allowimport_help'] = 'Allow any user to import pages from other blog activities they have access to.'; +$string['allowimport_invalid'] = 'Posts can only be imported when activity is set to individual mode.'; +$string['import'] = 'Import posts'; +$string['import_notallowed'] = 'Importing posts is disabled for this {$a}.'; +$string['import_step0_nonefound'] = 'You do not have access to any activities where posts can be imported from.'; +$string['import_step0_inst'] = 'Select an activity to import posts from:'; +$string['import_step0_numposts'] = '({$a} posts)'; +$string['import_step1_inst'] = 'Select posts to import:'; +$string['import_step1_from'] = 'Import from:'; +$string['import_step1_table_title'] = 'Title'; +$string['import_step1_table_posted'] = 'Date posted'; +$string['import_step1_table_tags'] = 'Tags'; +$string['import_step1_table_include'] = 'Include in import'; +$string['import_step1_addtag'] = 'Filter by tag - {$a}'; +$string['import_step1_removetag'] = 'Remove tag filter - {$a}'; +$string['import_step1_include_label'] = 'Import post - {$a}'; +$string['import_step1_submit'] = 'Import posts'; +$string['import_step1_all'] = 'Select all'; +$string['import_step1_none'] = 'Select none'; +$string['import_step2_inst'] = 'Importing posts:'; +$string['import_step2_none'] = 'No posts selected for import.'; +$string['import_step2_prog'] = 'Importing in progress'; +$string['import_step2_total'] = 'Imported {$a} posts.'; +$string['import_step2_conflicts'] = '{$a} posts to import were identified as conflicts with existing posts.'; +$string['import_step2_conflicts_submit'] = 'Import conflicting posts'; diff --git a/lib.php b/lib.php index 6f3dc58..c4d90c7 100644 --- a/lib.php +++ b/lib.php @@ -792,8 +792,9 @@ function oublog_pluginfile($course, $cm, $context, $filearea, $args, $forcedownl } // Make sure we're allowed to see it... - - if ($filearea != 'summary' && !oublog_can_view_post($post, $USER, $context, $oublog->global)) { + // Check if coming from webservice - if so always allow. + $ajax = constant('AJAX_SCRIPT') ? true : false; + if ($filearea != 'summary' && !$ajax && !oublog_can_view_post($post, $USER, $context, $oublog->global)) { return false; } if ($filearea == 'attachment') { diff --git a/locallib.php b/locallib.php index ab1b4e7..809cdf9 100644 --- a/locallib.php +++ b/locallib.php @@ -4194,3 +4194,346 @@ function oublog_oualerts_enabled() { return false; } + +/** + * Calls a remote server externallib web services during import + * We use the Moodle curl cache to store responses (for 120 secs default) + * @param string $function + * @param array $params (name => value) + * @return array json decoded result or false if not configured + */ +function oublog_import_remote_call($function, $params = null) { + $settings = get_config('mod_oublog'); + if (empty($settings->remoteserver) && empty($settings->remotetoken)) { + return false; + } + if (is_null($params)) { + $params = array(); + } + $curl = new curl(array('cache' => true, 'module_cache' => 'oublog_import')); + $url = $settings->remoteserver . '/webservice/rest/server.php'; + $params['moodlewsrestformat'] = 'json'; + $params['wsfunction'] = $function; + $params['wstoken'] = $settings->remotetoken; + $options = array(); + $options['RETURNTRANSFER'] = true; + $options['SSL_VERIFYPEER'] = false; + $result = $curl->get($url, $params, $options); + $json = json_decode($result); + if (empty($result) || $curl->get_errno() || !empty($json->exception)) { + $errinfo = !empty($json->exception) ? !empty($json->debuginfo) ? $json->debuginfo : $json->message : $curl->error; + throw new moodle_exception('Failed to contact ' . $settings->remoteserver . ' : ' . $errinfo); + return false; + } + return $json; +} + +/** + * Class defined here to extend the curl class and call the multi() function with no options set. + */ +class oublog_public_curl_multi extends curl { + public function public_multi($requests, $options = array()) { + return $this->multi($requests, $options); + } +} + +/** + * Downloads files from remote system and adds into local file table + * Uses webservice pluginfile so lib picks up is from this and allows access to files. + * @param array $files - array of file-like objects (returned from externallib get_posts) + * @param int $newcontid - context id to use for new files + * @param int $newitemid - item id to use for new files + */ +function oublog_import_remotefiles($files, $newcontid, $newitemid) { + $settings = get_config('mod_oublog'); + if (empty($settings->remoteserver) && empty($settings->remotetoken) || empty($files)) { + return false; + } + $fs = get_file_storage(); + $options = array('RETURNTRANSFER' => true); + $requests = array(); + foreach ($files as $file) { + $requests[] = array('url' => $settings->remoteserver . '/webservice/pluginfile.php/' . + $file->contextid . '/mod_oublog/' . $file->filearea . '/' . $file->itemid . $file->filepath . + rawurlencode($file->filename) . '?token=' . $settings->remotetoken); + } + $curl = new oublog_public_curl_multi(); + $responses = $curl->public_multi($requests, $options); + $count = count($files); + for ($i = 0; $i < $count; $i++) { + if (empty($files[$i])) { + continue; + } + $fileinfo = $files[$i]; + $fileinfo->contextid = $newcontid; + $fileinfo->itemid = $newitemid; + $fileinfo->component = 'mod_oublog'; + $fs->create_file_from_string($fileinfo, $responses[$i]); + } + return true; +} + +/** + * Gets all blogs on the system (and on remote system if defined) that can be imported from + * @param int $userid + * @param int $curcmid Current blog cmid (excludes this from list returned) + * @return array of blog 'info objects' [cmid, name, coursename, numposts] + */ +function oublog_import_getblogs($userid = 0, $curcmid = null) { + global $DB, $USER, $SITE; + if ($userid == 0) { + $userid = $USER->id; + } + $retarray = array(); + $courses = enrol_get_users_courses($userid, true, array('modinfo', 'sectioncache')); + array_unshift($courses, get_site()); + $courses[$SITE->id]->site = true;// Mark the global site. + foreach ($courses as $course) { + $crsmodinfo = get_fast_modinfo($course, $userid); + $blogs = $crsmodinfo->get_instances_of('oublog'); + foreach ($blogs as $blogcm) { + if ($curcmid && $blogcm->id == $curcmid) { + continue;// Ignore current blog. + } + $blogcontext = context_module::instance($blogcm->id); + if ($blogoublog = $DB->get_record('oublog', array('id' => $blogcm->instance))) { + $canview = $blogcm->uservisible; + if ($canview) { + $canview = has_capability('mod/oublog:view', $blogcontext, $userid); + } + if ($blogoublog->global) { + // Ignore uservisible for global blog and only check cap. + $canview = has_capability('mod/oublog:viewpersonal', context_system::instance(), $userid); + } + if ($canview) { + if ($blogoublog->global) { + // Global blog, only show if user instance available. + if (!$blogoubloginst = $DB->get_record('oublog_instances', + array('oublogid' => $blogoublog->id, 'userid' => $userid))) { + continue; + } + } else if ($blogoublog->individual == OUBLOG_NO_INDIVIDUAL_BLOGS) { + // Only allow individual blogs. + continue; + } + $blogob = new stdClass(); + $blogob->cmid = $blogcm->id; + $blogob->coursename = ''; + if (!$blogoublog->global) { + $blogob->coursename = $blogcm->get_course()->shortname . ' ' . + get_course_display_name_for_list($blogcm->get_course()); + } + // Get number of posts (specific to user, doesn't work with group blogs). + $sql = 'SELECT count(p.id) as total + FROM {oublog_posts} p + INNER JOIN {oublog_instances} bi on bi.id = p.oubloginstancesid + WHERE bi.userid = ? + AND bi.oublogid = ? + AND p.deletedby IS NULL'; + $count = $DB->get_field_sql($sql, array($userid, $blogoublog->id)); + $blogob->numposts = $count ? $count : 0; + $blogoublogname = $blogcm->get_formatted_name(); + if ($blogoublog->global) { + $blogoublogname = $blogoubloginst->name; + } + $blogob->name = $blogoublogname; + $retarray[] = $blogob; + } + } + } + } + return $retarray; +} +/** + * Returns blog info - cm, oublog + * Also checks is a valid blog for import + * (Throws exception on access error) + * @param int $cmid + * @param int $userid + * @return array (cm id, oublog id, context id, blog name, course shortname) + */ +function oublog_import_getbloginfo($cmid, $userid = 0) { + global $DB, $USER; + if ($userid == 0) { + $userid = $USER->id; + } + $bcourse = $DB->get_record_select('course', + 'id = (SELECT course FROM {course_modules} WHERE id = ?)', array($cmid), + '*', MUST_EXIST); + $bmodinfo = get_fast_modinfo($bcourse, $userid); + $bcm = $bmodinfo->get_cm($cmid); + if ($bcm->modname !== 'oublog') { + throw new moodle_exception('invalidcoursemodule', 'error'); + } + if (!$boublog = $DB->get_record('oublog', array('id' => $bcm->instance))) { + throw new moodle_exception('invalidcoursemodule', 'error'); + } + $bcontext = get_context_instance(CONTEXT_MODULE, $bcm->id); + $canview = $bcm->uservisible; + if ($canview) { + $canview = has_capability('mod/oublog:view', $bcontext, $userid); + } + if ($boublog->global) { + // Ignore uservisible for global blog and only check cap. + $canview = has_capability('mod/oublog:viewpersonal', context_system::instance(), $userid); + } + if (!$canview || + (!$boublog->global && $boublog->individual == OUBLOG_NO_INDIVIDUAL_BLOGS)) { + // Not allowed to get pages from selected blog. + throw new moodle_exception('import_notallowed', 'oublog', '', oublog_get_displayname($boublog)); + } + if ($boublog->global) { + $boublogname = $DB->get_field('oublog_instances', 'name', + array('oublogid' => $boublog->id, 'userid' => $userid)); + $shortname = ''; + } else { + $boublogname = $bcm->get_course()->shortname . ' ' . + get_course_display_name_for_list($bcm->get_course()) . + ' : ' . $bcm->get_formatted_name(); + $shortname = $bcm->get_course()->shortname; + } + return array($bcm->id, $boublog->id, $bcontext->id, $boublogname, $shortname); +} + +/** + * Returns importable posts, total posts and selected tag info + * @param int $blogid - ID of blog + * @param string $sort - SQL sort for posts + * @param int $userid + * @param int $page - page number for pagination (100 per page) + * @param array $tags - comma separated sequence of selected tag ids to filter by + * @return array (posts, total in DB, selected tag info) + */ +function oublog_import_getallposts($blogid, $sort, $userid = 0, $page = 0, $tags = null) { + global $DB, $USER; + if ($userid == 0) { + $userid = $USER->id; + } + $perpage = 100;// Must match value in import.php. + $sqlparams = array($userid, $blogid); + $tagjoin = ''; + $tagwhere = ''; + $tagnames = ''; + $total = 0; + if ($tags) { + $tagarr = array_unique(explode(',', $tags)); + // Filter by joining tag instances. + list($taginwhere, $tagparams) = $DB->get_in_or_equal($tagarr); + $tagjoin = "INNER JOIN ( + SELECT ti.postid, count(*) as tagcount FROM {oublog_taginstances} ti WHERE ti.tagid $taginwhere + group by ti.postid) as hastags on hastags.postid = p.id"; + $tagwhere = 'AND hastags.tagcount = ?'; + $sqlparams = array_merge($tagparams, $sqlparams, array(count($tagarr))); + // Get selected tag names. + $tagnames = $DB->get_records_select('oublog_tags', "id $taginwhere", $tagparams, 'tag ASC'); + } + $sql = "SELECT p.id, p.timeposted, p.title + FROM {oublog_posts} p + INNER JOIN {oublog_instances} bi on bi.id = p.oubloginstancesid + $tagjoin + WHERE bi.userid = ? + AND bi.oublogid = ? + AND p.deletedby IS NULL + $tagwhere + ORDER BY p." . $sort; + + $limitfrom = $page * $perpage; + + if ($posts = $DB->get_records_sql($sql, $sqlparams, $limitfrom, $perpage)) { + // Add in post tags from single query. + list($inwhere, $inparams) = $DB->get_in_or_equal(array_keys($posts)); + $tsql = 'SELECT t.*, ti.postid + FROM {oublog_taginstances} ti + INNER JOIN {oublog_tags} t ON ti.tagid = t.id + WHERE ti.postid ' . $inwhere . ' ORDER BY t.tag'; + $rs = $DB->get_recordset_sql($tsql, $inparams); + foreach ($rs as $tag) { + $postid = $tag->postid; + if (!isset($posts[$postid]->tags)) { + $posts[$postid]->tags = array(); + } + $posts[$postid]->tags[$tag->id] = $tag->tag; + } + $rs->close(); + // Add total record count. + $total = $DB->get_field_sql('SELECT count(tot.id) FROM (' . $sql . ') as tot', $sqlparams); + } + return array($posts, $total, $tagnames); +} + +/** + * Returns posts specified (inc tags and comments) + * @param int $blogid - oublog id + * @param int $bcontextid - oublog mod context id + * @param array $selected - array of selected post ids + * @param bool $inccomments - include comments? + * @param int $userid - user id (ensures user is post author) + * @return array posts + */ +function oublog_import_getposts($blogid, $bcontextid, $selected, $inccomments = false, $userid = 0) { + global $DB, $USER; + if ($userid == 0) { + $userid = $USER->id; + } + list($inwhere, $sqlparams) = $DB->get_in_or_equal($selected); + $sql = "SELECT p.* + FROM {oublog_posts} p + INNER JOIN {oublog_instances} bi on bi.id = p.oubloginstancesid + WHERE bi.userid = ? + AND bi.oublogid = ? + AND p.deletedby IS NULL + AND p.id $inwhere + ORDER BY p.id ASC"; + + $sqlparams = array_merge(array($userid, $blogid), $sqlparams); + if (!$posts = $DB->get_records_sql($sql, $sqlparams)) { + return array(); + } + $files = get_file_storage(); + // Get post images and attachments. + foreach ($posts as &$post) { + $post->comments = array();// Add in a comment array for use later. + $post->images = $files->get_area_files($bcontextid, 'mod_oublog', 'message', $post->id, 'itemid', false); + $post->attachments = $files->get_area_files($bcontextid, 'mod_oublog', 'attachment', $post->id, 'itemid', false); + if (empty($post->images)) { + $post->images = array(); + } + if (empty($post->attachments)) { + $post->attachments = array(); + } + } + // Add in post tags from single query. + list($inwhere, $inparams) = $DB->get_in_or_equal(array_keys($posts)); + $tsql = 'SELECT t.*, ti.postid + FROM {oublog_taginstances} ti + INNER JOIN {oublog_tags} t ON ti.tagid = t.id + WHERE ti.postid ' . $inwhere; + $rs = $DB->get_recordset_sql($tsql, $inparams); + foreach ($rs as $tag) { + $postid = $tag->postid; + if (!isset($posts[$postid]->tags)) { + $posts[$postid]->tags = array(); + } + $posts[$postid]->tags[] = $tag; + } + $rs->close(); + if ($inccomments) { + // Get comments for post on the page. + $sql = "SELECT c.* + FROM {oublog_comments} c + WHERE c.postid $inwhere AND c.deletedby IS NULL AND c.userid = ? + ORDER BY c.timeposted ASC"; + $inparams[] = $userid; + $rs = $DB->get_recordset_sql($sql, $inparams); + foreach ($rs as $comment) { + $comment->images = $files->get_area_files($bcontextid, 'mod_oublog', 'messagecomment', + $comment->id, 'itemid', false); + if (empty($comment->images)) { + $comment->images = array(); + } + $posts[$comment->postid]->comments[$comment->id] = $comment; + } + $rs->close(); + } + return $posts; +} diff --git a/mod_form.php b/mod_form.php index 5173b7e..8a08167 100644 --- a/mod_form.php +++ b/mod_form.php @@ -92,7 +92,7 @@ public function definition() { $mform->setDefault('maxattachments', $modulesettings->maxattachments); // Enable the stats block. - $mform->addElement('checkbox', 'statblockon', get_string('statblockon', 'oublog', 0)); + $mform->addElement('checkbox', 'statblockon', get_string('statblockon', 'oublog'), '', 0); $mform->addHelpButton('statblockon', 'statblockon', 'oublog'); // Show OU Alerts reporting link. @@ -112,6 +112,9 @@ public function definition() { $mform->addRule('displayname', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); + $mform->addElement('checkbox', 'allowimport', get_string('allowimport', 'oublog'), '', 0); + $mform->addHelpButton('allowimport', 'allowimport', 'oublog'); + $this->standard_grading_coursemodule_elements(); $mform->setDefault('grade', 0); @@ -193,6 +196,9 @@ public function get_data() { if (empty($data->displayname)) { $data->displayname = null; } + if (empty($data->allowimport)) { + $data->allowimport = 0; + } return $data; } @@ -213,6 +219,7 @@ public function data_preprocessing(&$default_values) { } public function validation($data, $files) { + global $DB; $errors = parent::validation($data, $files); if (!empty($data['groupmode']) && isset($data['allowcomments']) && $data['allowcomments'] == OUBLOG_COMMENTS_ALLOWPUBLIC) { @@ -226,6 +233,16 @@ public function validation($data, $files) { } } } + if (!empty($data['allowimport']) && $data['individual'] == OUBLOG_NO_INDIVIDUAL_BLOGS) { + // Can only import on individual or global blogs. + if (!empty($data['instance'])) { + if (!$DB->get_field('oublog', 'global', array('id' => $data['instance']))) { + $errors['allowimport'] = get_string('allowimport_invalid', 'oublog'); + } + } else { + $errors['allowimport'] = get_string('allowimport_invalid', 'oublog'); + } + } return $errors; } } diff --git a/module.js b/module.js index f8b4bee..7a6f59d 100644 --- a/module.js +++ b/module.js @@ -140,4 +140,84 @@ M.mod_oublog.init_deleteandemail = function(Y, cmid, postid) { e.preventDefault(); Y.one('a.oublog_deleteandemail_' + postid).focus(); }); + +}; + +/* Import table */ +M.mod_oublog.init_posttable = function(Y) { + var includehead = Y.one('.flexible .header.c3'); + var postchecks = Y.all('.flexible td.c3 input[type=checkbox]'); + if (includehead && postchecks) { + // Add select all/none links to column header. + includehead.append('' + M.util.get_string('import_step1_all', 'oublog') + + ' / ' + M.util.get_string('import_step1_none', 'oublog') + ''); + var all = Y.one('.flexible .c3 .oublog_import_all'); + if (all) { + all.on('click', function(e) { + postchecks.set('checked', true); + postchecks.each(function(check){updatepreselect(check);}); + e.preventDefault(); + return false; + }); + } + var none = Y.one('.flexible .c3 .oublog_import_none'); + if (none) { + none.on('click', function(e) { + postchecks.set('checked', false); + postchecks.each(function(check){updatepreselect(check);}); + e.preventDefault(); + return false;}); + } + } + + var updatepreselect = function(check) { + var preselect = preselectinput.get('value'); + var id = check.get('name').substr(5); + if (check.get('checked')) { + // Add id to preselect value. + if (id) { + var prearray = preselect.split(','); + for (var i = 0, len = prearray.length; i < len; i++) { + if (prearray[i] == id) { + // Already have, return. + return; + } + } + prearray.push(id); + preselectinput.set('value', prearray.join()); + updatelinks(prearray); + } + } else { + // De-selecting, remove from preselect. + if (preselect && id) { + var prearray = preselect.split(','); + for (var i = 0, len = prearray.length; i < len; i++) { + if (prearray[i] == id) { + prearray.splice(i, 1); + preselectinput.set('value', prearray.join()); + updatelinks(prearray); + return; + } + } + } + } + }; + + var updatelinks = function(prearray) { + // Update link query strings. + Y.all('.oublog_import_step1 form .paging a, .flexible a').each(function(link) { + var linkurl = link.get('href'); + var params = Y.QueryString.parse(linkurl.substr(linkurl.indexOf('&'))); + params.preselected = prearray.join(); + var newurl = Y.QueryString.stringify(params); + link.set('href', linkurl.substr(0, linkurl.indexOf('&')) + '&' + newurl); + }); + }; + + var preselectinput = Y.one('form input[name=preselected]'); + if (postchecks && preselectinput) { + postchecks.on('click', function(check) { + updatepreselect(check.target); + }); + } }; diff --git a/settings.php b/settings.php index a645c7e..4b2115d 100644 --- a/settings.php +++ b/settings.php @@ -47,3 +47,10 @@ $settings->add(new admin_setting_configtext('mod_oublog/globalusageexclude', get_string('globalusageexclude', 'oublog'), get_string('globalusageexclude_desc', 'oublog'), '')); + +$settings->add(new admin_setting_configtext('mod_oublog/remoteserver', + get_string('remoteserver', 'oublog'), + get_string('configremoteserver', 'oublog'), '', PARAM_URL)); +$settings->add(new admin_setting_configtext('mod_oublog/remotetoken', + get_string('remotetoken', 'oublog'), + get_string('configremotetoken', 'oublog'), '', PARAM_ALPHANUM)); diff --git a/styles.css b/styles.css index 37d0688..07c3eb6 100644 --- a/styles.css +++ b/styles.css @@ -444,3 +444,46 @@ div.oublog-accordion-closed { cursor: pointer; font-size: 1.15em; } +/** Import Posts **/ +.oublog_importpostbutton { + margin-left: .5em; +} +.oublog_import_step UL { + list-style: none; +} +#page-mod-oublog-import .oublog_import_step h3 { + font-weight: bold; +} +.oublog_import_step1_from span { + font-weight: bold; +} +.oublog_import_step.oublog_import_step1 form div.no-overflow { + overflow: visible; +} +.oublog_import_step1 a.oublog_import_tag { + display: inline; + margin-left: .25em; +} +.oublog_import_step1 table.flexible { + width: 100%; +} +.oublog_import_step1 .flexible td label { + position: absolute; + top: -1000em; +} +.oublog_import_step1 .flexible .cell.c0 { + min-width: 10em; +} +.oublog_import_step1 .flexible .cell.c2 { + min-width: 8em; +} +.oublog_import_step1 .flexible .cell.c3, +.oublog_import_step1 .flexible .header.c3 { + max-width: 8em; +} +.oublog_import_step1 form div input[type=submit] { + margin-right: .5em; +} +.oublog_import_step1 .flexible .header.c3 a { + font-size: .75em; +} diff --git a/version.php b/version.php index fec043e..f4a0beb 100644 --- a/version.php +++ b/version.php @@ -23,7 +23,7 @@ * @package oublog **/ -$module->version = 2014012701; +$module->version = 2014012702; $module->requires = 2013040500; $module->cron = 60*60*4; // 4 hours. diff --git a/view.php b/view.php index d694986..ec68c9e 100644 --- a/view.php +++ b/view.php @@ -369,6 +369,13 @@ echo $OUTPUT->single_button(new moodle_url('/mod/oublog/editpost.php', array('blog' => $cm->instance)), $straddpost, 'get'); echo ''; + if ($oublog->allowimport && ($oublog->global || + $oublog->individual != OUBLOG_NO_INDIVIDUAL_BLOGS)) { + echo '
'; + echo $OUTPUT->single_button(new moodle_url('/mod/oublog/import.php', array('id' => + $cm->id)), get_string('import', 'oublog'), 'get'); + echo '
'; + } } // View participation button.