<?php
/**
* @package Grav\Framework\Acl
*
* @copyright Copyright (c) 2015 - 2025 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Acl;
use Grav\Common\File\CompiledYamlFile;
use RuntimeException;
use stdClass;
use function is_array;
/**
* Class PermissionsReader
* @package Grav\Framework\Acl
*/
class PermissionsReader
{
/** @var array */
protected static $types;
/**
* @param string $filename
* @return Action[]
*/
public static function fromYaml(string $filename): array
{
$content = CompiledYamlFile::instance($filename)->content();
$actions = $content['actions'] ?? [];
$types = $content['types'] ?? [];
return static::fromArray($actions, $types);
}
/**
* @param array $actions
* @param array $types
* @return Action[]
*/
public static function fromArray(array $actions, array $types): array
{
static::initTypes($types);
$list = [];
foreach (static::read($actions) as $type => $data) {
$list[$type] = new Action($type, $data);
}
return $list;
}
/**
* @param array $actions
* @param string $prefix
* @return array
*/
public static function read(array $actions, string $prefix = ''): array
{
$list = [];
foreach ($actions as $name => $action) {
$prefixName = $prefix . $name;
$list[$prefixName] = null;
// Support nested sets of actions.
if (isset($action['actions']) && is_array($action['actions'])) {
$innerList = static::read($action['actions'], "{$prefixName}.");
$list += $innerList;
}
unset($action['actions']);
// Add defaults if they exist.
$action = static::addDefaults($action);
// Build flat list of actions.
$list[$prefixName] = $action;
}
return $list;
}
/**
* @param array $types
* @return void
*/
protected static function initTypes(array $types)
{
static::$types = [];
$dependencies = [];
foreach ($types as $type => $defaults) {
$current = array_fill_keys((array)($defaults['use'] ?? null), null);
$defType = $defaults['type'] ?? $type;
if ($type !== $defType) {
$current[$defaults['type']] = null;
}
$dependencies[$type] = (object)$current;
}
// Build dependency tree.
foreach ($dependencies as $type => $dep) {
foreach (get_object_vars($dep) as $k => &$val) {
if (null === $val) {
$val = $dependencies[$k] ?? new stdClass();
}
}
unset($val);
}
$encoded = json_encode($dependencies);
if ($encoded === false) {
throw new RuntimeException('json_encode(): failed to encode dependencies');
}
$dependencies = json_decode($encoded, true);
foreach (static::getDependencies($dependencies) as $type) {
$defaults = $types[$type] ?? null;
if ($defaults) {
static::$types[$type] = static::addDefaults($defaults);
}
}
}
/**
* @param array $dependencies
* @return array
*/
protected static function getDependencies(array $dependencies): array
{
$list = [[]];
foreach ($dependencies as $name => $deps) {
$current = $deps ? static::getDependencies($deps) : [];
$current[] = $name;
$list[] = $current;
}
return array_unique(array_merge(...$list));
}
/**
* @param array $action
* @return array
*/
protected static function addDefaults(array $action): array
{
$scopes = [];
// Add used properties.
$use = (array)($action['use'] ?? null);
foreach ($use as $type) {
if (isset(static::$types[$type])) {
$used = static::$types[$type];
unset($used['type']);
$scopes[] = $used;
}
}
unset($action['use']);
// Add type defaults.
$type = $action['type'] ?? 'default';
$defaults = static::$types[$type] ?? null;
if (is_array($defaults)) {
$scopes[] = $defaults;
}
if ($scopes) {
$scopes[] = $action;
$action = array_replace_recursive(...$scopes);
$newType = $defaults['type'] ?? null;
if ($newType && $newType !== $type) {
$action['type'] = $newType;
}
}
return $action;
}
}