mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 16:32:18 +02:00
MDL-81011 core: Add attribute alternative to hooks interfaces
This change replaces the requirement for: - \core\hook\deprecated_callback_replacement - \core\hook\described_hook These are replaced by appropriate Attributes.
This commit is contained in:
parent
b2fa19f45d
commit
2b7754ccc2
@ -154,11 +154,7 @@ class hook_list_table extends flexible_table {
|
||||
return '';
|
||||
}
|
||||
|
||||
$rc = new \ReflectionClass($row->classname);
|
||||
if (!$rc->implementsInterface(\core\hook\deprecated_callback_replacement::class)) {
|
||||
return '';
|
||||
}
|
||||
$deprecates = call_user_func([$row->classname, 'get_deprecated_plugin_callbacks']);
|
||||
$deprecates = \core\hook\manager::get_replaced_callbacks($row->classname);
|
||||
if (count($deprecates) === 0) {
|
||||
return '';
|
||||
}
|
||||
|
@ -19,6 +19,11 @@
|
||||
"allowedlevel2": true,
|
||||
"allowedspread": true
|
||||
},
|
||||
"attribute": {
|
||||
"component": "core",
|
||||
"allowedlevel2": true,
|
||||
"allowedspread": true
|
||||
},
|
||||
"availability": {
|
||||
"component": "core_availability",
|
||||
"allowedlevel2": false,
|
||||
|
35
lib/classes/attribute/hook/replaces_callbacks.php
Normal file
35
lib/classes/attribute/hook/replaces_callbacks.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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\attribute\hook;
|
||||
|
||||
/**
|
||||
* A set of callbacks that this hook replaces.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[\Attribute]
|
||||
class replaces_callbacks {
|
||||
/** @var string[] A list of callbacks that this hook replaces */
|
||||
public readonly array $callbacks;
|
||||
public function __construct(
|
||||
...$callbacks,
|
||||
) {
|
||||
$this->callbacks = $callbacks;
|
||||
}
|
||||
}
|
32
lib/classes/attribute/label.php
Normal file
32
lib/classes/attribute/label.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?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\attribute;
|
||||
|
||||
/**
|
||||
* An unstranslated string attribute used to label an object.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[\Attribute]
|
||||
class label {
|
||||
public function __construct(
|
||||
public readonly string $label,
|
||||
) {
|
||||
}
|
||||
}
|
37
lib/classes/attribute/tags.php
Normal file
37
lib/classes/attribute/tags.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?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\attribute;
|
||||
|
||||
/**
|
||||
* A set of string tags used to categorise an object.
|
||||
*
|
||||
* Note: These are not the same as the tags API.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[\Attribute]
|
||||
class tags {
|
||||
/** @var string[] A list of tags */
|
||||
public readonly array $tags;
|
||||
public function __construct(
|
||||
...$tags,
|
||||
) {
|
||||
$this->tags = $tags;
|
||||
}
|
||||
}
|
245
lib/classes/attribute_helper.php
Normal file
245
lib/classes/attribute_helper.php
Normal file
@ -0,0 +1,245 @@
|
||||
<?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;
|
||||
|
||||
use ReflectionAttribute;
|
||||
|
||||
/**
|
||||
* Helper for loading attributes.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class attribute_helper {
|
||||
|
||||
/**
|
||||
* Get an instance of an attribute from a reference.
|
||||
*
|
||||
* The reference can be:
|
||||
* - a string, in which case it will be checked for a function, class, method, property, constant, or enum.
|
||||
* - an array
|
||||
* - an instantiated object, in which case the object will be checked for a class, method, property, or constant.
|
||||
*
|
||||
* @param array|string|object $reference A reference of where to find the attribute
|
||||
* @param null|string $attributename The name of the attribute to find
|
||||
* @param int $attributeflags The flags to use when finding the attribute
|
||||
* @return ?object
|
||||
*/
|
||||
public static function instance(
|
||||
array|string|object $reference,
|
||||
?string $attributename = null,
|
||||
int $attributeflags = ReflectionAttribute::IS_INSTANCEOF,
|
||||
): ?object {
|
||||
return self::one_from($reference, $attributename, $attributeflags)?->newInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all instance of an attribute from a reference.
|
||||
*
|
||||
* The reference can be:
|
||||
* - a string, in which case it will be checked for a function, class, method, property, constant, or enum.
|
||||
* - an array
|
||||
* - an instantiated object, in which case the object will be checked for a class, method, property, or constant.
|
||||
*
|
||||
* @param array|string|object $reference A reference of where to find the attribute
|
||||
* @param null|string $attributename The name of the attribute to find
|
||||
* @param int $attributeflags The flags to use when finding the attribute
|
||||
* @return ?object[]
|
||||
*/
|
||||
public static function instances(
|
||||
array|string|object $reference,
|
||||
?string $attributename = null,
|
||||
int $attributeflags = ReflectionAttribute::IS_INSTANCEOF,
|
||||
): ?array {
|
||||
if ($attributes = self::from($reference, $attributename, $attributeflags)) {
|
||||
return array_map(fn ($attribute) => $attribute->newInstance(), $attributes);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one attribute from a reference.
|
||||
*
|
||||
* The reference can be:
|
||||
* - a string, in which case it will be checked for a function, class, method, property, constant, or enum.
|
||||
* - an array
|
||||
* - an instantiated object, in which case the object will be checked for a class, method, property, or constant.
|
||||
*
|
||||
* @param array|string|object $reference A reference of where to find the attribute
|
||||
* @param null|string $attributename The name of the attribute to find
|
||||
* @param int $attributeflags The flags to use when finding the attribute
|
||||
* @return \ReflectionAttribute|null
|
||||
*/
|
||||
public static function one_from(
|
||||
array|string|object $reference,
|
||||
?string $attributename = null,
|
||||
int $attributeflags = ReflectionAttribute::IS_INSTANCEOF,
|
||||
): ?\ReflectionAttribute {
|
||||
$attributes = self::from($reference, $attributename, $attributeflags);
|
||||
|
||||
if ($attributes && count($attributes) > 1) {
|
||||
throw new \coding_exception('More than one attribute found');
|
||||
}
|
||||
|
||||
return $attributes ? $attributes[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attribute from a reference.
|
||||
*
|
||||
* The reference can be:
|
||||
* - a string, in which case it will be checked for a function, class, method, property, constant, or enum.
|
||||
* - an array
|
||||
* - an instantiated object, in which case the object will be checked for a class, method, property, or constant.
|
||||
*
|
||||
* @param array|string|object $reference A reference of where to find the attribute
|
||||
* @param null|string $attributename The name of the attribute to find
|
||||
* @param int $attributeflags The flags to use when finding the attribute
|
||||
* @return \ReflectionAttribute[]|null
|
||||
*/
|
||||
public static function from(
|
||||
array|string|object $reference,
|
||||
?string $attributename = null,
|
||||
int $attributeflags = ReflectionAttribute::IS_INSTANCEOF,
|
||||
): ?array {
|
||||
if (is_string($reference)) {
|
||||
if (str_contains($reference, '::')) {
|
||||
// The reference is a string but it looks to be in the format `object::item`.
|
||||
return self::from(explode('::', $reference), $attributename, $attributeflags);;
|
||||
}
|
||||
|
||||
if (class_exists($reference)) {
|
||||
// The reference looks to be a class name.
|
||||
return self::from([$reference], $attributename, $attributeflags);
|
||||
}
|
||||
|
||||
if (function_exists($reference)) {
|
||||
// The reference looks to be a global function.
|
||||
$ref = new \ReflectionFunction($reference);
|
||||
return $ref->getAttributes(
|
||||
name: $attributename,
|
||||
flags: $attributeflags,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_object($reference)) {
|
||||
// The reference is an object. Normalise and check again.
|
||||
return self::from([$reference], $attributename, $attributeflags);
|
||||
}
|
||||
|
||||
if (is_array($reference) && count($reference)) {
|
||||
if (is_object($reference[0])) {
|
||||
// The first array key is an instance of a class, enum, etc.
|
||||
$rc = new \ReflectionObject($reference[0]);
|
||||
|
||||
if ($rc->isEnum() && $reference[0]->name) {
|
||||
// Enums can be passed via ::from([enum::NAME]).
|
||||
// In this case they will have a 'name', which must exist.
|
||||
return self::from_reflected_object(
|
||||
rc: $rc,
|
||||
referenceproperty: $reference[0]->name,
|
||||
attributename: $attributename,
|
||||
flags: $attributeflags,
|
||||
);
|
||||
}
|
||||
|
||||
// The object is an instance of a class, or similar.
|
||||
// That means that, if provided, the second array key is the name of the property, constant, method, etc.
|
||||
return self::from_reflected_object(
|
||||
rc: $rc,
|
||||
referenceproperty: $reference[1] ?? null,
|
||||
attributename: $attributename,
|
||||
flags: $attributeflags,
|
||||
);
|
||||
}
|
||||
|
||||
if (is_string($reference[0]) && class_exists($reference[0])) {
|
||||
// The first array key is a class name.
|
||||
// That means that, if provided, the second array key is the name of the property, constant, method, etc.
|
||||
$rc = new \ReflectionClass($reference[0]);
|
||||
return self::from_reflected_object(
|
||||
rc: $rc,
|
||||
referenceproperty: $reference[1] ?? null,
|
||||
attributename: $attributename,
|
||||
flags: $attributeflags,
|
||||
);
|
||||
}
|
||||
|
||||
// The reference is an array, but it's not an object or a class that currently exists.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch an attribute from a reflected object.
|
||||
*
|
||||
* @param \ReflectionClass $rc The reflected object
|
||||
* @param null|string $referenceproperty The name of the thing to find attributes on
|
||||
* @param null|string $attributename The name of the attribute to find
|
||||
* @param int $attributeflags The flags to use when finding the attribute
|
||||
* @return \ReflectionAttribute[]|null
|
||||
*/
|
||||
protected static function from_reflected_object(
|
||||
\ReflectionClass $rc,
|
||||
?string $referenceproperty,
|
||||
?string $attributename = null,
|
||||
int $flags = 0,
|
||||
): ?array {
|
||||
if ($referenceproperty === null) {
|
||||
// No name specified - may be the whole class..
|
||||
return $rc->getAttributes(
|
||||
name: $attributename,
|
||||
flags: $flags,
|
||||
);
|
||||
}
|
||||
|
||||
if ($rc->hasConstant($referenceproperty)) {
|
||||
// This class has a constant with the specified name.
|
||||
// Note: This also applies to enums.
|
||||
$ref = $rc->getReflectionConstant($referenceproperty);
|
||||
return $ref->getAttributes(
|
||||
name: $attributename,
|
||||
flags: $flags,
|
||||
);
|
||||
}
|
||||
|
||||
if ($rc->hasMethod($referenceproperty)) {
|
||||
// This class has a method with the specified name.
|
||||
$ref = $rc->getMethod($referenceproperty);
|
||||
return $ref->getAttributes(
|
||||
name: $attributename,
|
||||
flags: $flags,
|
||||
);
|
||||
}
|
||||
|
||||
if ($rc->hasProperty($referenceproperty)) {
|
||||
// This class has a property with the specified name.
|
||||
$ref = $rc->getProperty($referenceproperty);
|
||||
return $ref->getAttributes(
|
||||
name: $attributename,
|
||||
flags: $flags,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -16,8 +16,6 @@
|
||||
|
||||
namespace core\hook\backup;
|
||||
|
||||
use core\hook\described_hook;
|
||||
|
||||
/**
|
||||
* Get a list of event names which are excluded to trigger from course changes in automated backup.
|
||||
*
|
||||
@ -25,31 +23,14 @@ use core\hook\described_hook;
|
||||
* @copyright 2023 Tomo Tsuyuki <tomotsuyuki@catalyst-au.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
final class get_excluded_events implements described_hook {
|
||||
|
||||
#[\core\attribute\label('Get a list of event names which are excluded to trigger from course changes in automated backup.')]
|
||||
#[\core\attribute\tags('backup')]
|
||||
final class get_excluded_events {
|
||||
/**
|
||||
* @var string[] Array of event names.
|
||||
*/
|
||||
private $events = [];
|
||||
|
||||
/**
|
||||
* Describes the hook purpose.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_hook_description(): string {
|
||||
return 'Get a list of event names which are excluded to trigger from course changes in automated backup.';
|
||||
}
|
||||
|
||||
/**
|
||||
* List of tags that describe this hook.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function get_hook_tags(): array {
|
||||
return ['backup'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an array of event names which are excluded to trigger from course changes in automated backup.
|
||||
* This is set from plugin hook.
|
||||
|
@ -19,6 +19,8 @@ namespace core\hook;
|
||||
/**
|
||||
* Interface for describing of lib.php callbacks that were deprecated by the hook.
|
||||
*
|
||||
* Please note that, from Moodle 4.4, you can instead use the \core\attribute\hook\replaces_callback attribute.
|
||||
*
|
||||
* @package core
|
||||
* @author Petr Skoda
|
||||
* @copyright 2022 Open LMS
|
||||
|
@ -17,6 +17,7 @@
|
||||
namespace core\hook;
|
||||
|
||||
use DI\ContainerBuilder;
|
||||
use core\attribute\label;
|
||||
|
||||
/**
|
||||
* Allow for init-time configuration of the Dependency Injection container.
|
||||
@ -25,7 +26,8 @@ use DI\ContainerBuilder;
|
||||
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class di_configuration implements described_hook {
|
||||
#[label('The DI container, which allows plugins to register any service requiring configuration or initialisation.')]
|
||||
class di_configuration {
|
||||
/**
|
||||
* Create the Dependency Injection configuration hook instance.
|
||||
*
|
||||
@ -78,12 +80,4 @@ class di_configuration implements described_hook {
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function get_hook_description(): string {
|
||||
return 'The DI container, which allows plugins to register any service requiring configuration or initialisation.';
|
||||
}
|
||||
|
||||
public static function get_hook_tags(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
namespace core\hook;
|
||||
|
||||
use core\attribute_helper;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\EventDispatcher\ListenerProviderInterface;
|
||||
use Psr\EventDispatcher\StoppableEventInterface;
|
||||
@ -171,6 +172,30 @@ final class manager implements
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of callbacks that the given hook class replaces (if any).
|
||||
*
|
||||
* @param string $hookclassname
|
||||
* @return array
|
||||
*/
|
||||
public static function get_replaced_callbacks(string $hookclassname): array {
|
||||
if (!class_exists($hookclassname)) {
|
||||
return [];
|
||||
}
|
||||
if (is_subclass_of($hookclassname, \core\hook\deprecated_callback_replacement::class)) {
|
||||
return $hookclassname::get_deprecated_plugin_callbacks();
|
||||
}
|
||||
|
||||
// Ensure that the replaces_callbacks attribute is loaded.
|
||||
// TODO MDL-81134 Remove after LTS+1.
|
||||
require_once(dirname(__DIR__) . '/attribute/hook/replaces_callbacks.php');
|
||||
if ($replaces = attribute_helper::instance($hookclassname, \core\attribute\hook\replaces_callbacks::class)) {
|
||||
return $replaces->callbacks;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that callback is valid.
|
||||
*
|
||||
@ -450,20 +475,9 @@ final class manager implements
|
||||
private function fetch_deprecated_callbacks(): void {
|
||||
$candidates = self::discover_known_hooks();
|
||||
|
||||
/** @var class-string<deprecated_callback_replacement> $hookclassname */
|
||||
foreach (array_keys($candidates) as $hookclassname) {
|
||||
if (!class_exists($hookclassname)) {
|
||||
continue;
|
||||
}
|
||||
if (!is_subclass_of($hookclassname, \core\hook\deprecated_callback_replacement::class)) {
|
||||
continue;
|
||||
}
|
||||
$deprecations = $hookclassname::get_deprecated_plugin_callbacks();
|
||||
if (!$deprecations) {
|
||||
continue;
|
||||
}
|
||||
foreach ($deprecations as $deprecation) {
|
||||
$this->alldeprecations[$deprecation][] = $hookclassname;
|
||||
foreach (self::get_replaced_callbacks($hookclassname) as $replacedcallback) {
|
||||
$this->alldeprecations[$replacedcallback][] = $hookclassname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
namespace core\hook\navigation;
|
||||
|
||||
use core\hook\described_hook;
|
||||
use core\hook\stoppable_trait;
|
||||
use core\navigation\views\primary;
|
||||
|
||||
@ -27,8 +26,9 @@ use core\navigation\views\primary;
|
||||
* @copyright 2023 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class primary_extend implements described_hook,
|
||||
\Psr\EventDispatcher\StoppableEventInterface {
|
||||
#[\core\attribute\label('Allows plugins to insert nodes into site primary navigation')]
|
||||
#[\core\attribute\tags('navigation')]
|
||||
class primary_extend implements \Psr\EventDispatcher\StoppableEventInterface {
|
||||
use stoppable_trait;
|
||||
|
||||
/**
|
||||
@ -36,7 +36,9 @@ class primary_extend implements described_hook,
|
||||
*
|
||||
* @param primary $primaryview Primary navigation view
|
||||
*/
|
||||
public function __construct(protected primary $primaryview) {
|
||||
public function __construct(
|
||||
public readonly primary $primaryview,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,22 +49,4 @@ class primary_extend implements described_hook,
|
||||
public function get_primaryview(): primary {
|
||||
return $this->primaryview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the hook purpose.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_hook_description(): string {
|
||||
return 'Allows plugins to insert nodes into site primary navigation';
|
||||
}
|
||||
|
||||
/**
|
||||
* List of tags that describe this hook.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function get_hook_tags(): array {
|
||||
return ['navigation'];
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,6 @@
|
||||
|
||||
namespace core\hook\output;
|
||||
|
||||
use core\hook\described_hook;
|
||||
use core\hook\deprecated_callback_replacement;
|
||||
|
||||
/**
|
||||
* Allows plugins to add any elements to the page <head> html tag
|
||||
*
|
||||
@ -26,37 +23,13 @@ use core\hook\deprecated_callback_replacement;
|
||||
* @copyright 2023 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class standard_head_html_prepend implements described_hook, deprecated_callback_replacement {
|
||||
#[\core\attribute\tags('output')]
|
||||
#[\core\attribute\label('Allows plugins to add any elements to the page <head> html tag.')]
|
||||
#[\core\attribute\hook\replaces_callbacks('before_standard_html_head')]
|
||||
class standard_head_html_prepend {
|
||||
/** @var string $output Stores results from callbacks */
|
||||
private $output = '';
|
||||
|
||||
/**
|
||||
* Describes the hook purpose.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_hook_description(): string {
|
||||
return 'Allows plugins to add any elements to the page <head> html tag.';
|
||||
}
|
||||
|
||||
/**
|
||||
* List of tags that describe this hook.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function get_hook_tags(): array {
|
||||
return ['output'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of lib.php plugin callbacks that were deprecated by the hook.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_deprecated_plugin_callbacks(): array {
|
||||
return ['before_standard_html_head'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugins implementing callback can add any HTML to the page.
|
||||
*
|
||||
|
@ -81,6 +81,14 @@ final class hooks implements \core\hook\discovery_agent {
|
||||
if (is_subclass_of($classname, \core\hook\described_hook::class)) {
|
||||
$hooks[$classname]['description'] = $classname::get_hook_description();
|
||||
$hooks[$classname]['tags'] = $classname::get_hook_tags();
|
||||
} else {
|
||||
if ($description = attribute_helper::instance($classname, \core\attribute\label::class)) {
|
||||
$hooks[$classname]['description'] = (string) $description->label;
|
||||
}
|
||||
|
||||
if ($tags = attribute_helper::instance($classname, \core\attribute\tags::class)) {
|
||||
$hooks[$classname]['tags'][] = $tags->tags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
170
lib/tests/attribute_helper_test.php
Normal file
170
lib/tests/attribute_helper_test.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Tests for the attribute_helper.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core\attribute_helper
|
||||
*/
|
||||
final class attribute_helper_test extends \advanced_testcase {
|
||||
public static function setUpBeforeClass(): void {
|
||||
require_once(__DIR__ . '/fixtures/attribute_helper_example.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider get_attributes_provider
|
||||
*/
|
||||
public function test_get_attributes(
|
||||
int $expectedcount,
|
||||
array $args,
|
||||
): void {
|
||||
$attributes = attribute_helper::from(...$args);
|
||||
$instances = attribute_helper::instances(...$args);
|
||||
if ($expectedcount) {
|
||||
$this->assertNotEmpty($attributes);
|
||||
$this->assertCount($expectedcount, $attributes);
|
||||
$this->assertCount($expectedcount, $instances);
|
||||
|
||||
} else {
|
||||
$this->assertEmpty($attributes);
|
||||
$this->assertEmpty($instances);
|
||||
}
|
||||
}
|
||||
|
||||
public static function get_attributes_provider(): array {
|
||||
return [
|
||||
[3, [[attribute_helper_example::class]]],
|
||||
[0, [[attribute_helper_example_without::class]]],
|
||||
[3, [[attribute_helper_example::class, 'WITH_ATTRIBUTES']]],
|
||||
[0, [[attribute_helper_example::class, 'WITHOUT_ATTRIBUTE']]],
|
||||
[3, [[attribute_helper_example::class, 'withattributes']]],
|
||||
[0, [[attribute_helper_example::class, 'withoutattributes']]],
|
||||
[3, [[attribute_helper_example::class, 'with_attributes']]],
|
||||
[0, [[attribute_helper_example::class, 'without_attributes']]],
|
||||
[3, [[attribute_helper_enum::class, 'WITH_ATTRIBUTES']]],
|
||||
[0, [[attribute_helper_enum::class, 'WITHOUT_ATTRIBUTE']]],
|
||||
[3, [__NAMESPACE__ . '\\attribute_helper_method_with']],
|
||||
[0, [__NAMESPACE__ . '\\attribute_helper_method_without']],
|
||||
[0, [__NAMESPACE__ . '\\function_not_exists']],
|
||||
|
||||
[3, [attribute_helper_example::class]],
|
||||
[0, [attribute_helper_example_without::class]],
|
||||
[3, [attribute_helper_example::class . '::WITH_ATTRIBUTES']],
|
||||
[0, [attribute_helper_example::class . '::WITHOUT_ATTRIBUTE']],
|
||||
[3, [attribute_helper_example::class . '::withattributes']],
|
||||
[0, [attribute_helper_example::class . '::withoutattributes']],
|
||||
[3, [attribute_helper_example::class . '::with_attributes']],
|
||||
[0, [attribute_helper_example::class . '::without_attributes']],
|
||||
[3, [attribute_helper_enum::class . '::WITH_ATTRIBUTES']],
|
||||
[0, [attribute_helper_enum::class . '::WITHOUT_ATTRIBUTE']],
|
||||
|
||||
[2, [[attribute_helper_example::class], attribute_helper_attribute_a::class]],
|
||||
[0, [[attribute_helper_example_without::class], attribute_helper_attribute_a::class]],
|
||||
[2, [[attribute_helper_example::class, 'WITH_ATTRIBUTES'], attribute_helper_attribute_a::class]],
|
||||
[0, [[attribute_helper_example::class, 'WITHOUT_ATTRIBUTE'], attribute_helper_attribute_a::class]],
|
||||
[2, [[attribute_helper_example::class, 'withattributes'], attribute_helper_attribute_a::class]],
|
||||
[0, [[attribute_helper_example::class, 'withoutattributes'], attribute_helper_attribute_a::class]],
|
||||
[2, [[attribute_helper_example::class, 'with_attributes'], attribute_helper_attribute_a::class]],
|
||||
[0, [[attribute_helper_example::class, 'without_attributes'], attribute_helper_attribute_a::class]],
|
||||
[2, [[attribute_helper_enum::class, 'WITH_ATTRIBUTES'], attribute_helper_attribute_a::class]],
|
||||
[0, [[attribute_helper_enum::class, 'WITHOUT_ATTRIBUTE'], attribute_helper_attribute_a::class]],
|
||||
[2, [__NAMESPACE__ . '\\attribute_helper_method_with', attribute_helper_attribute_a::class]],
|
||||
[0, [__NAMESPACE__ . '\\attribute_helper_method_without', attribute_helper_attribute_a::class]],
|
||||
];
|
||||
}
|
||||
|
||||
public function test_get_attributes_references(): void {
|
||||
$attributes = attribute_helper::from(new attribute_helper_example());
|
||||
$this->assertCount(3, $attributes);
|
||||
|
||||
$attributes = attribute_helper::from(
|
||||
new attribute_helper_example(),
|
||||
attribute_helper_attribute_a::class,
|
||||
);
|
||||
$this->assertCount(2, $attributes);
|
||||
|
||||
$attributes = attribute_helper::from(attribute_helper_enum::WITH_ATTRIBUTES);
|
||||
$this->assertCount(3, $attributes);
|
||||
$instances = attribute_helper::instances(attribute_helper_enum::WITH_ATTRIBUTES);
|
||||
$this->assertCount(3, $instances);
|
||||
|
||||
$attributes = attribute_helper::from(
|
||||
attribute_helper_enum::WITH_ATTRIBUTES,
|
||||
attribute_helper_attribute_a::class,
|
||||
);
|
||||
$this->assertCount(2, $attributes);
|
||||
$instances = attribute_helper::instances(
|
||||
attribute_helper_enum::WITH_ATTRIBUTES,
|
||||
attribute_helper_attribute_a::class,
|
||||
);
|
||||
$this->assertCount(2, $instances);
|
||||
array_map(
|
||||
fn($instance) => $this->assertInstanceOf(attribute_helper_attribute_a::class, $instance),
|
||||
$instances,
|
||||
);
|
||||
|
||||
// Singular fetches.
|
||||
$attribute = attribute_helper::one_from(
|
||||
attribute_helper_enum::WITH_ATTRIBUTES,
|
||||
attribute_helper_attribute_b::class,
|
||||
);
|
||||
$this->assertInstanceOf(\ReflectionAttribute::class, $attribute);
|
||||
$instance = attribute_helper::instance(
|
||||
attribute_helper_enum::WITH_ATTRIBUTES,
|
||||
attribute_helper_attribute_b::class,
|
||||
);
|
||||
$this->assertInstanceOf(attribute_helper_attribute_b::class, $instance);
|
||||
}
|
||||
|
||||
public function test_get_attributes_invalid(): void {
|
||||
$this->assertNull(attribute_helper::from(non_existent_class::class));
|
||||
$this->assertNull(attribute_helper::from([non_existent_class::class]));
|
||||
$this->assertNull(attribute_helper::from([attribute_helper_example::class, 'non_existent']));
|
||||
$this->assertNull(attribute_helper::from([non_existent_class::class, 'non_existent']));
|
||||
$this->assertNull(attribute_helper::instances(non_existent_class::class));
|
||||
$this->assertNull(attribute_helper::instances([non_existent_class::class]));
|
||||
$this->assertNull(attribute_helper::instances([attribute_helper_example::class, 'non_existent']));
|
||||
$this->assertNull(attribute_helper::instances([non_existent_class::class, 'non_existent']));
|
||||
|
||||
// Test singular fetches.
|
||||
$this->assertNull(attribute_helper::one_from(non_existent_class::class));
|
||||
$this->assertNull(attribute_helper::one_from([non_existent_class::class]));
|
||||
$this->assertNull(attribute_helper::one_from([attribute_helper_example::class, 'non_existent']));
|
||||
$this->assertNull(attribute_helper::one_from([non_existent_class::class, 'non_existent']));
|
||||
$this->assertNull(attribute_helper::instance(non_existent_class::class));
|
||||
$this->assertNull(attribute_helper::instance([non_existent_class::class]));
|
||||
$this->assertNull(attribute_helper::instance([attribute_helper_example::class, 'non_existent']));
|
||||
$this->assertNull(attribute_helper::instance([non_existent_class::class, 'non_existent']));
|
||||
}
|
||||
|
||||
public function test_get_attribute_too_many(): void {
|
||||
$this->expectException(\coding_exception::class);
|
||||
$this->expectExceptionMessage('More than one attribute found');
|
||||
attribute_helper::one_from(attribute_helper_example::class);
|
||||
}
|
||||
|
||||
public function test_get_instance_too_many(): void {
|
||||
$this->expectException(\coding_exception::class);
|
||||
$this->expectExceptionMessage('More than one attribute found');
|
||||
attribute_helper::instance(attribute_helper_example::class);
|
||||
}
|
||||
}
|
88
lib/tests/fixtures/attribute_helper_example.php
vendored
Normal file
88
lib/tests/fixtures/attribute_helper_example.php
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
<?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;
|
||||
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_ALL)]
|
||||
class attribute_helper_attribute_a {
|
||||
public function __construct(
|
||||
public readonly string $value,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
#[\Attribute]
|
||||
class attribute_helper_attribute_b {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for loading attributes.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2024 Andrew Lyons <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[attribute_helper_attribute_a('a')]
|
||||
#[attribute_helper_attribute_a('b')]
|
||||
#[attribute_helper_attribute_b]
|
||||
class attribute_helper_example {
|
||||
#[attribute_helper_attribute_a('a')]
|
||||
#[attribute_helper_attribute_a('b')]
|
||||
#[attribute_helper_attribute_b]
|
||||
public const WITH_ATTRIBUTES = 'examplevalue';
|
||||
|
||||
public const WITHOUT_ATTRIBUTE = 'examplevalue';
|
||||
|
||||
#[attribute_helper_attribute_a('a')]
|
||||
#[attribute_helper_attribute_a('b')]
|
||||
#[attribute_helper_attribute_b]
|
||||
public string $withattributes = 'With attributes';
|
||||
|
||||
public string $withoutattributes = 'Without attributes';
|
||||
|
||||
#[attribute_helper_attribute_a('a')]
|
||||
#[attribute_helper_attribute_a('b')]
|
||||
#[attribute_helper_attribute_b]
|
||||
public function with_attributes(): void {
|
||||
}
|
||||
|
||||
public function without_attributes(): void {
|
||||
}
|
||||
}
|
||||
|
||||
class attribute_helper_example_without {
|
||||
}
|
||||
|
||||
#[attribute_helper_attribute_a('a')]
|
||||
#[attribute_helper_attribute_a('b')]
|
||||
#[attribute_helper_attribute_b]
|
||||
enum attribute_helper_enum: string {
|
||||
#[attribute_helper_attribute_a('a')]
|
||||
#[attribute_helper_attribute_a('b')]
|
||||
#[attribute_helper_attribute_b]
|
||||
case WITH_ATTRIBUTES = 'With attributes';
|
||||
|
||||
case WITHOUT_ATTRIBUTE = 'Without attributes';
|
||||
}
|
||||
|
||||
#[attribute_helper_attribute_a('a')]
|
||||
#[attribute_helper_attribute_a('b')]
|
||||
#[attribute_helper_attribute_b]
|
||||
function attribute_helper_method_with(): void {
|
||||
}
|
||||
|
||||
function attribute_helper_method_without(): void {
|
||||
}
|
@ -66,6 +66,16 @@ information provided here is intended especially for developers.
|
||||
* Removed \zip_writer::sanitise_filepath and \zipwriter::sanitise_filename as they are now automatically sanitised in the zipstream.
|
||||
* Plugins implementing callback `bulk_user_actions()` should be aware that bulk user actions can be executed
|
||||
from /admin/user.php as well as from the bulk actions page. The 'returnurl' parameter will be passed in the request.
|
||||
* A new attribute helper has been created at \core\attribute_helper.
|
||||
This helper contains methods to fetch a single \ReflectionAttribute, or an array of \ReflectionAttribute for an item,
|
||||
or an instance, or a set of instances.
|
||||
* New generic attributes have been added for:
|
||||
- \core\attribute\label - An untranslated text label of an object
|
||||
- \core\attribute\tags - A set of tags for an object
|
||||
* The hook API now supports the use of \core\attribute\label and \core\attribute\tags
|
||||
as an alternative to implementing the \core\hook\described_hook interface.
|
||||
* The hook API now supports the use of the new \core\attribute\hook\replaces_callbacks() attribute
|
||||
as an alternative to implementing the \core\hook\deprecated_callback_replacement interface.
|
||||
|
||||
=== 4.3 ===
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user