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');
}
}