first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+290
View File
@@ -0,0 +1,290 @@
<?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(),
)
);
}
}
+187
View File
@@ -0,0 +1,187 @@
<?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/>.
namespace core_blog\external;
use core_external\external_api;
use core_external\external_format_value;
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 context_module;
use moodle_exception;
/**
* This is the external method for adding a blog post entry.
*
* @package core_blog
* @copyright 2024 Juan Leyva <juan@moodle.com>
* @category external
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class add_entry extends external_api {
/**
* Parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'subject' => new external_value(PARAM_TEXT, 'Blog subject'),
'summary' => new external_value(PARAM_RAW, 'Blog post content'),
'summaryformat' => new external_format_value('summary'),
'options' => new external_multiple_structure (
new external_single_structure(
[
'name' => new external_value(PARAM_ALPHANUM,
'The allowed keys (value format) are:
inlineattachmentsid (int); the draft file area id for inline attachments. Default to 0.
attachmentsid (int); the draft file area id for attachments. Default to 0.
publishstate (str); the publish state of the entry (draft, site or public). Default to site.
courseassoc (int); the course id to associate the entry with. Default to 0.
modassoc (int); the module id to associate the entry with. Default to 0.
tags (str); the tags to associate the entry with, comma separated. Default to empty.'),
'value' => new external_value(PARAM_RAW, 'the value of the option (validated inside the function)'),
]
), 'Optional settings', VALUE_DEFAULT, []
),
]);
}
/**
* Add the indicated glossary entry.
*
* @param string $subject the glossary subject
* @param string $summary the subject summary
* @param int $summaryformat the subject summary format
* @param array $options additional settings
* @return array with result and warnings
* @throws moodle_exception
*/
public static function execute(string $subject, string $summary, int $summaryformat,
array $options = []): array {
global $DB, $CFG;
require_once($CFG->dirroot . '/blog/lib.php');
require_once($CFG->dirroot . '/blog/locallib.php');
$params = self::validate_parameters(self::execute_parameters(), compact('subject', 'summary',
'summaryformat', 'options'));
if (empty($CFG->enableblogs)) {
throw new moodle_exception('blogdisable', 'blog');
}
$context = context_system::instance();
if (!has_capability('moodle/blog:create', $context)) {
throw new \moodle_exception('cannoteditentryorblog', 'blog');
}
// Prepare the entry object.
$entrydata = new \stdClass();
$entrydata->subject = $params['subject'];
$entrydata->summary_editor = [
'text' => $params['summary'],
'format' => $params['summaryformat'],
];
$entrydata->publishstate = 'site';
// Options.
foreach ($params['options'] as $option) {
$name = trim($option['name']);
switch ($name) {
case 'inlineattachmentsid':
$entrydata->summary_editor['itemid'] = clean_param($option['value'], PARAM_INT);
break;
case 'attachmentsid':
$entrydata->attachment_filemanager = clean_param($option['value'], PARAM_INT);
break;
case 'publishstate':
$entrydata->publishstate = clean_param($option['value'], PARAM_ALPHA);
$applicable = \blog_entry::get_applicable_publish_states();
if (empty($applicable[$entrydata->publishstate])) {
throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
}
break;
case 'courseassoc':
case 'modassoc':
$entrydata->{$name} = clean_param($option['value'], PARAM_INT);
if (!$CFG->useblogassociations) {
throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
}
break;
case 'tags':
$entrydata->tags = clean_param($option['value'], PARAM_TAGLIST);
// Convert to the expected format.
$entrydata->tags = explode(',', $entrydata->tags);
break;
default:
throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
}
}
// Validate course association. We need to convert the course id to context.
if (!empty($entrydata->courseassoc)) {
$coursecontext = context_course::instance($entrydata->courseassoc);
$entrydata->courseid = $entrydata->courseassoc;
$entrydata->courseassoc = $coursecontext->id; // Convert to context.
$context = $coursecontext;
}
// Validate mod association.
if (!empty($entrydata->modassoc)) {
$modcontext = context_module::instance($entrydata->modassoc);
if (!empty($coursecontext) && $coursecontext->id != $modcontext->get_course_context(true)->id) {
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'modassoc');
}
$entrydata->coursemoduleid = $entrydata->modassoc;
$entrydata->modassoc = $modcontext->id; // Convert to context.
$context = $modcontext;
}
// Validate context for where the blog entry is going to be posted.
self::validate_context($context);
[$summaryoptions, $attachmentoptions] = blog_get_editor_options();
$blogentry = new \blog_entry(null, $entrydata, null);
$blogentry->add();
$blogentry->edit((array) $entrydata, null, $summaryoptions, $attachmentoptions);
return [
'entryid' => $blogentry->id,
'warnings' => [],
];
}
/**
* Return.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'entryid' => new external_value(PARAM_INT, 'The new entry id.'),
'warnings' => new external_warnings(),
]);
}
}
+104
View File
@@ -0,0 +1,104 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
namespace core_blog\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use context_course;
use moodle_exception;
/**
* This is the external method for deleting a blog post entry.
*
* @package core_blog
* @copyright 2024 Juan Leyva <juan@moodle.com>
* @category external
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_entry extends external_api {
/**
* Describes the external function parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'entryid' => new external_value(PARAM_INT, 'The entry id to remove.'),
]
);
}
/**
* Deletes a blog entry.
*
* @param int $entryid The entry id to remove.
* @throws moodle_exception;
* @return array result of the operation
*/
public static function execute(int $entryid): array {
global $USER, $CFG;
require_once($CFG->dirroot . '/blog/lib.php');
require_once($CFG->dirroot . '/blog/locallib.php');
$params = self::validate_parameters(self::execute_parameters(), ['entryid' => $entryid]);
if (empty($CFG->enableblogs)) {
throw new moodle_exception('blogdisable', 'blog');
}
if (!$entry = new \blog_entry($params['entryid'])) {
throw new moodle_exception('wrongentryid', 'blog');
}
$courseid = !empty($entry->courseid) ? $entry->courseid : SITEID;
$context = context_course::instance($courseid);
self::validate_context($context);
if (!blog_user_can_edit_entry($entry)) {
throw new \moodle_exception('nopermissionstodeleteentry', 'blog');
}
$entry->delete();
$result = [
'status' => true,
'warnings' => [],
];
return $result;
}
/**
* Return.
*
* @return external_single_structure
*/
public static function execute_returns() : external_single_structure {
return new external_single_structure(
[
'status' => new external_value(PARAM_BOOL, 'True indicates the entry was deleted.'),
'warnings' => new external_warnings(),
]
);
}
}
+89
View File
@@ -0,0 +1,89 @@
<?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/>.
namespace core_blog\external;
use context_system;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
/**
* External blog API implementation
*
* @package core_blog
* @copyright 2024 Juan Leyva <juan@moodle.com>
* @category external
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_access_information extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters.
* @since Moodle 4.4
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([]);
}
/**
* Convenience function to retrieve some permissions information for the blogs system.
*
* @return array The access information
* @since Moodle 4.4
*/
public static function execute(): array {
$context = context_system::instance();
self::validate_context($context);
return [
'canview' => has_capability('moodle/blog:view', $context),
'cansearch' => has_capability('moodle/blog:search', $context),
'canviewdrafts' => has_capability('moodle/blog:viewdrafts', $context),
'cancreate' => has_capability('moodle/blog:create', $context),
'canmanageentries' => has_capability('moodle/blog:manageentries', $context),
'canmanageexternal' => has_capability('moodle/blog:manageexternal', $context),
'warnings' => [],
];
}
/**
* Returns description of method result value.
*
* @return external_single_structure.
* @since Moodle 4.4
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure(
[
'canview' => new external_value(PARAM_BOOL, 'Whether the user can view blogs'),
'cansearch' => new external_value(PARAM_BOOL, 'Whether the user can search blogs'),
'canviewdrafts' => new external_value(PARAM_BOOL, 'Whether the user can view drafts'),
'cancreate' => new external_value(PARAM_BOOL, 'Whether the user can create blog entries'),
'canmanageentries' => new external_value(PARAM_BOOL, 'Whether the user can manage blog entries'),
'canmanageexternal' => new external_value(PARAM_BOOL, 'Whether the user can manage external blogs'),
'warnings' => new external_warnings(),
]
);
}
}
+207
View File
@@ -0,0 +1,207 @@
<?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,
),
'canedit' => array(
'type' => PARAM_BOOL,
'description' => 'Whether the user can edit the post.',
'optional' => true,
),
);
}
protected function get_other_values(renderer_base $output) {
global $CFG;
require_once($CFG->dirroot . '/blog/lib.php');
$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);
}
$values['canedit'] = blog_user_can_edit_entry($this->data);
return $values;
}
}
+159
View File
@@ -0,0 +1,159 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
namespace core_blog\external;
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;
/**
* This is the external method for preparing a blog entry to be edited.
*
* @package core_blog
* @copyright 2024 Juan Leyva <juan@moodle.com>
* @category external
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class prepare_entry_for_edition extends external_api {
/**
* Describes the external function parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'entryid' => new external_value(PARAM_INT, 'The entry id to edit.'),
]
);
}
/**
* Prepare a draft area for editing a blog entry.
*
* @param int $entryid The entry id to edit.
* @throws moodle_exception;
* @return array draft area information
*/
public static function execute(int $entryid): array {
global $USER, $CFG;
require_once($CFG->dirroot . '/blog/lib.php');
require_once($CFG->dirroot . '/blog/locallib.php');
$params = self::validate_parameters(
self::execute_parameters(),
[
'entryid' => $entryid,
]
);
if (empty($CFG->enableblogs)) {
throw new moodle_exception('blogdisable', 'blog');
}
if (!$entry = new \blog_entry($params['entryid'])) {
throw new moodle_exception('wrongentryid', 'blog');
}
$courseid = !empty($entry->courseid) ? $entry->courseid : SITEID;
$context = context_course::instance($courseid);
$sitecontext = context_system::instance();
self::validate_context($context);
if (!blog_user_can_edit_entry($entry)) {
throw new \moodle_exception('cannoteditentryorblog', 'blog');
}
[$summaryoptions, $attachmentoptions] = blog_get_editor_options($entry);
$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);
// Just get a structure compatible with external API.
array_walk($attachmentoptions, function(&$item, $key) use(&$attachmentoptions) {
if (!is_scalar($item)) {
unset($attachmentoptions[$key]);
return;
}
$item = ['name' => $key, 'value' => $item];
});
array_walk($summaryoptions, function(&$item, $key) use(&$summaryoptions) {
if (!is_scalar($item)) {
unset($summaryoptions[$key]);
return;
}
$item = ['name' => $key, 'value' => $item];
});
$result = [
'inlineattachmentsid' => $entry->summary_editor['itemid'],
'attachmentsid' => $entry->attachment_filemanager,
'areas' => [
[
'area' => 'summary',
'options' => $summaryoptions,
],
[
'area' => 'attachment',
'options' => $attachmentoptions,
],
],
'warnings' => [],
];
return $result;
}
/**
* Return.
*
* @return external_single_structure
*/
public static function execute_returns() : external_single_structure {
return new external_single_structure(
[
'inlineattachmentsid' => new external_value(PARAM_INT, 'Draft item id for the text editor.'),
'attachmentsid' => new external_value(PARAM_INT, 'Draft item id for the file manager.'),
'areas' => new external_multiple_structure(
new external_single_structure(
[
'area' => new external_value(PARAM_ALPHA, 'File area name.'),
'options' => new external_multiple_structure(
new external_single_structure(
[
'name' => new external_value(PARAM_RAW, 'Name of option.'),
'value' => new external_value(PARAM_RAW, 'Value of option.'),
]
), 'Draft file area options.'
),
]
), 'File areas including options'
),
'warnings' => new external_warnings(),
]
);
}
}
+202
View File
@@ -0,0 +1,202 @@
<?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/>.
namespace core_blog\external;
use core_external\external_api;
use core_external\external_format_value;
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 context_module;
use moodle_exception;
/**
* This is the external method for updating a blog post entry.
*
* @package core_blog
* @copyright 2024 Juan Leyva <juan@moodle.com>
* @category external
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class update_entry extends external_api {
/**
* Parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'entryid' => new external_value(PARAM_INT, 'Blog entry id to update'),
'subject' => new external_value(PARAM_TEXT, 'Blog subject'),
'summary' => new external_value(PARAM_RAW, 'Blog post content'),
'summaryformat' => new external_format_value('summary'),
'options' => new external_multiple_structure (
new external_single_structure(
[
'name' => new external_value(PARAM_ALPHANUM,
'The allowed keys (value format) are:
inlineattachmentsid (int); the draft file area id for inline attachments. Default to 0.
attachmentsid (int); the draft file area id for attachments. Default to 0.
publishstate (str); the publish state of the entry (draft, site or public). Default to site.
courseassoc (int); the course id to associate the entry with. Default to 0.
modassoc (int); the module id to associate the entry with. Default to 0.
tags (str); the tags to associate the entry with, comma separated. Default to empty.'),
'value' => new external_value(PARAM_RAW, 'the value of the option (validated inside the function)'),
]
), 'Optional settings', VALUE_DEFAULT, []
),
]);
}
/**
* Update the indicated glossary entry.
*
* @param int $entryid The entry to update
* @param string $subject the glossary subject
* @param string $summary the subject summary
* @param int $summaryformat the subject summary format
* @param array $options additional settings
* @return array with result and warnings
* @throws moodle_exception
*/
public static function execute(int $entryid, string $subject, string $summary, int $summaryformat,
array $options = []): array {
global $DB, $CFG;
require_once($CFG->dirroot . '/blog/lib.php');
require_once($CFG->dirroot . '/blog/locallib.php');
$params = self::validate_parameters(self::execute_parameters(), compact('entryid', 'subject', 'summary',
'summaryformat', 'options'));
if (empty($CFG->enableblogs)) {
throw new moodle_exception('blogdisable', 'blog');
}
if (!$entry = new \blog_entry($params['entryid'])) {
throw new moodle_exception('wrongentryid', 'blog');
}
if (!blog_user_can_edit_entry($entry)) {
throw new \moodle_exception('cannoteditentryorblog', 'blog');
}
// Prepare the entry object.
$entrydata = new \stdClass();
$entrydata->id = $entry->id;
$entrydata->subject = $params['subject'];
$entrydata->summary_editor = [
'text' => $params['summary'],
'format' => $params['summaryformat'],
];
$entrydata->publishstate = $entry->publishstate;
$entrydata->courseassoc = $entry->courseassoc;
$entrydata->modassoc = $entry->modassoc;
$entrydata->tags = \core_tag_tag::get_item_tags_array('core', 'post', $entry->id);
// Options.
foreach ($params['options'] as $option) {
$name = trim($option['name']);
switch ($name) {
case 'inlineattachmentsid':
$entrydata->summary_editor['itemid'] = clean_param($option['value'], PARAM_INT);
break;
case 'attachmentsid':
$entrydata->attachment_filemanager = clean_param($option['value'], PARAM_INT);
break;
case 'publishstate':
$entrydata->publishstate = clean_param($option['value'], PARAM_ALPHA);
$applicable = \blog_entry::get_applicable_publish_states();
if (empty($applicable[$entrydata->publishstate])) {
throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
}
break;
case 'courseassoc':
case 'modassoc':
$entrydata->{$name} = clean_param($option['value'], PARAM_INT);
if (!$CFG->useblogassociations) {
throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
}
break;
case 'tags':
$entrydata->tags = clean_param($option['value'], PARAM_TAGLIST);
// Convert to the expected format.
$entrydata->tags = explode(',', $entrydata->tags);
break;
default:
throw new moodle_exception('errorinvalidparam', 'webservice', '', $name);
}
}
$context = context_system::instance();
// Validate course association. We need to convert the course id to context.
if (isset($entrydata->courseassoc)) {
$entrydata->courseid = $entrydata->courseassoc;
if (!empty($entrydata->courseid)) {
$coursecontext = context_course::instance($entrydata->courseassoc);
$entrydata->courseassoc = $coursecontext->id; // Convert to context.
$context = $coursecontext;
}
}
// Validate mod association.
if (isset($entrydata->modassoc)) {
$entrydata->coursemoduleid = $entrydata->modassoc;
if (!empty($entrydata->coursemoduleid)) {
$modcontext = context_module::instance($entrydata->modassoc);
if (!empty($coursecontext) && $coursecontext->id != $modcontext->get_course_context(true)->id) {
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'modassoc');
}
$entrydata->modassoc = $modcontext->id; // Convert to context.
$context = $modcontext;
}
}
// Validate context. It might be upated because of the new association.
self::validate_context($context);
[$summaryoptions, $attachmentoptions] = blog_get_editor_options($entrydata);
$entry->edit((array) $entrydata, null, $summaryoptions, $attachmentoptions);
return [
'status' => true,
'warnings' => [],
];
}
/**
* Return.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'The update result, true if everything went well.'),
'warnings' => new external_warnings(),
]);
}
}
+545
View File
@@ -0,0 +1,545 @@
<?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');
}
}
@@ -0,0 +1,179 @@
<?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.
$commententity = new comment();
$commentalias = $commententity->get_table_alias('comments');
$this->add_entity($commententity
->add_join("LEFT JOIN {comments} {$commentalias} ON {$commentalias}.component = 'blog'
AND {$commentalias}.itemid = {$postalias}.id"));
// Join the user entity to represent the comment author.
$commenterentity = (new user())
->set_entity_name('commenter')
->set_entity_title(new lang_string('commenter', 'core_comment'));
$commenteralias = $commenterentity->get_table_alias('user');
$this->add_entity($commenterentity
->add_joins($commententity->get_joins())
->add_join("LEFT JOIN {user} {$commenteralias} ON {$commenteralias}.id = {$commentalias}.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',
];
}
}
@@ -0,0 +1,339 @@
<?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 core_collator;
use html_writer;
use lang_string;
use moodle_url;
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
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'post',
'tag_instance',
'tag',
];
}
/**
* 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);
// Title with link.
$columns[] = (new column(
'titlewithlink',
new lang_string('entrytitlewithlink', 'core_blog'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$postalias}.subject, {$postalias}.id")
->set_is_sortable(true)
->add_callback(static function(?string $subject, stdClass $post): string {
if ($subject === null) {
return '';
}
return html_writer::link(new moodle_url('/blog/index.php', ['entryid' => $post->id]), $subject);
});
// 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 $post): 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', $post->id);
return format_text($summary, $post->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('published', 'core_blog'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$postalias}.publishstate")
->set_is_sortable(true)
->add_callback(static function(?string $publishstate): string {
$states = [
'draft' => new lang_string('publishtodraft', 'core_blog'),
'site' => new lang_string('publishtosite', 'core_blog'),
'public' => new lang_string('publishtoworld', 'core_blog'),
];
if ($publishstate === null || !array_key_exists($publishstate, $states)) {
return (string) $publishstate;
}
return (string) $states[$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('published', 'core_blog'),
$this->get_entity_name(),
"{$postalias}.publishstate"
))
->add_joins($this->get_joins())
->set_options_callback(static function(): array {
$states = [
'draft' => new lang_string('publishtodraft', 'core_blog'),
'site' => new lang_string('publishtosite', 'core_blog'),
'public' => new lang_string('publishtoworld', 'core_blog'),
];
core_collator::asort($states);
return $states;
});
// 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');
}
}
+292
View File
@@ -0,0 +1,292 @@
<?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) {
$entry = new blog_entry($id); // Will trigger exception if not found.
$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', 'blog');
}
// 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');
}
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, $attachmentoptions] = blog_get_editor_options($entry);
$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');
}
$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');
}
$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;
+193
View File
@@ -0,0 +1,193 @@
<?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;
}
}
+149
View File
@@ -0,0 +1,149 @@
<?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();
+126
View File
@@ -0,0 +1,126 @@
<?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().
}
}
}
+123
View File
@@ -0,0 +1,123 @@
<?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();
+190
View File
@@ -0,0 +1,190 @@
<?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();
+1331
View File
File diff suppressed because it is too large Load Diff
+1182
View File
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
<?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();
+47
View File
@@ -0,0 +1,47 @@
<?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();
}
}
+248
View File
@@ -0,0 +1,248 @@
<?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('&nbsp;', '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);
}
}
+332
View File
@@ -0,0 +1,332 @@
<?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);
}
}
+32
View File
@@ -0,0 +1,32 @@
@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"
+34
View File
@@ -0,0 +1,34 @@
@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"
+70
View File
@@ -0,0 +1,70 @@
@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\"!"
+45
View File
@@ -0,0 +1,45 @@
@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"
+563
View File
@@ -0,0 +1,563 @@
<?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(): void {
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(): void {
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(): void {
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(): void {
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(): void {
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(): void {
$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(): void {
$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(): void {
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(): void {
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(): void {
// 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(): void {
// 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(): void {
// 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(): void {
// 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();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,51 @@
<?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'],
],
];
}
}
+55
View File
@@ -0,0 +1,55 @@
<?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;
}
}
+285
View File
@@ -0,0 +1,285 @@
<?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(): void {
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(): void {
global $CFG, $PAGE, $OUTPUT;
$blogheaders = blog_get_headers();
$this->assertEquals($blogheaders['heading'], get_string('siteblogheading', 'blog'));
}
public function test_blog_get_headers_case_6(): void {
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(): void {
global $CFG, $PAGE, $OUTPUT;
$blogheaders = blog_get_headers(null, $this->groupid);
$this->assertNotEquals($blogheaders['heading'], '');
}
public function test_blog_get_headers_case_10(): void {
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(): void {
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');
$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(): void {
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');
$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(): void {
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');
$this->assertArrayNotHasKey('blogs', $nodes->getValue($tree));
}
public function test_blog_get_listing_course(): void {
$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(): void {
$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());
}
}
+933
View File
@@ -0,0 +1,933 @@
<?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(): void {
$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(): void {
$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(): void {
$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(): void {
$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(): void {
$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(): void {
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(): void {
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(): void {
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(): void {
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(): void {
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(): void {
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(): void {
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;
}
}
@@ -0,0 +1,301 @@
<?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:titlewithlink']);
$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);
[
$link,
$body,
$attachment,
$publishstate,
$timemodified,
$tags,
$filesize,
$comment,
$commenter,
] = array_values($content[0]);
$this->assertEquals("<a href=\"https://www.example.com/moodle/blog/index.php?entryid={$blog->id}\">{$blog->subject}</a>",
$link);
$this->assertStringContainsString('Horses', $body);
$this->assertStringContainsString('hello.txt', $attachment);
$this->assertEquals('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');
}
}
+13
View File
@@ -0,0 +1,13 @@
This files describes API changes in /blog/* ,
information provided here is intended especially for developers.
=== 4.4 ===
* The blog_entry class constructor now throws an exception if the indicated entry id does not exist.
=== 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.