Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions classes/courseformat/overview.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
<?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 mod_scheduler\courseformat;

use mod_scheduler\model\scheduler;
use core\output\action_link;
use core\output\local\properties\button;
use core\output\local\properties\text_align;
use core\url;
use core_courseformat\local\overview\overviewitem;

/**
* Scheduler overview integration (for Moodle 5.1+)
*
* @package mod_scheduler
* @copyright 2025 Luca Bösch <luca.boesch@bfh.ch>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class overview extends \core_courseformat\activityoverviewbase {
/** @var scheduler $scheduler the scheduler instance. */
private scheduler $scheduler;

/**
* Constructor.
*
* @param \cm_info $cm the course module instance.
* @param \core\output\renderer_helper $rendererhelper the renderer helper.
*/
public function __construct(
\cm_info $cm,
/** @var \core\output\renderer_helper $rendererhelper the renderer helper */
protected readonly \core\output\renderer_helper $rendererhelper,
) {
global $CFG;
require_once($CFG->dirroot . '/mod/scheduler/locallib.php');
parent::__construct($cm);
$this->scheduler = \mod_scheduler\model\scheduler::load_by_coursemodule_id($cm->id);
}

#[\Override]
public function get_actions_overview(): ?overviewitem {
$url = new url(
'/mod/scheduler/view.php',
['id' => $this->cm->id],
);

$text = get_string('view');

if (
class_exists(button::class) &&
(new \ReflectionClass(button::class))->hasConstant('BODY_OUTLINE')
) {
$bodyoutline = button::BODY_OUTLINE;
$buttonclass = $bodyoutline->classes();
} else {
$buttonclass = "btn btn-outline-secondary";
}

$content = new action_link($url, $text, null, ['class' => $buttonclass]);
return new overviewitem(get_string('actions'), $text, $content, text_align::CENTER);
}

#[\Override]
public function get_extra_overview_items(): array {
return [
'openappointments' => $this->get_extra_open_appointments_overview(),
'appointmentstatus' => $this->get_extra_appointment_status_overview(),
];
}

/**
* Retrieves an overview of submissions for the assignment.
*
* @return overviewitem|null An overview item c, or null if the user lacks the required capability.
*/
private function get_extra_open_appointments_overview(): ?overviewitem {
global $USER;

if (!has_capability('mod/scheduler:manageallappointments', $this->cm->context)) {
return null;
}

if (
class_exists(button::class) &&
(new \ReflectionClass(button::class))->hasConstant('BODY_OUTLINE')
) {
$bodyoutline = button::BODY_OUTLINE;
$buttonclass = $bodyoutline->classes();
} else {
$buttonclass = "btn btn-outline-secondary";
}

$groupscheduling = $this->scheduler->is_group_scheduling_enabled();
if (!$groupscheduling) {
$openappointments = $this->scheduler->get_students_for_scheduling();
if (is_array($openappointments)) {
$openappointments = count($openappointments);
}
$total = count($this->scheduler->get_available_students());

$content = new action_link(
url: new url(
'/mod/scheduler/view.php',
[
'id' => $this->cm->id,
'what' => 'view',
'scope' => 'activity',
'subpage' => 'allappointments',
]
),
text: get_string(
'count_of_total',
'core',
['count' => $openappointments, 'total' => $total]
),
attributes: ['class' => $buttonclass],
);
} else {
$openappointments = count($this->scheduler->get_groups_for_scheduling());
$total = count($this->scheduler->get_available_groups());

$content = new action_link(
url: new url(
'/mod/scheduler/view.php',
[
'id' => $this->cm->id,
'what' => 'view',
'scope' => 'activity',
'subpage' => 'allappointments',
]
),
text: get_string(
'count_of_total_groups',
'scheduler',
['count' => $openappointments, 'total' => $total]
),
attributes: ['class' => $buttonclass],
);
}

return new overviewitem(
name: get_string('needmakeappointment', 'scheduler'),
value: $openappointments,
content: $content,
textalign: text_align::CENTER,
);
}

