Skip to content

Commit

Permalink
MDL-78551 core_course: Add hooks api for course updates
Browse files Browse the repository at this point in the history
  • Loading branch information
safatshahin committed Mar 26, 2024
1 parent d2c5d26 commit 99b7dc1
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 198 deletions.
43 changes: 43 additions & 0 deletions course/classes/hook/after_course_created.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?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_course\hook;

use stdClass;

/**
* Hook after course creation.
*
* This hook will be dispatched after the course is created and events are fired.
*
* @package core_course
* @copyright 2024 Safat Shahin <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allows plugins or features to perform actions after a course is created.')]
#[\core\attribute\tags('course')]
class after_course_created {

/**
* Constructor for the hook.
*
* @param stdClass $course The course instance.
*/
public function __construct(
public readonly stdClass $course,
) {
}
}
45 changes: 45 additions & 0 deletions course/classes/hook/after_course_updated.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?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_course\hook;

use stdClass;

/**
* Hook after course updates.
*
* @package core_course
* @copyright 2024 Safat Shahin <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allows plugins or features to perform actions after a course is updated.')]
#[\core\attribute\tags('course')]
class after_course_updated {

/**
* Constructor for the hook.
*
* @param stdClass $course The course instance.
* @param stdClass $oldcourse The old course instance.
* @param bool $changeincoursecat Whether the course category has changed.
*/
public function __construct(
public readonly stdClass $course,
public readonly stdClass $oldcourse,
public readonly bool $changeincoursecat = false,
) {
}
}
59 changes: 59 additions & 0 deletions course/classes/hook/before_course_delete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?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_course\hook;

use stdClass;
use Psr\EventDispatcher\StoppableEventInterface;

/**
* Hook before course deletion.
*
* @package core_course
* @copyright 2024 Safat Shahin <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Allows plugins or features to perform actions before a course is deleted.')]
#[\core\attribute\tags('course')]
class before_course_delete implements
StoppableEventInterface {

/**
* @var bool Whether the propagation of this event has been stopped.
*/
protected bool $stopped = false;

/**
* Constructor for the hook.
*
* @param stdClass $course The course instance.
*/
public function __construct(
public readonly stdClass $course,
) {
}

public function isPropagationStopped(): bool {
return $this->stopped;
}

/**
* Stop the propagation of this event.
*/
public function stop(): void {
$this->stopped = true;
}
}
10 changes: 10 additions & 0 deletions course/edit_form.php
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,16 @@ function definition() {
$mform->addElement('hidden', 'id', null);
$mform->setType('id', PARAM_INT);

// Communication api call to set the communication data in the form for handling actions for group feature changes.
// We only need to set the data for courses already created.
if (!empty($course->id)) {
$communication = core_communication\helper::load_by_course(
courseid: $course->id,
context: $coursecontext,
);
$communication->set_data($course);
}

// Prepare custom fields data.
$handler->instance_form_before_set_data($course);
// Finally set the current form data
Expand Down
181 changes: 20 additions & 161 deletions course/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -2119,6 +2119,14 @@ function create_course($data, $editoroptions = NULL) {

$event->trigger();

$data->id = $newcourseid;

// Dispatch the hook for post course create actions.
$hook = new \core_course\hook\after_course_created(
course: $data,
);
\core\di::get(\core\hook\manager::class)->dispatch($hook);

// Setup the blocks
blocks_add_default_course_blocks($course);

Expand All @@ -2141,33 +2149,6 @@ function create_course($data, $editoroptions = NULL) {
if (isset($data->tags)) {
core_tag_tag::set_item_tags('core', 'course', $course->id, $context, $data->tags);
}
// Set up communication.
if (core_communication\api::is_available()) {
// Check for default provider config setting.
$defaultprovider = get_config('moodlecourse', 'coursecommunicationprovider');
$provider = (isset($data->selectedcommunication)) ? $data->selectedcommunication : $defaultprovider;

if (!empty($provider)) {
// Prepare the communication api data.
$courseimage = course_get_courseimage($course);
$communicationroomname = !empty($data->communicationroomname) ? $data->communicationroomname : $data->fullname;

// Communication api call.
$communication = \core_communication\api::load_by_instance(
context: $context,
component: 'core_course',
instancetype: 'coursecommunication',
instanceid: $course->id,
provider: $provider,
);
$communication->create_and_configure_room(
$communicationroomname,
$courseimage ?: null,
$data,
);
}
}

// Save custom fields if there are any of them in the form.
$handler = core_course\customfield\course_handler::create();
// Make sure to set the handler's parent context first.
Expand Down Expand Up @@ -2291,139 +2272,6 @@ function update_course($data, $editoroptions = NULL) {
if (isset($data->enablecompletion) && $data->enablecompletion == COMPLETION_DISABLED) {
$data->showcompletionconditions = null;
}

// Check if provider is selected.
$provider = $data->selectedcommunication ?? null;
// If the course moved to hidden category, set provider to none.
if ($changesincoursecat && empty($data->visible)) {
$provider = 'none';
}

// Attempt to get the communication provider if it wasn't provided in the data.
if (empty($provider) && core_communication\api::is_available()) {
$provider = \core_communication\api::load_by_instance(
context: $context,
component: 'core_course',
instancetype: 'coursecommunication',
instanceid: $data->id,
)->get_provider();
}

// Communication api call.
if (!empty($provider) && core_communication\api::is_available()) {
// Prepare the communication api data.
$courseimage = course_get_courseimage($data);

// This nasty logic is here because of hide course doesn't pass anything in the data object.
if (!empty($data->communicationroomname)) {
$communicationroomname = $data->communicationroomname;
} else {
$communicationroomname = $data->fullname ?? $oldcourse->fullname;
}

// Update communication room membership of enrolled users.
require_once($CFG->libdir . '/enrollib.php');
$courseusers = enrol_get_course_users($data->id);
$enrolledusers = [];

foreach ($courseusers as $user) {
$enrolledusers[] = $user->id;
}

// Existing communication provider.
$communication = \core_communication\api::load_by_instance(
context: $context,
component: 'core_course',
instancetype: 'coursecommunication',
instanceid: $data->id,
);
$existingprovider = $communication->get_provider();
$addusersrequired = false;
$enablenewprovider = false;
$instanceexists = true;

// Action required changes if provider has changed.
if ($provider !== $existingprovider) {
// Provider changed, flag new one to be enabled.
$enablenewprovider = true;

// If provider set to none, remove all the members from previous provider.
if ($provider === 'none' && $existingprovider !== '') {
$communication->remove_members_from_room($enrolledusers);
} else if (
// If previous provider was not none and current provider is not none,
// remove members from previous provider.
$existingprovider !== '' &&
$existingprovider !== 'none'
) {
$communication->remove_members_from_room($enrolledusers);
$addusersrequired = true;
} else if (
// If previous provider was none and current provider is not none,
// remove members from previous provider.
($existingprovider === '' || $existingprovider === 'none')
) {
$addusersrequired = true;
}

// Disable previous provider, if one was enabled.
if ($existingprovider !== '' && $existingprovider !== 'none') {
$communication->update_room(
active: \core_communication\processor::PROVIDER_INACTIVE,
);
}

// Switch to the newly selected provider so it can be updated.
if ($provider !== 'none') {
$communication = \core_communication\api::load_by_instance(
context: $context,
component: 'core_course',
instancetype: 'coursecommunication',
instanceid: $data->id,
provider: $provider,
);

// Create it if it does not exist.
if ($communication->get_provider() === '') {
$communication->create_and_configure_room(
communicationroomname: $communicationroomname,
avatar: $courseimage,
instance: $data
);

$communication = \core_communication\api::load_by_instance(
context: $context,
component: 'core_course',
instancetype: 'coursecommunication',
instanceid: $data->id,
provider: $provider,
);

$addusersrequired = true;
$instanceexists = false;
}
}
}

if ($provider !== 'none' && $instanceexists) {
// Update the currently enabled provider's room data.
// Newly created providers do not need to run this, the create process handles it.
$communication->update_room(
active: $enablenewprovider ? \core_communication\processor::PROVIDER_ACTIVE : null,
communicationroomname: $communicationroomname,
avatar: $courseimage,
instance: $data,
);
}

// Complete room membership tasks if required.
// Newly created providers complete the user mapping but do not queue the task
// (it will be handled by the room creation task).
if ($addusersrequired) {
$communication->add_members_to_room($enrolledusers, $instanceexists);
}
}

// Update custom fields if there are any of them in the form.
$handler = core_course\customfield\course_handler::create();
$handler->instance_form_save($data);
Expand Down Expand Up @@ -2484,6 +2332,14 @@ function update_course($data, $editoroptions = NULL) {

$event->trigger();

// Dispatch the hook for post course update actions.
$hook = new \core_course\hook\after_course_updated(
course: $data,
oldcourse: $oldcourse,
changeincoursecat: $changesincoursecat,
);
\core\di::get(\core\hook\manager::class)->dispatch($hook);

if ($oldcourse->format !== $course->format) {
// Remove all options stored for the previous format
// We assume that new course format migrated everything it needed watching trigger
Expand Down Expand Up @@ -5093,7 +4949,10 @@ function course_get_communication_instance_data(int $courseid): array {
*/
function course_update_communication_instance_data(stdClass $data): void {
$data->id = $data->instanceid; // For correct use in update_course.
update_course($data);
core_communication\helper::update_course_communication_instance(
course: $data,
changesincoursecat: false,
);
}

/**
Expand Down
Loading

0 comments on commit 99b7dc1

Please sign in to comment.