Файловый менеджер - Редактировать - /home/harasnat/www/mf/blog.tar
Назад
edit.php 0000604 00000023247 15062070561 0006210 0 ustar 00 <?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/>. /** * Blog entry edit page * * @package moodlecore * @subpackage blog * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../config.php'); require_once($CFG->dirroot . '/blog/lib.php'); require_once($CFG->dirroot . '/blog/locallib.php'); require_once($CFG->dirroot . '/comment/lib.php'); require_once($CFG->dirroot . '/blog/edit_form.php'); $action = required_param('action', PARAM_ALPHA); $id = optional_param('entryid', 0, PARAM_INT); $confirm = optional_param('confirm', 0, PARAM_BOOL); $modid = optional_param('modid', 0, PARAM_INT); // To associate the entry with a module instance. $courseid = optional_param('courseid', 0, PARAM_INT); // To associate the entry with a course. if ($action == 'edit') { $id = required_param('entryid', PARAM_INT); } $PAGE->set_url('/blog/edit.php', array('action' => $action, 'entryid' => $id, 'confirm' => $confirm, 'modid' => $modid, 'courseid' => $courseid)); // If action is add, we ignore $id to avoid any further problems. if (!empty($id) && $action == 'add') { $id = null; } $entry = new stdClass(); $entry->id = null; if ($id) { if (!$entry = new blog_entry($id)) { throw new \moodle_exception('wrongentryid', 'blog'); } $userid = $entry->userid; } else { $userid = $USER->id; } $sitecontext = context_system::instance(); $usercontext = context_user::instance($userid); if ($modid) { $PAGE->set_context($sitecontext); } else { $PAGE->set_context($usercontext); $blognode = $PAGE->settingsnav->find('blogadd', null); $blognode->make_active(); } require_login($courseid); if (empty($CFG->enableblogs)) { throw new \moodle_exception('blogdisable', 'blog'); } if (isguestuser()) { throw new \moodle_exception('noguest'); } $returnurl = new moodle_url('/blog/index.php'); if (!empty($courseid) && empty($modid)) { $returnurl->param('courseid', $courseid); } // If a modid is given, guess courseid. if (!empty($modid)) { $returnurl->param('modid', $modid); $courseid = $DB->get_field('course_modules', 'course', array('id' => $modid)); $returnurl->param('courseid', $courseid); } $blogheaders = blog_get_headers(); if (!has_capability('moodle/blog:create', $sitecontext) && !has_capability('moodle/blog:manageentries', $sitecontext)) { throw new \moodle_exception('cannoteditentryorblog'); } // Make sure that the person trying to edit has access right. if ($id) { if (!blog_user_can_edit_entry($entry)) { throw new \moodle_exception('notallowedtoedit', 'blog'); } $entry->subject = clean_text($entry->subject); $entry->summary = clean_text($entry->summary, $entry->format); } else { if (!has_capability('moodle/blog:create', $sitecontext)) { throw new \moodle_exception('noentry', 'blog'); // The capability "manageentries" is not enough for adding. } } $returnurl->param('userid', $userid); // Blog renderer. $output = $PAGE->get_renderer('blog'); $strblogs = get_string('blogs', 'blog'); if ($action === 'delete') { // Init comment JS strings. comment::init(); if (empty($entry->id)) { throw new \moodle_exception('wrongentryid', 'blog'); } if (data_submitted() && $confirm && confirm_sesskey()) { // Make sure the current user is the author of the blog entry, or has some deleteanyentry capability. if (!blog_user_can_edit_entry($entry)) { throw new \moodle_exception('nopermissionstodeleteentry', 'blog'); } else { $entry->delete(); blog_rss_delete_file($userid); redirect($returnurl); } } else if (blog_user_can_edit_entry($entry)) { $optionsyes = array('entryid' => $id, 'action' => 'delete', 'confirm' => 1, 'sesskey' => sesskey(), 'courseid' => $courseid); $optionsno = array('userid' => $entry->userid, 'courseid' => $courseid); $PAGE->set_title($strblogs); $PAGE->set_heading($SITE->fullname); echo $OUTPUT->header(); // Output edit mode title. echo $OUTPUT->heading($strblogs . ': ' . get_string('deleteentry', 'blog'), 2); echo $OUTPUT->confirm(get_string('blogdeleteconfirm', 'blog', format_string($entry->subject)), new moodle_url('edit.php', $optionsyes), new moodle_url('index.php', $optionsno)); echo '<br />'; // Output the entry. $entry->prepare_render(); echo $output->render($entry); echo $OUTPUT->footer(); die; } } else if ($action == 'add') { $editmodetitle = $strblogs . ': ' . get_string('addnewentry', 'blog'); $PAGE->set_title($editmodetitle); $PAGE->set_heading(fullname($USER)); } else if ($action == 'edit') { $editmodetitle = $strblogs . ': ' . get_string('editentry', 'blog'); $PAGE->set_title($editmodetitle); $PAGE->set_heading(fullname($USER)); } if (!empty($entry->id)) { if ($CFG->useblogassociations && ($blogassociations = $DB->get_records('blog_association', array('blogid' => $entry->id)))) { foreach ($blogassociations as $assocrec) { $context = context::instance_by_id($assocrec->contextid); switch ($context->contextlevel) { case CONTEXT_COURSE: $entry->courseassoc = $assocrec->contextid; break; case CONTEXT_MODULE: $entry->modassoc = $assocrec->contextid; break; } } } } $summaryoptions = array('maxfiles' => 99, 'maxbytes' => $CFG->maxbytes, 'trusttext' => true, 'context' => $sitecontext, 'subdirs' => file_area_contains_subdirs($sitecontext, 'blog', 'post', $entry->id)); $attachmentoptions = array('subdirs' => false, 'maxfiles' => 99, 'maxbytes' => $CFG->maxbytes); $blogeditform = new blog_edit_form(null, compact('entry', 'summaryoptions', 'attachmentoptions', 'sitecontext', 'courseid', 'modid')); $entry = file_prepare_standard_editor($entry, 'summary', $summaryoptions, $sitecontext, 'blog', 'post', $entry->id); $entry = file_prepare_standard_filemanager($entry, 'attachment', $attachmentoptions, $sitecontext, 'blog', 'attachment', $entry->id); if (!empty($entry->id)) { $entry->tags = core_tag_tag::get_item_tags_array('core', 'post', $entry->id); } $entry->action = $action; // Set defaults. $blogeditform->set_data($entry); if ($blogeditform->is_cancelled()) { redirect($returnurl); } else if ($data = $blogeditform->get_data()) { switch ($action) { case 'add': $blogentry = new blog_entry(null, $data, $blogeditform); $blogentry->add(); $blogentry->edit($data, $blogeditform, $summaryoptions, $attachmentoptions); break; case 'edit': if (empty($entry->id)) { throw new \moodle_exception('wrongentryid', 'blog'); } $entry->edit($data, $blogeditform, $summaryoptions, $attachmentoptions); break; default : throw new \moodle_exception('invalidaction'); } redirect($returnurl); } // GUI setup. switch ($action) { case 'add': // Prepare new empty form. $entry->publishstate = 'site'; $strformheading = get_string('addnewentry', 'blog'); $entry->action = $action; if ($CFG->useblogassociations) { // Pre-select the course for associations. if ($courseid) { $context = context_course::instance($courseid); $entry->courseassoc = $context->id; } // Pre-select the mod for associations. if ($modid) { $context = context_module::instance($modid); $entry->modassoc = $context->id; } } break; case 'edit': if (empty($entry->id)) { throw new \moodle_exception('wrongentryid', 'blog'); } $strformheading = get_string('updateentrywithid', 'blog'); break; default : throw new \moodle_exception('unknowaction'); } $entry->modid = $modid; $entry->courseid = $courseid; echo $OUTPUT->header(); // Output title for editing mode. if (isset($editmodetitle)) { echo $OUTPUT->heading($editmodetitle, 2); } $blogeditform->display(); echo $OUTPUT->footer(); die; renderer.php 0000604 00000022601 15062070561 0007062 0 ustar 00 <?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/>. /** * Renderers for outputting blog data * * @package core_blog * @subpackage blog * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Blog renderer */ class core_blog_renderer extends plugin_renderer_base { /** * Renders a blog entry * * @param blog_entry $entry * @return string The table HTML */ public function render_blog_entry(blog_entry $entry) { global $CFG; $syscontext = context_system::instance(); $stredit = get_string('edit'); $strdelete = get_string('delete'); // Header. $mainclass = 'forumpost blog_entry blog clearfix '; if ($entry->renderable->unassociatedentry) { $mainclass .= 'draft'; } else { $mainclass .= $entry->publishstate; } $o = $this->output->container_start($mainclass, 'b' . $entry->id); $o .= $this->output->container_start('row header clearfix'); // User picture. $o .= $this->output->container_start('left picture header'); $o .= $this->output->user_picture($entry->renderable->user); $o .= $this->output->container_end(); $o .= $this->output->container_start('topic starter header clearfix'); // Title. $titlelink = html_writer::link(new moodle_url('/blog/index.php', array('entryid' => $entry->id)), format_string($entry->subject)); $o .= $this->output->container($titlelink, 'subject'); // Post by. $by = new stdClass(); $fullname = fullname($entry->renderable->user, has_capability('moodle/site:viewfullnames', $syscontext)); $userurlparams = array('id' => $entry->renderable->user->id, 'course' => $this->page->course->id); $by->name = html_writer::link(new moodle_url('/user/view.php', $userurlparams), $fullname); $by->date = userdate($entry->created); $o .= $this->output->container(get_string('bynameondate', 'forum', $by), 'author'); // Adding external blog link. if (!empty($entry->renderable->externalblogtext)) { $o .= $this->output->container($entry->renderable->externalblogtext, 'externalblog'); } // Closing subject tag and header tag. $o .= $this->output->container_end(); $o .= $this->output->container_end(); // Post content. $o .= $this->output->container_start('row maincontent clearfix'); // Entry. $o .= $this->output->container_start('no-overflow content '); // Determine text for publish state. switch ($entry->publishstate) { case 'draft': $blogtype = get_string('publishtonoone', 'blog'); break; case 'site': $blogtype = get_string('publishtosite', 'blog'); break; case 'public': $blogtype = get_string('publishtoworld', 'blog'); break; default: $blogtype = ''; break; } $o .= $this->output->container($blogtype, 'audience'); // Attachments. $attachmentsoutputs = array(); if ($entry->renderable->attachments) { foreach ($entry->renderable->attachments as $attachment) { $o .= $this->render($attachment, false); } } // Body. $o .= format_text($entry->summary, $entry->summaryformat, array('overflowdiv' => true)); if (!empty($entry->uniquehash)) { // Uniquehash is used as a link to an external blog. $url = clean_param($entry->uniquehash, PARAM_URL); if (!empty($url)) { $o .= $this->output->container_start('externalblog'); $o .= html_writer::link($url, get_string('linktooriginalentry', 'blog')); $o .= $this->output->container_end(); } } // Links to tags. $o .= $this->output->tag_list(core_tag_tag::get_item_tags('core', 'post', $entry->id)); // Add associations. if (!empty($CFG->useblogassociations) && !empty($entry->renderable->blogassociations)) { // First find and show the associated course. $assocstr = ''; $coursesarray = array(); foreach ($entry->renderable->blogassociations as $assocrec) { if ($assocrec->contextlevel == CONTEXT_COURSE) { $coursesarray[] = $this->output->action_icon($assocrec->url, $assocrec->icon, null, array(), true); } } if (!empty($coursesarray)) { $assocstr .= get_string('associated', 'blog', get_string('course')) . ': ' . implode(', ', $coursesarray); } // Now show mod association. $modulesarray = array(); foreach ($entry->renderable->blogassociations as $assocrec) { if ($assocrec->contextlevel == CONTEXT_MODULE) { $str = get_string('associated', 'blog', $assocrec->type) . ': '; $str .= $this->output->action_icon($assocrec->url, $assocrec->icon, null, array(), true); $modulesarray[] = $str; } } if (!empty($modulesarray)) { if (!empty($coursesarray)) { $assocstr .= '<br/>'; } $assocstr .= implode('<br/>', $modulesarray); } // Adding the asociations to the output. $o .= $this->output->container($assocstr, 'tags'); } if ($entry->renderable->unassociatedentry) { $o .= $this->output->container(get_string('associationunviewable', 'blog'), 'noticebox'); } // Commands. $o .= $this->output->container_start('commands'); if ($entry->renderable->usercanedit) { // External blog entries should not be edited. if (empty($entry->uniquehash)) { $o .= html_writer::link(new moodle_url('/blog/edit.php', array('action' => 'edit', 'entryid' => $entry->id)), $stredit) . ' | '; } $o .= html_writer::link(new moodle_url('/blog/edit.php', array('action' => 'delete', 'entryid' => $entry->id)), $strdelete) . ' | '; } $entryurl = new moodle_url('/blog/index.php', array('entryid' => $entry->id)); $o .= html_writer::link($entryurl, get_string('permalink', 'blog')); $o .= $this->output->container_end(); // Last modification. if ($entry->created != $entry->lastmodified) { $o .= $this->output->container(' [ '.get_string('modified').': '.userdate($entry->lastmodified).' ]'); } // Comments. if (!empty($entry->renderable->comment)) { $o .= $entry->renderable->comment->output(true); } $o .= $this->output->container_end(); // Closing maincontent div. $o .= $this->output->container(' ', 'side options'); $o .= $this->output->container_end(); $o .= $this->output->container_end(); return $o; } /** * Renders an entry attachment * * Print link for non-images and returns images as HTML * * @param blog_entry_attachment $attachment * @return string List of attachments depending on the $return input */ public function render_blog_entry_attachment(blog_entry_attachment $attachment) { $syscontext = context_system::instance(); // Image attachments don't get printed as links. if (file_mimetype_in_typegroup($attachment->file->get_mimetype(), 'web_image')) { $attrs = array('src' => $attachment->url, 'alt' => ''); $o = html_writer::empty_tag('img', $attrs); $class = 'attachedimages'; } else { $image = $this->output->pix_icon(file_file_icon($attachment->file), $attachment->filename, 'moodle', array('class' => 'icon')); $o = html_writer::link($attachment->url, $image); $o .= format_text(html_writer::link($attachment->url, $attachment->filename), FORMAT_HTML, array('context' => $syscontext)); $class = 'attachments'; } return $this->output->container($o, $class); } } locallib.php 0000604 00000117444 15062070561 0007047 0 ustar 00 <?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/>. /** * Classes for Blogs. * * @package moodlecore * @subpackage blog * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/filelib.php'); /** * Blog_entry class. Represents an entry in a user's blog. Contains all methods for managing this entry. * This class does not contain any HTML-generating code. See blog_listing sub-classes for such code. * This class follows the Object Relational Mapping technique, its member variables being mapped to * the fields of the post table. * * @package moodlecore * @subpackage blog * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class blog_entry implements renderable { // Public Database fields. public $id; public $userid; public $subject; public $summary; public $rating = 0; public $attachment; public $publishstate; // Locked Database fields (Don't touch these). public $courseid = 0; public $groupid = 0; public $module = 'blog'; public $moduleid = 0; public $coursemoduleid = 0; public $content; public $format = 1; public $uniquehash = ''; public $lastmodified; public $created; public $usermodified; // Other class variables. public $form; public $tags = array(); /** @var StdClass Data needed to render the entry */ public $renderable; /** @var string summary format. */ public string $summaryformat; /** @var array summary editor. */ public array $summary_editor; /** @var string */ public $summarytrust; /** @var int course associated with the blog post. */ public $courseassoc; /** @var string module associated with the blog post. */ public $modassoc; /** @var mixed attachment. */ public $attachment_filemanager; /** @var string blog post body. */ public $body; /** @var int attachment entry id. */ public $entryid; /** @var string|null submit button. */ public $submitbutton; /** @var string|null user alias. */ public $useridalias; /** @var string|null user picture. */ public $picture; /** @var string|null user first name. */ public $firstname; /** @var string|null user middle name. */ public $middlename; /** @var string|null user last name. */ public $lastname; /** @var string|null user first name phonetic. */ public $firstnamephonetic; /** @var string|null user last name phonetic. */ public $lastnamephonetic; /** @var string|null user alternate name. */ public $alternatename; /** @var string|null user email address. */ public $email; /** @var string */ public $action; /** @var string|null user picture description. */ public $imagealt; /** @var int module instance id. */ public $modid; /** * Constructor. If given an id, will fetch the corresponding record from the DB. * * @param mixed $idorparams A blog entry id if INT, or data for a new entry if array */ public function __construct($id=null, $params=null, $form=null) { global $DB, $PAGE, $CFG; if (!empty($id)) { $object = $DB->get_record('post', array('id' => $id)); foreach ($object as $var => $val) { $this->$var = $val; } } else if (!empty($params) && (is_array($params) || is_object($params))) { foreach ($params as $var => $val) { $this->$var = $val; } } if (!empty($CFG->useblogassociations)) { $associations = $DB->get_records('blog_association', array('blogid' => $this->id)); foreach ($associations as $association) { $context = context::instance_by_id($association->contextid); if ($context->contextlevel == CONTEXT_COURSE) { $this->courseassoc = $association->contextid; } else if ($context->contextlevel == CONTEXT_MODULE) { $this->modassoc = $association->contextid; } } } $this->form = $form; } /** * Gets the required data to print the entry */ public function prepare_render() { global $DB, $CFG, $PAGE; $this->renderable = new StdClass(); $this->renderable->user = $DB->get_record('user', array('id' => $this->userid)); // Entry comments. if (!empty($CFG->usecomments) and $CFG->blogusecomments) { require_once($CFG->dirroot . '/comment/lib.php'); $cmt = new stdClass(); $cmt->context = context_user::instance($this->userid); $cmt->courseid = $PAGE->course->id; $cmt->area = 'format_blog'; $cmt->itemid = $this->id; $cmt->showcount = $CFG->blogshowcommentscount; $cmt->component = 'blog'; $this->renderable->comment = new comment($cmt); } $this->summary = file_rewrite_pluginfile_urls($this->summary, 'pluginfile.php', SYSCONTEXTID, 'blog', 'post', $this->id); // External blog link. if ($this->uniquehash && $this->content) { if ($externalblog = $DB->get_record('blog_external', array('id' => $this->content))) { $urlparts = parse_url($externalblog->url); $this->renderable->externalblogtext = get_string('retrievedfrom', 'blog') . get_string('labelsep', 'langconfig'); $this->renderable->externalblogtext .= html_writer::link($urlparts['scheme'] . '://' . $urlparts['host'], $externalblog->name); } } // Retrieve associations. $this->renderable->unassociatedentry = false; if (!empty($CFG->useblogassociations)) { // Adding the entry associations data. if ($associations = $associations = $DB->get_records('blog_association', array('blogid' => $this->id))) { // Check to see if the entry is unassociated with group/course level access. if ($this->publishstate == 'group' || $this->publishstate == 'course') { $this->renderable->unassociatedentry = true; } foreach ($associations as $key => $assocrec) { if (!$context = context::instance_by_id($assocrec->contextid, IGNORE_MISSING)) { unset($associations[$key]); continue; } // The renderer will need the contextlevel of the association. $associations[$key]->contextlevel = $context->contextlevel; // Course associations. if ($context->contextlevel == CONTEXT_COURSE) { // TODO: performance!!!! $instancename = $DB->get_field('course', 'shortname', array('id' => $context->instanceid)); $associations[$key]->url = $assocurl = new moodle_url('/course/view.php', array('id' => $context->instanceid)); $associations[$key]->text = $instancename; $associations[$key]->icon = new pix_icon('i/course', $associations[$key]->text); } // Mod associations. if ($context->contextlevel == CONTEXT_MODULE) { // Getting the activity type and the activity instance id. $sql = 'SELECT cm.instance, m.name FROM {course_modules} cm JOIN {modules} m ON m.id = cm.module WHERE cm.id = :cmid'; $modinfo = $DB->get_record_sql($sql, array('cmid' => $context->instanceid)); // TODO: performance!!!! $instancename = $DB->get_field($modinfo->name, 'name', array('id' => $modinfo->instance)); $associations[$key]->type = get_string('modulename', $modinfo->name); $associations[$key]->url = new moodle_url('/mod/' . $modinfo->name . '/view.php', array('id' => $context->instanceid)); $associations[$key]->text = $instancename; $associations[$key]->icon = new pix_icon('icon', $associations[$key]->text, $modinfo->name); } } } $this->renderable->blogassociations = $associations; } // Entry attachments. $this->renderable->attachments = $this->get_attachments(); $this->renderable->usercanedit = blog_user_can_edit_entry($this); } /** * Gets the entry attachments list * @return array List of blog_entry_attachment instances */ public function get_attachments() { global $CFG; require_once($CFG->libdir.'/filelib.php'); $syscontext = context_system::instance(); $fs = get_file_storage(); $files = $fs->get_area_files($syscontext->id, 'blog', 'attachment', $this->id); // Adding a blog_entry_attachment for each non-directory file. $attachments = array(); foreach ($files as $file) { if ($file->is_directory()) { continue; } $attachments[] = new blog_entry_attachment($file, $this->id); } return $attachments; } /** * Inserts this entry in the database. Access control checks must be done by calling code. * * @param mform $form Used for attachments * @return void */ public function process_attachment($form) { $this->form = $form; } /** * Inserts this entry in the database. Access control checks must be done by calling code. * TODO Set the publishstate correctly * @return void */ public function add() { global $CFG, $USER, $DB; unset($this->id); $this->module = 'blog'; $this->userid = (empty($this->userid)) ? $USER->id : $this->userid; $this->lastmodified = time(); $this->created = time(); // Insert the new blog entry. $this->id = $DB->insert_record('post', $this); if (!empty($CFG->useblogassociations)) { $this->add_associations(); } core_tag_tag::set_item_tags('core', 'post', $this->id, context_user::instance($this->userid), $this->tags); // Trigger an event for the new entry. $event = \core\event\blog_entry_created::create(array( 'objectid' => $this->id, 'relateduserid' => $this->userid )); $event->set_blog_entry($this); $event->trigger(); } /** * Updates this entry in the database. Access control checks must be done by calling code. * * @param array $params Entry parameters. * @param moodleform $form Used for attachments. * @param array $summaryoptions Summary options. * @param array $attachmentoptions Attachment options. * * @return void */ public function edit($params=array(), $form=null, $summaryoptions=array(), $attachmentoptions=array()) { global $CFG, $DB; $sitecontext = context_system::instance(); $entry = $this; $this->form = $form; foreach ($params as $var => $val) { $entry->$var = $val; } $entry = file_postupdate_standard_editor($entry, 'summary', $summaryoptions, $sitecontext, 'blog', 'post', $entry->id); $entry = file_postupdate_standard_filemanager($entry, 'attachment', $attachmentoptions, $sitecontext, 'blog', 'attachment', $entry->id); if (!empty($CFG->useblogassociations)) { $entry->add_associations(); } $entry->lastmodified = time(); // Update record. $DB->update_record('post', $entry); core_tag_tag::set_item_tags('core', 'post', $entry->id, context_user::instance($this->userid), $entry->tags); $event = \core\event\blog_entry_updated::create(array( 'objectid' => $entry->id, 'relateduserid' => $entry->userid )); $event->set_blog_entry($entry); $event->trigger(); } /** * Deletes this entry from the database. Access control checks must be done by calling code. * * @return void */ public function delete() { global $DB; $this->delete_attachments(); $this->remove_associations(); // Get record to pass onto the event. $record = $DB->get_record('post', array('id' => $this->id)); $DB->delete_records('post', array('id' => $this->id)); core_tag_tag::remove_all_item_tags('core', 'post', $this->id); $event = \core\event\blog_entry_deleted::create(array( 'objectid' => $this->id, 'relateduserid' => $this->userid )); $event->add_record_snapshot("post", $record); $event->set_blog_entry($this); $event->trigger(); } /** * Function to add all context associations to an entry. * * @param string $unused This does nothing, do not use it. */ public function add_associations($unused = null) { if ($unused !== null) { debugging('Illegal argument used in blog_entry->add_associations()', DEBUG_DEVELOPER); } $this->remove_associations(); if (!empty($this->courseassoc)) { $this->add_association($this->courseassoc); } if (!empty($this->modassoc)) { $this->add_association($this->modassoc); } } /** * Add a single association for a blog entry * * @param int $contextid - id of context to associate with the blog entry. * @param string $unused This does nothing, do not use it. */ public function add_association($contextid, $unused = null) { global $DB; if ($unused !== null) { debugging('Illegal argument used in blog_entry->add_association()', DEBUG_DEVELOPER); } $assocobject = new StdClass; $assocobject->contextid = $contextid; $assocobject->blogid = $this->id; $id = $DB->insert_record('blog_association', $assocobject); // Trigger an association created event. $context = context::instance_by_id($contextid); $eventparam = array( 'objectid' => $id, 'other' => array('associateid' => $context->instanceid, 'subject' => $this->subject, 'blogid' => $this->id), 'relateduserid' => $this->userid ); if ($context->contextlevel == CONTEXT_COURSE) { $eventparam['other']['associatetype'] = 'course'; } else if ($context->contextlevel == CONTEXT_MODULE) { $eventparam['other']['associatetype'] = 'coursemodule'; } $event = \core\event\blog_association_created::create($eventparam); $event->trigger(); } /** * remove all associations for a blog entry * * @return void */ public function remove_associations() { global $DB; $associations = $DB->get_records('blog_association', array('blogid' => $this->id)); foreach ($associations as $association) { // Trigger an association deleted event. $context = context::instance_by_id($association->contextid); $eventparam = array( 'objectid' => $this->id, 'other' => array('subject' => $this->subject, 'blogid' => $this->id), 'relateduserid' => $this->userid ); $event = \core\event\blog_association_deleted::create($eventparam); $event->add_record_snapshot('blog_association', $association); $event->trigger(); // Now remove the association. $DB->delete_records('blog_association', array('id' => $association->id)); } } /** * Deletes all the user files in the attachments area for an entry * * @return void */ public function delete_attachments() { $fs = get_file_storage(); $fs->delete_area_files(SYSCONTEXTID, 'blog', 'attachment', $this->id); $fs->delete_area_files(SYSCONTEXTID, 'blog', 'post', $this->id); } /** * User can edit a blog entry if this is their own blog entry and they have * the capability moodle/blog:create, or if they have the capability * moodle/blog:manageentries. * This also applies to deleting of entries. * * @param int $userid Optional. If not given, $USER is used * @return boolean */ public function can_user_edit($userid=null) { global $CFG, $USER; if (empty($userid)) { $userid = $USER->id; } $sitecontext = context_system::instance(); if (has_capability('moodle/blog:manageentries', $sitecontext)) { return true; // Can edit any blog entry. } if ($this->userid == $userid && has_capability('moodle/blog:create', $sitecontext)) { return true; // Can edit own when having blog:create capability. } return false; } /** * Checks to see if a user can view the blogs of another user. * Only blog level is checked here, the capabilities are enforced * in blog/index.php * * @param int $targetuserid ID of the user we are checking * * @return bool */ public function can_user_view($targetuserid) { global $CFG, $USER, $DB; $sitecontext = context_system::instance(); if (empty($CFG->enableblogs) || !has_capability('moodle/blog:view', $sitecontext)) { return false; // Blog system disabled or user has no blog view capability. } if (isloggedin() && $USER->id == $targetuserid) { return true; // Can view own entries in any case. } if (has_capability('moodle/blog:manageentries', $sitecontext)) { return true; // Can manage all entries. } // Coming for 1 entry, make sure it's not a draft. if ($this->publishstate == 'draft' && !has_capability('moodle/blog:viewdrafts', $sitecontext)) { return false; // Can not view draft of others. } // Coming for 1 entry, make sure user is logged in, if not a public blog. if ($this->publishstate != 'public' && !isloggedin()) { return false; } switch ($CFG->bloglevel) { case BLOG_GLOBAL_LEVEL: return true; break; case BLOG_SITE_LEVEL: if (isloggedin()) { // Not logged in viewers forbidden. return true; } return false; break; case BLOG_USER_LEVEL: default: $personalcontext = context_user::instance($targetuserid); return has_capability('moodle/user:readuserblogs', $personalcontext); break; } } /** * Use this function to retrieve a list of publish states available for * the currently logged in user. * * @return array This function returns an array ideal for sending to moodles' * choose_from_menu function. */ public static function get_applicable_publish_states() { global $CFG; $options = array(); // Everyone gets draft access. if ($CFG->bloglevel >= BLOG_USER_LEVEL) { $options['draft'] = get_string('publishtonoone', 'blog'); } if ($CFG->bloglevel > BLOG_USER_LEVEL) { $options['site'] = get_string('publishtosite', 'blog'); } if ($CFG->bloglevel >= BLOG_GLOBAL_LEVEL) { $options['public'] = get_string('publishtoworld', 'blog'); } return $options; } } /** * Abstract Blog_Listing class: used to gather blog entries and output them as listings. One of the subclasses must be used. * * @package moodlecore * @subpackage blog * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class blog_listing { /** * Array of blog_entry objects. * @var array $entries */ public $entries = null; /** * Caches the total number of the entries. * @var int */ public $totalentries = null; /** * An array of blog_filter_* objects * @var array $filters */ public $filters = array(); /** * Constructor * * @param array $filters An associative array of filtername => filterid */ public function __construct($filters=array()) { // Unset filters overridden by more specific filters. foreach ($filters as $type => $id) { if (!empty($type) && !empty($id)) { $this->filters[$type] = blog_filter::get_instance($id, $type); } } foreach ($this->filters as $type => $filter) { foreach ($filter->overrides as $override) { if (array_key_exists($override, $this->filters)) { unset($this->filters[$override]); } } } } /** * Fetches the array of blog entries. * * @return array */ public function get_entries($start=0, $limit=10) { global $DB; if ($this->entries === null) { if ($sqlarray = $this->get_entry_fetch_sql(false, 'created DESC')) { $this->entries = $DB->get_records_sql($sqlarray['sql'], $sqlarray['params'], $start, $limit); if (!$start && count($this->entries) < $limit) { $this->totalentries = count($this->entries); } } else { return false; } } return $this->entries; } /** * Finds total number of blog entries * * @return int */ public function count_entries() { global $DB; if ($this->totalentries === null) { if ($sqlarray = $this->get_entry_fetch_sql(true)) { $this->totalentries = $DB->count_records_sql($sqlarray['sql'], $sqlarray['params']); } else { $this->totalentries = 0; } } return $this->totalentries; } public function get_entry_fetch_sql($count=false, $sort='lastmodified DESC', $userid = false) { global $DB, $USER, $CFG; if (!$userid) { $userid = $USER->id; } $userfieldsapi = \core_user\fields::for_userpic(); $allnamefields = $userfieldsapi->get_sql('u', false, '', 'useridalias', false)->selects; // The query used to locate blog entries is complicated. It will be built from the following components: $requiredfields = "p.*, $allnamefields"; // The SELECT clause. $tables = array('p' => 'post', 'u' => 'user'); // Components of the FROM clause (table_id => table_name). // Components of the WHERE clause (conjunction). $conditions = array('u.deleted = 0', 'p.userid = u.id', '(p.module = \'blog\' OR p.module = \'blog_external\')'); // Build up a clause for permission constraints. $params = array(); // Fix for MDL-9165, use with readuserblogs capability in a user context can read that user's private blogs. // Admins can see all blogs regardless of publish states, as described on the help page. if (has_capability('moodle/user:readuserblogs', context_system::instance())) { // Don't add permission constraints. } else if (!empty($this->filters['user']) && has_capability('moodle/user:readuserblogs', context_user::instance((empty($this->filters['user']->id) ? 0 : $this->filters['user']->id)))) { // Don't add permission constraints. } else { if (isloggedin() and !isguestuser()) { // Dont check association records if there aren't any. $assocexists = $DB->record_exists('blog_association', array()); // Begin permission sql clause. $permissionsql = '(p.userid = ? '; $params[] = $userid; if ($CFG->bloglevel >= BLOG_SITE_LEVEL) { // Add permission to view site-level entries. $permissionsql .= " OR p.publishstate = 'site' "; } if ($CFG->bloglevel >= BLOG_GLOBAL_LEVEL) { // Add permission to view global entries. $permissionsql .= " OR p.publishstate = 'public' "; } $permissionsql .= ') '; // Close permissions sql clause. } else { // Default is access to public entries. $permissionsql = "p.publishstate = 'public'"; } $conditions[] = $permissionsql; // Add permission constraints. } foreach ($this->filters as $type => $blogfilter) { $conditions = array_merge($conditions, $blogfilter->conditions); $params = array_merge($params, $blogfilter->params); $tables = array_merge($tables, $blogfilter->tables); } $tablessql = ''; // Build up the FROM clause. foreach ($tables as $tablename => $table) { $tablessql .= ($tablessql ? ', ' : '').'{'.$table.'} '.$tablename; } $sql = ($count) ? 'SELECT COUNT(*)' : 'SELECT ' . $requiredfields; $sql .= " FROM $tablessql WHERE " . implode(' AND ', $conditions); $sql .= ($count) ? '' : " ORDER BY $sort"; return array('sql' => $sql, 'params' => $params); } /** * Outputs all the blog entries aggregated by this blog listing. * * @return void */ public function print_entries() { global $CFG, $USER, $DB, $OUTPUT, $PAGE; $sitecontext = context_system::instance(); // Blog renderer. $output = $PAGE->get_renderer('blog'); $page = optional_param('blogpage', 0, PARAM_INT); $limit = optional_param('limit', get_user_preferences('blogpagesize', 10), PARAM_INT); $start = $page * $limit; $morelink = '<br /> '; $entries = $this->get_entries($start, $limit); $totalentries = $this->count_entries(); $pagingbar = new paging_bar($totalentries, $page, $limit, $this->get_baseurl()); $pagingbar->pagevar = 'blogpage'; $blogheaders = blog_get_headers(); echo $OUTPUT->render($pagingbar); if (has_capability('moodle/blog:create', $sitecontext)) { // The user's blog is enabled and they are viewing their own blog. $userid = optional_param('userid', null, PARAM_INT); if (empty($userid) || (!empty($userid) && $userid == $USER->id)) { $courseid = optional_param('courseid', null, PARAM_INT); $modid = optional_param('modid', null, PARAM_INT); $addurl = new moodle_url("$CFG->wwwroot/blog/edit.php"); $urlparams = array('action' => 'add', 'userid' => $userid, 'courseid' => $courseid, 'groupid' => optional_param('groupid', null, PARAM_INT), 'modid' => $modid, 'tagid' => optional_param('tagid', null, PARAM_INT), 'tag' => optional_param('tag', null, PARAM_INT), 'search' => optional_param('search', null, PARAM_INT)); $urlparams = array_filter($urlparams); $addurl->params($urlparams); $addlink = '<div class="addbloglink">'; $addlink .= '<a href="'.$addurl->out().'">'. $blogheaders['stradd'].'</a>'; $addlink .= '</div>'; echo $addlink; } } if ($entries) { $count = 0; foreach ($entries as $entry) { $blogentry = new blog_entry(null, $entry); // Get the required blog entry data to render it. $blogentry->prepare_render(); echo $output->render($blogentry); $count++; } echo $OUTPUT->render($pagingbar); if (!$count) { print '<br /><div style="text-align:center">'. get_string('noentriesyet', 'blog') .'</div><br />'; } print $morelink.'<br />'."\n"; return; } } // Find the base url from $_GET variables, for print_paging_bar. public function get_baseurl() { $getcopy = $_GET; unset($getcopy['blogpage']); if (!empty($getcopy)) { $first = false; $querystring = ''; foreach ($getcopy as $var => $val) { if (!$first) { $first = true; $querystring .= "?$var=$val"; } else { $querystring .= '&'.$var.'='.$val; $hasparam = true; } } } else { $querystring = '?'; } return strip_querystring(qualified_me()) . $querystring; } } /** * Abstract class for blog_filter objects. * A set of core filters are implemented here. To write new filters, you need to subclass * blog_filter and give it the name of the type you want (for example, blog_filter_entry). * The blog_filter abstract class will automatically use it when the filter is added to the * URL. The first parameter of the constructor is the ID of your filter, but it can be a string * or have any other meaning you wish it to have. The second parameter is called $type and is * used as a sub-type for filters that have a very similar implementation (see blog_filter_context for an example) */ abstract class blog_filter { /** * An array of strings representing the available filter types for each blog_filter. * @var array $availabletypes */ public $availabletypes = array(); /** * The type of filter (for example, types of blog_filter_context are site, course and module) * @var string $type */ public $type; /** * The unique ID for a filter's associated record * @var int $id */ public $id; /** * An array of table aliases that are used in the WHERE conditions * @var array $tables */ public $tables = array(); /** * An array of WHERE conditions * @var array $conditions */ public $conditions = array(); /** * An array of SQL params * @var array $params */ public $params = array(); /** * An array of filter types which this particular filter type overrides: their conditions will not be evaluated */ public $overrides = array(); public function __construct($id, $type=null) { $this->id = $id; $this->type = $type; } /** * TODO This is poor design. A parent class should not know anything about its children. * The default case helps to resolve this design issue */ public static function get_instance($id, $type) { switch ($type) { case 'site': case 'course': case 'module': return new blog_filter_context($id, $type); break; case 'group': case 'user': return new blog_filter_user($id, $type); break; case 'tag': return new blog_filter_tag($id); break; default: $classname = "blog_filter_$type"; if (class_exists($classname)) { return new $classname($id, $type); } } } } /** * This filter defines the context level of the blog entries being searched: site, course, module */ class blog_filter_context extends blog_filter { /** * Constructor * * @param string $type * @param int $id */ public function __construct($id=null, $type='site') { global $SITE, $CFG, $DB; if (empty($id)) { $this->type = 'site'; } else { $this->id = $id; $this->type = $type; } $this->availabletypes = array('site' => get_string('site'), 'course' => get_string('course'), 'module' => get_string('activity'), 'context' => get_string('coresystem')); switch ($this->type) { case 'course': // Careful of site course! // Ignore course filter if blog associations are not enabled. if ($this->id != $SITE->id && !empty($CFG->useblogassociations)) { $this->overrides = array('site', 'context'); $context = context_course::instance($this->id); $this->tables['ba'] = 'blog_association'; $this->conditions[] = 'p.id = ba.blogid'; $this->conditions[] = 'ba.contextid = '.$context->id; break; } else { // We are dealing with the site course, do not break from the current case. } case 'site': // No special constraints. break; case 'module': if (!empty($CFG->useblogassociations)) { $this->overrides = array('course', 'site', 'context'); $context = context_module::instance($this->id); $this->tables['ba'] = 'blog_association'; $this->tables['p'] = 'post'; $this->conditions = array('p.id = ba.blogid', 'ba.contextid = ?'); $this->params = array($context->id); } break; case 'context': if ($id != context_system::instance()->id && !empty($CFG->useblogassociations)) { $this->overrides = array('site'); $context = context::instance_by_id($this->id); $this->tables['ba'] = 'blog_association'; $this->tables['ctx'] = 'context'; $this->conditions[] = 'p.id = ba.blogid'; $this->conditions[] = 'ctx.id = ba.contextid'; $this->conditions[] = 'ctx.path LIKE ?'; $this->params = array($context->path . '%'); } break; } } } /** * This filter defines the user level of the blog entries being searched: a userid or a groupid. * It can be combined with a context filter in order to refine the search. */ class blog_filter_user extends blog_filter { public $tables = array('u' => 'user'); /** * Constructor * * @param string $type * @param int $id */ public function __construct($id=null, $type='user') { global $CFG, $DB, $USER; $this->availabletypes = array('user' => get_string('user'), 'group' => get_string('group')); if (empty($id)) { $this->id = $USER->id; $this->type = 'user'; } else { $this->id = $id; $this->type = $type; } if ($this->type == 'user') { $this->conditions = array('u.id = ?'); $this->params = array($this->id); $this->overrides = array('group'); } else if ($this->type == 'group') { $this->overrides = array('course', 'site'); $this->tables['gm'] = 'groups_members'; $this->conditions[] = 'p.userid = gm.userid'; $this->conditions[] = 'gm.groupid = ?'; $this->params[] = $this->id; if (!empty($CFG->useblogassociations)) { // Only show blog entries associated with this course. $coursecontext = context_course::instance($DB->get_field('groups', 'courseid', array('id' => $this->id))); $this->tables['ba'] = 'blog_association'; $this->conditions[] = 'gm.groupid = ?'; $this->conditions[] = 'ba.contextid = ?'; $this->conditions[] = 'ba.blogid = p.id'; $this->params[] = $this->id; $this->params[] = $coursecontext->id; } } } } /** * This filter defines a tag by which blog entries should be searched. */ class blog_filter_tag extends blog_filter { public $tables = array('t' => 'tag', 'ti' => 'tag_instance', 'p' => 'post'); /** * Constructor * * @return void */ public function __construct($id) { global $DB; $this->id = $id; $this->conditions = array('ti.tagid = t.id', "ti.itemtype = 'post'", "ti.component = 'core'", 'ti.itemid = p.id', 't.id = ?'); $this->params = array($this->id); } } /** * This filter defines a specific blog entry id. */ class blog_filter_entry extends blog_filter { public $conditions = array('p.id = ?'); public $overrides = array('site', 'course', 'module', 'group', 'user', 'tag'); public function __construct($id) { $this->id = $id; $this->params[] = $this->id; } } /** * This filter restricts the results to a time interval in seconds up to time() */ class blog_filter_since extends blog_filter { public function __construct($interval) { $this->conditions[] = 'p.lastmodified >= ? AND p.lastmodified <= ?'; $this->params[] = time() - $interval; $this->params[] = time(); } } /** * Filter used to perform full-text search on an entry's subject, summary and content */ class blog_filter_search extends blog_filter { public function __construct($searchterm) { global $DB; $this->conditions = array("(".$DB->sql_like('p.summary', '?', false)." OR ".$DB->sql_like('p.content', '?', false)." OR ".$DB->sql_like('p.subject', '?', false).")"); $this->params[] = "%$searchterm%"; $this->params[] = "%$searchterm%"; $this->params[] = "%$searchterm%"; } } /** * Renderable class to represent an entry attachment */ class blog_entry_attachment implements renderable { public $filename; public $url; public $file; /** * Gets the file data * * @param stored_file $file * @param int $entryid Attachment entry id */ public function __construct($file, $entryid) { global $CFG; $this->file = $file; $this->filename = $file->get_filename(); $this->url = file_encode_url($CFG->wwwroot . '/pluginfile.php', '/' . SYSCONTEXTID . '/blog/attachment/' . $entryid . '/' . $this->filename); } } external_blog_edit_form.php 0000604 00000011057 15062070561 0012134 0 ustar 00 <?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/>. /** * Moodleform for the user interface for managing external blog links. * * @package moodlecore * @subpackage blog * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ if (!defined('MOODLE_INTERNAL')) { die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. } require_once($CFG->libdir.'/formslib.php'); class blog_edit_external_form extends moodleform { public function definition() { global $CFG; $mform =& $this->_form; $mform->addElement('url', 'url', get_string('url', 'blog'), array('size' => 60), array('usefilepicker' => false)); $mform->setType('url', PARAM_URL); $mform->addRule('url', get_string('emptyurl', 'blog'), 'required', null, 'client'); $mform->addHelpButton('url', 'url', 'blog'); $mform->addElement('text', 'name', get_string('name', 'blog')); $mform->setType('name', PARAM_TEXT); $mform->addHelpButton('name', 'name', 'blog'); $mform->addElement('textarea', 'description', get_string('description', 'blog'), array('cols' => 50, 'rows' => 7)); $mform->addHelpButton('description', 'description', 'blog'); // To filter external blogs by their tags we do not need to check if tags in moodle are enabled. $mform->addElement('text', 'filtertags', get_string('filtertags', 'blog'), array('size' => 50)); $mform->setType('filtertags', PARAM_TAGLIST); $mform->addHelpButton('filtertags', 'filtertags', 'blog'); $mform->addElement('tags', 'autotags', get_string('autotags', 'blog'), array('itemtype' => 'blog_external', 'component' => 'core')); $mform->addHelpButton('autotags', 'autotags', 'blog'); $this->add_action_buttons(); $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); $mform->setDefault('id', 0); $mform->addElement('hidden', 'returnurl'); $mform->setType('returnurl', PARAM_LOCALURL); $mform->setDefault('returnurl', 0); } /** * Additional validation includes checking URL and tags */ public function validation($data, $files) { global $CFG; $errors = parent::validation($data, $files); require_once($CFG->libdir . '/simplepie/moodle_simplepie.php'); $rss = new moodle_simplepie(); $rssfile = $rss->registry->create('File', array($data['url'])); $filetest = $rss->registry->create('Locator', array($rssfile)); if (!$filetest->is_feed($rssfile)) { $errors['url'] = get_string('feedisinvalid', 'blog'); } else { $rss->set_feed_url($data['url']); if (!$rss->init()) { $errors['url'] = get_string('emptyrssfeed', 'blog'); } } return $errors; } public function definition_after_data() { global $CFG, $COURSE; $mform =& $this->_form; $name = trim($mform->getElementValue('name') ?? ''); $description = trim($mform->getElementValue('description') ?? ''); $url = $mform->getElementValue('url'); if (empty($name) || empty($description)) { $rss = new moodle_simplepie($url); if (empty($name) && $rss->get_title()) { $mform->setDefault('name', $rss->get_title()); } if (empty($description) && $rss->get_description()) { $mform->setDefault('description', $rss->get_description()); } } if ($id = $mform->getElementValue('id')) { $mform->freeze('url'); if ($mform->elementExists('filtertags')) { $mform->freeze('filtertags'); } // TODO change the filtertags element to a multiple select, using the tags of the external blog // Use $rss->get_channel_tags(). } } } classes/external.php 0000604 00000025660 15062070561 0010543 0 ustar 00 <?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/>. /** * This is the external API for blogs. * * @package core_blog * @copyright 2018 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_blog; defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot .'/blog/lib.php'); require_once($CFG->dirroot .'/blog/locallib.php'); use core_external\external_api; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use core_external\external_warnings; use context_system; use context_course; use moodle_exception; use core_blog\external\post_exporter; /** * This is the external API for blogs. * * @copyright 2018 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class external extends external_api { /** * Validate access to the blog and the filters to apply when listing entries. * * @param array $rawwsfilters array containing the filters in WS format * @return array context, filters to apply and the calculated courseid and user * @since Moodle 3.6 */ protected static function validate_access_and_filters($rawwsfilters) { global $CFG; if (empty($CFG->enableblogs)) { throw new moodle_exception('blogdisable', 'blog'); } // Init filters. $filterstype = array( 'courseid' => PARAM_INT, 'groupid' => PARAM_INT, 'userid' => PARAM_INT, 'tagid' => PARAM_INT, 'tag' => PARAM_NOTAGS, 'cmid' => PARAM_INT, 'entryid' => PARAM_INT, 'search' => PARAM_RAW ); $filters = array( 'courseid' => null, 'groupid' => null, 'userid' => null, 'tagid' => null, 'tag' => null, 'cmid' => null, 'entryid' => null, 'search' => null ); foreach ($rawwsfilters as $filter) { $name = trim($filter['name']); if (!isset($filterstype[$name])) { throw new moodle_exception('errorinvalidparam', 'webservice', '', $name); } $filters[$name] = clean_param($filter['value'], $filterstype[$name]); } // Do not overwrite here the filters, blog_get_headers and blog_listing will take care of that. list($courseid, $userid) = blog_validate_access($filters['courseid'], $filters['cmid'], $filters['groupid'], $filters['entryid'], $filters['userid']); if ($courseid && $courseid != SITEID) { $context = context_course::instance($courseid); self::validate_context($context); } else { $context = context_system::instance(); if ($CFG->bloglevel == BLOG_GLOBAL_LEVEL) { // Everybody can see anything - no login required unless site is locked down using forcelogin. if ($CFG->forcelogin) { self::validate_context($context); } } else { self::validate_context($context); } } // Courseid and userid may not be the same that the ones in $filters. return array($context, $filters, $courseid, $userid); } /** * Returns description of get_entries() parameters. * * @return external_function_parameters * @since Moodle 3.6 */ public static function get_entries_parameters() { return new external_function_parameters( array( 'filters' => new external_multiple_structure ( new external_single_structure( array( 'name' => new external_value(PARAM_ALPHA, 'The expected keys (value format) are: tag PARAM_NOTAGS blog tag tagid PARAM_INT blog tag id userid PARAM_INT blog author (userid) cmid PARAM_INT course module id entryid PARAM_INT entry id groupid PARAM_INT group id courseid PARAM_INT course id search PARAM_RAW search term ' ), 'value' => new external_value(PARAM_RAW, 'The value of the filter.') ) ), 'Parameters to filter blog listings.', VALUE_DEFAULT, array() ), 'page' => new external_value(PARAM_INT, 'The blog page to return.', VALUE_DEFAULT, 0), 'perpage' => new external_value(PARAM_INT, 'The number of posts to return per page.', VALUE_DEFAULT, 10), ) ); } /** * Return blog entries. * * @param array $filters the parameters to filter the blog listing * @param int $page the blog page to return * @param int $perpage the number of posts to return per page * @return array with the blog entries and warnings * @since Moodle 3.6 */ public static function get_entries($filters = array(), $page = 0, $perpage = 10) { global $PAGE; $warnings = array(); $params = self::validate_parameters(self::get_entries_parameters(), array('filters' => $filters, 'page' => $page, 'perpage' => $perpage)); list($context, $filters, $courseid, $userid) = self::validate_access_and_filters($params['filters']); $PAGE->set_context($context); // Needed by internal APIs. $output = $PAGE->get_renderer('core'); // Get filters. $blogheaders = blog_get_headers($filters['courseid'], $filters['groupid'], $filters['userid'], $filters['tagid'], $filters['tag'], $filters['cmid'], $filters['entryid'], $filters['search']); $bloglisting = new \blog_listing($blogheaders['filters']); $page = $params['page']; $limit = empty($params['perpage']) ? get_user_preferences('blogpagesize', 10) : $params['perpage']; $start = $page * $limit; $entries = $bloglisting->get_entries($start, $limit); $totalentries = $bloglisting->count_entries(); $exportedentries = array(); foreach ($entries as $entry) { $exporter = new post_exporter($entry, array('context' => $context)); $exportedentries[] = $exporter->export($output); } return array( 'warnings' => $warnings, 'entries' => $exportedentries, 'totalentries' => $totalentries, ); } /** * Returns description of get_entries() result value. * * @return \core_external\external_description * @since Moodle 3.6 */ public static function get_entries_returns() { return new external_single_structure( array( 'entries' => new external_multiple_structure( post_exporter::get_read_structure() ), 'totalentries' => new external_value(PARAM_INT, 'The total number of entries found.'), 'warnings' => new external_warnings(), ) ); } /** * Returns description of view_entries() parameters. * * @return external_function_parameters * @since Moodle 3.6 */ public static function view_entries_parameters() { return new external_function_parameters( array( 'filters' => new external_multiple_structure ( new external_single_structure( array( 'name' => new external_value(PARAM_ALPHA, 'The expected keys (value format) are: tag PARAM_NOTAGS blog tag tagid PARAM_INT blog tag id userid PARAM_INT blog author (userid) cmid PARAM_INT course module id entryid PARAM_INT entry id groupid PARAM_INT group id courseid PARAM_INT course id search PARAM_RAW search term ' ), 'value' => new external_value(PARAM_RAW, 'The value of the filter.') ) ), 'Parameters used in the filter of view_entries.', VALUE_DEFAULT, array() ), ) ); } /** * Trigger the blog_entries_viewed event. * * @param array $filters the parameters used in the filter of get_entries * @return array with status result and warnings * @since Moodle 3.6 */ public static function view_entries($filters = array()) { $warnings = array(); $params = self::validate_parameters(self::view_entries_parameters(), array('filters' => $filters)); list($context, $filters, $courseid, $userid) = self::validate_access_and_filters($params['filters']); $eventparams = array( 'other' => array('entryid' => $filters['entryid'], 'tagid' => $filters['tagid'], 'userid' => $userid, 'modid' => $filters['cmid'], 'groupid' => $filters['groupid'], 'search' => $filters['search'] ) ); if (!empty($userid)) { $eventparams['relateduserid'] = $userid; } $eventparams['other']['courseid'] = ($courseid === SITEID) ? 0 : $courseid; $event = \core\event\blog_entries_viewed::create($eventparams); $event->trigger(); return array( 'warnings' => $warnings, 'status' => true, ); } /** * Returns description of view_entries() result value. * * @return \core_external\external_description * @since Moodle 3.6 */ public static function view_entries_returns() { return new external_single_structure( array( 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 'warnings' => new external_warnings(), ) ); } } classes/external/post_exporter.php 0000604 00000016040 15062070561 0013450 0 ustar 00 <?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/>. /** * Class for exporting a blog post (entry). * * @package core_blog * @copyright 2018 Juan Leyva <juan@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_blog\external; defined('MOODLE_INTERNAL') || die(); use core\external\exporter; use core_external\util as external_util; use core_external\external_files; use renderer_base; use context_system; use core_tag\external\tag_item_exporter; /** * Class for exporting a blog post (entry). * * @copyright 2018 Juan Leyva <juan@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class post_exporter extends exporter { /** * Return the list of properties. * * @return array list of properties */ protected static function define_properties() { return array( 'id' => array( 'type' => PARAM_INT, 'null' => NULL_ALLOWED, 'description' => 'Post/entry id.', ), 'module' => array( 'type' => PARAM_ALPHANUMEXT, 'null' => NULL_NOT_ALLOWED, 'description' => 'Where it was published the post (blog, blog_external...).', ), 'userid' => array( 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0, 'description' => 'Post author.', ), 'courseid' => array( 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0, 'description' => 'Course where the post was created.', ), 'groupid' => array( 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0, 'description' => 'Group post was created for.', ), 'moduleid' => array( 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0, 'description' => 'Module id where the post was created (not used anymore).', ), 'coursemoduleid' => array( 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0, 'description' => 'Course module id where the post was created.', ), 'subject' => array( 'type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED, 'description' => 'Post subject.', ), 'summary' => array( 'type' => PARAM_RAW, 'null' => NULL_ALLOWED, 'description' => 'Post summary.', ), 'content' => array( 'type' => PARAM_RAW, 'null' => NULL_ALLOWED, 'description' => 'Post content.', ), 'uniquehash' => array( 'type' => PARAM_RAW, 'null' => NULL_NOT_ALLOWED, 'description' => 'Post unique hash.', ), 'rating' => array( 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0, 'description' => 'Post rating.', ), 'format' => array( 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0, 'description' => 'Post content format.', ), 'summaryformat' => array( 'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN), 'type' => PARAM_INT, 'default' => FORMAT_MOODLE, 'description' => 'Format for the summary field.', ), 'attachment' => array( 'type' => PARAM_RAW, 'null' => NULL_ALLOWED, 'description' => 'Post atachment.', ), 'publishstate' => array( 'type' => PARAM_ALPHA, 'null' => NULL_NOT_ALLOWED, 'default' => 'draft', 'description' => 'Post publish state.', ), 'lastmodified' => array( 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0, 'description' => 'When it was last modified.', ), 'created' => array( 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0, 'description' => 'When it was created.', ), 'usermodified' => array( 'type' => PARAM_INT, 'null' => NULL_ALLOWED, 'description' => 'User that updated the post.', ), ); } protected static function define_related() { return array( 'context' => 'context' ); } protected static function define_other_properties() { return array( 'summaryfiles' => array( 'type' => external_files::get_properties_for_exporter(), 'multiple' => true ), 'attachmentfiles' => array( 'type' => external_files::get_properties_for_exporter(), 'multiple' => true, 'optional' => true ), 'tags' => array( 'type' => tag_item_exporter::read_properties_definition(), 'description' => 'Tags.', 'multiple' => true, 'optional' => true, ), ); } protected function get_other_values(renderer_base $output) { $context = context_system::instance(); // Files always on site context. $values['summaryfiles'] = external_util::get_area_files($context->id, 'blog', 'post', $this->data->id); $values['attachmentfiles'] = external_util::get_area_files($context->id, 'blog', 'attachment', $this->data->id); if ($this->data->module == 'blog_external') { // For external blogs, the content field has the external blog id. $values['tags'] = \core_tag\external\util::get_item_tags('core', 'blog_external', $this->data->content); } else { $values['tags'] = \core_tag\external\util::get_item_tags('core', 'post', $this->data->id); } return $values; } } classes/privacy/provider.php 0000604 00000055562 15062070561 0012234 0 ustar 00 <?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/>. /** * Data provider. * * @package core_blog * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_blog\privacy; defined('MOODLE_INTERNAL') || die(); use blog_entry; use context; use context_helper; use context_user; use context_system; use core_tag_tag; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; require_once($CFG->dirroot . '/blog/locallib.php'); /** * Data provider class. * * @package core_blog * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\subsystem\provider, \core_privacy\local\request\core_userlist_provider { /** * Returns metadata. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection) : collection { $collection->add_database_table('post', [ 'userid' => 'privacy:metadata:post:userid', 'subject' => 'privacy:metadata:post:subject', 'summary' => 'privacy:metadata:post:summary', 'uniquehash' => 'privacy:metadata:post:uniquehash', 'publishstate' => 'privacy:metadata:post:publishstate', 'created' => 'privacy:metadata:post:created', 'lastmodified' => 'privacy:metadata:post:lastmodified', // The following columns are unused: // coursemoduleid, courseid, moduleid, groupid, rating, usermodified. ], 'privacy:metadata:post'); $collection->link_subsystem('core_comment', 'privacy:metadata:core_comments'); $collection->link_subsystem('core_files', 'privacy:metadata:core_files'); $collection->link_subsystem('core_tag', 'privacy:metadata:core_tag'); $collection->add_database_table('blog_external', [ 'userid' => 'privacy:metadata:external:userid', 'name' => 'privacy:metadata:external:name', 'description' => 'privacy:metadata:external:description', 'url' => 'privacy:metadata:external:url', 'filtertags' => 'privacy:metadata:external:filtertags', 'timemodified' => 'privacy:metadata:external:timemodified', 'timefetched' => 'privacy:metadata:external:timefetched', ], 'privacy:metadata:external'); // We do not report on blog_association because this is just context-related data. return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist { global $DB; $contextlist = new \core_privacy\local\request\contextlist(); // There are at least one blog post. if ($DB->record_exists_select('post', 'userid = :userid AND module IN (:blog, :blogext)', [ 'userid' => $userid, 'blog' => 'blog', 'blogext' => 'blog_external'])) { $sql = " SELECT ctx.id FROM {context} ctx WHERE ctx.contextlevel = :ctxlevel AND ctx.instanceid = :ctxuserid"; $params = [ 'ctxlevel' => CONTEXT_USER, 'ctxuserid' => $userid, ]; $contextlist->add_from_sql($sql, $params); // Add the associated context of the blog posts. $sql = " SELECT DISTINCT ctx.id FROM {post} p JOIN {blog_association} ba ON ba.blogid = p.id JOIN {context} ctx ON ctx.id = ba.contextid WHERE p.userid = :userid"; $params = [ 'userid' => $userid, ]; $contextlist->add_from_sql($sql, $params); } // If there is at least one external blog, we add the user context. This is done this // way because we can't directly add context to a contextlist. if ($DB->record_exists('blog_external', ['userid' => $userid])) { $sql = " SELECT ctx.id FROM {context} ctx WHERE ctx.contextlevel = :ctxlevel AND ctx.instanceid = :ctxuserid"; $params = [ 'ctxlevel' => CONTEXT_USER, 'ctxuserid' => $userid, ]; $contextlist->add_from_sql($sql, $params); } // Include the user contexts in which the user comments. $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {comments} c ON c.contextid = ctx.id WHERE c.component = :component AND c.commentarea = :commentarea AND c.userid = :userid"; $params = [ 'component' => 'blog', 'commentarea' => 'format_blog', 'userid' => $userid ]; $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users who have data within a context. * * @param \core_privacy\local\request\userlist $userlist The userlist containing the list of users who have * data in this context/plugin combination. */ public static function get_users_in_context(\core_privacy\local\request\userlist $userlist) { global $DB; $context = $userlist->get_context(); if ($context->contextlevel == CONTEXT_COURSE || $context->contextlevel == CONTEXT_MODULE) { $params = ['contextid' => $context->id]; $sql = "SELECT p.id, p.userid FROM {post} p JOIN {blog_association} ba ON ba.blogid = p.id AND ba.contextid = :contextid"; $posts = $DB->get_records_sql($sql, $params); $userids = array_map(function($post) { return $post->userid; }, $posts); $userlist->add_users($userids); if (!empty($posts)) { // Add any user's who posted on the blog. list($insql, $inparams) = $DB->get_in_or_equal(array_keys($posts), SQL_PARAMS_NAMED); \core_comment\privacy\provider::get_users_in_context_from_sql($userlist, 'c', 'blog', 'format_blog', null, $insql, $inparams); } } else if ($context->contextlevel == CONTEXT_USER) { $params = ['userid' => $context->instanceid]; $sql = "SELECT userid FROM {blog_external} WHERE userid = :userid"; $userlist->add_from_sql('userid', $sql, $params); $sql = "SELECT userid FROM {post} WHERE userid = :userid"; $userlist->add_from_sql('userid', $sql, $params); // Add any user's who posted on the blog. \core_comment\privacy\provider::get_users_in_context_from_sql($userlist, 'c', 'blog', 'format_blog', $context->id); } } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { global $DB; $sysctx = context_system::instance(); $fs = get_file_storage(); $userid = $contextlist->get_user()->id; $ctxfields = context_helper::get_preload_record_columns_sql('ctx'); $rootpath = [get_string('blog', 'core_blog')]; $associations = []; foreach ($contextlist as $context) { switch ($context->contextlevel) { case CONTEXT_USER: $contextuserid = $context->instanceid; $insql = ' > 0'; $inparams = []; if ($contextuserid != $userid) { // We will only be exporting comments, so fetch the IDs of the relevant entries. $entryids = $DB->get_fieldset_sql(" SELECT DISTINCT c.itemid FROM {comments} c WHERE c.contextid = :contextid AND c.userid = :userid AND c.component = :component AND c.commentarea = :commentarea", [ 'contextid' => $context->id, 'userid' => $userid, 'component' => 'blog', 'commentarea' => 'format_blog' ]); if (empty($entryids)) { // This should not happen, as the user context should not have been reported then. continue 2; } list($insql, $inparams) = $DB->get_in_or_equal($entryids, SQL_PARAMS_NAMED); } // Loop over each blog entry in context. $sql = "userid = :userid AND module IN (:blog, :blogext) AND id $insql"; $params = array_merge($inparams, ['userid' => $contextuserid, 'blog' => 'blog', 'blogext' => 'blog_external']); $recordset = $DB->get_recordset_select('post', $sql, $params, 'id'); foreach ($recordset as $record) { $subject = format_string($record->subject); $path = array_merge($rootpath, [get_string('blogentries', 'core_blog'), $subject . " ({$record->id})"]); // If the context is not mine, then we ONLY export the comments made by the exporting user. if ($contextuserid != $userid) { \core_comment\privacy\provider::export_comments($context, 'blog', 'format_blog', $record->id, $path, true); continue; } // Manually export the files as they reside in the system context so we can't use // the write's helper methods. The same happens for attachments. foreach ($fs->get_area_files($sysctx->id, 'blog', 'post', $record->id) as $f) { writer::with_context($context)->export_file($path, $f); } foreach ($fs->get_area_files($sysctx->id, 'blog', 'attachment', $record->id) as $f) { writer::with_context($context)->export_file($path, $f); } // Rewrite the summary files. $summary = writer::with_context($context)->rewrite_pluginfile_urls($path, 'blog', 'post', $record->id, $record->summary); // Fetch associations. $assocs = []; $sql = "SELECT ba.contextid, $ctxfields FROM {blog_association} ba JOIN {context} ctx ON ba.contextid = ctx.id WHERE ba.blogid = :blogid"; $assocset = $DB->get_recordset_sql($sql, ['blogid' => $record->id]); foreach ($assocset as $assocrec) { context_helper::preload_from_record($assocrec); $assocctx = context::instance_by_id($assocrec->contextid); $assocs[] = $assocctx->get_context_name(); } $assocset->close(); // Export associated tags. \core_tag\privacy\provider::export_item_tags($userid, $context, $path, 'core', 'post', $record->id); // Export all comments made on my post. \core_comment\privacy\provider::export_comments($context, 'blog', 'format_blog', $record->id, $path, false); // Add blog entry data. $entry = (object) [ 'subject' => $subject, 'summary' => format_text($summary, $record->summaryformat), 'uniquehash' => $record->uniquehash, 'publishstate' => static::transform_publishstate($record->publishstate), 'created' => transform::datetime($record->created), 'lastmodified' => transform::datetime($record->lastmodified), 'associations' => $assocs ]; writer::with_context($context)->export_data($path, $entry); } $recordset->close(); // Export external blogs. $recordset = $DB->get_recordset('blog_external', ['userid' => $userid]); foreach ($recordset as $record) { $path = array_merge($rootpath, [get_string('externalblogs', 'core_blog'), $record->name . " ({$record->id})"]); // Export associated tags. \core_tag\privacy\provider::export_item_tags($userid, $context, $path, 'core', 'blog_external', $record->id); // Add data. $external = (object) [ 'name' => $record->name, 'description' => $record->description, 'url' => $record->url, 'filtertags' => $record->filtertags, 'modified' => transform::datetime($record->timemodified), 'lastfetched' => transform::datetime($record->timefetched), ]; writer::with_context($context)->export_data($path, $external); } $recordset->close(); break; case CONTEXT_COURSE: case CONTEXT_MODULE: $associations[] = $context->id; break; } } // Export associations. if (!empty($associations)) { list($insql, $inparams) = $DB->get_in_or_equal($associations, SQL_PARAMS_NAMED); $sql = " SELECT ba.contextid, p.subject, $ctxfields FROM {post} p JOIN {blog_association} ba ON ba.blogid = p.id JOIN {context} ctx ON ctx.id = ba.contextid WHERE ba.contextid $insql AND p.userid = :userid ORDER BY ba.contextid ASC"; $params = array_merge($inparams, ['userid' => $userid]); $path = [get_string('privacy:path:blogassociations', 'core_blog')]; $flushassocs = function($context, $assocs) use ($path) { writer::with_context($context)->export_data($path, (object) [ 'associations' => $assocs ]); }; $lastcontextid = null; $assocs = []; $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { context_helper::preload_from_record($record); if ($lastcontextid && $record->contextid != $lastcontextid) { $flushassocs(context::instance_by_id($lastcontextid), $assocs); $assocs = []; } $assocs[] = format_string($record->subject); $lastcontextid = $record->contextid; } if ($lastcontextid) { $flushassocs(context::instance_by_id($lastcontextid), $assocs); } $recordset->close(); } } /** * Delete all data for all users in the specified context. * * @param context $context The specific context to delete data for. */ public static function delete_data_for_all_users_in_context(context $context) { global $DB; switch ($context->contextlevel) { case CONTEXT_USER: static::delete_all_user_data($context); break; case CONTEXT_COURSE: case CONTEXT_MODULE: // We only delete associations here. $DB->delete_records('blog_association', ['contextid' => $context->id]); break; } // Delete all the comments. \core_comment\privacy\provider::delete_comments_for_all_users($context, 'blog', 'format_blog'); } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { global $DB; $userid = $contextlist->get_user()->id; $associationcontextids = []; foreach ($contextlist as $context) { if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $userid) { static::delete_all_user_data($context); \core_comment\privacy\provider::delete_comments_for_all_users($context, 'blog', 'format_blog'); } else if ($context->contextlevel == CONTEXT_COURSE) { // Only delete the course associations. $associationcontextids[] = $context->id; } else if ($context->contextlevel == CONTEXT_MODULE) { // Only delete the module associations. $associationcontextids[] = $context->id; } else { \core_comment\privacy\provider::delete_comments_for_user($contextlist, 'blog', 'format_blog'); } } // Delete the associations. if (!empty($associationcontextids)) { list($insql, $inparams) = $DB->get_in_or_equal($associationcontextids, SQL_PARAMS_NAMED); $sql = "SELECT ba.id FROM {blog_association} ba JOIN {post} p ON p.id = ba.blogid WHERE ba.contextid $insql AND p.userid = :userid"; $params = array_merge($inparams, ['userid' => $userid]); $associds = $DB->get_fieldset_sql($sql, $params); $DB->delete_records_list('blog_association', 'id', $associds); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(\core_privacy\local\request\approved_userlist $userlist) { global $DB; $context = $userlist->get_context(); $userids = $userlist->get_userids(); if ($context->contextlevel == CONTEXT_USER) { // If one of the listed users matches this context then delete the blog, associations, and comments. if (array_search($context->instanceid, $userids) !== false) { self::delete_all_user_data($context); \core_comment\privacy\provider::delete_comments_for_all_users($context, 'blog', 'format_blog'); return; } \core_comment\privacy\provider::delete_comments_for_users($userlist, 'blog', 'format_blog'); } else { list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); $sql = "SELECT ba.id FROM {blog_association} ba JOIN {post} p ON p.id = ba.blogid WHERE ba.contextid = :contextid AND p.userid $insql"; $inparams['contextid'] = $context->id; $associds = $DB->get_fieldset_sql($sql, $inparams); if (!empty($associds)) { list($insql, $inparams) = $DB->get_in_or_equal($associds, SQL_PARAMS_NAMED, 'param', true); $DB->delete_records_select('blog_association', "id $insql", $inparams); } } } /** * Helper method to delete all user data. * * @param context_user $usercontext The user context. * @return void */ protected static function delete_all_user_data(context_user $usercontext) { global $DB; $userid = $usercontext->instanceid; // Delete all blog posts. $recordset = $DB->get_recordset_select('post', 'userid = :userid AND module IN (:blog, :blogext)', [ 'userid' => $userid, 'blog' => 'blog', 'blogext' => 'blog_external']); foreach ($recordset as $record) { $entry = new blog_entry(null, $record); $entry->delete(); // Takes care of files and associations. } $recordset->close(); // Delete all external blogs, and their associated tags. $DB->delete_records('blog_external', ['userid' => $userid]); core_tag_tag::delete_instances('core', 'blog_external', $usercontext->id); } /** * Transform a publish state. * * @param string $publishstate The publish state. * @return string */ public static function transform_publishstate($publishstate) { switch ($publishstate) { case 'draft': return get_string('publishtonoone', 'core_blog'); case 'site': return get_string('publishtosite', 'core_blog'); case 'public': return get_string('publishtoworld', 'core_blog'); default: } return get_string('privacy:unknown', 'core_blog'); } } classes/reportbuilder/datasource/blogs.php 0000604 00000015147 15062070561 0015042 0 ustar 00 <?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/>. declare(strict_types=1); namespace core_blog\reportbuilder\datasource; use lang_string; use core_reportbuilder\datasource; use core_reportbuilder\local\entities\{course, user}; use core_blog\reportbuilder\local\entities\blog; use core_files\reportbuilder\local\entities\file; use core_comment\reportbuilder\local\entities\comment; use core_tag\reportbuilder\local\entities\tag; /** * Blogs datasource * * @package core_blog * @copyright 2022 Paul Holden <paulh@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class blogs extends datasource { /** * Return user friendly name of the report source * * @return string */ public static function get_name(): string { return get_string('blogs', 'core_blog'); } /** * Initialise report */ protected function initialise(): void { $blogentity = new blog(); $postalias = $blogentity->get_table_alias('post'); $this->set_main_table('post', $postalias); $this->add_base_condition_simple("{$postalias}.module", 'blog'); $this->add_entity($blogentity); // Join the files entity. $fileentity = (new file()) ->set_entity_title(new lang_string('blogattachment', 'core_blog')); $filesalias = $fileentity->get_table_alias('files'); $this->add_entity($fileentity ->add_join("LEFT JOIN {files} {$filesalias} ON {$filesalias}.contextid = " . SYSCONTEXTID . " AND {$filesalias}.component = 'blog' AND {$filesalias}.filearea = 'attachment' AND {$filesalias}.itemid = {$postalias}.id AND {$filesalias}.filename != '.'")); // Join the tag entity. $tagentity = (new tag()) ->set_entity_title(new lang_string('blogtags', 'core_blog')) ->set_table_alias('tag', $blogentity->get_table_alias('tag')); $this->add_entity($tagentity ->add_joins($blogentity->get_tag_joins())); // Join the user entity to represent the blog author. $authorentity = (new user()) ->set_entity_title(new lang_string('author', 'core_blog')); $authoralias = $authorentity->get_table_alias('user'); $this->add_entity($authorentity ->add_join("LEFT JOIN {user} {$authoralias} ON {$authoralias}.id = {$postalias}.userid")); // Join the course entity for course blogs. $courseentity = new course(); $coursealias = $courseentity->get_table_alias('course'); $this->add_entity($courseentity ->add_join("LEFT JOIN {course} {$coursealias} ON {$coursealias}.id = {$postalias}.courseid")); // Join the comment entity (ensure differing alias from that used by course entity). $commententity = (new comment()) ->set_table_alias('comments', 'bcmt'); $this->add_entity($commententity ->add_join("LEFT JOIN {comments} bcmt ON bcmt.component = 'blog' AND bcmt.itemid = {$postalias}.id")); // Join the user entity to represent the comment author. Override table aliases to avoid clash with first instance. $commenterentity = (new user()) ->set_entity_name('commenter') ->set_entity_title(new lang_string('commenter', 'core_comment')) ->set_table_aliases([ 'user' => 'cu', 'context' => 'cuctx', ]); $this->add_entity($commenterentity ->add_joins($commententity->get_joins()) ->add_join("LEFT JOIN {user} cu ON cu.id = bcmt.userid")); // Add report elements from each of the entities we added to the report. $this->add_all_from_entity($blogentity->get_entity_name()); // Add specific file/tag entity elements. $this->add_columns_from_entity($fileentity->get_entity_name(), ['name', 'size', 'type', 'timecreated']); $this->add_filters_from_entity($fileentity->get_entity_name(), ['name', 'size', 'timecreated']); $this->add_conditions_from_entity($fileentity->get_entity_name(), ['name', 'size', 'timecreated']); $this->add_columns_from_entity($tagentity->get_entity_name(), ['name', 'namewithlink']); $this->add_filter($tagentity->get_filter('name')); $this->add_condition($tagentity->get_condition('name')); $this->add_all_from_entity($authorentity->get_entity_name()); $this->add_all_from_entity($courseentity->get_entity_name()); // Add specific comment entity elements. $this->add_columns_from_entity($commententity->get_entity_name(), ['content', 'timecreated']); $this->add_filter($commententity->get_filter('timecreated')); $this->add_condition($commententity->get_filter('timecreated')); $this->add_all_from_entity($commenterentity->get_entity_name()); } /** * Return the columns that will be added to the report upon creation * * @return string[] */ public function get_default_columns(): array { return [ 'user:fullname', 'course:fullname', 'blog:title', 'blog:timecreated', ]; } /** * Return the column sorting that will be added to the report upon creation * * @return int[] */ public function get_default_column_sorting(): array { return [ 'user:fullname' => SORT_ASC, 'blog:timecreated' => SORT_ASC, ]; } /** * Return the filters that will be added to the report upon creation * * @return string[] */ public function get_default_filters(): array { return [ 'user:fullname', 'blog:title', 'blog:timecreated', ]; } /** * Return the conditions that will be added to the report upon creation * * @return string[] */ public function get_default_conditions(): array { return [ 'blog:publishstate', ]; } } classes/reportbuilder/local/entities/blog.php 0000604 00000023554 15062070561 0015444 0 ustar 00 <?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/>. declare(strict_types=1); namespace core_blog\reportbuilder\local\entities; use blog_entry_attachment; use context_system; use lang_string; use stdClass; use core_reportbuilder\local\entities\base; use core_reportbuilder\local\filters\{boolean_select, date, select, text}; use core_reportbuilder\local\helpers\format; use core_reportbuilder\local\report\{column, filter}; /** * Blog entity * * @package core_blog * @copyright 2022 Paul Holden <paulh@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class blog extends base { /** * Database tables that this entity uses and their default aliases * * @return array */ protected function get_default_table_aliases(): array { return [ 'post' => 'bp', 'tag_instance' => 'bti', 'tag' => 'bt', ]; } /** * The default title for this entity * * @return lang_string */ protected function get_default_entity_title(): lang_string { return new lang_string('blog', 'core_blog'); } /** * Initialise the entity * * @return base */ public function initialise(): base { $columns = $this->get_all_columns(); foreach ($columns as $column) { $this->add_column($column); } // All the filters defined by the entity can also be used as conditions. $filters = $this->get_all_filters(); foreach ($filters as $filter) { $this ->add_filter($filter) ->add_condition($filter); } return $this; } /** * Returns list of all available columns * * @return column[] */ protected function get_all_columns(): array { global $DB; $postalias = $this->get_table_alias('post'); // Title. $columns[] = (new column( 'title', new lang_string('entrytitle', 'core_blog'), $this->get_entity_name() )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TEXT) ->add_fields("{$postalias}.subject") ->set_is_sortable(true); // Body. $summaryfieldsql = "{$postalias}.summary"; if ($DB->get_dbfamily() === 'oracle') { $summaryfieldsql = $DB->sql_order_by_text($summaryfieldsql, 1024); } $columns[] = (new column( 'body', new lang_string('entrybody', 'core_blog'), $this->get_entity_name() )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_LONGTEXT) ->add_field($summaryfieldsql, 'summary') ->add_fields("{$postalias}.summaryformat, {$postalias}.id") ->add_callback(static function(?string $summary, stdClass $blog): string { global $CFG; require_once("{$CFG->libdir}/filelib.php"); if ($summary === null) { return ''; } // All blog files are stored in system context. $context = context_system::instance(); $summary = file_rewrite_pluginfile_urls($summary, 'pluginfile.php', $context->id, 'blog', 'post', $blog->id); return format_text($summary, $blog->summaryformat, ['context' => $context->id]); }); // Attachment. $columns[] = (new column( 'attachment', new lang_string('attachment', 'core_repository'), $this->get_entity_name() )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_BOOLEAN) ->add_fields("{$postalias}.attachment, {$postalias}.id") ->add_callback(static function(?bool $attachment, stdClass $post): string { global $CFG, $PAGE; require_once("{$CFG->dirroot}/blog/locallib.php"); if (!$attachment) { return ''; } $renderer = $PAGE->get_renderer('core_blog'); $attachments = ''; // Loop over attached files, use blog renderer to generate appropriate content. $files = get_file_storage()->get_area_files(context_system::instance()->id, 'blog', 'attachment', $post->id, 'filename', false); foreach ($files as $file) { $attachments .= $renderer->render(new blog_entry_attachment($file, $post->id)); } return $attachments; }) ->set_disabled_aggregation_all(); // Publish state. $columns[] = (new column( 'publishstate', new lang_string('publishto', 'core_blog'), $this->get_entity_name() )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TEXT) ->add_fields("{$postalias}.publishstate") ->set_is_sortable(true) ->add_callback(static function(?string $publishstate): string { $states = [ 'draft' => new lang_string('publishtonoone', 'core_blog'), 'site' => new lang_string('publishtosite', 'core_blog'), 'public' => new lang_string('publishtoworld', 'core_blog'), ]; return (string) ($states[$publishstate] ?? $publishstate ?? ''); }); // Time created. $columns[] = (new column( 'timecreated', new lang_string('timecreated', 'core_reportbuilder'), $this->get_entity_name() )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TIMESTAMP) ->add_fields("{$postalias}.created") ->set_is_sortable(true) ->add_callback([format::class, 'userdate']); // Time modified. $columns[] = (new column( 'timemodified', new lang_string('timemodified', 'core_reportbuilder'), $this->get_entity_name() )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TIMESTAMP) ->add_fields("{$postalias}.lastmodified") ->set_is_sortable(true) ->add_callback([format::class, 'userdate']); return $columns; } /** * Return list of all available filters * * @return filter[] */ protected function get_all_filters(): array { global $DB; $postalias = $this->get_table_alias('post'); // Title. $filters[] = (new filter( text::class, 'title', new lang_string('entrytitle', 'core_blog'), $this->get_entity_name(), "{$postalias}.subject" )) ->add_joins($this->get_joins()); // Body. $filters[] = (new filter( text::class, 'body', new lang_string('entrybody', 'core_blog'), $this->get_entity_name(), $DB->sql_cast_to_char("{$postalias}.summary") )) ->add_joins($this->get_joins()); // Attachment. $filters[] = (new filter( boolean_select::class, 'attachment', new lang_string('attachment', 'core_repository'), $this->get_entity_name(), $DB->sql_cast_char2int("{$postalias}.attachment") )) ->add_joins($this->get_joins()); // Publish state. $filters[] = (new filter( select::class, 'publishstate', new lang_string('publishto', 'core_blog'), $this->get_entity_name(), "{$postalias}.publishstate" )) ->add_joins($this->get_joins()) ->set_options([ 'draft' => new lang_string('publishtonoone', 'core_blog'), 'site' => new lang_string('publishtosite', 'core_blog'), 'public' => new lang_string('publishtoworld', 'core_blog'), ]); // Time created. $filters[] = (new filter( date::class, 'timecreated', new lang_string('timecreated', 'core_reportbuilder'), $this->get_entity_name(), "{$postalias}.created" )) ->add_joins($this->get_joins()) ->set_limited_operators([ date::DATE_ANY, date::DATE_CURRENT, date::DATE_LAST, date::DATE_RANGE, ]); // Time modified. $filters[] = (new filter( date::class, 'timemodified', new lang_string('timemodified', 'core_reportbuilder'), $this->get_entity_name(), "{$postalias}.lastmodified" )) ->add_joins($this->get_joins()) ->set_limited_operators([ date::DATE_ANY, date::DATE_CURRENT, date::DATE_LAST, date::DATE_RANGE, ]); return $filters; } /** * Return joins necessary for retrieving tags * * @return string[] */ public function get_tag_joins(): array { return $this->get_tag_joins_for_entity('core', 'post', $this->get_table_alias('post') . '.id'); } } lib.php 0000604 00000145022 15062070561 0006025 0 ustar 00 <?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/>. /** * Core global functions for Blog. * * @package moodlecore * @subpackage blog * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /* * Library of functions and constants for blog */ require_once($CFG->dirroot .'/blog/rsslib.php'); /** * User can edit a blog entry if this is their own blog entry and they have * the capability moodle/blog:create, or if they have the capability * moodle/blog:manageentries. * * This also applies to deleting of entries. */ function blog_user_can_edit_entry($blogentry) { global $USER; $sitecontext = context_system::instance(); if (has_capability('moodle/blog:manageentries', $sitecontext)) { return true; // Can edit any blog entry. } if ($blogentry->userid == $USER->id && has_capability('moodle/blog:create', $sitecontext)) { return true; // Can edit own when having blog:create capability. } return false; } /** * Checks to see if a user can view the blogs of another user. * Only blog level is checked here, the capabilities are enforced * in blog/index.php */ function blog_user_can_view_user_entry($targetuserid, $blogentry=null) { global $CFG, $USER, $DB; if (empty($CFG->enableblogs)) { return false; // Blog system disabled. } if (isloggedin() && $USER->id == $targetuserid) { return true; // Can view own entries in any case. } $sitecontext = context_system::instance(); if (has_capability('moodle/blog:manageentries', $sitecontext)) { return true; // Can manage all entries. } // If blog is in draft state, then make sure user have proper capability. if ($blogentry && $blogentry->publishstate == 'draft' && !has_capability('moodle/blog:viewdrafts', $sitecontext)) { return false; // Can not view draft of others. } // If blog entry is not public, make sure user is logged in. if ($blogentry && $blogentry->publishstate != 'public' && !isloggedin()) { return false; } // If blogentry is not passed or all above checks pass, then check capability based on system config. switch ($CFG->bloglevel) { case BLOG_GLOBAL_LEVEL: return true; break; case BLOG_SITE_LEVEL: if (isloggedin()) { // Not logged in viewers forbidden. return true; } return false; break; case BLOG_USER_LEVEL: default: // If user is viewing other user blog, then user should have user:readuserblogs capability. $personalcontext = context_user::instance($targetuserid); return has_capability('moodle/user:readuserblogs', $personalcontext); break; } } /** * remove all associations for the blog entries of a particular user * @param int userid - id of user whose blog associations will be deleted */ function blog_remove_associations_for_user($userid) { global $DB; throw new coding_exception('function blog_remove_associations_for_user() is not finished'); /* $blogentries = blog_fetch_entries(array('user' => $userid), 'lasmodified DESC'); foreach ($blogentries as $entry) { if (blog_user_can_edit_entry($entry)) { blog_remove_associations_for_entry($entry->id); } } */ } /** * remove all associations for the blog entries of a particular course * @param int courseid - id of user whose blog associations will be deleted */ function blog_remove_associations_for_course($courseid) { global $DB; $context = context_course::instance($courseid); $DB->delete_records('blog_association', array('contextid' => $context->id)); } /** * Remove module associated blogs and blog tag instances. * * @param int $modcontextid Module context ID. */ function blog_remove_associations_for_module($modcontextid) { global $DB; if (!empty($assocblogids = $DB->get_fieldset_select('blog_association', 'blogid', 'contextid = :contextid', ['contextid' => $modcontextid]))) { list($sql, $params) = $DB->get_in_or_equal($assocblogids, SQL_PARAMS_NAMED); $DB->delete_records_select('tag_instance', "itemid $sql", $params); $DB->delete_records_select('post', "id $sql AND module = :module", array_merge($params, ['module' => 'blog'])); $DB->delete_records('blog_association', ['contextid' => $modcontextid]); } } /** * Given a record in the {blog_external} table, checks the blog's URL * for new entries not yet copied into Moodle. * Also attempts to identify and remove deleted blog entries * * @param object $externalblog * @return boolean False if the Feed is invalid */ function blog_sync_external_entries($externalblog) { global $CFG, $DB; require_once($CFG->libdir . '/simplepie/moodle_simplepie.php'); $rss = new moodle_simplepie(); $rssfile = $rss->registry->create('File', array($externalblog->url)); $filetest = $rss->registry->create('Locator', array($rssfile)); if (!$filetest->is_feed($rssfile)) { $externalblog->failedlastsync = 1; $DB->update_record('blog_external', $externalblog); return false; } else if (!empty($externalblog->failedlastsync)) { $externalblog->failedlastsync = 0; $DB->update_record('blog_external', $externalblog); } $rss->set_feed_url($externalblog->url); $rss->init(); if (empty($rss->data)) { return null; } // Used to identify blog posts that have been deleted from the source feed. $oldesttimestamp = null; $uniquehashes = array(); foreach ($rss->get_items() as $entry) { // If filtertags are defined, use them to filter the entries by RSS category. if (!empty($externalblog->filtertags)) { $containsfiltertag = false; $categories = $entry->get_categories(); $filtertags = explode(',', $externalblog->filtertags); $filtertags = array_map('trim', $filtertags); $filtertags = array_map('strtolower', $filtertags); if (!empty($categories)) { foreach ($categories as $category) { if (in_array(trim(strtolower($category->term)), $filtertags)) { $containsfiltertag = true; } } } if (!$containsfiltertag) { continue; } } $uniquehashes[] = $entry->get_permalink(); $newentry = new stdClass(); $newentry->userid = $externalblog->userid; $newentry->module = 'blog_external'; $newentry->content = $externalblog->id; $newentry->uniquehash = $entry->get_permalink(); $newentry->publishstate = 'site'; $newentry->format = FORMAT_HTML; // Clean subject of html, just in case. $newentry->subject = clean_param($entry->get_title(), PARAM_TEXT); // Observe 128 max chars in DB. // TODO: +1 to raise this to 255. if (core_text::strlen($newentry->subject) > 128) { $newentry->subject = core_text::substr($newentry->subject, 0, 125) . '...'; } $newentry->summary = $entry->get_description(); // Used to decide whether to insert or update. // Uses enty permalink plus creation date if available. $existingpostconditions = array('uniquehash' => $entry->get_permalink()); // Our DB doesnt allow null creation or modified timestamps so check the external blog supplied one. $entrydate = $entry->get_date('U'); if (!empty($entrydate)) { $existingpostconditions['created'] = $entrydate; } // The post ID or false if post not found in DB. $postid = $DB->get_field('post', 'id', $existingpostconditions); $timestamp = null; if (empty($entrydate)) { $timestamp = time(); } else { $timestamp = $entrydate; } // Only set created if its a new post so we retain the original creation timestamp if the post is edited. if ($postid === false) { $newentry->created = $timestamp; } $newentry->lastmodified = $timestamp; if (empty($oldesttimestamp) || $timestamp < $oldesttimestamp) { // Found an older post. $oldesttimestamp = $timestamp; } if (core_text::strlen($newentry->uniquehash) > 255) { // The URL for this item is too long for the field. Rather than add // the entry without the link we will skip straight over it. // RSS spec says recommended length 500, we use 255. debugging('External blog entry skipped because of oversized URL', DEBUG_DEVELOPER); continue; } if ($postid === false) { $id = $DB->insert_record('post', $newentry); // Set tags. if ($tags = core_tag_tag::get_item_tags_array('core', 'blog_external', $externalblog->id)) { core_tag_tag::set_item_tags('core', 'post', $id, context_user::instance($externalblog->userid), $tags); } } else { $newentry->id = $postid; $DB->update_record('post', $newentry); } } // Look at the posts we have in the database to check if any of them have been deleted from the feed. // Only checking posts within the time frame returned by the rss feed. Older items may have been deleted or // may just not be returned anymore. We can't tell the difference so we leave older posts alone. $sql = "SELECT id, uniquehash FROM {post} WHERE module = 'blog_external' AND " . $DB->sql_compare_text('content') . " = " . $DB->sql_compare_text(':blogid') . " AND created > :ts"; $dbposts = $DB->get_records_sql($sql, array('blogid' => $externalblog->id, 'ts' => $oldesttimestamp)); $todelete = array(); foreach ($dbposts as $dbpost) { if ( !in_array($dbpost->uniquehash, $uniquehashes) ) { $todelete[] = $dbpost->id; } } $DB->delete_records_list('post', 'id', $todelete); $DB->update_record('blog_external', array('id' => $externalblog->id, 'timefetched' => time())); } /** * Given an external blog object, deletes all related blog entries from the post table. * NOTE: The external blog's id is saved as post.content, a field that is not oterhwise used by blog entries. * @param object $externablog */ function blog_delete_external_entries($externalblog) { global $DB; require_capability('moodle/blog:manageexternal', context_system::instance()); $DB->delete_records_select('post', "module='blog_external' AND " . $DB->sql_compare_text('content') . " = ?", array($externalblog->id)); } /** * This function checks that blogs are enabled, and that the user can see blogs at all * @return bool */ function blog_is_enabled_for_user() { global $CFG; return (!empty($CFG->enableblogs) && (isloggedin() || ($CFG->bloglevel == BLOG_GLOBAL_LEVEL))); } /** * This function gets all of the options available for the current user in respect * to blogs. * * It loads the following if applicable: * - Module options {@see blog_get_options_for_module} * - Course options {@see blog_get_options_for_course} * - User specific options {@see blog_get_options_for_user} * - General options (BLOG_LEVEL_GLOBAL) * * @param moodle_page $page The page to load for (normally $PAGE) * @param stdClass $userid Load for a specific user * @return array An array of options organised by type. */ function blog_get_all_options(moodle_page $page, stdClass $userid = null) { global $CFG, $DB, $USER; $options = array(); // If blogs are enabled and the user is logged in and not a guest. if (blog_is_enabled_for_user()) { // If the context is the user then assume we want to load for the users context. if (is_null($userid) && $page->context->contextlevel == CONTEXT_USER) { $userid = $page->context->instanceid; } // Check the userid var. if (!is_null($userid) && $userid !== $USER->id) { // Load the user from the userid... it MUST EXIST throw a wobbly if it doesn't! $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST); } else { $user = null; } if ($CFG->useblogassociations && $page->cm !== null) { // Load for the module associated with the page. $options[CONTEXT_MODULE] = blog_get_options_for_module($page->cm, $user); } else if ($CFG->useblogassociations && $page->course->id != SITEID) { // Load the options for the course associated with the page. $options[CONTEXT_COURSE] = blog_get_options_for_course($page->course, $user); } // Get the options for the user. if ($user !== null and !isguestuser($user)) { // Load for the requested user. $options[CONTEXT_USER + 1] = blog_get_options_for_user($user); } // Load for the current user. if (isloggedin() and !isguestuser()) { $options[CONTEXT_USER] = blog_get_options_for_user(); } } // If blog level is global then display a link to view all site entries. if (!empty($CFG->enableblogs) && $CFG->bloglevel >= BLOG_GLOBAL_LEVEL && has_capability('moodle/blog:view', context_system::instance())) { $options[CONTEXT_SYSTEM] = array('viewsite' => array( 'string' => get_string('viewsiteentries', 'blog'), 'link' => new moodle_url('/blog/index.php') )); } // Return the options. return $options; } /** * Get all of the blog options that relate to the passed user. * * If no user is passed the current user is assumed. * * @staticvar array $useroptions Cache so we don't have to regenerate multiple times * @param stdClass $user * @return array The array of options for the requested user */ function blog_get_options_for_user(stdClass $user=null) { global $CFG, $USER; // Cache. static $useroptions = array(); $options = array(); // Blogs must be enabled and the user must be logged in. if (!blog_is_enabled_for_user()) { return $options; } // Sort out the user var. if ($user === null || $user->id == $USER->id) { $user = $USER; $iscurrentuser = true; } else { $iscurrentuser = false; } // If we've already generated serve from the cache. if (array_key_exists($user->id, $useroptions)) { return $useroptions[$user->id]; } $sitecontext = context_system::instance(); $canview = has_capability('moodle/blog:view', $sitecontext); if (!$iscurrentuser && $canview && ($CFG->bloglevel >= BLOG_SITE_LEVEL)) { // Not the current user, but we can view and its blogs are enabled for SITE or GLOBAL. $options['userentries'] = array( 'string' => get_string('viewuserentries', 'blog', fullname($user)), 'link' => new moodle_url('/blog/index.php', array('userid' => $user->id)) ); } else { // It's the current user. if ($canview) { // We can view our own blogs .... BIG surprise. $options['view'] = array( 'string' => get_string('blogentries', 'blog'), 'link' => new moodle_url('/blog/index.php', array('userid' => $USER->id)) ); } if (has_capability('moodle/blog:create', $sitecontext)) { // We can add to our own blog. $options['add'] = array( 'string' => get_string('addnewentry', 'blog'), 'link' => new moodle_url('/blog/edit.php', array('action' => 'add')) ); } } if ($canview && $CFG->enablerssfeeds) { $options['rss'] = array( 'string' => get_string('rssfeed', 'blog'), 'link' => new moodle_url(rss_get_url($sitecontext->id, $USER->id, 'blog', 'user/'.$user->id)) ); } // Cache the options. $useroptions[$user->id] = $options; // Return the options. return $options; } /** * Get the blog options that relate to the given course for the given user. * * @staticvar array $courseoptions A cache so we can save regenerating multiple times * @param stdClass $course The course to load options for * @param stdClass $user The user to load options for null == current user * @return array The array of options */ function blog_get_options_for_course(stdClass $course, stdClass $user=null) { global $CFG, $USER; // Cache. static $courseoptions = array(); $options = array(); // User must be logged in and blogs must be enabled. if (!blog_is_enabled_for_user()) { return $options; } // Check that the user can associate with the course. $sitecontext = context_system::instance(); // Generate the cache key. $key = $course->id.':'; if (!empty($user)) { $key .= $user->id; } else { $key .= $USER->id; } // Serve from the cache if we've already generated for this course. if (array_key_exists($key, $courseoptions)) { return $courseoptions[$key]; } if (has_capability('moodle/blog:view', $sitecontext)) { // We can view! if ($CFG->bloglevel >= BLOG_SITE_LEVEL) { // View entries about this course. $options['courseview'] = array( 'string' => get_string('viewcourseblogs', 'blog'), 'link' => new moodle_url('/blog/index.php', array('courseid' => $course->id)) ); } // View MY entries about this course. $options['courseviewmine'] = array( 'string' => get_string('viewmyentriesaboutcourse', 'blog'), 'link' => new moodle_url('/blog/index.php', array('courseid' => $course->id, 'userid' => $USER->id)) ); if (!empty($user) && ($CFG->bloglevel >= BLOG_SITE_LEVEL)) { // View the provided users entries about this course. $options['courseviewuser'] = array( 'string' => get_string('viewentriesbyuseraboutcourse', 'blog', fullname($user)), 'link' => new moodle_url('/blog/index.php', array('courseid' => $course->id, 'userid' => $user->id)) ); } } if (has_capability('moodle/blog:create', $sitecontext)) { // We can blog about this course. $options['courseadd'] = array( 'string' => get_string('blogaboutthiscourse', 'blog'), 'link' => new moodle_url('/blog/edit.php', array('action' => 'add', 'courseid' => $course->id)) ); } // Cache the options for this course. $courseoptions[$key] = $options; // Return the options. return $options; } /** * Get the blog options relating to the given module for the given user * * @staticvar array $moduleoptions Cache * @param stdClass|cm_info $module The module to get options for * @param stdClass $user The user to get options for null == currentuser * @return array */ function blog_get_options_for_module($module, $user=null) { global $CFG, $USER; // Cache. static $moduleoptions = array(); $options = array(); // User must be logged in, blogs must be enabled. if (!blog_is_enabled_for_user()) { return $options; } $sitecontext = context_system::instance(); // Generate the cache key. $key = $module->id.':'; if (!empty($user)) { $key .= $user->id; } else { $key .= $USER->id; } if (array_key_exists($key, $moduleoptions)) { // Serve from the cache so we don't have to regenerate. return $moduleoptions[$key]; } if (has_capability('moodle/blog:view', $sitecontext)) { // Save correct module name for later usage. $modulename = get_string('modulename', $module->modname); // We can view! if ($CFG->bloglevel >= BLOG_SITE_LEVEL) { // View all entries about this module. $a = new stdClass; $a->type = $modulename; $options['moduleview'] = array( 'string' => get_string('viewallmodentries', 'blog', $a), 'link' => new moodle_url('/blog/index.php', array('modid' => $module->id)) ); } // View MY entries about this module. $options['moduleviewmine'] = array( 'string' => get_string('viewmyentriesaboutmodule', 'blog', $modulename), 'link' => new moodle_url('/blog/index.php', array('modid' => $module->id, 'userid' => $USER->id)) ); if (!empty($user) && ($CFG->bloglevel >= BLOG_SITE_LEVEL)) { // View the given users entries about this module. $a = new stdClass; $a->mod = $modulename; $a->user = fullname($user); $options['moduleviewuser'] = array( 'string' => get_string('blogentriesbyuseraboutmodule', 'blog', $a), 'link' => new moodle_url('/blog/index.php', array('modid' => $module->id, 'userid' => $user->id)) ); } } if (has_capability('moodle/blog:create', $sitecontext)) { // The user can blog about this module. $options['moduleadd'] = array( 'string' => get_string('blogaboutthismodule', 'blog', $modulename), 'link' => new moodle_url('/blog/edit.php', array('action' => 'add', 'modid' => $module->id)) ); } // Cache the options. $moduleoptions[$key] = $options; // Return the options. return $options; } /** * This function encapsulates all the logic behind the complex * navigation, titles and headings of the blog listing page, depending * on URL params. It looks at URL params and at the current context level. * It builds and returns an array containing: * * 1. heading: The heading displayed above the blog entries * 2. stradd: The text to be used as the "Add entry" link * 3. strview: The text to be used as the "View entries" link * 4. url: The moodle_url object used as the base for add and view links * 5. filters: An array of parameters used to filter blog listings. Used by index.php and the Recent blogs block * * All other variables are set directly in $PAGE * * It uses the current URL to build these variables. * A number of mutually exclusive use cases are used to structure this function. * * @param int $courseid course id the the blog is associated to (can be null). * @param int $groupid group id to filter blogs I can see (can be null) * @param int $userid blog author id (can be null) * @param int $tagid tag id to filter (can be null) * @param string $tag tag name to filter (can be null) * @param int $modid module id the blog is associated to (can be null). * @param int $entryid blog entry id to filter(can be null) * @param string $search string to search (can be null) * @return array */ function blog_get_headers($courseid=null, $groupid=null, $userid=null, $tagid=null, $tag=null, $modid=null, $entryid=null, $search = null) { global $CFG, $PAGE, $DB, $USER; $id = optional_param('id', null, PARAM_INT); $tag = optional_param('tag', $tag, PARAM_NOTAGS); $tagid = optional_param('tagid', $tagid, PARAM_INT); $userid = optional_param('userid', $userid, PARAM_INT); $modid = optional_param('modid', $modid, PARAM_INT); $entryid = optional_param('entryid', $entryid, PARAM_INT); $groupid = optional_param('groupid', $groupid, PARAM_INT); $courseid = optional_param('courseid', $courseid, PARAM_INT); $search = optional_param('search', $search, PARAM_RAW); $action = optional_param('action', null, PARAM_ALPHA); $confirm = optional_param('confirm', false, PARAM_BOOL); // Ignore userid when action == add. if ($action == 'add' && $userid) { unset($userid); $PAGE->url->remove_params(array('userid')); } else if ($action == 'add' && $entryid) { unset($entryid); $PAGE->url->remove_params(array('entryid')); } $headers = array('title' => '', 'heading' => '', 'cm' => null, 'filters' => array()); $blogurl = new moodle_url('/blog/index.php'); $headers['stradd'] = get_string('addnewentry', 'blog'); $headers['strview'] = null; $site = $DB->get_record('course', array('id' => SITEID)); $sitecontext = context_system::instance(); // Common Lang strings. $strparticipants = get_string("participants"); $strblogentries = get_string("blogentries", 'blog'); // Prepare record objects as needed. if (!empty($courseid)) { $headers['filters']['course'] = $courseid; $course = $DB->get_record('course', array('id' => $courseid)); } if (!empty($userid)) { $headers['filters']['user'] = $userid; $user = $DB->get_record('user', array('id' => $userid)); } if (!empty($groupid)) { // The groupid always overrides courseid. $headers['filters']['group'] = $groupid; $group = $DB->get_record('groups', array('id' => $groupid)); $course = $DB->get_record('course', array('id' => $group->courseid)); } $PAGE->set_pagelayout('standard'); // The modid always overrides courseid, so the $course object may be reset here. if (!empty($modid) && $CFG->useblogassociations) { $headers['filters']['module'] = $modid; // A groupid param may conflict with this coursemod's courseid. Ignore groupid in that case. $courseid = $DB->get_field('course_modules', 'course', array('id' => $modid)); $course = $DB->get_record('course', array('id' => $courseid)); $cm = $DB->get_record('course_modules', array('id' => $modid)); $cm->modname = $DB->get_field('modules', 'name', array('id' => $cm->module)); $cm->name = $DB->get_field($cm->modname, 'name', array('id' => $cm->instance)); $a = new stdClass(); $a->type = get_string('modulename', $cm->modname); $PAGE->set_cm($cm, $course); $headers['stradd'] = get_string('blogaboutthis', 'blog', $a); $headers['strview'] = get_string('viewallmodentries', 'blog', $a); } // Case 1: No entry, mod, course or user params: all site entries to be shown (filtered by search and tag/tagid) // Note: if action is set to 'add' or 'edit', we do this at the end. if (empty($entryid) && empty($modid) && empty($courseid) && empty($userid) && !in_array($action, array('edit', 'add'))) { $PAGE->navbar->add($strblogentries, $blogurl); $strsiteblog = get_string('siteblogheading', 'blog'); $PAGE->set_title($strsiteblog); $PAGE->set_heading($site->fullname); $headers['heading'] = $strsiteblog; } // Case 2: only entryid is requested, ignore all other filters. courseid is used to give more contextual information. if (!empty($entryid)) { $headers['filters']['entry'] = $entryid; $sql = 'SELECT u.* FROM {user} u, {post} p WHERE p.id = ? AND p.userid = u.id'; $user = $DB->get_record_sql($sql, array($entryid)); $entry = $DB->get_record('post', array('id' => $entryid)); $blogurl->param('userid', $user->id); if (!empty($course)) { $mycourseid = $course->id; $blogurl->param('courseid', $mycourseid); } else { $mycourseid = $site->id; } $shortname = format_string($site->shortname, true, array('context' => context_course::instance(SITEID))); $PAGE->navbar->add($strblogentries, $blogurl); $blogurl->remove_params('userid'); $PAGE->navbar->add($entry->subject, $blogurl); $blogentryby = get_string('blogentrybyuser', 'blog', fullname($user)); $PAGE->set_title($entry->subject . moodle_page::TITLE_SEPARATOR . $blogentryby); $PAGE->set_heading("$shortname: " . fullname($user) . ": $entry->subject"); $headers['heading'] = $blogentryby; // We ignore tag and search params. if (empty($action) || !$CFG->useblogassociations) { $headers['url'] = $blogurl; return $headers; } } if (!empty($userid) && empty($entryid) && ((empty($courseid) && empty($modid)) || !$CFG->useblogassociations)) { // Case 3: A user's blog entries. $shortname = format_string($site->shortname, true, array('context' => context_course::instance(SITEID))); $blogurl->param('userid', $userid); $PAGE->set_title(fullname($user) . ": " . get_string('blog', 'blog')); $PAGE->set_heading("$shortname: " . fullname($user) . ": " . get_string('blog', 'blog')); $headers['heading'] = get_string('userblog', 'blog', fullname($user)); $headers['strview'] = get_string('viewuserentries', 'blog', fullname($user)); } else if (!$CFG->useblogassociations && empty($userid) && !in_array($action, array('edit', 'add'))) { // Case 4: No blog associations, no userid. $strsiteblog = get_string('siteblogheading', 'blog'); $PAGE->set_title($strsiteblog); $PAGE->set_heading($site->fullname); $headers['heading'] = $strsiteblog; } else if (!empty($userid) && !empty($modid) && empty($entryid)) { // Case 5: Blog entries associated with an activity by a specific user (courseid ignored). $shortname = format_string($site->shortname, true, array('context' => context_course::instance(SITEID))); $blogurl->param('userid', $userid); $blogurl->param('modid', $modid); // Course module navigation is handled by build_navigation as the second param. $headers['cm'] = $cm; $PAGE->navbar->add(fullname($user), "$CFG->wwwroot/user/view.php?id=$user->id"); $PAGE->navbar->add($strblogentries, $blogurl); $PAGE->set_title(fullname($user) . ': ' . get_string('blogentries', 'blog') . moodle_page::TITLE_SEPARATOR . $cm->name); $PAGE->set_heading("$shortname: $cm->name: " . fullname($user) . ': ' . get_string('blogentries', 'blog')); $a = new stdClass(); $a->user = fullname($user); $a->mod = $cm->name; $a->type = get_string('modulename', $cm->modname); $headers['heading'] = get_string('blogentriesbyuseraboutmodule', 'blog', $a); $headers['stradd'] = get_string('blogaboutthis', 'blog', $a); $headers['strview'] = get_string('viewallmodentries', 'blog', $a); } else if (!empty($userid) && !empty($courseid) && empty($modid) && empty($entryid)) { // Case 6: Blog entries associated with a course by a specific user. $blogurl->param('userid', $userid); $blogurl->param('courseid', $courseid); $PAGE->set_title($course->fullname); $PAGE->set_heading($course->fullname); $a = new stdClass(); $a->user = fullname($user); $a->course = format_string($course->fullname, true, array('context' => context_course::instance($course->id))); $a->type = get_string('course'); $headers['heading'] = get_string('blogentriesbyuseraboutcourse', 'blog', $a); $headers['stradd'] = get_string('blogaboutthis', 'blog', $a); $headers['strview'] = get_string('viewblogentries', 'blog', $a); // Remove the userid from the URL to inform the blog_menu block correctly. $blogurl->remove_params(array('userid')); } else if (!empty($groupid) && empty($modid) && empty($entryid)) { // Case 7: Blog entries by members of a group, associated with that group's course. $blogurl->param('courseid', $course->id); $PAGE->navbar->add($strblogentries, $blogurl); $blogurl->remove_params(array('courseid')); $blogurl->param('groupid', $groupid); $PAGE->navbar->add($group->name, $blogurl); $PAGE->set_title($course->fullname); $PAGE->set_heading($course->fullname); $a = new stdClass(); $a->group = $group->name; $a->course = format_string($course->fullname, true, array('context' => context_course::instance($course->id))); $a->type = get_string('course'); $headers['heading'] = get_string('blogentriesbygroupaboutcourse', 'blog', $a); $headers['stradd'] = get_string('blogaboutthis', 'blog', $a); $headers['strview'] = get_string('viewblogentries', 'blog', $a); } else if (!empty($groupid) && !empty($modid) && empty($entryid)) { // Case 8: Blog entries by members of a group, associated with an activity in that course. $headers['cm'] = $cm; $blogurl->param('modid', $modid); $PAGE->navbar->add($strblogentries, $blogurl); $blogurl->param('groupid', $groupid); $PAGE->navbar->add($group->name, $blogurl); $PAGE->set_title($course->fullname); $PAGE->set_heading($course->fullname); $a = new stdClass(); $a->group = $group->name; $a->mod = $cm->name; $a->type = get_string('modulename', $cm->modname); $headers['heading'] = get_string('blogentriesbygroupaboutmodule', 'blog', $a); $headers['stradd'] = get_string('blogaboutthis', 'blog', $a); $headers['strview'] = get_string('viewallmodentries', 'blog', $a); } else if (!empty($modid) && empty($userid) && empty($groupid) && empty($entryid)) { // Case 9: All blog entries associated with an activity. $PAGE->set_cm($cm, $course); $blogurl->param('modid', $modid); $PAGE->navbar->add($strblogentries, $blogurl); $PAGE->set_title($course->fullname); $PAGE->set_heading($course->fullname); $headers['heading'] = get_string('blogentriesabout', 'blog', $cm->name); $a = new stdClass(); $a->type = get_string('modulename', $cm->modname); $headers['stradd'] = get_string('blogaboutthis', 'blog', $a); $headers['strview'] = get_string('viewallmodentries', 'blog', $a); } else if (!empty($courseid) && empty($userid) && empty($groupid) && empty($modid) && empty($entryid)) { // Case 10: All blog entries associated with a course. $blogurl->param('courseid', $courseid); $PAGE->navbar->add($strblogentries, $blogurl); $PAGE->set_title($course->fullname); $PAGE->set_heading($course->fullname); $a = new stdClass(); $a->type = get_string('course'); $headers['heading'] = get_string('blogentriesabout', 'blog', format_string($course->fullname, true, array('context' => context_course::instance($course->id)))); $headers['stradd'] = get_string('blogaboutthis', 'blog', $a); $headers['strview'] = get_string('viewblogentries', 'blog', $a); $blogurl->remove_params(array('userid')); } if (!in_array($action, array('edit', 'add'))) { // Append Tag info. if (!empty($tagid)) { $headers['filters']['tag'] = $tagid; $blogurl->param('tagid', $tagid); $tagrec = $DB->get_record('tag', array('id' => $tagid)); $PAGE->navbar->add($tagrec->name, $blogurl); } else if (!empty($tag)) { if ($tagrec = $DB->get_record('tag', array('name' => $tag))) { $tagid = $tagrec->id; $headers['filters']['tag'] = $tagid; $blogurl->param('tag', $tag); $PAGE->navbar->add(get_string('tagparam', 'blog', $tag), $blogurl); } } // Append Search info. if (!empty($search) && has_capability('moodle/blog:search', $sitecontext)) { $headers['filters']['search'] = $search; $blogurl->param('search', $search); $PAGE->navbar->add(get_string('searchterm', 'blog', s($search)), $blogurl->out()); } } // Append edit mode info. if (!empty($action) && $action == 'add') { } else if (!empty($action) && $action == 'edit') { $PAGE->navbar->add(get_string('editentry', 'blog')); } if (empty($headers['url'])) { $headers['url'] = $blogurl; } return $headers; } /** * Shortcut function for getting a count of blog entries associated with a course or a module * @param int $courseid The ID of the course * @param int $cmid The ID of the course_modules * @return string The number of associated entries */ function blog_get_associated_count($courseid, $cmid=null) { global $DB; $context = context_course::instance($courseid); if ($cmid) { $context = context_module::instance($cmid); } return $DB->count_records('blog_association', array('contextid' => $context->id)); } /** * Running addtional permission check on plugin, for example, plugins * may have switch to turn on/off comments option, this callback will * affect UI display, not like pluginname_comment_validate only throw * exceptions. * blog_comment_validate will be called before viewing/adding/deleting * comment, so don't repeat checks. * Capability check has been done in comment->check_permissions(), we * don't need to do it again here. * * @package core_blog * @category comment * * @param stdClass $commentparam { * context => context the context object * courseid => int course id * cm => stdClass course module object * commentarea => string comment area * itemid => int itemid * } * @return array */ function blog_comment_permissions($commentparam) { global $DB; // If blog is public and current user is guest, then don't let him post comments. $blogentry = $DB->get_record('post', array('id' => $commentparam->itemid), 'publishstate', MUST_EXIST); if ($blogentry->publishstate != 'public') { if (!isloggedin() || isguestuser()) { return array('post' => false, 'view' => true); } } return array('post' => true, 'view' => true); } /** * Validate comment parameter before perform other comments actions * * @package core_blog * @category comment * * @param stdClass $comment { * context => context the context object * courseid => int course id * cm => stdClass course module object * commentarea => string comment area * itemid => int itemid * } * @return boolean */ function blog_comment_validate($commentparam) { global $CFG, $DB, $USER; // Check if blogs are enabled user can comment. if (empty($CFG->enableblogs) || empty($CFG->blogusecomments)) { throw new comment_exception('nopermissiontocomment'); } // Validate comment area. if ($commentparam->commentarea != 'format_blog') { throw new comment_exception('invalidcommentarea'); } $blogentry = $DB->get_record('post', array('id' => $commentparam->itemid), '*', MUST_EXIST); // Validation for comment deletion. if (!empty($commentparam->commentid)) { if ($record = $DB->get_record('comments', array('id' => $commentparam->commentid))) { if ($record->commentarea != 'format_blog') { throw new comment_exception('invalidcommentarea'); } if ($record->contextid != $commentparam->context->id) { throw new comment_exception('invalidcontext'); } if ($record->itemid != $commentparam->itemid) { throw new comment_exception('invalidcommentitemid'); } } else { throw new comment_exception('invalidcommentid'); } } // Validate if user has blog view permission. $sitecontext = context_system::instance(); return has_capability('moodle/blog:view', $sitecontext) && blog_user_can_view_user_entry($blogentry->userid, $blogentry); } /** * Return a list of page types * @param string $pagetype current page type * @param stdClass $parentcontext Block's parent context * @param stdClass $currentcontext Current context of block */ function blog_page_type_list($pagetype, $parentcontext, $currentcontext) { return array( '*' => get_string('page-x', 'pagetype'), 'blog-*' => get_string('page-blog-x', 'blog'), 'blog-index' => get_string('page-blog-index', 'blog'), 'blog-edit' => get_string('page-blog-edit', 'blog') ); } /** * Add nodes to myprofile page. * * @param \core_user\output\myprofile\tree $tree Tree object * @param stdClass $user user object * @param bool $iscurrentuser * @param stdClass $course Course object * * @return bool */ function core_blog_myprofile_navigation(core_user\output\myprofile\tree $tree, $user, $iscurrentuser, $course) { global $CFG; if (!blog_is_enabled_for_user() || isguestuser($user)) { // The guest user cannot post, so it is not possible to view any posts. // Also blogs might be disabled. // May as well just bail aggressively here. return true; } if (!blog_user_can_view_user_entry($user->id)) { return true; } $url = new moodle_url("/blog/index.php", array('userid' => $user->id)); if (!empty($course)) { $url->param('courseid', $course->id); } if ($iscurrentuser) { $title = get_string('blogentries', 'core_blog'); } else { $title = get_string('myprofileuserblogs', 'core_blog'); } $blognode = new core_user\output\myprofile\node('miscellaneous', 'blogs', $title, null, $url); $tree->add_node($blognode); return true; } /** * Returns posts tagged with a specified tag. * * @param core_tag_tag $tag * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag * are displayed on the page and the per-page limit may be bigger * @param int $fromctx context id where the link was displayed, may be used by callbacks * to display items in the same context first * @param int $ctx context id where to search for records * @param bool $rec search in subcontexts as well * @param int $page 0-based number of page being displayed * @return \core_tag\output\tagindex */ function blog_get_tagged_posts($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = true, $page = 0) { global $CFG, $OUTPUT; require_once($CFG->dirroot.'/user/lib.php'); $systemcontext = context_system::instance(); $perpage = $exclusivemode ? 20 : 5; $context = $ctx ? context::instance_by_id($ctx) : context_system::instance(); $content = ''; if (empty($CFG->enableblogs) || !has_capability('moodle/blog:view', $systemcontext)) { // Blogs are not enabled or are not visible to the current user. $totalpages = 0; } else if ($context->contextlevel != CONTEXT_SYSTEM && empty($CFG->useblogassociations)) { // No blog entries can be associated to the non-system context. $totalpages = 0; } else if (!$rec && $context->contextlevel != CONTEXT_COURSE && $context->contextlevel != CONTEXT_MODULE) { // No blog entries can be associated with category or block context. $totalpages = 0; } else { require_once($CFG->dirroot.'/blog/locallib.php'); $filters = array('tag' => $tag->id); if ($rec) { if ($context->contextlevel != CONTEXT_SYSTEM) { $filters['context'] = $context->id; } } else if ($context->contextlevel == CONTEXT_COURSE) { $filters['course'] = $context->instanceid; } else if ($context->contextlevel == CONTEXT_MODULE) { $filters['module'] = $context->instanceid; } $bloglisting = new blog_listing($filters); $blogs = $bloglisting->get_entries($page * $perpage, $perpage); $totalcount = $bloglisting->count_entries(); $totalpages = ceil($totalcount / $perpage); if (!empty($blogs)) { $tagfeed = new core_tag\output\tagfeed(); foreach ($blogs as $blog) { $user = fullclone($blog); $user->id = $blog->userid; $user->deleted = 0; $img = $OUTPUT->user_picture($user, array('size' => 35)); $subject = format_string($blog->subject); if ($blog->publishstate == 'draft') { $class = 'dimmed'; } else { $class = ''; } $url = new moodle_url('/blog/index.php', array('entryid' => $blog->id)); $subject = html_writer::link($url, $subject, array('class' => $class)); $fullname = fullname($user); if (user_can_view_profile($user)) { $profilelink = new moodle_url('/user/view.php', array('id' => $blog->userid)); $fullname = html_writer::link($profilelink, $fullname); } $details = $fullname . ', ' . userdate($blog->created); $tagfeed->add($img, $subject, $details); } $items = $tagfeed->export_for_template($OUTPUT); $content = $OUTPUT->render_from_template('core_tag/tagfeed', $items); $urlparams = array('tagid' => $tag->id); if ($context->contextlevel == CONTEXT_COURSE) { $urlparams['courseid'] = $context->instanceid; } else if ($context->contextlevel == CONTEXT_MODULE) { $urlparams['modid'] = $context->instanceid; } $allblogsurl = new moodle_url('/blog/index.php', $urlparams); $rv = new core_tag\output\tagindex($tag, 'core', 'post', $content, $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages); $rv->exclusiveurl = $allblogsurl; return $rv; } } $rv = new core_tag\output\tagindex($tag, 'core', 'post', $content, $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages); $rv->exclusiveurl = null; return $rv; } /** * Validate the access to a blog. * * @param int $courseid course id the the blog is associated to (can be null). * @param int $modid module id the blog is associated to (can be null). * @param int $groupid group id to filter blogs I can see (can be null) * @param int $entryid blog entry id (can be null) * @param int $userid blog author id (can be null) * @return array with the calculated course and id * @since Moodle 3.6 */ function blog_validate_access($courseid, $modid, $groupid, $entryid, $userid) { global $CFG, $DB, $USER, $COURSE; $sitecontext = context_system::instance(); // Add courseid if modid or groupid is specified: This is used for navigation and title. if (!empty($modid) && empty($courseid)) { $courseid = $DB->get_field('course_modules', 'course', array('id' => $modid)); } if (!empty($groupid) && empty($courseid)) { $courseid = $DB->get_field('groups', 'courseid', array('id' => $groupid)); } if (!$userid && has_capability('moodle/blog:view', $sitecontext) && $CFG->bloglevel > BLOG_USER_LEVEL) { if ($entryid) { if (!$entryobject = $DB->get_record('post', array('id' => $entryid))) { throw new \moodle_exception('nosuchentry', 'blog'); } $userid = $entryobject->userid; } } else if (!$userid) { $userid = $USER->id; } if (!empty($modid)) { if ($CFG->bloglevel < BLOG_SITE_LEVEL) { throw new \moodle_exception('courseblogdisable', 'blog'); } if (!$mod = $DB->get_record('course_modules', array('id' => $modid))) { throw new \moodle_exception('invalidmoduleid', 'error', $modid); } $courseid = $mod->course; } if ((empty($courseid) ? true : $courseid == SITEID) && empty($userid)) { if ($CFG->bloglevel < BLOG_SITE_LEVEL) { throw new \moodle_exception('siteblogdisable', 'blog'); } if (!has_capability('moodle/blog:view', $sitecontext)) { throw new \moodle_exception('cannotviewsiteblog', 'blog'); } $COURSE = $DB->get_record('course', array('format' => 'site')); $courseid = $COURSE->id; } if (!empty($courseid)) { if (!$course = $DB->get_record('course', array('id' => $courseid))) { throw new \moodle_exception('invalidcourseid'); } $courseid = $course->id; if (!has_capability('moodle/blog:view', $sitecontext)) { throw new \moodle_exception('cannotviewcourseblog', 'blog'); } } else { $coursecontext = context_course::instance(SITEID); } if (!empty($groupid)) { if ($CFG->bloglevel < BLOG_SITE_LEVEL) { throw new \moodle_exception('groupblogdisable', 'blog'); } if (! $group = groups_get_group($groupid)) { throw new \moodle_exception('invalidgroupid', 'blog'); } if (!$course = $DB->get_record('course', array('id' => $group->courseid))) { throw new \moodle_exception('invalidcourseid'); } $coursecontext = context_course::instance($course->id); $courseid = $course->id; if (!has_capability('moodle/blog:view', $sitecontext)) { throw new \moodle_exception('cannotviewcourseorgroupblog', 'blog'); } if (groups_get_course_groupmode($course) == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $coursecontext)) { if (!groups_is_member($groupid)) { throw new \moodle_exception('notmemberofgroup'); } } } if (!empty($userid)) { if ($CFG->bloglevel < BLOG_USER_LEVEL) { throw new \moodle_exception('blogdisable', 'blog'); } if (!$user = $DB->get_record('user', array('id' => $userid))) { throw new \moodle_exception('invaliduserid'); } if ($user->deleted) { throw new \moodle_exception('userdeleted'); } if ($USER->id == $userid) { if (!has_capability('moodle/blog:create', $sitecontext) && !has_capability('moodle/blog:view', $sitecontext)) { throw new \moodle_exception('donothaveblog', 'blog'); } } else { if (!has_capability('moodle/blog:view', $sitecontext) || !blog_user_can_view_user_entry($userid)) { throw new \moodle_exception('cannotviewcourseblog', 'blog'); } } } return array($courseid, $userid); } rsslib.php 0000604 00000026543 15062070561 0006563 0 ustar 00 <?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/>. /** * Blog RSS Management * * @package core_blog * @category rss * @copyright 2010 Andrew Davis * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot.'/lib/rsslib.php'); require_once($CFG->dirroot .'/blog/lib.php'); /** * Build the URL for the RSS feed * * @param int $contextid The context under which the URL should be created * @param int $userid The id of the user requesting the RSS Feed * @param string $filtertype The source of the RSS feed (site/course/group/user) * @param int $filterselect The id of the item defined by $filtertype * @param int $tagid The id of the row in the tag table that identifies the RSS Feed * @return string */ function blog_rss_get_url($contextid, $userid, $filtertype, $filterselect = 0, $tagid = 0) { $componentname = 'blog'; $additionalargs = null; switch ($filtertype) { case 'site': $additionalargs = 'site/'.SITEID; break; case 'course': $additionalargs = 'course/'.$filterselect; break; case 'group': $additionalargs = 'group/'.$filterselect; break; case 'user': $additionalargs = 'user/'.$filterselect; break; } if ($tagid) { $additionalargs .= '/'.$tagid; } return rss_get_url($contextid, $userid, $componentname, $additionalargs); } /** * Print the link for the RSS feed with the correct RSS icon (Theme based) * * @param stdClass $context The context under which the URL should be created * @param string $filtertype The source of the RSS feed (site/course/group/user) * @param int $filterselect The id of the item defined by $filtertype * @param int $tagid The id of the row in the tag table that identifies the RSS Feed * @param string $tooltiptext The tooltip to be displayed with the link */ function blog_rss_print_link($context, $filtertype, $filterselect = 0, $tagid = 0, $tooltiptext = '') { global $CFG, $USER, $OUTPUT; if (!isloggedin()) { $userid = $CFG->siteguest; } else { $userid = $USER->id; } $url = blog_rss_get_url($context->id, $userid, $filtertype, $filterselect, $tagid); $rsspix = $OUTPUT->pix_icon('i/rss', get_string('rss'), 'core', array('title' => $tooltiptext)); print '<div class="float-sm-right"><a href="'. $url .'">' . $rsspix . '</a></div>'; } /** * Build the URL for the RSS feed amd add it as a header * * @param stdClass $context The context under which the URL should be created * @param string $title Name for the link to be added to the page header * @param string $filtertype The source of the RSS feed (site/course/group/user) * @param int $filterselect The id of the item defined by $filtertype * @param int $tagid The id of the row in the tag table that identifies the RSS Feed */ function blog_rss_add_http_header($context, $title, $filtertype, $filterselect = 0, $tagid = 0) { global $PAGE, $USER, $CFG; if (!isloggedin()) { $userid = $CFG->siteguest; } else { $userid = $USER->id; } $rsspath = blog_rss_get_url($context->id, $userid, $filtertype, $filterselect, $tagid); $PAGE->add_alternate_version($title, $rsspath, 'application/rss+xml'); } /** * Utility function to extract parameters needed to generate RSS URLs from the blog filters * * @param array $filters filters for the blog * @return array array containing the id of the user/course/group, the relevant context and the filter type: site/user/course/group */ function blog_rss_get_params($filters) { $thingid = $rsscontext = $filtertype = null; $sitecontext = context_system::instance(); if (!$filters) { $thingid = SITEID; $filtertype = 'site'; } else if (array_key_exists('course', $filters)) { $thingid = $filters['course']; $filtertype = 'course'; } else if (array_key_exists('user', $filters)) { $thingid = $filters['user']; $filtertype = 'user'; } else if (array_key_exists('group', $filters)) { $thingid = $filters['group']; $filtertype = 'group'; } return array($thingid, $rsscontext, $filtertype); } /** * Generate any blog RSS feed via one function * * @param stdClass $context The context of the blog for which the feed it being generated * @param array $args An array of arguements needed to build the feed (contextid, token, componentname, type, id, tagid) */ function blog_rss_get_feed($context, $args) { global $CFG, $SITE, $DB; if (empty($CFG->enableblogs)) { debugging('Blogging disabled on this site, RSS feeds are not available'); return null; } if (empty($CFG->enablerssfeeds)) { debugging('Sorry, RSS feeds are disabled on this site'); return ''; } if ($CFG->bloglevel == BLOG_SITE_LEVEL) { if (isguestuser()) { debugging(get_string('nopermissiontoshow', 'error')); return ''; } } $sitecontext = context_system::instance(); if (!has_capability('moodle/blog:view', $sitecontext)) { return null; } $type = clean_param($args[3], PARAM_ALPHA); $id = clean_param($args[4], PARAM_INT); // Could be groupid / courseid / userid depending on $type. $tagid = 0; if ($args[5] != 'rss.xml') { $tagid = clean_param($args[5], PARAM_INT); } else { $tagid = 0; } $filename = blog_rss_file_name($type, $id, $tagid); if (file_exists($filename)) { if (filemtime($filename) + 3600 > time()) { return $filename; // It's already done so we return cached version. } } $courseid = $groupid = $userid = null; switch ($type) { case 'site': break; case 'course': $courseid = $id; break; case 'group': $groupid = $id; break; case 'user': $userid = $id; break; } // Get all the entries from the database. require_once($CFG->dirroot .'/blog/locallib.php'); $blogheaders = blog_get_headers($courseid, $groupid, $userid, $tagid); $bloglisting = new blog_listing($blogheaders['filters']); $blogentries = $bloglisting->get_entries(); // Now generate an array of RSS items. if ($blogentries) { $items = array(); foreach ($blogentries as $blogentry) { $item = new stdClass(); $item->author = fullname($DB->get_record('user', array('id' => $blogentry->userid))); // TODO: this is slow. $item->title = $blogentry->subject; $item->pubdate = $blogentry->lastmodified; $item->link = $CFG->wwwroot.'/blog/index.php?entryid='.$blogentry->id; $summary = file_rewrite_pluginfile_urls($blogentry->summary, 'pluginfile.php', $sitecontext->id, 'blog', 'post', $blogentry->id); $item->description = format_text($summary, $blogentry->format); if ($blogtags = core_tag_tag::get_item_tags_array('core', 'post', $blogentry->id)) { $item->tags = $blogtags; $item->tagscheme = $CFG->wwwroot . '/tag'; } $items[] = $item; } $articles = rss_add_items($items); // Change structure to XML. } else { $articles = ''; } // Get header and footer information. switch ($type) { case 'user': $userfieldsapi = \core_user\fields::for_name(); $info = fullname($DB->get_record('user', array('id' => $id), $userfieldsapi->get_sql('', false, '', '', false)->selects)); break; case 'course': $info = $DB->get_field('course', 'fullname', array('id' => $id)); $info = format_string($info, true, array('context' => context_course::instance($id))); break; case 'site': $info = format_string($SITE->fullname, true, array('context' => context_course::instance(SITEID))); break; case 'group': $group = groups_get_group($id); $info = $group->name; // TODO: $DB->get_field('groups', 'name', array('id' => $id)). break; default: $info = ''; break; } if ($tagid) { $info .= ': '.$DB->get_field('tags', 'text', array('id' => $tagid)); } $header = rss_standard_header(get_string($type.'blog', 'blog', $info), $CFG->wwwroot.'/blog/index.php', get_string('intro', 'blog')); $footer = rss_standard_footer(); // Save the XML contents to file. $rssdata = $header.$articles.$footer; if (blog_rss_save_file($type, $id, $tagid, $rssdata)) { return $filename; } else { return false; // Couldn't find it or make it. } } /** * Retrieve the location and file name of a cached RSS feed * * @param string $type The source of the RSS feed (site/course/group/user) * @param int $id The id of the item defined by $type * @param int $tagid The id of the row in the tag table that identifies the RSS Feed * @return string */ function blog_rss_file_name($type, $id, $tagid = 0) { global $CFG; if ($tagid) { return "$CFG->cachedir/rss/blog/$type/$id/$tagid.xml"; } else { return "$CFG->cachedir/rss/blog/$type/$id.xml"; } } /** * This function saves to file the rss feed specified in the parameters * * @param string $type The source of the RSS feed (site/course/group/user) * @param int $id The id of the item defined by $type * @param int $tagid The id of the row in the tag table that identifies the RSS Feed * @param string $contents The contents of the RSS Feed file * @return bool whether the save was successful or not */ function blog_rss_save_file($type, $id, $tagid = 0, $contents = '') { global $CFG; $status = true; // Blog creates some additional dirs within the rss cache so make sure they all exist. make_cache_directory('rss/blog'); make_cache_directory('rss/blog/'.$type); $filename = blog_rss_file_name($type, $id, $tagid); $expandfilename = false; // We are supplying a full file path. $status = rss_save_file('blog', $filename, $contents, $expandfilename); return $status; } /** * Delete the supplied user's cached blog post RSS feed. * Only user blogs are available by RSS. * This doesn't call rss_delete_file() as blog RSS caching uses it's own file structure. * * @param int $userid */ function blog_rss_delete_file($userid) { $filename = blog_rss_file_name('user', $userid); if (file_exists($filename)) { unlink($filename); } } edit_form.php 0000604 00000017751 15062070561 0007236 0 ustar 00 <?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/>. if (!defined('MOODLE_INTERNAL')) { die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. } require_once($CFG->libdir.'/formslib.php'); class blog_edit_form extends moodleform { public $modnames = array(); /** * Blog form definition. */ public function definition() { global $CFG, $DB; $mform =& $this->_form; $entry = $this->_customdata['entry']; $courseid = $this->_customdata['courseid']; $modid = $this->_customdata['modid']; $summaryoptions = $this->_customdata['summaryoptions']; $attachmentoptions = $this->_customdata['attachmentoptions']; $sitecontext = $this->_customdata['sitecontext']; $mform->addElement('header', 'general', get_string('general', 'form')); $mform->addElement('text', 'subject', get_string('entrytitle', 'blog'), array('size' => 60, 'maxlength' => 128)); $mform->addElement('editor', 'summary_editor', get_string('entrybody', 'blog'), null, $summaryoptions); $mform->setType('subject', PARAM_TEXT); $mform->addRule('subject', get_string('emptytitle', 'blog'), 'required', null, 'client'); $mform->addRule('subject', get_string('maximumchars', '', 128), 'maxlength', 128, 'client'); $mform->setType('summary_editor', PARAM_RAW); $mform->addRule('summary_editor', get_string('emptybody', 'blog'), 'required', null, 'client'); $mform->addElement('filemanager', 'attachment_filemanager', get_string('attachment', 'forum'), null, $attachmentoptions); // Disable publishstate options that are not allowed. $publishstates = array(); $i = 0; foreach (blog_entry::get_applicable_publish_states() as $state => $desc) { $publishstates[$state] = $desc; // No maximum was set. $i++; } $mform->addElement('select', 'publishstate', get_string('publishto', 'blog'), $publishstates); $mform->addHelpButton('publishstate', 'publishto', 'blog'); $mform->setDefault('publishstate', 'site'); if (core_tag_tag::is_enabled('core', 'post')) { $mform->addElement('header', 'tagshdr', get_string('tags', 'tag')); } $mform->addElement('tags', 'tags', get_string('tags'), array('itemtype' => 'post', 'component' => 'core')); $allmodnames = array(); if (!empty($CFG->useblogassociations)) { if ((!empty($entry->courseassoc) || (!empty($courseid) && empty($modid)))) { if (!empty($courseid)) { $course = $DB->get_record('course', array('id' => $courseid)); $context = context_course::instance($courseid); $a = new stdClass(); $a->coursename = format_string($course->fullname, true, array('context' => $context)); $contextid = $context->id; } else { $context = context::instance_by_id($entry->courseassoc); $sql = 'SELECT fullname FROM {course} cr LEFT JOIN {context} ct ON ct.instanceid = cr.id WHERE ct.id = ?'; $a = new stdClass(); $a->coursename = $DB->get_field_sql($sql, array($entry->courseassoc)); $contextid = $entry->courseassoc; } $mform->addElement('header', 'assochdr', get_string('associations', 'blog')); $mform->addElement('advcheckbox', 'courseassoc', get_string('associatewithcourse', 'blog', $a), null, null, array(0, $contextid)); $mform->setDefault('courseassoc', $contextid); } else if ((!empty($entry->modassoc) || !empty($modid))) { if (!empty($modid)) { $mod = get_coursemodule_from_id(false, $modid); $a = new stdClass(); $a->modtype = get_string('modulename', $mod->modname); $a->modname = $mod->name; $context = context_module::instance($modid); } else { $context = context::instance_by_id($entry->modassoc); $cm = $DB->get_record('course_modules', array('id' => $context->instanceid)); $a = new stdClass(); $a->modtype = $DB->get_field('modules', 'name', array('id' => $cm->module)); $a->modname = $DB->get_field($a->modtype, 'name', array('id' => $cm->instance)); $modid = $context->instanceid; } $mform->addElement('header', 'assochdr', get_string('associations', 'blog')); $mform->addElement('advcheckbox', 'modassoc', get_string('associatewithmodule', 'blog', $a), null, null, array(0, $context->id)); $mform->setDefault('modassoc', $context->id); } } $this->add_action_buttons(); $mform->addElement('hidden', 'action'); $mform->setType('action', PARAM_ALPHANUMEXT); $mform->setDefault('action', ''); $mform->addElement('hidden', 'entryid'); $mform->setType('entryid', PARAM_INT); $mform->setDefault('entryid', $entry->id); $mform->addElement('hidden', 'modid'); $mform->setType('modid', PARAM_INT); $mform->setDefault('modid', $modid); $mform->addElement('hidden', 'courseid'); $mform->setType('courseid', PARAM_INT); $mform->setDefault('courseid', $courseid); } /** * Validate the blog form data. * @param array $data Data to be validated * @param array $files unused * @return array|bool */ public function validation($data, $files) { global $CFG, $DB, $USER; $errors = parent::validation($data, $files); // Validate course association. if (!empty($data['courseassoc'])) { $coursecontext = context::instance_by_id($data['courseassoc']); if ($coursecontext->contextlevel != CONTEXT_COURSE) { $errors['courseassoc'] = get_string('error'); } } // Validate mod association. if (!empty($data['modassoc'])) { $modcontextid = $data['modassoc']; $modcontext = context::instance_by_id($modcontextid); if ($modcontext->contextlevel == CONTEXT_MODULE) { // Get context of the mod's course. $coursecontext = $modcontext->get_course_context(true); // Ensure only one course is associated. if (!empty($data['courseassoc'])) { if ($data['courseassoc'] != $coursecontext->id) { $errors['modassoc'] = get_string('onlyassociateonecourse', 'blog'); } } else { $data['courseassoc'] = $coursecontext->id; } } else { $errors['modassoc'] = get_string('error'); } } if ($errors) { return $errors; } return true; } } upgrade.txt 0000604 00000000541 15062070561 0006732 0 ustar 00 This files describes API changes in /blog/* , information provided here is intended especially for developers. === 3.7 === * External function get_entries now returns an additional field "tags" returning the post tags. === 2.7 === * blog_entry->add_associations() does not accept any params. * blog_entry->add_association() accepts only one param. preferences.php 0000604 00000006072 15062070561 0007561 0 ustar 00 <?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/>. /** * Form page for blog preferences * * @package moodlecore * @subpackage blog * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once('../config.php'); require_once($CFG->dirroot.'/blog/lib.php'); require_once('preferences_form.php'); require_once($CFG->dirroot.'/user/editlib.php'); $courseid = optional_param('courseid', SITEID, PARAM_INT); $modid = optional_param('modid', null, PARAM_INT); $userid = optional_param('userid', null, PARAM_INT); $tagid = optional_param('tagid', null, PARAM_INT); $groupid = optional_param('groupid', null, PARAM_INT); $url = new moodle_url('/blog/preferences.php'); if ($courseid !== SITEID) { $url->param('courseid', $courseid); } if ($modid !== null) { $url->param('modid', $modid); } if ($userid !== null) { $url->param('userid', $userid); } if ($tagid !== null) { $url->param('tagid', $tagid); } if ($groupid !== null) { $url->param('groupid', $groupid); } $PAGE->set_url($url); $PAGE->set_pagelayout('admin'); $sitecontext = context_system::instance(); $usercontext = context_user::instance($USER->id); $PAGE->set_context($usercontext); require_login($courseid); if (empty($CFG->enableblogs)) { throw new \moodle_exception('blogdisable', 'blog'); } if (isguestuser()) { throw new \moodle_exception('noguest'); } // The preference is site wide not blog specific. Hence user should have permissions in site level. require_capability('moodle/blog:view', $sitecontext); // If data submitted, then process and store. $mform = new blog_preferences_form('preferences.php'); $mform->set_data(array('pagesize' => get_user_preferences('blogpagesize'))); if (!$mform->is_cancelled() && $data = $mform->get_data()) { $pagesize = $data->pagesize; if ($pagesize < 1) { throw new \moodle_exception('invalidpagesize'); } useredit_update_user_preference(['id' => $USER->id, 'preference_blogpagesize' => $pagesize]); } if ($mform->is_cancelled()) { redirect($CFG->wwwroot . '/user/preferences.php'); } $site = get_site(); $strpreferences = get_string('preferences'); $strblogs = get_string('blogs', 'blog'); $title = "$strblogs : $strpreferences"; $PAGE->set_title($title); $PAGE->set_heading(fullname($USER)); echo $OUTPUT->header(); echo $OUTPUT->heading("$strblogs : $strpreferences", 2); $mform->display(); echo $OUTPUT->footer(); tests/event/events_test.php 0000604 00000050601 15062070561 0012103 0 ustar 00 <?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/>. /** * Events tests. * * @package core_blog * @category test * @copyright 2016 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_blog\event; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/blog/locallib.php'); require_once($CFG->dirroot . '/blog/lib.php'); /** * Unit tests for the blog events. * * @copyright 2016 Stephen Bourget * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class events_test extends \advanced_testcase { /** @var $courseid */ private $courseid; /** @var $cmid */ private $cmid; /** @var $groupid */ private $groupid; /** @var $userid */ private $userid; /** @var $tagid */ private $tagid; /** @var $postid */ private $postid; /** * Setup the tests. */ protected function setUp(): void { global $DB; parent::setUp(); $this->resetAfterTest(); // Create default course. $course = $this->getDataGenerator()->create_course(array('category' => 1, 'shortname' => 'ANON')); $this->assertNotEmpty($course); $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id)); $this->assertNotEmpty($page); // Create default group. $group = new \stdClass(); $group->courseid = $course->id; $group->name = 'ANON'; $group->id = $DB->insert_record('groups', $group); // Create default user. $user = $this->getDataGenerator()->create_user(array( 'username' => 'testuser', 'firstname' => 'Jimmy', 'lastname' => 'Kinnon' )); // Create default tag. $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id, 'rawname' => 'Testtagname', 'isstandard' => 1)); // Create default post. $post = new \stdClass(); $post->userid = $user->id; $post->groupid = $group->id; $post->content = 'test post content text'; $post->module = 'blog'; $post->id = $DB->insert_record('post', $post); // Grab important ids. $this->courseid = $course->id; $this->cmid = $page->cmid; $this->groupid = $group->id; $this->userid = $user->id; $this->tagid = $tag->id; $this->postid = $post->id; } /** * Test various blog related events. */ public function test_blog_entry_created_event() { global $USER; $this->setAdminUser(); $this->resetAfterTest(); // Create a blog entry for another user as Admin. $sink = $this->redirectEvents(); $blog = new \blog_entry(); $blog->subject = "Subject of blog"; $blog->userid = $this->userid; $states = \blog_entry::get_applicable_publish_states(); $blog->publishstate = reset($states); $blog->add(); $events = $sink->get_events(); $sink->close(); $event = reset($events); $sitecontext = \context_system::instance(); // Validate event data. $this->assertInstanceOf('\core\event\blog_entry_created', $event); $url = new \moodle_url('/blog/index.php', array('entryid' => $event->objectid)); $this->assertEquals($url, $event->get_url()); $this->assertEquals($sitecontext->id, $event->contextid); $this->assertEquals($blog->id, $event->objectid); $this->assertEquals($USER->id, $event->userid); $this->assertEquals($this->userid, $event->relateduserid); $this->assertEquals("post", $event->objecttable); $this->assertEventContextNotUsed($event); } /** * Tests for event blog_entry_updated. */ public function test_blog_entry_updated_event() { global $USER; $this->setAdminUser(); $this->resetAfterTest(); $sitecontext = \context_system::instance(); // Edit a blog entry as Admin. $blog = new \blog_entry($this->postid); $sink = $this->redirectEvents(); $blog->summary_editor = array('text' => 'Something', 'format' => FORMAT_MOODLE); $blog->edit(array(), null, array(), array()); $events = $sink->get_events(); $event = array_pop($events); $sink->close(); // Validate event data. $this->assertInstanceOf('\core\event\blog_entry_updated', $event); $url = new \moodle_url('/blog/index.php', array('entryid' => $event->objectid)); $this->assertEquals($url, $event->get_url()); $this->assertEquals($sitecontext->id, $event->contextid); $this->assertEquals($blog->id, $event->objectid); $this->assertEquals($USER->id, $event->userid); $this->assertEquals($this->userid, $event->relateduserid); $this->assertEquals("post", $event->objecttable); $this->assertEventContextNotUsed($event); } /** * Tests for event blog_entry_deleted. */ public function test_blog_entry_deleted_event() { global $USER, $DB; $this->setAdminUser(); $this->resetAfterTest(); $sitecontext = \context_system::instance(); // Delete a user blog entry as Admin. $blog = new \blog_entry($this->postid); $sink = $this->redirectEvents(); $record = $DB->get_record('post', array('id' => $blog->id)); $blog->delete(); $events = $sink->get_events(); $event = array_pop($events); $sink->close(); // Validate event data. $this->assertInstanceOf('\core\event\blog_entry_deleted', $event); $this->assertEquals(null, $event->get_url()); $this->assertEquals($sitecontext->id, $event->contextid); $this->assertEquals($blog->id, $event->objectid); $this->assertEquals($USER->id, $event->userid); $this->assertEquals($this->userid, $event->relateduserid); $this->assertEquals("post", $event->objecttable); $this->assertEquals($record, $event->get_record_snapshot("post", $blog->id)); $this->assertEventContextNotUsed($event); } /** * Tests for event blog_association_deleted. */ public function test_blog_association_deleted_event() { global $USER; $this->setAdminUser(); $this->resetAfterTest(); $sitecontext = \context_system::instance(); $coursecontext = \context_course::instance($this->courseid); $contextmodule = \context_module::instance($this->cmid); // Add blog associations with a course. $blog = new \blog_entry($this->postid); $blog->add_association($coursecontext->id); $sink = $this->redirectEvents(); $blog->remove_associations(); $events = $sink->get_events(); $event = reset($events); $sink->close(); // Validate event data. $this->assertInstanceOf('\core\event\blog_association_deleted', $event); $this->assertEquals($sitecontext->id, $event->contextid); $this->assertEquals($blog->id, $event->other['blogid']); $this->assertEquals($USER->id, $event->userid); $this->assertEquals($this->userid, $event->relateduserid); $this->assertEquals('blog_association', $event->objecttable); // Add blog associations with a module. $blog = new \blog_entry($this->postid); $blog->add_association($contextmodule->id); $sink = $this->redirectEvents(); $blog->remove_associations(); $events = $sink->get_events(); $event = reset($events); $sink->close(); // Validate event data. $this->assertEquals($blog->id, $event->other['blogid']); $this->assertEquals($USER->id, $event->userid); $this->assertEventContextNotUsed($event); } /** * Tests for event blog_association_created. */ public function test_blog_association_created_event() { global $USER; $this->setAdminUser(); $this->resetAfterTest(); $sitecontext = \context_system::instance(); $coursecontext = \context_course::instance($this->courseid); $contextmodule = \context_module::instance($this->cmid); // Add blog associations with a course. $blog = new \blog_entry($this->postid); $sink = $this->redirectEvents(); $blog->add_association($coursecontext->id); $events = $sink->get_events(); $event = reset($events); $sink->close(); // Validate event data. $this->assertInstanceOf('\core\event\blog_association_created', $event); $this->assertEquals($sitecontext->id, $event->contextid); $url = new \moodle_url('/blog/index.php', array('entryid' => $event->other['blogid'])); $this->assertEquals($url, $event->get_url()); $this->assertEquals($blog->id, $event->other['blogid']); $this->assertEquals($this->courseid, $event->other['associateid']); $this->assertEquals('course', $event->other['associatetype']); $this->assertEquals($blog->subject, $event->other['subject']); $this->assertEquals($USER->id, $event->userid); $this->assertEquals($this->userid, $event->relateduserid); $this->assertEquals('blog_association', $event->objecttable); // Add blog associations with a module. $blog = new \blog_entry($this->postid); $sink = $this->redirectEvents(); $blog->add_association($contextmodule->id); $events = $sink->get_events(); $event = reset($events); $sink->close(); // Validate event data. $this->assertEquals($blog->id, $event->other['blogid']); $this->assertEquals($this->cmid, $event->other['associateid']); $this->assertEquals('coursemodule', $event->other['associatetype']); $this->assertEventContextNotUsed($event); } /** * Tests for event blog_association_created validations. */ public function test_blog_association_created_event_validations() { $this->resetAfterTest(); // Make sure associatetype validations work. try { \core\event\blog_association_created::create(array( 'contextid' => 1, 'objectid' => 3, 'relateduserid' => 2, 'other' => array('associateid' => 2 , 'blogid' => 3, 'subject' => 'blog subject'))); } catch (\coding_exception $e) { $this->assertStringContainsString('The \'associatetype\' value must be set in other and be a valid type.', $e->getMessage()); } try { \core\event\blog_association_created::create(array( 'contextid' => 1, 'objectid' => 3, 'relateduserid' => 2, 'other' => array('associateid' => 2 , 'blogid' => 3, 'associatetype' => 'random', 'subject' => 'blog subject'))); } catch (\coding_exception $e) { $this->assertStringContainsString('The \'associatetype\' value must be set in other and be a valid type.', $e->getMessage()); } // Make sure associateid validations work. try { \core\event\blog_association_created::create(array( 'contextid' => 1, 'objectid' => 3, 'relateduserid' => 2, 'other' => array('blogid' => 3, 'associatetype' => 'course', 'subject' => 'blog subject'))); } catch (\coding_exception $e) { $this->assertStringContainsString('The \'associateid\' value must be set in other.', $e->getMessage()); } // Make sure blogid validations work. try { \core\event\blog_association_created::create(array( 'contextid' => 1, 'objectid' => 3, 'relateduserid' => 2, 'other' => array('associateid' => 3, 'associatetype' => 'course', 'subject' => 'blog subject'))); } catch (\coding_exception $e) { $this->assertStringContainsString('The \'blogid\' value must be set in other.', $e->getMessage()); } // Make sure blogid validations work. try { \core\event\blog_association_created::create(array( 'contextid' => 1, 'objectid' => 3, 'relateduserid' => 2, 'other' => array('blogid' => 3, 'associateid' => 3, 'associatetype' => 'course'))); } catch (\coding_exception $e) { $this->assertStringContainsString('The \'subject\' value must be set in other.', $e->getMessage()); } } /** * Tests for event blog_entries_viewed. */ public function test_blog_entries_viewed_event() { $this->setAdminUser(); $other = array('entryid' => $this->postid, 'tagid' => $this->tagid, 'userid' => $this->userid, 'modid' => $this->cmid, 'groupid' => $this->groupid, 'courseid' => $this->courseid, 'search' => 'search', 'fromstart' => 2); // Trigger event. $sink = $this->redirectEvents(); $eventparams = array('other' => $other); $eventinst = \core\event\blog_entries_viewed::create($eventparams); $eventinst->trigger(); $events = $sink->get_events(); $event = reset($events); $sink->close(); // Validate event data. $url = new \moodle_url('/blog/index.php', $other); $this->assertEquals($url, $event->get_url()); $this->assertEventContextNotUsed($event); } /** * Test comment_created event. */ public function test_blog_comment_created_event() { global $USER, $CFG; $this->setAdminUser(); require_once($CFG->dirroot . '/comment/lib.php'); $context = \context_user::instance($USER->id); $cmt = new \stdClass(); $cmt->context = $context; $cmt->courseid = $this->courseid; $cmt->area = 'format_blog'; $cmt->itemid = $this->postid; $cmt->showcount = 1; $cmt->component = 'blog'; $manager = new \comment($cmt); // Triggering and capturing the event. $sink = $this->redirectEvents(); $manager->add("New comment"); $events = $sink->get_events(); $this->assertCount(1, $events); $event = reset($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\core\event\blog_comment_created', $event); $this->assertEquals($context, $event->get_context()); $this->assertEquals($this->postid, $event->other['itemid']); $url = new \moodle_url('/blog/index.php', array('entryid' => $this->postid)); $this->assertEquals($url, $event->get_url()); $this->assertEventContextNotUsed($event); } /** * Test comment_deleted event. */ public function test_blog_comment_deleted_event() { global $USER, $CFG; $this->setAdminUser(); require_once($CFG->dirroot . '/comment/lib.php'); $context = \context_user::instance($USER->id); $cmt = new \stdClass(); $cmt->context = $context; $cmt->courseid = $this->courseid; $cmt->area = 'format_blog'; $cmt->itemid = $this->postid; $cmt->showcount = 1; $cmt->component = 'blog'; $manager = new \comment($cmt); $newcomment = $manager->add("New comment"); // Triggering and capturing the event. $sink = $this->redirectEvents(); $manager->delete($newcomment->id); $events = $sink->get_events(); $this->assertCount(1, $events); $event = reset($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\core\event\blog_comment_deleted', $event); $this->assertEquals($context, $event->get_context()); $this->assertEquals($this->postid, $event->other['itemid']); $url = new \moodle_url('/blog/index.php', array('entryid' => $this->postid)); $this->assertEquals($url, $event->get_url()); $this->assertEventContextNotUsed($event); } /** * Test external blog added event. * * There is no external API for this, so the unit test will simply * create and trigger the event and ensure data is returned as expected. */ public function test_external_blog_added_event() { // Trigger an event: external blog added. $eventparams = array( 'context' => $context = \context_system::instance(), 'objectid' => 1001, 'other' => array('url' => 'http://moodle.org') ); $event = \core\event\blog_external_added::create($eventparams); // Trigger and capture the event. $sink = $this->redirectEvents(); $event->trigger(); $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\blog_external_added', $event); $this->assertEquals(1001, $event->objectid); $this->assertEquals('http://moodle.org', $event->other['url']); $this->assertDebuggingNotCalled(); } /** * Test external blog updated event. * * There is no external API for this, so the unit test will simply * create and trigger the event and ensure data is returned as expected. */ public function test_external_blog_updated_event() { // Trigger an event: external blog updated. $eventparams = array( 'context' => $context = \context_system::instance(), 'objectid' => 1001, 'other' => array('url' => 'http://moodle.org') ); $event = \core\event\blog_external_updated::create($eventparams); // Trigger and capture the event. $sink = $this->redirectEvents(); $event->trigger(); $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\blog_external_updated', $event); $this->assertEquals(1001, $event->objectid); $this->assertEquals('http://moodle.org', $event->other['url']); $this->assertDebuggingNotCalled(); } /** * Test external blog removed event. * * There is no external API for this, so the unit test will simply * create and trigger the event and ensure data is returned as expected. */ public function test_external_blog_removed_event() { // Trigger an event: external blog removed. $eventparams = array( 'context' => $context = \context_system::instance(), 'objectid' => 1001, ); $event = \core\event\blog_external_removed::create($eventparams); // Trigger and capture the event. $sink = $this->redirectEvents(); $event->trigger(); $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\blog_external_removed', $event); $this->assertEquals(1001, $event->objectid); $this->assertDebuggingNotCalled(); } /** * Test external blogs viewed event. * * There is no external API for this, so the unit test will simply * create and trigger the event and ensure data is returned as expected. */ public function test_external_blogs_viewed_event() { // Trigger an event: external blogs viewed. $eventparams = array( 'context' => $context = \context_system::instance(), ); $event = \core\event\blog_external_viewed::create($eventparams); // Trigger and capture the event. $sink = $this->redirectEvents(); $event->trigger(); $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\blog_external_viewed', $event); $this->assertDebuggingNotCalled(); } } tests/reportbuilder/datasource/blogs_test.php 0000604 00000031043 15062070561 0015577 0 ustar 00 <?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/>. declare(strict_types=1); namespace core_blog\reportbuilder\datasource; use context_system; use context_user; use core_blog_generator; use core_comment_generator; use core_reportbuilder_generator; use core_reportbuilder_testcase; use core_reportbuilder\local\filters\{boolean_select, date, select, text}; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php"); /** * Unit tests for blogs datasource * * @package core_blog * @covers \core_blog\reportbuilder\datasource\blogs * @copyright 2022 Paul Holden <paulh@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class blogs_test extends core_reportbuilder_testcase { /** * Test default datasource */ public function test_datasource_default(): void { $this->resetAfterTest(); /** @var core_blog_generator $blogsgenerator */ $blogsgenerator = $this->getDataGenerator()->get_plugin_generator('core_blog'); // Our first user will create a course blog. $course = $this->getDataGenerator()->create_course(); $userone = $this->getDataGenerator()->create_and_enrol($course, 'student', ['firstname' => 'Zoe']); $courseblog = $blogsgenerator->create_entry(['publishstate' => 'site', 'userid' => $userone->id, 'subject' => 'Course', 'summary' => 'Course summary', 'courseid' => $course->id]); // Our second user will create a personal and site blog. $usertwo = $this->getDataGenerator()->create_user(['firstname' => 'Amy']); $personalblog = $blogsgenerator->create_entry(['publishstate' => 'draft', 'userid' => $usertwo->id, 'subject' => 'Personal', 'summary' => 'Personal summary']); $this->waitForSecond(); // For consistent ordering we need distinct time for second user blogs. $siteblog = $blogsgenerator->create_entry(['publishstate' => 'public', 'userid' => $usertwo->id, 'subject' => 'Site', 'summary' => 'Site summary']); /** @var core_reportbuilder_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); $report = $generator->create_report(['name' => 'Blogs', 'source' => blogs::class, 'default' => 1]); $content = $this->get_custom_report_content($report->get('id')); // Default columns are user, course, title, time created. Sorted by user and time created. $this->assertEquals([ [fullname($usertwo), '', $personalblog->subject, userdate($personalblog->created)], [fullname($usertwo), '', $siteblog->subject, userdate($siteblog->created)], [fullname($userone), $course->fullname, $courseblog->subject, userdate($courseblog->created)], ], array_map('array_values', $content)); } /** * Test datasource columns that aren't added by default */ public function test_datasource_non_default_columns(): void { global $DB; $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); /** @var core_blog_generator $blogsgenerator */ $blogsgenerator = $this->getDataGenerator()->get_plugin_generator('core_blog'); $blog = $blogsgenerator->create_entry(['publishstate' => 'draft', 'userid' => $user->id, 'subject' => 'My blog', 'summary' => 'Horses', 'tags' => ['horse']]); // Add an attachment. $blog->attachment = 1; get_file_storage()->create_file_from_string([ 'contextid' => context_system::instance()->id, 'component' => 'blog', 'filearea' => 'attachment', 'itemid' => $blog->id, 'filepath' => '/', 'filename' => 'hello.txt', ], 'hello'); /** @var core_comment_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_comment'); $generator->create_comment([ 'context' => context_user::instance($user->id), 'component' => 'blog', 'area' => 'format_blog', 'itemid' => $blog->id, 'content' => 'Cool', ]); // Manually update the created/modified date of the blog. $blog->created = 1654038000; $blog->lastmodified = $blog->created + HOURSECS; $DB->update_record('post', $blog); /** @var core_reportbuilder_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); $report = $generator->create_report(['name' => 'Blogs', 'source' => blogs::class, 'default' => 0]); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'blog:body']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'blog:attachment']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'blog:publishstate']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'blog:timemodified']); // Tag entity (course/user presence already checked by default columns). $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:name']); // File entity. $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:size']); // Comment entity. $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'comment:content']); // Commenter entity. $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'commenter:fullname']); $content = $this->get_custom_report_content($report->get('id')); $this->assertCount(1, $content); [ $body, $attachment, $publishstate, $timemodified, $tags, $filesize, $comment, $commenter, ] = array_values($content[0]); $this->assertStringContainsString('Horses', $body); $this->assertStringContainsString('hello.txt', $attachment); $this->assertEquals('Yourself (draft)', $publishstate); $this->assertEquals(userdate($blog->lastmodified), $timemodified); $this->assertEquals('horse', $tags); $this->assertEquals("5\xc2\xa0bytes", $filesize); $this->assertEquals(format_text('Cool'), $comment); $this->assertEquals(fullname($user), $commenter); } /** * Data provider for {@see test_datasource_filters} * * @return array[] */ public function datasource_filters_provider(): array { return [ 'Filter title' => ['subject', 'Cool', 'blog:title', [ 'blog:title_operator' => text::CONTAINS, 'blog:title_value' => 'Cool', ], true], 'Filter title (no match)' => ['subject', 'Cool', 'blog:title', [ 'blog:title_operator' => text::CONTAINS, 'blog:title_value' => 'Beans', ], false], 'Filter body' => ['summary', 'Awesome', 'blog:body', [ 'blog:body_operator' => select::EQUAL_TO, 'blog:body_value' => 'Awesome', ], true], 'Filter body (no match)' => ['summary', 'Awesome', 'blog:body', [ 'blog:body_operator' => select::EQUAL_TO, 'blog:body_value' => 'Beans', ], false], 'Filter attachment' => ['attachment', 1, 'blog:attachment', [ 'blog:attachment_operator' => boolean_select::CHECKED, ], true], 'Filter attachment (no match)' => ['attachment', 1, 'blog:attachment', [ 'blog:attachment_operator' => boolean_select::NOT_CHECKED, ], false], 'Filter publish state' => ['publishstate', 'site', 'blog:publishstate', [ 'blog:publishstate_operator' => select::EQUAL_TO, 'blog:publishstate_value' => 'site', ], true], 'Filter publish state (no match)' => ['publishstate', 'site', 'blog:publishstate', [ 'blog:publishstate_operator' => select::EQUAL_TO, 'blog:publishstate_value' => 'draft', ], false], 'Filter time created' => ['created', 1654038000, 'blog:timecreated', [ 'blog:timecreated_operator' => date::DATE_RANGE, 'blog:timecreated_from' => 1622502000, ], true], 'Filter time created (no match)' => ['created', 1654038000, 'blog:timecreated', [ 'blog:timecreated_operator' => date::DATE_RANGE, 'blog:timecreated_to' => 1622502000, ], false], 'Filter time modified' => ['lastmodified', 1654038000, 'blog:timemodified', [ 'blog:timemodified_operator' => date::DATE_RANGE, 'blog:timemodified_from' => 1622502000, ], true], 'Filter time modified (no match)' => ['lastmodified', 1654038000, 'blog:timemodified', [ 'blog:timemodified_operator' => date::DATE_RANGE, 'blog:timemodified_to' => 1622502000, ], false], ]; } /** * Test datasource filters * * @param string $field * @param mixed $value * @param string $filtername * @param array $filtervalues * @param bool $expectmatch * * @dataProvider datasource_filters_provider */ public function test_datasource_filters( string $field, $value, string $filtername, array $filtervalues, bool $expectmatch ): void { global $DB; $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); /** @var core_blog_generator $blogsgenerator */ $blogsgenerator = $this->getDataGenerator()->get_plugin_generator('core_blog'); // Create default blog, then manually override one of it's properties to use for filtering. $blog = $blogsgenerator->create_entry(['userid' => $user->id, 'subject' => 'My blog', 'summary' => 'Horses']); $DB->set_field('post', $field, $value, ['id' => $blog->id]); /** @var core_reportbuilder_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); // Create report containing single user column, and given filter. $report = $generator->create_report(['name' => 'Blogs', 'source' => blogs::class, 'default' => 0]); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']); // Add filter, set it's values. $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]); $content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues); if ($expectmatch) { $this->assertCount(1, $content); $this->assertEquals(fullname($user), reset($content[0])); } else { $this->assertEmpty($content); } } /** * Stress test datasource * * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php */ public function test_stress_datasource(): void { if (!PHPUNIT_LONGTEST) { $this->markTestSkipped('PHPUNIT_LONGTEST is not defined'); } $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); /** @var core_blog_generator $blogsgenerator */ $blogsgenerator = $this->getDataGenerator()->get_plugin_generator('core_blog'); $blogsgenerator->create_entry(['userid' => $user->id, 'subject' => 'My blog', 'summary' => 'Horses']); $this->datasource_stress_test_columns(blogs::class); $this->datasource_stress_test_columns_aggregation(blogs::class); $this->datasource_stress_test_conditions(blogs::class, 'blog:title'); } } tests/behat/comment.feature 0000604 00000006005 15062070561 0012007 0 ustar 00 @core @core_blog @javascript Feature: Comment on a blog entry In order to respond to a blog post As a user I need to be able to comment on a blog entry Background: Given the following "users" exist: | username | firstname | lastname | email | | testuser | Test | User | moodle@example.com | | testuser2 | Test2 | User2 | moodle2@example.com | And the following "core_blog > entries" exist: | subject | body | user | | Blog post from user 1 | User 1 blog post content | testuser | And I log in as "admin" And I am on site homepage And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| # TODO MDL-57120 "Site blogs" link not accessible without navigation block. And I add the "Navigation" block if not present And I configure the "Navigation" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" And I log out Scenario: Commenting on my own blog entry Given I log in as "testuser" And I click on "Site pages" "list_item" in the "Navigation" "block" And I click on "Site blogs" "link" in the "Navigation" "block" And I follow "Blog post from user 1" And I should see "User 1 blog post content" And I follow "Comments (0)" When I set the field "content" to "$My own >nasty< \"string\"!" And I follow "Save comment" Then I should see "$My own >nasty< \"string\"!" And I set the field "content" to "Another $Nasty <string?>" And I follow "Save comment" And I should see "Comments (2)" in the ".comment-link" "css_element" Scenario: Deleting my own comment Given I log in as "testuser" And I click on "Site pages" "list_item" in the "Navigation" "block" And I click on "Site blogs" "link" in the "Navigation" "block" And I follow "Blog post from user 1" And I should see "User 1 blog post content" And I follow "Comments (0)" And I set the field "content" to "$My own >nasty< \"string\"!" And I follow "Save comment" When I click on ".comment-delete a" "css_element" # Waiting for the animation to finish. And I wait "4" seconds Then I should not see "$My own >nasty< \"string\"!" And I follow "Blog post from user 1" And I click on ".comment-link" "css_element" And I should not see "$My own >nasty< \"string\"!" And I should see "Comments (0)" in the ".comment-link" "css_element" Scenario: Commenting on someone's blog post Given I am on site homepage And I log in as "testuser2" And I am on site homepage And I click on "Site pages" "list_item" in the "Navigation" "block" And I click on "Site blogs" "link" in the "Navigation" "block" And I follow "Blog post from user 1" When I follow "Comments (0)" And I set the field "content" to "$My own >nasty< \"string\"!" And I follow "Save comment" Then I should see "$My own >nasty< \"string\"!" tests/behat/delete.feature 0000604 00000003360 15062070561 0011610 0 ustar 00 @core @core_blog Feature: Delete a blog entry In order to manage my blog entries As a user I need to be able to delete entries I no longer wish to appear Background: Given the following "users" exist: | username | firstname | lastname | email | | testuser | Test | User | moodle@example.com | And the following "core_blog > entries" exist: | subject | body | user | | Blog post one | User 1 blog post content | testuser | | Blog post two | User 1 blog post content | testuser | And I log in as "admin" And I am on site homepage And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| # TODO MDL-57120 "Site blogs" link not accessible without navigation block. And I add the "Navigation" block if not present And I configure the "Navigation" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" And I log out And I log in as "testuser" And I am on site homepage And I click on "Site blogs" "link" in the "Navigation" "block" Scenario: Delete blog post results in post deleted Given I follow "Blog post one" And I follow "Delete" And I should see "Delete the blog entry 'Blog post one'?" When I press "Continue" Then I should not see "Blog post one" And I should see "Blog post two" Scenario: Delete confirmation screen works and allows cancel Given I follow "Blog post one" When I follow "Delete" Then I should see "Delete the blog entry 'Blog post one'?" And I press "Cancel" And I should see "Blog post one" And I should see "Blog post two" tests/behat/blog_entry.feature 0000604 00000002245 15062070561 0012513 0 ustar 00 @core @core_blog @_file_upload @javascript Feature: Blog entries can be added, modified and deleted In order to modify or delete a blog entry As a user I need to be able to add a blog entry Background: Given the following "users" exist: | username | firstname | lastname | email | | testuser | Test | User | moodle@example.com | And I am on the "testuser" "user > profile" page logged in as testuser And I follow "Blog entries" And I follow "Add a new entry" And I should see "Blogs: Add a new entry" And I set the following fields to these values: | Entry title | Entry 1 | | Blog entry body | Entry 1 content | | Attachment | lib/tests/fixtures/gd-logo.png | And I press "Save changes" Scenario: Modify a blog entry When I click on "Edit" "link" And I set the following fields to these values: | Entry title | Blog entry 1 | And I press "Save changes" Then I should see "Blog entry 1" Scenario: Delete a blog entry When I click on "Delete" "link" And I press "Continue" Then I should not see "Entry 1" And I should see "Add a new entry" tests/behat/blog_visibility.feature 0000604 00000002504 15062070561 0013537 0 ustar 00 @core @core_blog Feature: Blogs can be set to be only visible by the author. In order to make blogs personal only As a user I need to set the blog level to Users can only see their own blogs. Background: Given the following "users" exist: | username | firstname | lastname | email | | testuser | Test | User | moodle@example.com | | testuser2 | Test2 | User2 | moodle2@example.com | And the following "courses" exist: | fullname | shortname | | Course 1 | C1 | And the following "course enrolments" exist: | user | course | role | | testuser | C1 | student | | testuser2 | C1 | student | And I log in as "admin" And I am on site homepage And I navigate to "Appearance > Blog" in site administration And I set the following fields to these values: | Blog visibility | Users can only see their own blog | And I press "Save changes" Scenario: A student can not see another student's blog entries. Given I am on the "Course 1" course page logged in as testuser And I navigate to course participants When I follow "Test2 User2" And I should see "Miscellaneous" Then I should not see "Blog entries" And I follow "Profile" in the user menu And I follow "Blog entries" And I should see "User blog: Test User" tests/generator/lib.php 0000604 00000003173 15062070561 0011155 0 ustar 00 <?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/>. /** * Generator for blog area. * * @package core_blog * @category test * @copyright 2022 Noel De Martin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/blog/locallib.php'); /** * Blog module test data generator class * * @package core_blog * @category test * @copyright 2022 Noel De Martin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class core_blog_generator extends component_generator_base { /** * Create a blog entry * * @param array $data Entry data. * @return blog_entry Entry instance. */ public function create_entry(array $data = []): blog_entry { $data['publishstate'] = $data['publishstate'] ?? 'site'; $data['summary'] = $data['summary'] ?? $data['body']; $entry = new blog_entry(null, $data); $entry->add(); return $entry; } } tests/generator/behat_core_blog_generator.php 0000604 00000003136 15062070561 0015552 0 ustar 00 <?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/>. /** * Behat data generator for core_blog. * * @package core_blog * @category test * @copyright 2022 Noel De Martin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Behat data generator for core_blog. * * @package core_blog * @category test * @copyright 2022 Noel De Martin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_core_blog_generator extends behat_generator_base { /** * Get a list of the entities that can be created. * * @return array entity name => information about how to generate. */ protected function get_creatable_entities(): array { return [ 'entries' => [ 'singular' => 'entry', 'datagenerator' => 'entry', 'required' => ['subject', 'body'], 'switchids' => ['user' => 'userid'], ], ]; } } tests/lib_test.php 0000604 00000026437 15062070561 0010236 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Unit tests for blog * * @package core_blog * @category phpunit * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_blog; use blog_listing; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/blog/locallib.php'); require_once($CFG->dirroot . '/blog/lib.php'); /** * Test functions that rely on the DB tables */ class lib_test extends \advanced_testcase { private $courseid; private $cmid; private $groupid; private $userid; private $tagid; private $postid; protected function setUp(): void { global $DB; parent::setUp(); $this->resetAfterTest(); // Create default course. $course = $this->getDataGenerator()->create_course(array('category' => 1, 'shortname' => 'ANON')); $this->assertNotEmpty($course); $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id)); $this->assertNotEmpty($page); // Create default group. $group = new \stdClass(); $group->courseid = $course->id; $group->name = 'ANON'; $group->id = $DB->insert_record('groups', $group); // Create default user. $user = $this->getDataGenerator()->create_user(array( 'username' => 'testuser', 'firstname' => 'Jimmy', 'lastname' => 'Kinnon' )); // Create default tag. $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id, 'rawname' => 'Testtagname', 'isstandard' => 1)); // Create default post. $post = new \stdClass(); $post->userid = $user->id; $post->groupid = $group->id; $post->content = 'test post content text'; $post->module = 'blog'; $post->id = $DB->insert_record('post', $post); // Grab important ids. $this->courseid = $course->id; $this->cmid = $page->cmid; $this->groupid = $group->id; $this->userid = $user->id; $this->tagid = $tag->id; $this->postid = $post->id; } public function test_overrides() { global $SITE; // Try all the filters at once: Only the entry filter is active. $filters = array('site' => $SITE->id, 'course' => $this->courseid, 'module' => $this->cmid, 'group' => $this->groupid, 'user' => $this->userid, 'tag' => $this->tagid, 'entry' => $this->postid); $bloglisting = new blog_listing($filters); $this->assertFalse(array_key_exists('site', $bloglisting->filters)); $this->assertFalse(array_key_exists('course', $bloglisting->filters)); $this->assertFalse(array_key_exists('module', $bloglisting->filters)); $this->assertFalse(array_key_exists('group', $bloglisting->filters)); $this->assertFalse(array_key_exists('user', $bloglisting->filters)); $this->assertFalse(array_key_exists('tag', $bloglisting->filters)); $this->assertTrue(array_key_exists('entry', $bloglisting->filters)); // Again, but without the entry filter: This time, the tag, user and module filters are active. $filters = array('site' => $SITE->id, 'course' => $this->courseid, 'module' => $this->cmid, 'group' => $this->groupid, 'user' => $this->userid, 'tag' => $this->postid); $bloglisting = new blog_listing($filters); $this->assertFalse(array_key_exists('site', $bloglisting->filters)); $this->assertFalse(array_key_exists('course', $bloglisting->filters)); $this->assertFalse(array_key_exists('group', $bloglisting->filters)); $this->assertTrue(array_key_exists('module', $bloglisting->filters)); $this->assertTrue(array_key_exists('user', $bloglisting->filters)); $this->assertTrue(array_key_exists('tag', $bloglisting->filters)); // We should get the same result by removing the 3 inactive filters: site, course and group. $filters = array('module' => $this->cmid, 'user' => $this->userid, 'tag' => $this->tagid); $bloglisting = new blog_listing($filters); $this->assertFalse(array_key_exists('site', $bloglisting->filters)); $this->assertFalse(array_key_exists('course', $bloglisting->filters)); $this->assertFalse(array_key_exists('group', $bloglisting->filters)); $this->assertTrue(array_key_exists('module', $bloglisting->filters)); $this->assertTrue(array_key_exists('user', $bloglisting->filters)); $this->assertTrue(array_key_exists('tag', $bloglisting->filters)); } // The following series of 'test_blog..' functions correspond to the blog_get_headers() function within blog/lib.php. // Some cases are omitted due to the optional_param variables used. public function test_blog_get_headers_case_1() { global $CFG, $PAGE, $OUTPUT; $blogheaders = blog_get_headers(); $this->assertEquals($blogheaders['heading'], get_string('siteblogheading', 'blog')); } public function test_blog_get_headers_case_6() { global $CFG, $PAGE, $OUTPUT; $blogheaders = blog_get_headers($this->courseid, null, $this->userid); $this->assertNotEquals($blogheaders['heading'], ''); } public function test_blog_get_headers_case_7() { global $CFG, $PAGE, $OUTPUT; $blogheaders = blog_get_headers(null, $this->groupid); $this->assertNotEquals($blogheaders['heading'], ''); } public function test_blog_get_headers_case_10() { global $CFG, $PAGE, $OUTPUT; $blogheaders = blog_get_headers($this->courseid); $this->assertNotEquals($blogheaders['heading'], ''); } /** * Tests the core_blog_myprofile_navigation() function. */ public function test_core_blog_myprofile_navigation() { global $USER; // Set up the test. $tree = new \core_user\output\myprofile\tree(); $this->setAdminUser(); $iscurrentuser = true; $course = null; // Enable blogs. set_config('enableblogs', true); // Check the node tree is correct. core_blog_myprofile_navigation($tree, $USER, $iscurrentuser, $course); $reflector = new \ReflectionObject($tree); $nodes = $reflector->getProperty('nodes'); $nodes->setAccessible(true); $this->assertArrayHasKey('blogs', $nodes->getValue($tree)); } /** * Tests the core_blog_myprofile_navigation() function as a guest. */ public function test_core_blog_myprofile_navigation_as_guest() { global $USER; // Set up the test. $tree = new \core_user\output\myprofile\tree(); $iscurrentuser = false; $course = null; // Set user as guest. $this->setGuestUser(); // Check the node tree is correct. core_blog_myprofile_navigation($tree, $USER, $iscurrentuser, $course); $reflector = new \ReflectionObject($tree); $nodes = $reflector->getProperty('nodes'); $nodes->setAccessible(true); $this->assertArrayNotHasKey('blogs', $nodes->getValue($tree)); } /** * Tests the core_blog_myprofile_navigation() function when blogs are disabled. */ public function test_core_blog_myprofile_navigation_blogs_disabled() { global $USER; // Set up the test. $tree = new \core_user\output\myprofile\tree(); $this->setAdminUser(); $iscurrentuser = false; $course = null; // Disable blogs. set_config('enableblogs', false); // Check the node tree is correct. core_blog_myprofile_navigation($tree, $USER, $iscurrentuser, $course); $reflector = new \ReflectionObject($tree); $nodes = $reflector->getProperty('nodes'); $nodes->setAccessible(true); $this->assertArrayNotHasKey('blogs', $nodes->getValue($tree)); } public function test_blog_get_listing_course() { $this->setAdminUser(); $coursecontext = \context_course::instance($this->courseid); $anothercourse = $this->getDataGenerator()->create_course(); // Add blog associations with a course. $blog = new \blog_entry($this->postid); $blog->add_association($coursecontext->id); // There is one entry associated with a course. $bloglisting = new blog_listing(array('course' => $this->courseid)); $this->assertCount(1, $bloglisting->get_entries()); // There is no entry associated with a wrong course. $bloglisting = new blog_listing(array('course' => $anothercourse->id)); $this->assertCount(0, $bloglisting->get_entries()); // There is no entry associated with a module. $bloglisting = new blog_listing(array('module' => $this->cmid)); $this->assertCount(0, $bloglisting->get_entries()); // There is one entry associated with a site (id is ignored). $bloglisting = new blog_listing(array('site' => 12345)); $this->assertCount(1, $bloglisting->get_entries()); // There is one entry associated with course context. $bloglisting = new blog_listing(array('context' => $coursecontext->id)); $this->assertCount(1, $bloglisting->get_entries()); } public function test_blog_get_listing_module() { $this->setAdminUser(); $coursecontext = \context_course::instance($this->courseid); $contextmodule = \context_module::instance($this->cmid); $anothermodule = $this->getDataGenerator()->create_module('page', array('course' => $this->courseid)); // Add blog associations with a course. $blog = new \blog_entry($this->postid); $blog->add_association($contextmodule->id); // There is no entry associated with a course. $bloglisting = new blog_listing(array('course' => $this->courseid)); $this->assertCount(0, $bloglisting->get_entries()); // There is one entry associated with a module. $bloglisting = new blog_listing(array('module' => $this->cmid)); $this->assertCount(1, $bloglisting->get_entries()); // There is no entry associated with a wrong module. $bloglisting = new blog_listing(array('module' => $anothermodule->cmid)); $this->assertCount(0, $bloglisting->get_entries()); // There is one entry associated with a site (id is ignored). $bloglisting = new blog_listing(array('site' => 12345)); $this->assertCount(1, $bloglisting->get_entries()); // There is one entry associated with course context (module is a subcontext of a course). $bloglisting = new blog_listing(array('context' => $coursecontext->id)); $this->assertCount(1, $bloglisting->get_entries()); } } tests/privacy/provider_test.php 0000604 00000134327 15062070561 0012775 0 ustar 00 <?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/>. /** * Data provider tests. * * @package core_blog * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_blog\privacy; defined('MOODLE_INTERNAL') || die(); global $CFG; use core_privacy\tests\provider_testcase; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\writer; use core_blog\privacy\provider; require_once($CFG->dirroot . '/blog/locallib.php'); require_once($CFG->dirroot . '/comment/lib.php'); /** * Data provider testcase class. * * @package core_blog * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart <fred@branchup.tech> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider_test extends provider_testcase { public function setUp(): void { $this->resetAfterTest(); } public function test_get_contexts_for_userid() { $dg = $this->getDataGenerator(); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $c3 = $dg->create_course(); $cm1a = $dg->create_module('page', ['course' => $c1]); $cm1b = $dg->create_module('page', ['course' => $c1]); $cm2a = $dg->create_module('page', ['course' => $c2]); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); // Blog share a table with notes, so throw data in there and make sure it doesn't get reported. $dg->get_plugin_generator('core_notes')->create_instance(['userid' => $u1->id, 'courseid' => $c3->id]); $this->assertEmpty(provider::get_contexts_for_userid($u1->id)->get_contextids()); $this->assertEmpty(provider::get_contexts_for_userid($u2->id)->get_contextids()); // Gradually create blog posts for user 1. First system one. $this->create_post(['userid' => $u1->id]); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(1, $contextids); $this->assertEquals($u1ctx->id, $contextids[0]); $this->assertEmpty(provider::get_contexts_for_userid($u2->id)->get_contextids()); // Create a blog post associated with c1. $post = $this->create_post(['userid' => $u1->id, 'courseid' => $c1->id]); $entry = new \blog_entry($post->id); $entry->add_association(\context_course::instance($c1->id)->id); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(2, $contextids); $this->assertTrue(in_array($u1ctx->id, $contextids)); $this->assertTrue(in_array(\context_course::instance($c1->id)->id, $contextids)); $this->assertEmpty(provider::get_contexts_for_userid($u2->id)->get_contextids()); // Create a blog post associated with cm2a. $post = $this->create_post(['userid' => $u1->id, 'courseid' => $c2->id]); $entry = new \blog_entry($post->id); $entry->add_association(\context_module::instance($cm2a->cmid)->id); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(3, $contextids); $this->assertTrue(in_array($u1ctx->id, $contextids)); $this->assertTrue(in_array(\context_course::instance($c1->id)->id, $contextids)); $this->assertTrue(in_array(\context_module::instance($cm2a->cmid)->id, $contextids)); $this->assertEmpty(provider::get_contexts_for_userid($u2->id)->get_contextids()); // User 2 comments on u1's post. $comment = $this->get_comment_object($u1ctx, $post->id); $this->setUser($u2); $comment->add('Hello, it\'s me!'); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(3, $contextids); $this->assertTrue(in_array($u1ctx->id, $contextids)); $this->assertTrue(in_array(\context_course::instance($c1->id)->id, $contextids)); $this->assertTrue(in_array(\context_module::instance($cm2a->cmid)->id, $contextids)); $contextids = provider::get_contexts_for_userid($u2->id)->get_contextids(); $this->assertCount(1, $contextids); $this->assertTrue(in_array($u1ctx->id, $contextids)); } public function test_get_contexts_for_userid_with_one_associated_post_only() { $dg = $this->getDataGenerator(); $c1 = $dg->create_course(); $u1 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $this->assertEmpty(provider::get_contexts_for_userid($u1->id)->get_contextids()); // Create a blog post associated with c1. It should always return both the course and user context. $post = $this->create_post(['userid' => $u1->id, 'courseid' => $c1->id]); $entry = new \blog_entry($post->id); $entry->add_association(\context_course::instance($c1->id)->id); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(2, $contextids); $this->assertTrue(in_array($u1ctx->id, $contextids)); $this->assertTrue(in_array(\context_course::instance($c1->id)->id, $contextids)); } /** * Test that user IDs are returned for a specificed course or module context. */ public function test_get_users_in_context_course_and_module() { $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $c1ctx = \context_course::instance($course->id); $post = $this->create_post(['userid' => $user1->id, 'courseid' => $course->id]); $entry = new \blog_entry($post->id); $entry->add_association($c1ctx->id); // Add a comment from user 2. $comment = $this->get_comment_object(\context_user::instance($user1->id), $entry->id); $this->setUser($user2); $comment->add('Nice blog post'); $userlist = new \core_privacy\local\request\userlist($c1ctx, 'core_blog'); provider::get_users_in_context($userlist); $userids = $userlist->get_userids(); $this->assertCount(2, $userids); // Add an association for a module. $cm1a = $this->getDataGenerator()->create_module('page', ['course' => $course]); $cm1ctx = \context_module::instance($cm1a->cmid); $post2 = $this->create_post(['userid' => $user2->id, 'courseid' => $course->id]); $entry2 = new \blog_entry($post2->id); $entry2->add_association($cm1ctx->id); $userlist = new \core_privacy\local\request\userlist($cm1ctx, 'core_blog'); provider::get_users_in_context($userlist); $userids = $userlist->get_userids(); $this->assertCount(1, $userids); } /** * Test that user IDs are returned for a specificed user context. */ public function test_get_users_in_context_user_context() { $user1 = $this->getDataGenerator()->create_user(); $user2 = $this->getDataGenerator()->create_user(); $u1ctx = \context_user::instance($user1->id); $post = $this->create_post(['userid' => $user1->id]); $entry = new \blog_entry($post->id); // Add a comment from user 2. $comment = $this->get_comment_object($u1ctx, $entry->id); $this->setUser($user2); $comment->add('Another nice blog post'); $userlist = new \core_privacy\local\request\userlist($u1ctx, 'core_blog'); provider::get_users_in_context($userlist); $userids = $userlist->get_userids(); $this->assertCount(2, $userids); } /** * Test that user IDs are returned for a specificed user context for an external blog. */ public function test_get_users_in_context_external_blog() { $user1 = $this->getDataGenerator()->create_user(); $u1ctx = \context_user::instance($user1->id); $extu1 = $this->create_external_blog(['userid' => $user1->id]); $userlist = new \core_privacy\local\request\userlist($u1ctx, 'core_blog'); provider::get_users_in_context($userlist); $userids = $userlist->get_userids(); $this->assertCount(1, $userids); } public function test_delete_data_for_user() { global $DB; $dg = $this->getDataGenerator(); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $cm1a = $dg->create_module('page', ['course' => $c1]); $cm1b = $dg->create_module('page', ['course' => $c1]); $cm2a = $dg->create_module('page', ['course' => $c2]); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $cm1actx = \context_module::instance($cm1a->cmid); $cm1bctx = \context_module::instance($cm1b->cmid); $cm2actx = \context_module::instance($cm2a->cmid); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); // Blog share a table with notes, so throw data in there and make sure it doesn't get deleted. $this->assertFalse($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); $dg->get_plugin_generator('core_notes')->create_instance(['userid' => $u1->id, 'courseid' => $c1->id]); $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); // Create two external blogs. $extu1 = $this->create_external_blog(['userid' => $u1->id]); $extu2 = $this->create_external_blog(['userid' => $u2->id]); // Create a set of posts. $entry = new \blog_entry($this->create_post(['userid' => $u1->id])->id); $commentedon = $entry; $entry = new \blog_entry($this->create_post(['userid' => $u2->id])->id); // Two course associations for u1. $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); $entry->add_association($c1ctx->id); $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); $entry->add_association($c1ctx->id); // Two module associations with cm1a, and 1 with cm1b for u1. $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); $entry->add_association($cm1actx->id); $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); $entry->add_association($cm1actx->id); $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); $entry->add_association($cm1bctx->id); // One association for u2 in c1, cm1a and cm2a. $entry = new \blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c1->id])->id); $entry->add_association($c1ctx->id); $entry = new \blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c1->id])->id); $entry->add_association($cm1actx->id); $entry = new \blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c2->id])->id); $entry->add_association($cm2actx->id); // One association for u1 in c2 and cm2a. $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c2->id])->id); $entry->add_association($c2ctx->id); $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c2->id])->id); $entry->add_association($cm2actx->id); // Add comments. $comment = $this->get_comment_object($u1ctx, $commentedon->id); $this->setUser($u1); $comment->add('Hello, it\'s me!'); $comment->add('I was wondering...'); $this->setUser($u2); $comment->add('If after all these years'); $this->setUser($u3); $comment->add('You\'d like to meet'); // Assert current setup. $this->assertCount(6, provider::get_contexts_for_userid($u1->id)->get_contextids()); $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); $this->assertCount(5, provider::get_contexts_for_userid($u2->id)->get_contextids()); $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); $this->assertCount(2, $DB->get_records('comments', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('comments', ['userid' => $u2->id])); $this->assertCount(1, $DB->get_records('comments', ['userid' => $u3->id])); // Delete for u1 in cm1a. $appctxs = new approved_contextlist($u1, 'core_blog', [$cm1actx->id]); provider::delete_data_for_user($appctxs); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(5, $contextids); $this->assertFalse(in_array($cm1actx->id, $contextids)); $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); $this->assertCount(5, provider::get_contexts_for_userid($u2->id)->get_contextids()); $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); // Delete for u1 in c1. $appctxs = new approved_contextlist($u1, 'core_blog', [$c1ctx->id]); provider::delete_data_for_user($appctxs); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(4, $contextids); $this->assertFalse(in_array($c1ctx->id, $contextids)); $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); $this->assertCount(5, provider::get_contexts_for_userid($u2->id)->get_contextids()); $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); // Delete for u1 in c2. $appctxs = new approved_contextlist($u1, 'core_blog', [$c2ctx->id]); provider::delete_data_for_user($appctxs); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(3, $contextids); $this->assertFalse(in_array($c2ctx->id, $contextids)); $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); $this->assertCount(5, provider::get_contexts_for_userid($u2->id)->get_contextids()); $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); // Delete for u1 in another user's context, shouldn't do anything. provider::delete_data_for_user(new approved_contextlist($u1, 'core_blog', [$u2ctx->id])); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(3, $contextids); $this->assertFalse(in_array($c2ctx->id, $contextids)); $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); $this->assertCount(5, provider::get_contexts_for_userid($u2->id)->get_contextids()); $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); $this->assertCount(2, $DB->get_records('comments', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('comments', ['userid' => $u2->id])); // Delete for u2 in u1 context. provider::delete_data_for_user(new approved_contextlist($u2, 'core_blog', [$u1ctx->id])); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(3, $contextids); $this->assertFalse(in_array($c2ctx->id, $contextids)); $this->assertCount(9, $DB->get_records('post', ['userid' => $u1->id])); $this->assertCount(4, provider::get_contexts_for_userid($u2->id)->get_contextids()); $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); $this->assertCount(2, $DB->get_records('comments', ['userid' => $u1->id])); $this->assertCount(0, $DB->get_records('comments', ['userid' => $u2->id])); $this->assertCount(1, $DB->get_records('comments', ['userid' => $u3->id])); // Delete for u1 in their context. $appctxs = new approved_contextlist($u1, 'core_blog', [$u1ctx->id]); provider::delete_data_for_user($appctxs); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(0, $contextids); $this->assertCount(1, $DB->get_records('post', ['userid' => $u1->id])); $this->assertCount(4, provider::get_contexts_for_userid($u2->id)->get_contextids()); $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); $this->assertCount(0, $DB->get_records('blog_external', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); $this->assertCount(0, $DB->get_records('comments', ['userid' => $u1->id])); $this->assertCount(0, $DB->get_records('comments', ['userid' => $u2->id])); $this->assertCount(0, $DB->get_records('comments', ['userid' => $u3->id])); $this->assertTrue($DB->record_exists('post', ['courseid' => $c1->id, 'userid' => $u1->id, 'module' => 'notes'])); } /** * Test provider delete_data_for_user with a context that contains no entries * * @return void */ public function test_delete_data_for_user_empty_context() { global $DB; $user = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $context = \context_course::instance($course->id); // Create a blog entry for user, associated with course. $entry = new \blog_entry($this->create_post(['userid' => $user->id, 'courseid' => $course->id])->id); $entry->add_association($context->id); // Generate list of contexts for user. $contexts = provider::get_contexts_for_userid($user->id); $this->assertContainsEquals($context->id, $contexts->get_contextids()); // Now delete the blog entry. $entry->delete(); // Try to delete user data using contexts obtained prior to entry deletion. $contextlist = new approved_contextlist($user, 'core_blog', $contexts->get_contextids()); provider::delete_data_for_user($contextlist); // Sanity check to ensure blog_associations is really empty. $this->assertEmpty($DB->get_records('blog_association', ['contextid' => $context->id])); } public function test_delete_data_for_all_users_in_context() { global $DB; $dg = $this->getDataGenerator(); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $cm1a = $dg->create_module('page', ['course' => $c1]); $cm1b = $dg->create_module('page', ['course' => $c1]); $cm2a = $dg->create_module('page', ['course' => $c2]); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $cm1actx = \context_module::instance($cm1a->cmid); $cm1bctx = \context_module::instance($cm1b->cmid); $cm2actx = \context_module::instance($cm2a->cmid); $u1ctx = \context_user::instance($u1->id); // Create two external blogs. $extu1 = $this->create_external_blog(['userid' => $u1->id]); $extu2 = $this->create_external_blog(['userid' => $u2->id]); // Create a set of posts. $entry = new \blog_entry($this->create_post(['userid' => $u1->id])->id); $entry = new \blog_entry($this->create_post(['userid' => $u2->id])->id); // Course associations for u1 and u2. $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); $entry->add_association($c1ctx->id); $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); $entry->add_association($c1ctx->id); $entry = new \blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c1->id])->id); $entry->add_association($c1ctx->id); // Module associations for u1 and u2. $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); $entry->add_association($cm1actx->id); $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); $entry->add_association($cm1actx->id); $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id])->id); $entry->add_association($cm1bctx->id); $entry = new \blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c1->id])->id); $entry->add_association($cm1actx->id); // Foreign associations for u1, u2. $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c2->id])->id); $entry->add_association($c2ctx->id); $entry = new \blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c2->id])->id); $entry->add_association($c2ctx->id); $entry = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $cm2a->id])->id); $entry->add_association($cm2actx->id); // Validate what we've got. $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(8, $DB->get_records('post', ['userid' => $u1->id])); $this->assertCount(6, $contextids); $this->assertTrue(in_array($c1ctx->id, $contextids)); $this->assertTrue(in_array($c2ctx->id, $contextids)); $this->assertTrue(in_array($cm1actx->id, $contextids)); $this->assertTrue(in_array($cm1bctx->id, $contextids)); $this->assertTrue(in_array($cm2actx->id, $contextids)); $this->assertTrue(in_array($u1ctx->id, $contextids)); $contextids = provider::get_contexts_for_userid($u2->id)->get_contextids(); $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); $this->assertCount(4, $contextids); $this->assertTrue(in_array($c1ctx->id, $contextids)); $this->assertTrue(in_array($c2ctx->id, $contextids)); $this->assertTrue(in_array($cm1actx->id, $contextids)); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); // Delete cm1a context. provider::delete_data_for_all_users_in_context($cm1actx); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(8, $DB->get_records('post', ['userid' => $u1->id])); $this->assertCount(5, $contextids); $this->assertTrue(in_array($c1ctx->id, $contextids)); $this->assertTrue(in_array($c2ctx->id, $contextids)); $this->assertFalse(in_array($cm1actx->id, $contextids)); $this->assertTrue(in_array($cm1bctx->id, $contextids)); $this->assertTrue(in_array($cm2actx->id, $contextids)); $this->assertTrue(in_array($u1ctx->id, $contextids)); $contextids = provider::get_contexts_for_userid($u2->id)->get_contextids(); $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); $this->assertCount(3, $contextids); $this->assertTrue(in_array($c1ctx->id, $contextids)); $this->assertTrue(in_array($c2ctx->id, $contextids)); $this->assertFalse(in_array($cm1actx->id, $contextids)); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); // Delete c1 context. provider::delete_data_for_all_users_in_context($c1ctx); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(8, $DB->get_records('post', ['userid' => $u1->id])); $this->assertCount(4, $contextids); $this->assertFalse(in_array($c1ctx->id, $contextids)); $this->assertTrue(in_array($c2ctx->id, $contextids)); $this->assertFalse(in_array($cm1actx->id, $contextids)); $this->assertTrue(in_array($cm1bctx->id, $contextids)); $this->assertTrue(in_array($cm2actx->id, $contextids)); $this->assertTrue(in_array($u1ctx->id, $contextids)); $contextids = provider::get_contexts_for_userid($u2->id)->get_contextids(); $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); $this->assertCount(2, $contextids); $this->assertFalse(in_array($c1ctx->id, $contextids)); $this->assertTrue(in_array($c2ctx->id, $contextids)); $this->assertFalse(in_array($cm1actx->id, $contextids)); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); // Delete u1 context. provider::delete_data_for_all_users_in_context($u1ctx); $contextids = provider::get_contexts_for_userid($u1->id)->get_contextids(); $this->assertCount(0, $DB->get_records('post', ['userid' => $u1->id])); $this->assertCount(0, $contextids); $this->assertFalse(in_array($c1ctx->id, $contextids)); $this->assertFalse(in_array($c2ctx->id, $contextids)); $this->assertFalse(in_array($cm1actx->id, $contextids)); $this->assertFalse(in_array($cm1bctx->id, $contextids)); $this->assertFalse(in_array($cm2actx->id, $contextids)); $this->assertFalse(in_array($u1ctx->id, $contextids)); $contextids = provider::get_contexts_for_userid($u2->id)->get_contextids(); $this->assertCount(4, $DB->get_records('post', ['userid' => $u2->id])); $this->assertCount(2, $contextids); $this->assertFalse(in_array($c1ctx->id, $contextids)); $this->assertTrue(in_array($c2ctx->id, $contextids)); $this->assertFalse(in_array($cm1actx->id, $contextids)); $this->assertCount(0, $DB->get_records('blog_external', ['userid' => $u1->id])); $this->assertCount(1, $DB->get_records('blog_external', ['userid' => $u2->id])); } public function test_export_data_for_user() { global $DB; $dg = $this->getDataGenerator(); $c1 = $dg->create_course(); $cm1a = $dg->create_module('page', ['course' => $c1]); $cm1b = $dg->create_module('page', ['course' => $c1]); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $cm1actx = \context_module::instance($cm1a->cmid); $cm1bctx = \context_module::instance($cm1b->cmid); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); // System entries. $e1 = new \blog_entry($this->create_post(['userid' => $u1->id, 'subject' => 'Hello world!', 'publishstate' => 'public'])->id); $e2 = new \blog_entry($this->create_post(['userid' => $u1->id, 'subject' => 'Hi planet!', 'publishstate' => 'draft'])->id); $e3 = new \blog_entry($this->create_post(['userid' => $u2->id, 'subject' => 'Ignore me'])->id); // Create a blog entry associated with contexts. $e4 = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id, 'subject' => 'Course assoc'])->id); $e4->add_association($c1ctx->id); $e4b = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id, 'subject' => 'Course assoc 2'])->id); $e4b->add_association($c1ctx->id); $e5 = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id, 'subject' => 'Module assoc', 'publishstate' => 'public'])->id); $e5->add_association($cm1actx->id); $e5b = new \blog_entry($this->create_post(['userid' => $u1->id, 'courseid' => $c1->id, 'subject' => 'C/CM assoc'])->id); $e5b->add_association($c1ctx->id); $e5b->add_association($cm1actx->id); $e6 = new \blog_entry($this->create_post(['userid' => $u2->id, 'courseid' => $c1->id, 'subject' => 'Module assoc'])->id); $e6->add_association($cm1actx->id); // External blogs. $ex1 = $this->create_external_blog(['userid' => $u1->id, 'url' => 'https://moodle.org', 'name' => 'Moodle RSS']); $ex2 = $this->create_external_blog(['userid' => $u1->id, 'url' => 'https://example.com', 'name' => 'Example']); $ex3 = $this->create_external_blog(['userid' => $u2->id, 'url' => 'https://example.com', 'name' => 'Ignore me']); // Attach tags. \core_tag_tag::set_item_tags('core', 'post', $e1->id, $u1ctx, ['Beer', 'Golf']); \core_tag_tag::set_item_tags('core', 'blog_external', $ex1->id, $u1ctx, ['Car', 'Golf']); \core_tag_tag::set_item_tags('core', 'post', $e3->id, $u2ctx, ['ITG']); \core_tag_tag::set_item_tags('core', 'blog_external', $ex3->id, $u2ctx, ['DDR']); \core_tag_tag::set_item_tags('core', 'dontfindme', $e1->id, $u1ctx, ['Lone tag']); // Attach comments. $comment = $this->get_comment_object($u1ctx, $e1->id); $this->setUser($u1); $comment->add('Hello, it\'s me!'); $this->setUser($u2); $comment->add('I was wondering if after'); $this->setUser($u1); $comment = $this->get_comment_object($u2ctx, $e3->id); $comment->add('All these years'); // Blog share a table with notes, so throw some data in there, it should not be exported. $note = $dg->get_plugin_generator('core_notes')->create_instance(['userid' => $u1->id, 'courseid' => $c1->id, 'subject' => 'ABC']); // Validate module associations. $contextlist = new approved_contextlist($u1, 'core_blog', [$cm1actx->id]); provider::export_user_data($contextlist); $writer = writer::with_context($cm1actx); $assocs = $writer->get_data([get_string('privacy:path:blogassociations', 'core_blog')]); $this->assertCount(2, $assocs->associations); $this->assertTrue(in_array('Module assoc', $assocs->associations)); $this->assertTrue(in_array('C/CM assoc', $assocs->associations)); // Validate course associations. $contextlist = new approved_contextlist($u1, 'core_blog', [$c1ctx->id]); provider::export_user_data($contextlist); $writer = writer::with_context($c1ctx); $assocs = $writer->get_data([get_string('privacy:path:blogassociations', 'core_blog')]); $this->assertCount(3, $assocs->associations); $this->assertTrue(in_array('Course assoc', $assocs->associations)); $this->assertTrue(in_array('Course assoc 2', $assocs->associations)); $this->assertTrue(in_array('C/CM assoc', $assocs->associations)); // Confirm we're not exporting for another user. $contextlist = new approved_contextlist($u2, 'core_blog', [$u2ctx->id]); $writer = writer::with_context($u1ctx); $this->assertFalse($writer->has_any_data()); // Now export user context for u2. $this->setUser($u2); $contextlist = new approved_contextlist($u2, 'core_blog', [$u1ctx->id]); provider::export_user_data($contextlist); $writer = writer::with_context($u1ctx); $data = $writer->get_data([get_string('blog', 'core_blog'), get_string('externalblogs', 'core_blog'), $ex1->name . " ({$ex1->id})"]); $this->assertEmpty($data); $data = $writer->get_data([get_string('blog', 'core_blog'), get_string('blogentries', 'core_blog'), $e2->subject . " ({$e2->id})"]); $this->assertEmpty($data); $data = $writer->get_data([get_string('blog', 'core_blog'), get_string('blogentries', 'core_blog'), $e1->subject . " ({$e1->id})"]); $this->assertEmpty($data); $data = $writer->get_data([get_string('blog', 'core_blog'), get_string('blogentries', 'core_blog'), $e1->subject . " ({$e1->id})", get_string('commentsubcontext', 'core_comment')]); $this->assertNotEmpty($data); $this->assertCount(1, $data->comments); $comment = array_shift($data->comments); $this->assertEquals('I was wondering if after', strip_tags($comment->content)); // Now export user context data. $this->setUser($u1); $contextlist = new approved_contextlist($u1, 'core_blog', [$u1ctx->id]); writer::reset(); provider::export_user_data($contextlist); $writer = writer::with_context($u1ctx); // Check external blogs. $externals = [$ex1, $ex2]; foreach ($externals as $ex) { $data = $writer->get_data([get_string('blog', 'core_blog'), get_string('externalblogs', 'core_blog'), $ex->name . " ({$ex->id})"]); $this->assertNotEmpty($data); $this->assertEquals($data->name, $ex->name); $this->assertEquals($data->description, $ex->description); $this->assertEquals($data->url, $ex->url); $this->assertEquals($data->filtertags, $ex->filtertags); $this->assertEquals($data->modified, transform::datetime($ex->timemodified)); $this->assertEquals($data->lastfetched, transform::datetime($ex->timefetched)); } // Check entries. $entries = [$e1, $e2, $e4, $e4b, $e5, $e5b]; $associations = [ $e1->id => null, $e2->id => null, $e4->id => $c1ctx->get_context_name(), $e4b->id => $c1ctx->get_context_name(), $e5->id => $cm1actx->get_context_name(), $e5b->id => [$c1ctx->get_context_name(), $cm1actx->get_context_name()], ]; foreach ($entries as $e) { $path = [get_string('blog', 'core_blog'), get_string('blogentries', 'core_blog'), $e->subject . " ({$e->id})"]; $data = $writer->get_data($path); $this->assertNotEmpty($data); $this->assertEquals($data->subject, $e->subject); $this->assertEquals($data->summary, $e->summary); $this->assertEquals($data->publishstate, provider::transform_publishstate($e->publishstate)); $this->assertEquals($data->created, transform::datetime($e->created)); $this->assertEquals($data->lastmodified, transform::datetime($e->lastmodified)); // We attached comments and tags to this entry. $commentpath = array_merge($path, [get_string('commentsubcontext', 'core_comment')]); if ($e->id == $e1->id) { $tagdata = $writer->get_related_data($path, 'tags'); $this->assertEqualsCanonicalizing(['Beer', 'Golf'], $tagdata); $comments = $writer->get_data($commentpath); $this->assertCount(2, $comments->comments); $c0 = strip_tags($comments->comments[0]->content); $c1 = strip_tags($comments->comments[1]->content); $expectedcomments = [ 'Hello, it\'s me!', 'I was wondering if after', ]; $this->assertNotFalse(array_search($c0, $expectedcomments)); $this->assertNotFalse(array_search($c1, $expectedcomments)); $this->assertNotEquals($c0, $c1); } else { $tagdata = $writer->get_related_data($path, 'tags'); $this->assertEmpty($tagdata); $comments = $writer->get_data($commentpath); $this->assertEmpty($comments); } if (isset($associations[$e->id])) { $assocs = $associations[$e->id]; if (is_array($assocs)) { $this->assertCount(count($assocs), $data->associations); foreach ($assocs as $v) { $this->assertTrue(in_array($v, $data->associations)); } } else { $this->assertCount(1, $data->associations); $this->assertTrue(in_array($assocs, $data->associations)); } } } // The note was not exported. $path = [get_string('blog', 'core_blog'), get_string('blogentries', 'core_blog'), "ABC ($note->id)"]; $this->assertEmpty($writer->get_data($path)); } /** * Test that deleting of blog information in a user context works as desired. */ public function test_delete_data_for_users_user_context() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $u4 = $this->getDataGenerator()->create_user(); $u5 = $this->getDataGenerator()->create_user(); $u1ctx = \context_user::instance($u1->id); $post = $this->create_post(['userid' => $u1->id]); $entry = new \blog_entry($post->id); $comment = $this->get_comment_object($u1ctx, $entry->id); $this->setUser($u1); $comment->add('Hello, I created the blog'); $this->setUser($u2); $comment->add('User 2 making a comment.'); $this->setUser($u3); $comment->add('User 3 here.'); $this->setUser($u4); $comment->add('User 4 is nice.'); $this->setUser($u5); $comment->add('User 5 for the win.'); // This will only delete the comments made by user 4 and 5. $this->assertCount(5, $DB->get_records('comments', ['contextid' => $u1ctx->id])); $userlist = new \core_privacy\local\request\approved_userlist($u1ctx, 'core_blog', [$u4->id, $u5->id]); provider::delete_data_for_users($userlist); $this->assertCount(3, $DB->get_records('comments', ['contextid' => $u1ctx->id])); $this->assertCount(1, $DB->get_records('post', ['userid' => $u1->id])); // As the owner of the post is here everything will be deleted. $userlist = new \core_privacy\local\request\approved_userlist($u1ctx, 'core_blog', [$u1->id, $u2->id]); provider::delete_data_for_users($userlist); $this->assertEmpty($DB->get_records('comments', ['contextid' => $u1ctx->id])); $this->assertEmpty($DB->get_records('post', ['userid' => $u1->id])); } /** * Test that deleting of an external blog in a user context works as desired. */ public function test_delete_data_for_users_external_blog() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $post = $this->create_external_blog(['userid' => $u1->id, 'url' => 'https://moodle.org', 'name' => 'Moodle RSS']); $post2 = $this->create_external_blog(['userid' => $u2->id, 'url' => 'https://moodle.com', 'name' => 'Some other thing']); // Check that we have two external blogs created. $this->assertCount(2, $DB->get_records('blog_external')); // This will only delete the external blog for user 1. $userlist = new \core_privacy\local\request\approved_userlist($u1ctx, 'core_blog', [$u1->id, $u2->id]); provider::delete_data_for_users($userlist); $this->assertCount(1, $DB->get_records('blog_external')); } public function test_delete_data_for_users_course_and_module_context() { global $DB; $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $u4 = $this->getDataGenerator()->create_user(); $u5 = $this->getDataGenerator()->create_user(); $course = $this->getDataGenerator()->create_course(); $module = $this->getDataGenerator()->create_module('page', ['course' => $course]); $u1ctx = \context_user::instance($u1->id); $u3ctx = \context_user::instance($u3->id); $c1ctx = \context_course::instance($course->id); $cm1ctx = \context_module::instance($module->cmid); // Blog with course association. $post1 = $this->create_post(['userid' => $u1->id, 'courseid' => $course->id]); $entry1 = new \blog_entry($post1->id); $entry1->add_association($c1ctx->id); // Blog with module association. $post2 = $this->create_post(['userid' => $u3->id, 'courseid' => $course->id]); $entry2 = new \blog_entry($post2->id); $entry2->add_association($cm1ctx->id); $comment = $this->get_comment_object($u1ctx, $entry1->id); $this->setUser($u1); $comment->add('Hello, I created the blog'); $this->setUser($u2); $comment->add('comment on first course blog'); $this->setUser($u4); $comment->add('user 4 on course blog'); $comment = $this->get_comment_object($u3ctx, $entry2->id); $this->setUser($u3); $comment->add('Hello, I created the module blog'); $this->setUser($u2); $comment->add('I am commenting on both'); $this->setUser($u5); $comment->add('User 5 for modules'); $this->assertCount(6, $DB->get_records('comments', ['component' => 'blog'])); $this->assertCount(2, $DB->get_records('post', ['courseid' => $course->id])); $this->assertCount(2, $DB->get_records('blog_association')); // When using the course or module context we are only removing the blog associations and the comments. $userlist = new \core_privacy\local\request\approved_userlist($c1ctx, 'core_blog', [$u2->id, $u1->id, $u5->id]); provider::delete_data_for_users($userlist); // Only one of the blog_associations should be removed. Everything else should be as before. $this->assertCount(6, $DB->get_records('comments', ['component' => 'blog'])); $this->assertCount(2, $DB->get_records('post', ['courseid' => $course->id])); $this->assertCount(1, $DB->get_records('blog_association')); $userlist = new \core_privacy\local\request\approved_userlist($cm1ctx, 'core_blog', [$u2->id, $u1->id, $u3->id]); provider::delete_data_for_users($userlist); // Now we've removed the other association. $this->assertCount(6, $DB->get_records('comments', ['component' => 'blog'])); $this->assertCount(2, $DB->get_records('post', ['courseid' => $course->id])); $this->assertEmpty($DB->get_records('blog_association')); } /** * Create a blog post. * * @param array $params The params. * @return stdClass */ protected function create_post(array $params) { global $DB; $post = new \stdClass(); $post->module = 'blog'; $post->courseid = 0; $post->subject = 'the test post'; $post->summary = 'test post summary text'; $post->summaryformat = FORMAT_PLAIN; $post->publishstate = 'site'; $post->created = time() - HOURSECS; $post->lastmodified = time(); foreach ($params as $key => $value) { $post->{$key} = $value; } $post->id = $DB->insert_record('post', $post); return $post; } /** * Create an extenral blog. * * @param array $params The params. * @return stdClass */ protected function create_external_blog(array $params) { global $DB; $post = new \stdClass(); $post->name = 'test external'; $post->description = 'the description'; $post->url = 'http://example.com'; $post->filtertags = 'a, c, b'; $post->timefetched = time() - HOURSECS; $post->timemodified = time(); foreach ($params as $key => $value) { $post->{$key} = $value; } $post->id = $DB->insert_record('blog_external', $post); return $post; } /** * Get the comment area. * * @param context $context The context. * @param int $itemid The item ID. * @param string $component The component. * @param string $area The area. * @return comment */ protected function get_comment_object(\context $context, $itemid) { $args = new \stdClass(); $args->context = $context; $args->course = get_course(SITEID); $args->area = 'format_blog'; $args->itemid = $itemid; $args->component = 'blog'; $comment = new \comment($args); $comment->set_post_permission(true); return $comment; } } tests/external/external_test.php 0000604 00000064474 15062070561 0013137 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Unit tests for blog external API. * * @package core_blog * @copyright 2018 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_blog\external; use core_external\external_api; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/blog/locallib.php'); require_once($CFG->dirroot . '/blog/lib.php'); /** * Unit tests for blog external API. * * @package core_blog * @copyright 2018 Juan Leyva * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class external_test extends \advanced_testcase { private $courseid; private $cmid; private $userid; private $groupid; private $tagid; private $postid; /** @var string publish state. */ protected $publishstate; protected function setUp(): void { global $DB, $CFG; parent::setUp(); $this->resetAfterTest(); // Create default course. $course = $this->getDataGenerator()->create_course(array('category' => 1, 'shortname' => 'ANON')); $this->assertNotEmpty($course); $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id)); $this->assertNotEmpty($page); // Create default user. $user = $this->getDataGenerator()->create_user(array( 'username' => 'testuser', 'firstname' => 'Jimmy', 'lastname' => 'Kinnon' )); // Enrol user. $this->getDataGenerator()->enrol_user($user->id, $course->id); $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $user->id)); // Create default post. $post = new \stdClass(); $post->userid = $user->id; $post->courseid = $course->id; $post->groupid = $group->id; $post->content = 'test post content text'; $post->module = 'blog'; $post->id = $DB->insert_record('post', $post); \core_tag_tag::set_item_tags('core', 'post', $post->id, \context_user::instance($user->id), array('tag1')); $tagid = $DB->get_field('tag', 'id', array('name' => 'tag1')); // Grab important ids. $this->courseid = $course->id; $this->cmid = $page->cmid; $this->userid = $user->id; $this->groupid = $group->id; $this->tagid = $tagid; $this->postid = $post->id; $this->publishstate = 'site'; // To be override in tests. // Set default blog level. $CFG->bloglevel = BLOG_SITE_LEVEL; } /** * Get global public entries even for not authenticated users. * We get the entry since is public. */ public function test_get_public_entries_global_level_by_non_logged_users() { global $CFG, $DB; $CFG->bloglevel = BLOG_GLOBAL_LEVEL; $CFG->forcelogin = 0; // Set current entry global. $DB->set_field('post', 'publishstate', 'public', array('id' => $this->postid)); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); $this->assertCount(1, $result['entries'][0]['tags']); $this->assertEquals('tag1', $result['entries'][0]['tags'][0]['rawname']); $this->assertEquals($this->postid, $result['entries'][0]['id']); } /** * Get global public entries even for not authenticated users in closed site. */ public function test_get_public_entries_global_level_by_non_logged_users_closed_site() { global $CFG, $DB; $CFG->bloglevel = BLOG_GLOBAL_LEVEL; $CFG->forcelogin = 1; // Set current entry global. $DB->set_field('post', 'publishstate', 'public', array('id' => $this->postid)); $this->expectException('\moodle_exception'); \core_blog\external::get_entries(); } /** * Get global public entries for guest users. * We get the entry since is public. */ public function test_get_public_entries_global_level_by_guest_users() { global $CFG, $DB; $CFG->bloglevel = BLOG_GLOBAL_LEVEL; // Set current entry global. $DB->set_field('post', 'publishstate', 'public', array('id' => $this->postid)); $this->setGuestUser(); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); $this->assertCount(1, $result['entries'][0]['tags']); $this->assertEquals('tag1', $result['entries'][0]['tags'][0]['rawname']); $this->assertEquals($this->postid, $result['entries'][0]['id']); } /** * Get global not public entries even for not authenticated users withouth being authenticated. * We don't get any because they are not public (restricted to site users). */ public function test_get_not_public_entries_global_level_by_non_logged_users() { global $CFG; $CFG->bloglevel = BLOG_GLOBAL_LEVEL; $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Get global not public entries users being guest. * We don't get any because they are not public (restricted to real site users). */ public function test_get_not_public_entries_global_level_by_guest_user() { global $CFG; $CFG->bloglevel = BLOG_GLOBAL_LEVEL; $this->setGuestUser(); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Get site not public entries for not authenticated users. * We don't get any because they are not public (restricted to real site users). */ public function test_get_not_public_entries_site_level_by_non_logged_users() { $this->expectException('require_login_exception'); // In this case we get a security exception. $result = \core_blog\external::get_entries(); } /** * Get site not public entries for guest users. * We don't get any because they are not public (restricted to real site users). */ public function test_get_not_public_entries_site_level_by_guest_users() { $this->setGuestUser(); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Get site entries at site level by system users. */ public function test_get_site_entries_site_level_by_normal_users() { $this->setUser($this->userid); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); $this->assertEquals($this->postid, $result['entries'][0]['id']); } /** * Get draft site entries by authors. */ public function test_get_draft_entries_site_level_by_author_users() { global $DB; // Set current entry global. $DB->set_field('post', 'publishstate', 'draft', array('id' => $this->postid)); $this->setUser($this->userid); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); $this->assertEquals($this->postid, $result['entries'][0]['id']); } /** * Get draft site entries by not authors. */ public function test_get_draft_entries_site_level_by_not_author_users() { global $DB; // Set current entry global. $DB->set_field('post', 'publishstate', 'draft', array('id' => $this->postid)); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Get draft site entries by admin. */ public function test_get_draft_entries_site_level_by_admin_users() { global $DB; // Set current entry global. $DB->set_field('post', 'publishstate', 'draft', array('id' => $this->postid)); $user = $this->getDataGenerator()->create_user(); $this->setAdminUser(); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); $this->assertEquals($this->postid, $result['entries'][0]['id']); } /** * Get draft user entries by authors. */ public function test_get_draft_entries_user_level_by_author_users() { global $CFG, $DB; $CFG->bloglevel = BLOG_USER_LEVEL; // Set current entry global. $DB->set_field('post', 'publishstate', 'draft', array('id' => $this->postid)); $this->setUser($this->userid); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); $this->assertEquals($this->postid, $result['entries'][0]['id']); } /** * Get draft user entries by not authors. */ public function test_get_draft_entries_user_level_by_not_author_users() { global $CFG, $DB; $CFG->bloglevel = BLOG_USER_LEVEL; // Set current entry global. $DB->set_field('post', 'publishstate', 'draft', array('id' => $this->postid)); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Get draft user entries by admin. */ public function test_get_draft_entries_user_level_by_admin_users() { global $CFG, $DB; $CFG->bloglevel = BLOG_USER_LEVEL; // Set current entry global. $DB->set_field('post', 'publishstate', 'draft', array('id' => $this->postid)); $user = $this->getDataGenerator()->create_user(); $this->setAdminUser(); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); $this->assertEquals($this->postid, $result['entries'][0]['id']); } /** * Test get all entries including testing pagination. */ public function test_get_all_entries_including_pagination() { global $DB, $USER; $DB->set_field('post', 'publishstate', 'site', array('id' => $this->postid)); // Create another entry. $this->setAdminUser(); $newpost = new \stdClass(); $newpost->userid = $USER->id; $newpost->content = 'test post content text'; $newpost->module = 'blog'; $newpost->publishstate = 'site'; $newpost->created = time() + HOURSECS; $newpost->lastmodified = time() + HOURSECS; $newpost->id = $DB->insert_record('post', $newpost); $this->setUser($this->userid); $result = \core_blog\external::get_entries(); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(2, $result['entries']); $this->assertEquals(2, $result['totalentries']); $this->assertCount(0, $result['entries'][0]['tags']); $this->assertCount(1, $result['entries'][1]['tags']); $this->assertEquals('tag1', $result['entries'][1]['tags'][0]['rawname']); $result = \core_blog\external::get_entries(array(), 0, 1); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); $this->assertEquals(2, $result['totalentries']); $this->assertEquals($newpost->id, $result['entries'][0]['id']); $result = \core_blog\external::get_entries(array(), 1, 1); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); $this->assertEquals(2, $result['totalentries']); $this->assertEquals($this->postid, $result['entries'][0]['id']); } /** * Test get entries filtering by course. */ public function test_get_entries_filtering_by_course() { global $CFG, $DB; $DB->set_field('post', 'publishstate', 'site', array('id' => $this->postid)); $this->setAdminUser(); $coursecontext = \context_course::instance($this->courseid); $anothercourse = $this->getDataGenerator()->create_course(); // Add blog associations with a course. $blog = new \blog_entry($this->postid); $blog->add_association($coursecontext->id); // There is one entry associated with a course. $result = \core_blog\external::get_entries(array(array('name' => 'courseid', 'value' => $this->courseid))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); $this->assertCount(1, $result['entries'][0]['tags']); $this->assertEquals('tag1', $result['entries'][0]['tags'][0]['rawname']); // There is no entry associated with a wrong course. $result = \core_blog\external::get_entries(array(array('name' => 'courseid', 'value' => $anothercourse->id))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); // There is no entry associated with a module. $result = \core_blog\external::get_entries(array(array('name' => 'cmid', 'value' => $this->cmid))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Test get entries filtering by module. */ public function test_get_entries_filtering_by_module() { global $CFG, $DB; $DB->set_field('post', 'publishstate', 'site', array('id' => $this->postid)); $this->setAdminUser(); $coursecontext = \context_course::instance($this->courseid); $contextmodule = \context_module::instance($this->cmid); $anothermodule = $this->getDataGenerator()->create_module('page', array('course' => $this->courseid)); // Add blog associations with a module. $blog = new \blog_entry($this->postid); $blog->add_association($contextmodule->id); // There is no entry associated with a course. $result = \core_blog\external::get_entries(array(array('name' => 'courseid', 'value' => $this->courseid))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); // There is one entry associated with a module. $result = \core_blog\external::get_entries(array(array('name' => 'cmid', 'value' => $this->cmid))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); // There is no entry associated with a wrong module. $result = \core_blog\external::get_entries(array(array('name' => 'cmid', 'value' => $anothermodule->cmid))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Test get entries filtering by author. */ public function test_get_entries_filtering_by_author() { $this->setAdminUser(); // Filter by author. $result = \core_blog\external::get_entries(array(array('name' => 'userid', 'value' => $this->userid))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); // No author. $anotheruser = $this->getDataGenerator()->create_user(); $result = \core_blog\external::get_entries(array(array('name' => 'userid', 'value' => $anotheruser->id))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Test get entries filtering by entry. */ public function test_get_entries_filtering_by_entry() { $this->setAdminUser(); // Filter by correct entry. $result = \core_blog\external::get_entries(array(array('name' => 'entryid', 'value' => $this->postid))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); // Non-existent entry. $this->expectException('\moodle_exception'); $result = \core_blog\external::get_entries(array(array('name' => 'entryid', 'value' => -1))); } /** * Test get entries filtering by search. */ public function test_get_entries_filtering_by_search() { $this->setAdminUser(); // Filter by correct search. $result = \core_blog\external::get_entries(array(array('name' => 'search', 'value' => 'test'))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); // Non-existent search. $result = \core_blog\external::get_entries(array(array('name' => 'search', 'value' => 'abc'))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Test get entries filtering by tag. */ public function test_get_entries_filtering_by_tag() { $this->setAdminUser(); // Filter by correct tag. $result = \core_blog\external::get_entries(array(array('name' => 'tag', 'value' => 'tag1'))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); // Create tag. $tag = $this->getDataGenerator()->create_tag(array('userid' => $this->userid, 'name' => 'tag2', 'isstandard' => 1)); $result = \core_blog\external::get_entries(array(array('name' => 'tag', 'value' => 'tag2'))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Test get entries filtering by tag id. */ public function test_get_entries_filtering_by_tagid() { $this->setAdminUser(); // Filter by correct tag. $result = \core_blog\external::get_entries(array(array('name' => 'tagid', 'value' => $this->tagid))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); // Non-existent tag. // Create tag. $tag = $this->getDataGenerator()->create_tag(array('userid' => $this->userid, 'name' => 'tag2', 'isstandard' => 1)); $result = \core_blog\external::get_entries(array(array('name' => 'tagid', 'value' => $tag->id))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Test get entries filtering by group. */ public function test_get_entries_filtering_by_group() { $this->setAdminUser(); // Add blog associations with a course. $coursecontext = \context_course::instance($this->courseid); $blog = new \blog_entry($this->postid); $blog->add_association($coursecontext->id); // Filter by correct group. $result = \core_blog\external::get_entries(array(array('name' => 'groupid', 'value' => $this->groupid))); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); // Non-existent group. $anotheruser = $this->getDataGenerator()->create_user(); $this->expectException('\moodle_exception'); \core_blog\external::get_entries(array(array('name' => 'groupid', 'value' => -1))); } /** * Test get entries multiple filter. */ public function test_get_entries_multiple_filter() { $this->setAdminUser(); // Add blog associations with a course. $coursecontext = \context_course::instance($this->courseid); $blog = new \blog_entry($this->postid); $blog->add_association($coursecontext->id); $result = \core_blog\external::get_entries(array( array('name' => 'tagid', 'value' => $this->tagid), array('name' => 'userid', 'value' => $this->userid), )); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(1, $result['entries']); // Non-existent multiple filter. $result = \core_blog\external::get_entries(array( array('name' => 'search', 'value' => 'www'), array('name' => 'userid', 'value' => $this->userid), )); $result = external_api::clean_returnvalue(\core_blog\external::get_entries_returns(), $result); $this->assertCount(0, $result['entries']); } /** * Test get entries filtering by invalid_filter. */ public function test_get_entries_filtering_by_invalid_filter() { $this->setAdminUser(); // Filter by incorrect filter. $this->expectException('\moodle_exception'); $result = \core_blog\external::get_entries(array(array('name' => 'zzZZzz', 'value' => 'wwWWww'))); } /** * Test get entries when blog is disabled. */ public function test_get_entries_blog_disabled() { global $CFG; $this->setAdminUser(); $CFG->enableblogs = 0; // Filter by incorrect filter. $this->expectException('\moodle_exception'); $result = \core_blog\external::get_entries(array(array('name' => 'zzZZzz', 'value' => 'wwWWww'))); } /** * Test view_blog_entries without filter. */ public function test_view_blog_entries_without_filtering() { // Test user with full capabilities. $this->setUser($this->userid); // Trigger and capture the event. $sink = $this->redirectEvents(); $result = \core_blog\external::view_entries(); $result = external_api::clean_returnvalue(\core_blog\external::view_entries_returns(), $result); $events = $sink->get_events(); $this->assertCount(1, $events); $event = array_shift($events); // Checking that the event contains the expected values (empty, no filtering done). $this->assertInstanceOf('\core\event\blog_entries_viewed', $event); $this->assertEmpty($event->get_data()['relateduserid']); $this->assertEmpty($event->get_data()['other']['entryid']); $this->assertEmpty($event->get_data()['other']['tagid']); $this->assertEmpty($event->get_data()['other']['userid']); $this->assertEmpty($event->get_data()['other']['modid']); $this->assertEmpty($event->get_data()['other']['groupid']); $this->assertEmpty($event->get_data()['other']['search']); $this->assertEmpty($event->get_data()['other']['courseid']); $this->assertEventContextNotUsed($event); $this->assertNotEmpty($event->get_name()); } /** * Test view_blog_entries doing filtering. */ public function test_view_blog_entries_with_filtering() { // Test user with full capabilities. $this->setUser($this->userid); // Trigger and capture the event. $sink = $this->redirectEvents(); $result = \core_blog\external::view_entries(array( array('name' => 'tagid', 'value' => $this->tagid), array('name' => 'userid', 'value' => $this->userid), )); $result = external_api::clean_returnvalue(\core_blog\external::view_entries_returns(), $result); $events = $sink->get_events(); $this->assertCount(1, $events); $event = array_shift($events); // Checking that the event contains the expected values (filter by user and tag). $this->assertInstanceOf('\core\event\blog_entries_viewed', $event); $this->assertEquals($this->userid, $event->get_data()['relateduserid']); $this->assertEmpty($event->get_data()['other']['entryid']); $this->assertEquals($this->tagid, $event->get_data()['other']['tagid']); $this->assertEquals($this->userid, $event->get_data()['other']['userid']); $this->assertEmpty($event->get_data()['other']['modid']); $this->assertEmpty($event->get_data()['other']['groupid']); $this->assertEmpty($event->get_data()['other']['search']); $this->assertEmpty($event->get_data()['other']['courseid']); $this->assertEventContextNotUsed($event); $this->assertNotEmpty($event->get_name()); } } index.php 0000604 00000014342 15062070561 0006366 0 ustar 00 <?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/>. /** * file index.php * index page to view blogs. if no blog is specified then site wide entries are shown * if a blog id is specified then the latest entries from that blog are shown */ require_once(__DIR__ . '/../config.php'); require_once($CFG->dirroot .'/blog/lib.php'); require_once($CFG->dirroot .'/blog/locallib.php'); require_once($CFG->dirroot .'/course/lib.php'); require_once($CFG->dirroot .'/comment/lib.php'); $id = optional_param('id', null, PARAM_INT); $start = optional_param('formstart', 0, PARAM_INT); $tag = optional_param('tag', '', PARAM_NOTAGS); $userid = optional_param('userid', null, PARAM_INT); $tagid = optional_param('tagid', null, PARAM_INT); $modid = optional_param('modid', null, PARAM_INT); $entryid = optional_param('entryid', null, PARAM_INT); $groupid = optional_param('groupid', null, PARAM_INT); $courseid = optional_param('courseid', null, PARAM_INT); $search = optional_param('search', null, PARAM_RAW); comment::init(); $urlparams = compact('id', 'start', 'tag', 'userid', 'tagid', 'modid', 'entryid', 'groupid', 'courseid', 'search'); foreach ($urlparams as $var => $val) { if (empty($val)) { unset($urlparams[$var]); } } $PAGE->set_url('/blog/index.php', $urlparams); // Correct tagid if a text tag is provided as a param. if (!empty($tag)) { if ($tagrec = $DB->get_record('tag', array('name' => $tag))) { $tagid = $tagrec->id; } else { unset($tagid); } } // Set the userid to the entry author if we have the entry ID. if ($entryid and !isset($userid)) { $entry = new blog_entry($entryid); $userid = $entry->userid; } if (isset($userid) && empty($courseid) && empty($modid)) { $context = context_user::instance($userid); } else if (!empty($courseid) && $courseid != SITEID) { $context = context_course::instance($courseid); } else { $context = context_system::instance(); } $PAGE->set_context($context); if (isset($userid) && $USER->id == $userid && !$PAGE->has_secondary_navigation()) { $blognode = $PAGE->navigation->find('siteblog', null); if ($blognode) { $blognode->make_inactive(); } } // Check basic permissions. if ($CFG->bloglevel == BLOG_GLOBAL_LEVEL) { // Everybody can see anything - no login required unless site is locked down using forcelogin. if ($CFG->forcelogin) { require_login(); } } else if ($CFG->bloglevel == BLOG_SITE_LEVEL) { // Users must log in and can not be guests. require_login(); if (isguestuser()) { // They must have entered the url manually. throw new \moodle_exception('noguest'); } } else if ($CFG->bloglevel == BLOG_USER_LEVEL) { // Users can see own blogs only! with the exception of people with special cap. require_login(); } else { // Weird! throw new \moodle_exception('blogdisable', 'blog'); } if (empty($CFG->enableblogs)) { throw new \moodle_exception('blogdisable', 'blog'); } list($courseid, $userid) = blog_validate_access($courseid, $modid, $groupid, $entryid, $userid); $courseid = (empty($courseid)) ? SITEID : $courseid; if ($courseid != SITEID) { $course = get_course($courseid); require_login($course); } if (!empty($userid)) { $user = core_user::get_user($userid, '*', MUST_EXIST); $PAGE->navigation->extend_for_user($user); } $blogheaders = blog_get_headers(); $rsscontext = null; $filtertype = null; $thingid = null; $rsstitle = ''; if ($CFG->enablerssfeeds) { list($thingid, $rsscontext, $filtertype) = blog_rss_get_params($blogheaders['filters']); if (empty($rsscontext)) { $rsscontext = context_system::instance(); } $rsstitle = $blogheaders['heading']; // Check we haven't started output by outputting an error message. if ($PAGE->state == moodle_page::STATE_BEFORE_HEADER) { blog_rss_add_http_header($rsscontext, $rsstitle, $filtertype, $thingid, $tagid); } } $usernode = $PAGE->navigation->find('user'.$userid, null); if ($usernode && $courseid != SITEID) { $courseblogsnode = $PAGE->navigation->find('courseblogs', null); if ($courseblogsnode) { $courseblogsnode->remove(); } $blogurl = new moodle_url($PAGE->url); $blognode = $usernode->add(get_string('blogscourse', 'blog'), $blogurl); $blognode->make_active(); } if ($courseid != SITEID) { $PAGE->set_heading($course->fullname); echo $OUTPUT->header(); if (!empty($user)) { $backurl = new moodle_url('/user/view.php', ['id' => $user->id, 'course' => $courseid]); echo $OUTPUT->single_button($backurl, get_string('back'), 'get', ['class' => 'mb-3']); $headerinfo = array('heading' => fullname($user), 'user' => $user); echo $OUTPUT->context_header($headerinfo, 2); } } else if (isset($userid)) { $PAGE->set_heading(fullname($user)); echo $OUTPUT->header(); } else if ($courseid == SITEID) { echo $OUTPUT->header(); } echo $OUTPUT->heading($blogheaders['heading'], 2); $bloglisting = new blog_listing($blogheaders['filters']); $bloglisting->print_entries(); if ($CFG->enablerssfeeds) { blog_rss_print_link($rsscontext, $filtertype, $thingid, $tagid, get_string('rssfeed', 'blog')); } echo $OUTPUT->footer(); $eventparams = array( 'other' => array('entryid' => $entryid, 'tagid' => $tagid, 'userid' => $userid, 'modid' => $modid, 'groupid' => $groupid, 'search' => $search, 'fromstart' => $start) ); if (!empty($userid)) { $eventparams['relateduserid'] = $userid; } $eventparams['other']['courseid'] = ($courseid === SITEID) ? 0 : $courseid; $event = \core\event\blog_entries_viewed::create($eventparams); $event->trigger(); preferences_form.php 0000604 00000003012 15062070561 0010573 0 ustar 00 <?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/>. /** * Form for blog preferences * * @package moodlecore * @subpackage blog * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ if (!defined('MOODLE_INTERNAL')) { die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. } require_once($CFG->libdir.'/formslib.php'); class blog_preferences_form extends moodleform { public function definition() { global $USER, $CFG; $mform =& $this->_form; $strpagesize = get_string('pagesize', 'blog'); $mform->addElement('text', 'pagesize', $strpagesize); $mform->setType('pagesize', PARAM_INT); $mform->addRule('pagesize', null, 'numeric', null, 'client'); $mform->setDefault('pagesize', 10); $this->add_action_buttons(); } } external_blog_edit.php 0000604 00000012670 15062070561 0011113 0 ustar 00 <?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/>. /** * Form page for an external blog link. * * @package moodlecore * @subpackage blog * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once('../config.php'); require_once('lib.php'); require_once('external_blog_edit_form.php'); require_once($CFG->libdir . '/simplepie/moodle_simplepie.php'); require_login(); $context = context_system::instance(); require_capability('moodle/blog:manageexternal', $context); // TODO redirect if $CFG->useexternalblogs is off, // $CFG->maxexternalblogsperuser == 0, // or if user doesn't have caps to manage external blogs. $id = optional_param('id', null, PARAM_INT); $url = new moodle_url('/blog/external_blog_edit.php'); if ($id !== null) { $url->param('id', $id); } $PAGE->set_url($url); $PAGE->set_context(context_user::instance($USER->id)); $PAGE->set_pagelayout('admin'); $returnurl = new moodle_url('/blog/external_blogs.php'); $action = (empty($id)) ? 'add' : 'edit'; $external = new stdClass(); // Retrieve the external blog record. if (!empty($id)) { if (!$external = $DB->get_record('blog_external', array('id' => $id, 'userid' => $USER->id))) { throw new \moodle_exception('wrongexternalid', 'blog'); } $external->autotags = core_tag_tag::get_item_tags_array('core', 'blog_external', $id); } $strformheading = ($action == 'edit') ? get_string('editexternalblog', 'blog') : get_string('addnewexternalblog', 'blog'); $strexternalblogs = get_string('externalblogs', 'blog'); $strblogs = get_string('blogs', 'blog'); $externalblogform = new blog_edit_external_form(); if ($externalblogform->is_cancelled()) { redirect($returnurl); } else if ($data = $externalblogform->get_data()) { // Save stuff in db. switch ($action) { case 'add': $rss = new moodle_simplepie($data->url); $newexternal = new stdClass(); $newexternal->name = (empty($data->name)) ? $rss->get_title() : $data->name; $newexternal->description = (empty($data->description)) ? $rss->get_description() : $data->description; $newexternal->userid = $USER->id; $newexternal->url = $data->url; $newexternal->filtertags = (!empty($data->filtertags)) ? $data->filtertags : null; $newexternal->timemodified = time(); $newexternal->id = $DB->insert_record('blog_external', $newexternal); core_tag_tag::set_item_tags('core', 'blog_external', $newexternal->id, context_user::instance($newexternal->userid), $data->autotags); blog_sync_external_entries($newexternal); // Log this action. $eventparms = array('context' => $context, 'objectid' => $newexternal->id, 'other' => array('url' => $newexternal->url)); $event = \core\event\blog_external_added::create($eventparms); $event->trigger(); break; case 'edit': if ($data->id && $DB->record_exists('blog_external', array('id' => $data->id))) { $rss = new moodle_simplepie($data->url); $external->id = $data->id; $external->name = (empty($data->name)) ? $rss->get_title() : $data->name; $external->description = (empty($data->description)) ? $rss->get_description() : $data->description; $external->userid = $USER->id; $external->url = $data->url; $external->filtertags = (!empty($data->filtertags)) ? $data->filtertags : null; $external->timemodified = time(); $DB->update_record('blog_external', $external); // Log this action. $eventparms = array('context' => $context, 'objectid' => $external->id, 'other' => array('url' => $external->url)); $event = \core\event\blog_external_updated::create($eventparms); $event->trigger(); core_tag_tag::set_item_tags('core', 'blog_external', $external->id, context_user::instance($external->userid), $data->autotags); } else { throw new \moodle_exception('wrongexternalid', 'blog'); } break; default : throw new \moodle_exception('invalidaction'); } redirect($returnurl); } navigation_node::override_active_url(new moodle_url('/blog/external_blogs.php')); $PAGE->navbar->add(get_string('addnewexternalblog', 'blog')); $PAGE->set_heading(fullname($USER)); $PAGE->set_title("$strblogs: $strexternalblogs"); echo $OUTPUT->header(); echo $OUTPUT->heading($strformheading, 2); $externalblogform->set_data($external); $externalblogform->display(); echo $OUTPUT->footer(); external_blogs.php 0000604 00000011625 15062070561 0010270 0 ustar 00 <?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/>. /** * List of external blogs for current user. * * @package moodlecore * @subpackage blog * @copyright 2009 Nicolas Connault * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once('../config.php'); require_once('lib.php'); require_login(); $context = context_system::instance(); $PAGE->set_context(context_user::instance($USER->id)); $PAGE->set_url(new moodle_url('/blog/external_blogs.php')); require_capability('moodle/blog:manageexternal', $context); $delete = optional_param('delete', null, PARAM_INT); $strexternalblogs = get_string('externalblogs', 'blog'); $straddnewexternalblog = get_string('addnewexternalblog', 'blog'); $strblogs = get_string('blogs', 'blog'); $message = null; if ($delete && confirm_sesskey()) { $externalblog = $DB->get_record('blog_external', array('id' => $delete)); if ($externalblog->userid == $USER->id) { // Delete the external blog. $DB->delete_records('blog_external', array('id' => $delete)); // Delete the external blog's posts. $deletewhere = 'module = :module AND userid = :userid AND ' . $DB->sql_isnotempty('post', 'uniquehash', false, false) . ' AND ' . $DB->sql_compare_text('content') . ' = ' . $DB->sql_compare_text(':delete'); $DB->delete_records_select('post', $deletewhere, array('module' => 'blog_external', 'userid' => $USER->id, 'delete' => $delete)); // Log this action. $eventparms = array('context' => $context, 'objectid' => $delete); $event = \core\event\blog_external_removed::create($eventparms); $event->add_record_snapshot('blog_external', $externalblog); $event->trigger(); $message = get_string('externalblogdeleted', 'blog'); } } $blogs = $DB->get_records('blog_external', array('userid' => $USER->id)); $PAGE->set_heading(fullname($USER)); $PAGE->set_title("$strblogs: $strexternalblogs"); $PAGE->set_pagelayout('standard'); echo $OUTPUT->header(); echo $OUTPUT->heading($strexternalblogs, 2); if (!empty($message)) { echo $OUTPUT->notification($message); } echo $OUTPUT->box_start('generalbox boxaligncenter'); if (!empty($blogs)) { $table = new html_table(); $table->cellpadding = 4; $table->attributes['class'] = 'generaltable boxaligncenter'; $table->head = array(get_string('name'), get_string('url', 'blog'), get_string('timefetched', 'blog'), get_string('valid', 'blog'), get_string('actions')); foreach ($blogs as $blog) { if ($blog->failedlastsync) { $validicon = $OUTPUT->pix_icon('i/invalid', get_string('feedisinvalid', 'blog')); } else { $validicon = $OUTPUT->pix_icon('i/valid', get_string('feedisvalid', 'blog')); } $editurl = new moodle_url('/blog/external_blog_edit.php', array('id' => $blog->id)); $editicon = $OUTPUT->action_icon($editurl, new pix_icon('t/edit', get_string('editexternalblog', 'blog'))); $deletelink = new moodle_url('/blog/external_blogs.php', array('delete' => $blog->id, 'sesskey' => sesskey())); $action = new confirm_action(get_string('externalblogdeleteconfirm', 'blog')); $deleteicon = $OUTPUT->action_icon($deletelink, new pix_icon('t/delete', get_string('deleteexternalblog', 'blog')), $action); $table->data[] = new html_table_row(array($blog->name, $blog->url, userdate($blog->timefetched), $validicon, $editicon . $deleteicon)); } echo html_writer::table($table); } $newexternalurl = new moodle_url('/blog/external_blog_edit.php'); echo html_writer::link($newexternalurl, $straddnewexternalblog); echo $OUTPUT->box_end(); // Log this page. $event = \core\event\blog_external_viewed::create(array('context' => $context)); $event->trigger(); echo $OUTPUT->footer();
| ver. 1.4 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка