290 lines
8.1 KiB
PHP
290 lines
8.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Grav\Plugin\Api;
|
|
|
|
use Grav\Common\Data\Blueprints;
|
|
use Grav\Common\Grav;
|
|
use Grav\Common\Language\Language;
|
|
use Grav\Common\Page\Interfaces\PageInterface;
|
|
use Grav\Common\Page\Pages;
|
|
use Grav\Common\User\Interfaces\UserInterface;
|
|
|
|
/**
|
|
* Lightweight admin proxy registered as $grav['admin'] during API requests.
|
|
*
|
|
* Grav core checks `isset($grav['admin'])` in multiple places to alter
|
|
* behavior: page routing/visibility, Flex authorization scope, blueprint
|
|
* field handling, and event firing. Without this proxy, API-driven changes
|
|
* operate in "site" scope rather than "admin" scope, causing subtle bugs:
|
|
*
|
|
* - Non-routable/hidden pages invisible to API (Pages.php:1047)
|
|
* - Flex onAdminSave/AfterSave events don't fire (FlexGravTrait.php:60)
|
|
* - Blueprint edit mode not set (Page.php:1261)
|
|
* - Flex authorization uses 'site' scope instead of 'admin' (FlexObject.php)
|
|
* - Plugins checking isAdmin() return false
|
|
*
|
|
* This class implements the minimum interface that Grav core actually calls
|
|
* on $grav['admin'], without pulling in the full admin plugin dependency.
|
|
*/
|
|
class AdminProxy
|
|
{
|
|
/** @var string Admin base route (not applicable for API, but required by getRouteDetails) */
|
|
public string $base = '';
|
|
|
|
/** @var string Current location segment */
|
|
public string $location = '';
|
|
|
|
/** @var string Current route */
|
|
public string $route = '';
|
|
|
|
/** @var UserInterface The authenticated API user */
|
|
public UserInterface $user;
|
|
|
|
/** @var bool Whether multilang is enabled */
|
|
public bool $multilang = false;
|
|
|
|
/** @var string Active language */
|
|
public string $language = '';
|
|
|
|
/** @var string[] Enabled languages */
|
|
public array $languages_enabled = [];
|
|
|
|
/** @var array<int, array{message: string, scope: string}> Queued temp messages */
|
|
public array $temp_messages = [];
|
|
|
|
private Grav $grav;
|
|
private ?Blueprints $blueprintsLoader = null;
|
|
|
|
/** @var array<string, PageInterface|null> Page cache */
|
|
private array $pages = [];
|
|
|
|
public function __construct(Grav $grav, UserInterface $user)
|
|
{
|
|
$this->grav = $grav;
|
|
$this->user = $user;
|
|
|
|
/** @var Language $language */
|
|
$language = $grav['language'];
|
|
$this->multilang = $language->enabled();
|
|
if ($this->multilang) {
|
|
$this->language = $language->getActive() ?? '';
|
|
$this->languages_enabled = (array) $grav['config']->get('system.languages.supported', []);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register this proxy as $grav['admin'].
|
|
*/
|
|
public function register(): void
|
|
{
|
|
$this->grav['admin'] = $this;
|
|
}
|
|
|
|
/**
|
|
* Get the current admin page (used by Pages.php and Plugin.php).
|
|
*
|
|
* In API context there's no "current admin page" being edited, so this
|
|
* returns the page at the current route if set, or null.
|
|
*/
|
|
public function page($route = false, $path = null): ?PageInterface
|
|
{
|
|
if (!$path) {
|
|
$path = $this->route;
|
|
}
|
|
if ($route && !$path) {
|
|
$path = '/';
|
|
}
|
|
if (!$path) {
|
|
return null;
|
|
}
|
|
|
|
if (!isset($this->pages[$path])) {
|
|
$this->pages[$path] = $this->getPage($path);
|
|
}
|
|
|
|
return $this->pages[$path];
|
|
}
|
|
|
|
/**
|
|
* Find a page by path (used by Pages.php for parent resolution).
|
|
*/
|
|
public function getPage(string $path): ?PageInterface
|
|
{
|
|
$pages = self::enablePages();
|
|
|
|
if ($path && $path[0] !== '/') {
|
|
$path = "/{$path}";
|
|
}
|
|
|
|
$path = urldecode($path);
|
|
|
|
return $path ? $pages->find($path, true) : $pages->root();
|
|
}
|
|
|
|
/**
|
|
* Return route details as [base, location, route] tuple.
|
|
*
|
|
* Used by Pages.php and AccountsServiceProvider.php to determine
|
|
* which admin section is active. For API requests, we return empty
|
|
* values since there's no admin page navigation happening.
|
|
*/
|
|
public function getRouteDetails(): array
|
|
{
|
|
return [$this->base, $this->location, $this->route];
|
|
}
|
|
|
|
/**
|
|
* Load a blueprint by type (used by Flex PageObject).
|
|
*/
|
|
public function blueprints(string $type)
|
|
{
|
|
if ($this->blueprintsLoader === null) {
|
|
$this->blueprintsLoader = new Blueprints('blueprints://');
|
|
}
|
|
|
|
return $this->blueprintsLoader->get($type);
|
|
}
|
|
|
|
/**
|
|
* Translate a string using Grav's language system.
|
|
*
|
|
* This is a static method in the real Admin class, but core calls it
|
|
* on the instance via $grav['admin']->translate().
|
|
*
|
|
* @param array|string $args
|
|
* @param array|string|null $languages
|
|
* @return string
|
|
*/
|
|
public static function translate($args, $languages = null): string
|
|
{
|
|
$grav = Grav::instance();
|
|
|
|
if (is_array($args)) {
|
|
$lookup = array_shift($args);
|
|
} else {
|
|
$lookup = $args;
|
|
$args = [];
|
|
}
|
|
|
|
if (!$languages) {
|
|
if ($grav['config']->get('system.languages.translations_fallback', true)) {
|
|
$languages = $grav['language']->getFallbackLanguages();
|
|
} else {
|
|
$languages = (array) $grav['language']->getDefault();
|
|
}
|
|
if (isset($grav['user']) && $grav['user']->authenticated) {
|
|
$languages = [$grav['user']->language];
|
|
}
|
|
} else {
|
|
$languages = (array) $languages;
|
|
}
|
|
|
|
foreach ((array) $languages as $lang) {
|
|
$translation = $grav['language']->getTranslation($lang, $lookup, true);
|
|
|
|
if (!$translation) {
|
|
$language = $grav['language']->getDefault() ?: 'en';
|
|
$translation = $grav['language']->getTranslation($language, $lookup, true);
|
|
}
|
|
|
|
if (!$translation) {
|
|
$translation = $grav['language']->getTranslation('en', $lookup, true);
|
|
}
|
|
|
|
if ($translation) {
|
|
if (count($args) >= 1) {
|
|
return vsprintf($translation, $args);
|
|
}
|
|
return $translation;
|
|
}
|
|
}
|
|
|
|
return $lookup;
|
|
}
|
|
|
|
/**
|
|
* Add a flash message to the session queue.
|
|
*
|
|
* Mirrors Admin::setMessage(). Admin-aware plugins routinely call this from
|
|
* onAdminSave/onAdminAfterSave handlers (e.g. to report generated image
|
|
* derivatives). The core `messages` service always resolves — returning a
|
|
* transient Messages instance when there's no active session — so queuing
|
|
* here is harmless under the API and simply discarded after the request.
|
|
*
|
|
* @param string $msg
|
|
* @param string $type
|
|
* @return void
|
|
*/
|
|
public function setMessage($msg, $type = 'info'): void
|
|
{
|
|
$messages = $this->grav['messages'];
|
|
$messages->add($msg, $type);
|
|
}
|
|
|
|
/**
|
|
* Fetch and clear messages from the session queue.
|
|
*
|
|
* Mirrors Admin::messages().
|
|
*
|
|
* @param string|null $type
|
|
* @return array
|
|
*/
|
|
public function messages($type = null): array
|
|
{
|
|
$messages = $this->grav['messages'];
|
|
|
|
return $messages->fetch($type);
|
|
}
|
|
|
|
/**
|
|
* Queue a temporary message.
|
|
*
|
|
* Mirrors Admin::addTempMessage().
|
|
*
|
|
* @param string $msg
|
|
* @param string $type
|
|
* @return void
|
|
*/
|
|
public function addTempMessage($msg, $type): void
|
|
{
|
|
$this->temp_messages[] = ['message' => $msg, 'scope' => $type];
|
|
}
|
|
|
|
/**
|
|
* Return queued temporary messages.
|
|
*
|
|
* Mirrors Admin::getTempMessages().
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getTempMessages(): array
|
|
{
|
|
return $this->temp_messages;
|
|
}
|
|
|
|
/**
|
|
* Enable and return the Pages service.
|
|
*
|
|
* Mirrors Admin::enablePages() — ensures pages are initialized
|
|
* (they are disabled by default during API requests for performance).
|
|
*/
|
|
public static function enablePages(): Pages
|
|
{
|
|
static $pages;
|
|
|
|
if ($pages) {
|
|
return $pages;
|
|
}
|
|
|
|
$grav = Grav::instance();
|
|
|
|
/** @var Pages $pages */
|
|
$pages = $grav['pages'];
|
|
$pages->enablePages();
|
|
|
|
return $pages;
|
|
}
|
|
}
|