<?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.

namespace local_kid\local;

defined('MOODLE_INTERNAL') || die();

/**
 * Capability resolution helper.
 *
 * `has_capability($cap, context_system::instance())` only succeeds for a
 * role assignment made AT the system context (or a context above it — there
 * is none). A role such as `teacher` ("Personnel INSP (lecteur)") is
 * fundamentally a course-enrolment role: virtually all of its real-world
 * assignments sit at course context, never at CONTEXT_SYSTEM, so a plain
 * `has_capability()` check against that role's system-context grant is
 * always false in practice for every real holder, even though the role
 * *definition* lists the capability as allowed.
 *
 * This helper widens a short, explicit list of read-only capabilities for
 * the `teacher` role, using `user_has_role_assignment()` (checks role
 * assignment in ANY context, ignoring the context hierarchy) as an
 * explicit allow-list — never a general "role X bypasses capability
 * checks" escape hatch, so a future caller cannot accidentally grant
 * `manage`/`export`/`evaluate` to all ~25,000 real holders of `teacher` by
 * reusing this function for a capability outside the list.
 */
class access_helper {

    /** @var string[] Capabilities the `teacher` role is allowed to satisfy from any context. */
    const TEACHER_WIDENED_CAPABILITIES = [
        'local/kid:view',
        'local/kid:viewany',
        'local/kid:viewjournal',
        'local/kid:viewannuaire',
        'local/kid:managedashboardfavourites',
    ];

    /**
     * Same semantics as has_capability(), but also succeeds for a `teacher`
     * role holder (in any context) for the two whitelisted capabilities.
     *
     * @param string $capability
     * @param \context $context
     * @param int|\stdClass|null $user
     * @return bool
     */
    public static function has_capability_or_teacher(string $capability, \context $context, $user = null): bool {
        global $DB;

        if (has_capability($capability, $context, $user)) {
            return true;
        }

        if (!in_array($capability, self::TEACHER_WIDENED_CAPABILITIES, true)) {
            return false;
        }

        $userid = self::resolve_userid($user);
        $teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'teacher']);
        if (!$teacherroleid) {
            return false;
        }

        return user_has_role_assignment($userid, (int) $teacherroleid);
    }

    /**
     * Same semantics as require_capability(), widened via
     * has_capability_or_teacher().
     *
     * @param string $capability
     * @param \context $context
     * @throws \required_capability_exception
     */
    public static function require_capability_or_teacher(string $capability, \context $context): void {
        if (!self::has_capability_or_teacher($capability, $context)) {
            throw new \required_capability_exception($context, $capability, 'nopermissions', '');
        }
    }

    /**
     * @param int|\stdClass|null $user
     * @return int
     */
    private static function resolve_userid($user): int {
        global $USER;

        if ($user === null) {
            return (int) $USER->id;
        }
        if (is_object($user)) {
            return (int) $user->id;
        }
        return (int) $user;
    }
}