/**
* Retrieves the appointment status overview for the current user.
*
* @return overviewitem|null The overview item, or null if the user does not have the required capabilities.
*/
private function get_extra_appointment_status_overview(): ?overviewitem {
global $USER;

if (
!has_capability('mod/scheduler:appoint', $this->context, $USER, false) ||
has_capability('mod/scheduler:manage', $this->context, $USER, false)
) {
return null;
}

$userappointment = $this->scheduler->get_appointments_for_student($USER->id);

if (!empty($userappointment)) {
$appointmentstatus = get_string('appointmentstatus_appointed', 'scheduler');
} else {
$appointmentstatus = get_string('appointmentstatus_unappointed', 'scheduler');
}

return new overviewitem(
name: get_string('appointmentstatus', 'scheduler'),
value: $appointmentstatus,
content: $appointmentstatus,
);
}
}
164 changes: 164 additions & 0 deletions classes/manager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?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 mod_scheduler;

use cm_info;
use context_module;
use stdClass;

/**
* Class manager for scheduler activity
*
* @package mod_scheduler
* @copyright 2025 Luca Bösch <luca.boesch@bfh.ch>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manager {
/** Module name. */
public const MODULE = 'scheduler';

/** @var context_module the current context. */
private $context;

/** @var stdClass $course record. */
private $course;

/** @var \moodle_database the database instance. */
private \moodle_database $db;

/**
* Class constructor.
*
* @param cm_info $cm course module info object
* @param stdClass $instance activity instance object.
*/
public function __construct(
/** @var cm_info $cm the given course module info */
private cm_info $cm,
/** @var stdClass $instance activity instance object */
private stdClass $instance
) {
$this->context = context_module::instance($cm->id);
$this->db = \core\di::get(\moodle_database::class);
$this->course = $cm->get_course();
}

/**
* Create a manager instance from an instance record.
*
* @param stdClass $instance an activity record
* @return manager
*/
public static function create_from_instance(stdClass $instance): self {
$cm = get_coursemodule_from_instance(self::MODULE, $instance->id);
// Ensure that $this->cm is a cm_info object.
$cm = cm_info::create($cm);
return new self($cm, $instance);
}

/**
* Create a manager instance from a course_modules record.
*
* @param stdClass|cm_info $cm an activity record
* @return manager
*/
public static function create_from_coursemodule(stdClass|cm_info $cm): self {
// Ensure that $this->cm is a cm_info object.
$cm = cm_info::create($cm);
$db = \core\di::get(\moodle_database::class);
$instance = $db->get_record(self::MODULE, ['id' => $cm->instance], '*', MUST_EXIST);
return new self($cm, $instance);
}

/**
* Return the current context.
*
* @return context_module
*/
public function get_context(): context_module {
return $this->context;
}

/**
* Return the current instance.
*
* @return stdClass the instance record
*/
public function get_instance(): stdClass {
return $this->instance;
}

/**
* Return the current cm_info.
*
* @return cm_info the course module
*/
public function get_coursemodule(): cm_info {
return $this->cm;
}

/**
* Return the current count of users who have booked slots in this scheduler module, that the current user can see.
*
* @param int[] $groupids the group identifiers to filter by, empty array means no filtering
* @param int|null $optionid the option ID to filter by, or null to count all answers
* @return int the number of answers that the user can see
*/
public function count_all_users_answered(
array $groupids = [],
?int $optionid = null,
): int {
if (!has_capability('mod/scheduler:manage', $this->context)) {
return 0;
}

$tableprefix = empty($groupids) ? '' : 'of.';
$select = $tableprefix . 'scheduler = :schedulerid';
$params = [
'schedulerid' => $this->instance->id,
];
if ($optionid) {
$select .= ' AND ' . $tableprefix . 'optionid = :optionid ';
$params['optionid'] = $optionid;
}

if (empty($groupids)) {
// No groups filtering, count all users answered.
return $this->db->count_records_select('scheduler_file', $select, $params, 'COUNT(DISTINCT userid)');
}

// Groups filtering is applied.
[$gsql, $gparams] = $this->db->get_in_or_equal($groupids, SQL_PARAMS_NAMED);
$query = "SELECT COUNT(DISTINCT of.userid)
FROM {scheduler_file} of, {groups_members} gm
WHERE $select
AND (gm.groupid $gsql OR gm.groupid = 0)
AND of.userid = gm.userid";
return $this->db->count_records_sql($query, $params + $gparams);
}

/**
* Check if the current user has booked a slot in this scheduler.
*
* @return bool true if the user has booked, false otherwise
*/
public function has_answered(): bool {
global $USER;
$conditions = ['scheduler' => $this->instance->id, 'userid' => $USER->id];
return $this->db->record_exists('scheduler_file', $conditions);
}
}
Loading
Loading