mirror of
https://git.friendi.ca/friendica/friendica.git
synced 2025-06-08 00:14:32 +02:00
Merge 8083fb6f8e
into fce04bfa5c
This commit is contained in:
commit
3417d2bbc7
12 changed files with 1095 additions and 228 deletions
|
@ -12,6 +12,8 @@ use Friendica\Util\Strings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some functions to handle addons
|
* Some functions to handle addons
|
||||||
|
*
|
||||||
|
* @deprecated 2025.02 Use implementation of `Friendica\Core\Addon\AddonHelper` instead
|
||||||
*/
|
*/
|
||||||
class Addon
|
class Addon
|
||||||
{
|
{
|
||||||
|
@ -43,6 +45,8 @@ class Addon
|
||||||
*/
|
*/
|
||||||
public static function getAvailableList(): array
|
public static function getAvailableList(): array
|
||||||
{
|
{
|
||||||
|
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
$addons = [];
|
$addons = [];
|
||||||
$files = glob('addon/*/');
|
$files = glob('addon/*/');
|
||||||
if (is_array($files)) {
|
if (is_array($files)) {
|
||||||
|
@ -75,6 +79,8 @@ class Addon
|
||||||
*/
|
*/
|
||||||
public static function getAdminList(): array
|
public static function getAdminList(): array
|
||||||
{
|
{
|
||||||
|
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
$addons_admin = [];
|
$addons_admin = [];
|
||||||
$addons = array_filter(DI::config()->get('addons') ?? []);
|
$addons = array_filter(DI::config()->get('addons') ?? []);
|
||||||
|
|
||||||
|
@ -109,6 +115,8 @@ class Addon
|
||||||
*/
|
*/
|
||||||
public static function loadAddons()
|
public static function loadAddons()
|
||||||
{
|
{
|
||||||
|
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
self::$addons = array_keys(array_filter(DI::config()->get('addons') ?? []));
|
self::$addons = array_keys(array_filter(DI::config()->get('addons') ?? []));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +131,8 @@ class Addon
|
||||||
*/
|
*/
|
||||||
public static function uninstall(string $addon)
|
public static function uninstall(string $addon)
|
||||||
{
|
{
|
||||||
|
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
$addon = Strings::sanitizeFilePathItem($addon);
|
$addon = Strings::sanitizeFilePathItem($addon);
|
||||||
|
|
||||||
DI::logger()->debug("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addon]);
|
DI::logger()->debug("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addon]);
|
||||||
|
@ -151,6 +161,8 @@ class Addon
|
||||||
*/
|
*/
|
||||||
public static function install(string $addon): bool
|
public static function install(string $addon): bool
|
||||||
{
|
{
|
||||||
|
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
$addon = Strings::sanitizeFilePathItem($addon);
|
$addon = Strings::sanitizeFilePathItem($addon);
|
||||||
|
|
||||||
$addon_file_path = 'addon/' . $addon . '/' . $addon . '.php';
|
$addon_file_path = 'addon/' . $addon . '/' . $addon . '.php';
|
||||||
|
@ -191,6 +203,8 @@ class Addon
|
||||||
*/
|
*/
|
||||||
public static function reload()
|
public static function reload()
|
||||||
{
|
{
|
||||||
|
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
$addons = array_filter(DI::config()->get('addons') ?? []);
|
$addons = array_filter(DI::config()->get('addons') ?? []);
|
||||||
|
|
||||||
foreach ($addons as $name => $data) {
|
foreach ($addons as $name => $data) {
|
||||||
|
@ -230,6 +244,8 @@ class Addon
|
||||||
*/
|
*/
|
||||||
public static function getInfo(string $addon): array
|
public static function getInfo(string $addon): array
|
||||||
{
|
{
|
||||||
|
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
$addon = Strings::sanitizeFilePathItem($addon);
|
$addon = Strings::sanitizeFilePathItem($addon);
|
||||||
|
|
||||||
$info = [
|
$info = [
|
||||||
|
@ -291,6 +307,8 @@ class Addon
|
||||||
*/
|
*/
|
||||||
public static function isEnabled(string $addon): bool
|
public static function isEnabled(string $addon): bool
|
||||||
{
|
{
|
||||||
|
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
return in_array($addon, self::$addons);
|
return in_array($addon, self::$addons);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,6 +321,8 @@ class Addon
|
||||||
*/
|
*/
|
||||||
public static function getEnabledList(): array
|
public static function getEnabledList(): array
|
||||||
{
|
{
|
||||||
|
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
return self::$addons;
|
return self::$addons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,6 +336,8 @@ class Addon
|
||||||
*/
|
*/
|
||||||
public static function getVisibleList(): array
|
public static function getVisibleList(): array
|
||||||
{
|
{
|
||||||
|
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
$visible_addons = [];
|
$visible_addons = [];
|
||||||
$addons = array_filter(DI::config()->get('addons') ?? []);
|
$addons = array_filter(DI::config()->get('addons') ?? []);
|
||||||
|
|
||||||
|
|
|
@ -55,12 +55,14 @@ interface AddonHelper
|
||||||
public function loadAddons(): void;
|
public function loadAddons(): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reload (uninstall and install) all updated addons.
|
* Reload (uninstall and install) all installed and modified addons.
|
||||||
*/
|
*/
|
||||||
public function reloadAddons(): void;
|
public function reloadAddons(): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the comment block of an addon as value object.
|
* Get the comment block of an addon as value object.
|
||||||
|
*
|
||||||
|
* @throws \Friendica\Core\Addon\Exception\InvalidAddonException if there is an error with the addon file
|
||||||
*/
|
*/
|
||||||
public function getAddonInfo(string $addonId): AddonInfo;
|
public function getAddonInfo(string $addonId): AddonInfo;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,74 @@ namespace Friendica\Core\Addon;
|
||||||
*/
|
*/
|
||||||
final class AddonInfo
|
final class AddonInfo
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Parse addon comment in search of addon infos.
|
||||||
|
*
|
||||||
|
* like
|
||||||
|
* \code
|
||||||
|
* * Name: addon
|
||||||
|
* * Description: An addon which plugs in
|
||||||
|
* . * Version: 1.2.3
|
||||||
|
* * Author: John <profile url>
|
||||||
|
* * Author: Jane <email>
|
||||||
|
* * Maintainer: Jess without link
|
||||||
|
* * Maintainer: Robin <email>
|
||||||
|
* * Status: in development
|
||||||
|
* \endcode
|
||||||
|
*
|
||||||
|
* @internal Never create this object by yourself, use `Friendica\Core\Addon\AddonHelper::getAddonInfo()` instead.
|
||||||
|
* @see Friendica\Core\Addon\AddonHelper::getAddonInfo()
|
||||||
|
*
|
||||||
|
* @param string $addonId the name of the addon
|
||||||
|
* @param string $raw The raw file content
|
||||||
|
*/
|
||||||
|
public static function fromString(string $addonId, string $raw): self
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'id' => $addonId,
|
||||||
|
];
|
||||||
|
|
||||||
|
$ll = explode("\n", $raw);
|
||||||
|
|
||||||
|
foreach ($ll as $l) {
|
||||||
|
$l = trim($l, "\t\n\r */");
|
||||||
|
if ($l !== '') {
|
||||||
|
$addon_info = array_map('trim', explode(":", $l, 2));
|
||||||
|
if (count($addon_info) < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($type, $v) = $addon_info;
|
||||||
|
$type = strtolower($type);
|
||||||
|
|
||||||
|
if ($type === 'author' || $type === 'maintainer') {
|
||||||
|
$r = preg_match("|([^<]+)<([^>]+)>|", $v, $m);
|
||||||
|
if ($r === false || $r === 0) {
|
||||||
|
$data[$type][] = ['name' => trim($v)];
|
||||||
|
} else {
|
||||||
|
$data[$type][] = ['name' => trim($m[1]), 'link' => $m[2]];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$data[$type] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename author to authors
|
||||||
|
if (array_key_exists('author', $data)) {
|
||||||
|
$data['authors'] = $data['author'];
|
||||||
|
unset($data['author']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename maintainer to maintainers
|
||||||
|
if (array_key_exists('maintainer', $data)) {
|
||||||
|
$data['maintainers'] = $data['maintainer'];
|
||||||
|
unset($data['maintainer']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::fromArray($data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal Never create this object by yourself, use `Friendica\Core\Addon\AddonHelper::getAddonInfo()` instead.
|
* @internal Never create this object by yourself, use `Friendica\Core\Addon\AddonHelper::getAddonInfo()` instead.
|
||||||
*
|
*
|
||||||
|
@ -21,25 +89,21 @@ final class AddonInfo
|
||||||
*/
|
*/
|
||||||
public static function fromArray(array $info): self
|
public static function fromArray(array $info): self
|
||||||
{
|
{
|
||||||
$id = array_key_exists('id', $info) ? (string) $info['id'] : '';
|
$addonInfo = new self();
|
||||||
$name = array_key_exists('name', $info) ? (string) $info['name'] : '';
|
$addonInfo->id = array_key_exists('id', $info) ? (string) $info['id'] : '';
|
||||||
$description = array_key_exists('description', $info) ? (string) $info['description'] : '';
|
$addonInfo->name = array_key_exists('name', $info) ? (string) $info['name'] : '';
|
||||||
$authors = array_key_exists('authors', $info) ? self::parseContributors($info['authors']) : [];
|
$addonInfo->description = array_key_exists('description', $info) ? (string) $info['description'] : '';
|
||||||
$maintainers = array_key_exists('maintainers', $info) ? self::parseContributors($info['maintainers']) : [];
|
$addonInfo->authors = array_key_exists('authors', $info) ? self::parseContributors($info['authors']) : [];
|
||||||
$version = array_key_exists('version', $info) ? (string) $info['version'] : '';
|
$addonInfo->maintainers = array_key_exists('maintainers', $info) ? self::parseContributors($info['maintainers']) : [];
|
||||||
$status = array_key_exists('status', $info) ? (string) $info['status'] : '';
|
$addonInfo->version = array_key_exists('version', $info) ? (string) $info['version'] : '';
|
||||||
|
$addonInfo->status = array_key_exists('status', $info) ? (string) $info['status'] : '';
|
||||||
|
|
||||||
return new self(
|
return $addonInfo;
|
||||||
$id,
|
|
||||||
$name,
|
|
||||||
$description,
|
|
||||||
$authors,
|
|
||||||
$maintainers,
|
|
||||||
$version,
|
|
||||||
$status
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $entries
|
||||||
|
*/
|
||||||
private static function parseContributors($entries): array
|
private static function parseContributors($entries): array
|
||||||
{
|
{
|
||||||
if (!is_array($entries)) {
|
if (!is_array($entries)) {
|
||||||
|
@ -85,22 +149,8 @@ final class AddonInfo
|
||||||
|
|
||||||
private string $status = '';
|
private string $status = '';
|
||||||
|
|
||||||
private function __construct(
|
private function __construct()
|
||||||
string $id,
|
{
|
||||||
string $name,
|
|
||||||
string $description,
|
|
||||||
array $authors,
|
|
||||||
array $maintainers,
|
|
||||||
string $version,
|
|
||||||
string $status
|
|
||||||
) {
|
|
||||||
$this->id = $id;
|
|
||||||
$this->name = $name;
|
|
||||||
$this->description = $description;
|
|
||||||
$this->authors = $authors;
|
|
||||||
$this->maintainers = $maintainers;
|
|
||||||
$this->version = $version;
|
|
||||||
$this->status = $status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): string
|
public function getId(): string
|
||||||
|
|
328
src/Core/Addon/AddonManagerHelper.php
Normal file
328
src/Core/Addon/AddonManagerHelper.php
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Copyright (C) 2010-2024, the Friendica project
|
||||||
|
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Friendica\Core\Addon;
|
||||||
|
|
||||||
|
use Friendica\Core\Addon\Exception\InvalidAddonException;
|
||||||
|
use Friendica\Core\Cache\Capability\ICanCache;
|
||||||
|
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||||
|
use Friendica\Database\Database;
|
||||||
|
use Friendica\Util\Profiler;
|
||||||
|
use Friendica\Util\Strings;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper functions to handle addons
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class AddonManagerHelper implements AddonHelper
|
||||||
|
{
|
||||||
|
private string $addonPath;
|
||||||
|
|
||||||
|
private Database $database;
|
||||||
|
|
||||||
|
private IManageConfigValues $config;
|
||||||
|
|
||||||
|
private ICanCache $cache;
|
||||||
|
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
|
private Profiler $profiler;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
private array $addons = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $addonPath,
|
||||||
|
Database $database,
|
||||||
|
IManageConfigValues $config,
|
||||||
|
ICanCache $cache,
|
||||||
|
LoggerInterface $logger,
|
||||||
|
Profiler $profiler
|
||||||
|
) {
|
||||||
|
$this->addonPath = $addonPath;
|
||||||
|
$this->database = $database;
|
||||||
|
$this->config = $config;
|
||||||
|
$this->cache = $cache;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->profiler = $profiler;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the absolute path to the addon folder
|
||||||
|
*
|
||||||
|
* e.g. `/var/www/html/addon`
|
||||||
|
*/
|
||||||
|
public function getAddonPath(): string
|
||||||
|
{
|
||||||
|
return $this->addonPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of available addons.
|
||||||
|
*
|
||||||
|
* This list is made from scanning the addon/ folder.
|
||||||
|
* Unsupported addons are excluded unless they already are enabled or system.show_unsupported_addon is set.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getAvailableAddons(): array
|
||||||
|
{
|
||||||
|
$dirs = scandir($this->getAddonPath());
|
||||||
|
|
||||||
|
if (!is_array($dirs)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = [];
|
||||||
|
|
||||||
|
foreach ($dirs as $dirname) {
|
||||||
|
// ignore hidden files and folders
|
||||||
|
// @TODO: Replace with str_starts_with() when PHP 8.0 is the minimum version
|
||||||
|
if (strncmp($dirname, '.', 1) === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir($this->getAddonPath() . '/' . $dirname)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files[] = $dirname;
|
||||||
|
}
|
||||||
|
|
||||||
|
$addons = [];
|
||||||
|
|
||||||
|
foreach ($files as $addonId) {
|
||||||
|
try {
|
||||||
|
$addonInfo = $this->getAddonInfo($addonId);
|
||||||
|
} catch (InvalidAddonException $th) {
|
||||||
|
$this->logger->error('Invalid addon found: ' . $addonId, ['exception' => $th]);
|
||||||
|
|
||||||
|
// skip invalid addons
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
$this->config->get('system', 'show_unsupported_addons')
|
||||||
|
|| strtolower($addonInfo->getStatus()) !== 'unsupported'
|
||||||
|
|| $this->isAddonEnabled($addonId)
|
||||||
|
) {
|
||||||
|
$addons[] = $addonId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $addons;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs an addon.
|
||||||
|
*
|
||||||
|
* @param string $addonId name of the addon
|
||||||
|
*
|
||||||
|
* @return bool true on success or false on failure
|
||||||
|
*/
|
||||||
|
public function installAddon(string $addonId): bool
|
||||||
|
{
|
||||||
|
$addonId = Strings::sanitizeFilePathItem($addonId);
|
||||||
|
|
||||||
|
$addon_file_path = $this->getAddonPath() . '/' . $addonId . '/' . $addonId . '.php';
|
||||||
|
|
||||||
|
// silently fail if addon was removed or if $addonId is funky
|
||||||
|
if (!file_exists($addon_file_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->debug("Addon {addon}: {action}", ['action' => 'install', 'addon' => $addonId]);
|
||||||
|
|
||||||
|
$timestamp = @filemtime($addon_file_path);
|
||||||
|
|
||||||
|
@include_once($addon_file_path);
|
||||||
|
|
||||||
|
if (function_exists($addonId . '_install')) {
|
||||||
|
$func = $addonId . '_install';
|
||||||
|
$func();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->config->set('addons', $addonId, [
|
||||||
|
'last_update' => $timestamp,
|
||||||
|
'admin' => function_exists($addonId . '_addon_admin'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$this->isAddonEnabled($addonId)) {
|
||||||
|
$this->addons[] = $addonId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls an addon.
|
||||||
|
*
|
||||||
|
* @param string $addonId name of the addon
|
||||||
|
*/
|
||||||
|
public function uninstallAddon(string $addonId): void
|
||||||
|
{
|
||||||
|
$addonId = Strings::sanitizeFilePathItem($addonId);
|
||||||
|
|
||||||
|
$this->logger->debug("Addon {addon}: {action}", ['action' => 'uninstall', 'addon' => $addonId]);
|
||||||
|
$this->config->delete('addons', $addonId);
|
||||||
|
|
||||||
|
$addon_file_path = $this->getAddonPath() . '/' . $addonId . '/' . $addonId . '.php';
|
||||||
|
|
||||||
|
@include_once($addon_file_path);
|
||||||
|
|
||||||
|
if (function_exists($addonId . '_uninstall')) {
|
||||||
|
$func = $addonId . '_uninstall';
|
||||||
|
$func();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove registered hooks for the addon
|
||||||
|
// Handles both relative and absolute file paths
|
||||||
|
$condition = ['`file` LIKE ?', "%/$addonId/$addonId.php"];
|
||||||
|
|
||||||
|
$result = $this->database->delete('hook', $condition);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
$this->cache->delete('routerDispatchData');
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this->addons[array_search($addonId, $this->addons)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load addons.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function loadAddons(): void
|
||||||
|
{
|
||||||
|
$this->addons = array_keys(array_filter($this->config->get('addons') ?? []));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload (uninstall and install) all installed and modified addons.
|
||||||
|
*/
|
||||||
|
public function reloadAddons(): void
|
||||||
|
{
|
||||||
|
$addons = array_filter($this->config->get('addons') ?? []);
|
||||||
|
|
||||||
|
foreach ($addons as $addonName => $data) {
|
||||||
|
$addonId = Strings::sanitizeFilePathItem(trim($addonName));
|
||||||
|
|
||||||
|
$addon_file_path = $this->getAddonPath() . '/' . $addonId . '/' . $addonId . '.php';
|
||||||
|
|
||||||
|
if (!file_exists($addon_file_path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('last_update', $data) && intval($data['last_update']) === filemtime($addon_file_path)) {
|
||||||
|
// Addon unmodified, skipping
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->debug("Addon {addon}: {action}", ['action' => 'reload', 'addon' => $addonId]);
|
||||||
|
|
||||||
|
$this->uninstallAddon($addonId);
|
||||||
|
$this->installAddon($addonId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the comment block of an addon as value object.
|
||||||
|
*
|
||||||
|
* @throws \Friendica\Core\Addon\Exception\InvalidAddonException if there is an error with the addon file
|
||||||
|
*/
|
||||||
|
public function getAddonInfo(string $addonId): AddonInfo
|
||||||
|
{
|
||||||
|
$default = [
|
||||||
|
'id' => $addonId,
|
||||||
|
'name' => $addonId,
|
||||||
|
];
|
||||||
|
|
||||||
|
$addonFile = $this->getAddonPath() . "/$addonId/$addonId.php";
|
||||||
|
|
||||||
|
if (!is_file($addonFile)) {
|
||||||
|
return AddonInfo::fromArray($default);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->profiler->startRecording('file');
|
||||||
|
|
||||||
|
$raw = file_get_contents($addonFile);
|
||||||
|
|
||||||
|
$this->profiler->stopRecording();
|
||||||
|
|
||||||
|
if ($raw === false) {
|
||||||
|
throw new InvalidAddonException('Could not read addon file: ' . $addonFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = preg_match("|/\*.*\*/|msU", $raw, $matches);
|
||||||
|
|
||||||
|
if ($result === false || $result === 0 || !is_array($matches) || count($matches) < 1) {
|
||||||
|
throw new InvalidAddonException('Could not find valid comment block in addon file: ' . $addonFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddonInfo::fromString($addonId, $matches[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the provided addon is enabled
|
||||||
|
*/
|
||||||
|
public function isAddonEnabled(string $addonId): bool
|
||||||
|
{
|
||||||
|
return in_array($addonId, $this->addons);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list with the IDs of the enabled addons
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getEnabledAddons(): array
|
||||||
|
{
|
||||||
|
return $this->addons;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list with the IDs of the non-hidden enabled addons
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getVisibleEnabledAddons(): array
|
||||||
|
{
|
||||||
|
$visible_addons = [];
|
||||||
|
$addons = array_filter($this->config->get('addons') ?? []);
|
||||||
|
|
||||||
|
foreach ($addons as $name => $data) {
|
||||||
|
$visible_addons[] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $visible_addons;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list with the IDs of the enabled addons that provides admin settings.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getEnabledAddonsWithAdminSettings(): array
|
||||||
|
{
|
||||||
|
$addons_admin = [];
|
||||||
|
$addons = array_filter($this->config->get('addons') ?? []);
|
||||||
|
|
||||||
|
ksort($addons);
|
||||||
|
|
||||||
|
foreach ($addons as $name => $data) {
|
||||||
|
if (array_key_exists('admin', $data) && $data['admin'] === true) {
|
||||||
|
$addons_admin[] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $addons_admin;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,154 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
// Copyright (C) 2010-2024, the Friendica project
|
|
||||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Friendica\Core\Addon;
|
|
||||||
|
|
||||||
use Friendica\Core\Addon;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Proxy to the Addon class
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
final class AddonProxy implements AddonHelper
|
|
||||||
{
|
|
||||||
private string $addonPath;
|
|
||||||
|
|
||||||
public function __construct(string $addonPath)
|
|
||||||
{
|
|
||||||
$this->addonPath = $addonPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the absolute path to the addon folder
|
|
||||||
*
|
|
||||||
* e.g. `/var/www/html/addon`
|
|
||||||
*/
|
|
||||||
public function getAddonPath(): string
|
|
||||||
{
|
|
||||||
return $this->addonPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of available addons.
|
|
||||||
*
|
|
||||||
* This list is made from scanning the addon/ folder.
|
|
||||||
* Unsupported addons are excluded unless they already are enabled or system.show_unsupported_addon is set.
|
|
||||||
*
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getAvailableAddons(): array
|
|
||||||
{
|
|
||||||
return array_map(
|
|
||||||
function (array $item) {
|
|
||||||
return $item[0];
|
|
||||||
},
|
|
||||||
Addon::getAvailableList()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs an addon.
|
|
||||||
*
|
|
||||||
* @param string $addonId name of the addon
|
|
||||||
*
|
|
||||||
* @return bool true on success or false on failure
|
|
||||||
*/
|
|
||||||
public function installAddon(string $addonId): bool
|
|
||||||
{
|
|
||||||
return Addon::install($addonId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstalls an addon.
|
|
||||||
*
|
|
||||||
* @param string $addonId name of the addon
|
|
||||||
*/
|
|
||||||
public function uninstallAddon(string $addonId): void
|
|
||||||
{
|
|
||||||
Addon::uninstall($addonId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load addons.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public function loadAddons(): void
|
|
||||||
{
|
|
||||||
Addon::loadAddons();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reload (uninstall and install) all updated addons.
|
|
||||||
*/
|
|
||||||
public function reloadAddons(): void
|
|
||||||
{
|
|
||||||
Addon::reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the comment block of an addon as value object.
|
|
||||||
*/
|
|
||||||
public function getAddonInfo(string $addonId): AddonInfo
|
|
||||||
{
|
|
||||||
$data = Addon::getInfo($addonId);
|
|
||||||
|
|
||||||
// add addon ID
|
|
||||||
$data['id'] = $addonId;
|
|
||||||
|
|
||||||
// rename author to authors
|
|
||||||
$data['authors'] = $data['author'];
|
|
||||||
unset($data['author']);
|
|
||||||
|
|
||||||
// rename maintainer to maintainers
|
|
||||||
$data['maintainers'] = $data['maintainer'];
|
|
||||||
unset($data['maintainer']);
|
|
||||||
|
|
||||||
return AddonInfo::fromArray($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the provided addon is enabled
|
|
||||||
*/
|
|
||||||
public function isAddonEnabled(string $addonId): bool
|
|
||||||
{
|
|
||||||
return Addon::isEnabled($addonId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list with the IDs of the enabled addons
|
|
||||||
*
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getEnabledAddons(): array
|
|
||||||
{
|
|
||||||
return Addon::getEnabledList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list with the IDs of the non-hidden enabled addons
|
|
||||||
*
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getVisibleEnabledAddons(): array
|
|
||||||
{
|
|
||||||
return Addon::getVisibleList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list with the IDs of the enabled addons that provides admin settings.
|
|
||||||
*
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function getEnabledAddonsWithAdminSettings(): array
|
|
||||||
{
|
|
||||||
return array_keys(Addon::getAdminList());
|
|
||||||
}
|
|
||||||
}
|
|
17
src/Core/Addon/Exception/InvalidAddonException.php
Normal file
17
src/Core/Addon/Exception/InvalidAddonException.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Copyright (C) 2010-2024, the Friendica project
|
||||||
|
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Friendica\Core\Addon\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception in case an addon is invalid
|
||||||
|
*/
|
||||||
|
final class InvalidAddonException extends \RuntimeException
|
||||||
|
{
|
||||||
|
}
|
|
@ -8,6 +8,8 @@
|
||||||
namespace Friendica\Module\Admin\Addons;
|
namespace Friendica\Module\Admin\Addons;
|
||||||
|
|
||||||
use Friendica\Content\Text\Markdown;
|
use Friendica\Content\Text\Markdown;
|
||||||
|
use Friendica\Core\Addon\AddonInfo;
|
||||||
|
use Friendica\Core\Addon\Exception\InvalidAddonException;
|
||||||
use Friendica\Core\Renderer;
|
use Friendica\Core\Renderer;
|
||||||
use Friendica\DI;
|
use Friendica\DI;
|
||||||
use Friendica\Model\Contact;
|
use Friendica\Model\Contact;
|
||||||
|
@ -45,6 +47,7 @@ class Details extends BaseAdmin
|
||||||
$addonHelper = DI::addonHelper();
|
$addonHelper = DI::addonHelper();
|
||||||
|
|
||||||
$addon = Strings::sanitizeFilePathItem($this->parameters['addon']);
|
$addon = Strings::sanitizeFilePathItem($this->parameters['addon']);
|
||||||
|
|
||||||
if (!is_file("addon/$addon/$addon.php")) {
|
if (!is_file("addon/$addon/$addon.php")) {
|
||||||
DI::sysmsg()->addNotice(DI::l10n()->t('Addon not found.'));
|
DI::sysmsg()->addNotice(DI::l10n()->t('Addon not found.'));
|
||||||
$addonHelper->uninstallAddon($addon);
|
$addonHelper->uninstallAddon($addon);
|
||||||
|
@ -91,12 +94,18 @@ class Details extends BaseAdmin
|
||||||
$func($admin_form);
|
$func($admin_form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
$addonInfo = $addonHelper->getAddonInfo($addon);
|
$addonInfo = $addonHelper->getAddonInfo($addon);
|
||||||
|
} catch (InvalidAddonException $th) {
|
||||||
|
$this->logger->error('Invalid addon found: ' . $addon, ['exception' => $th]);
|
||||||
|
DI::sysmsg()->addNotice(DI::l10n()->t('Invalid Addon found.'));
|
||||||
|
|
||||||
|
$addonInfo = AddonInfo::fromArray(['id' => $addon, 'name' => $addon]);
|
||||||
|
}
|
||||||
|
|
||||||
$addonAuthors = [];
|
$addonAuthors = [];
|
||||||
|
|
||||||
foreach ($addonInfo->getAuthors() as $addonAuthor) {
|
foreach ($addonInfo->getAuthors() as $addonAuthor) {
|
||||||
$addonAuthor['link'] = 'foo@bar.com';
|
|
||||||
if (array_key_exists('link', $addonAuthor) && empty(parse_url($addonAuthor['link'], PHP_URL_SCHEME))) {
|
if (array_key_exists('link', $addonAuthor) && empty(parse_url($addonAuthor['link'], PHP_URL_SCHEME))) {
|
||||||
$contact = Contact::getByURL($addonAuthor['link'], false);
|
$contact = Contact::getByURL($addonAuthor['link'], false);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
namespace Friendica\Module\Admin\Addons;
|
namespace Friendica\Module\Admin\Addons;
|
||||||
|
|
||||||
|
use Friendica\Core\Addon\Exception\InvalidAddonException;
|
||||||
use Friendica\Core\Renderer;
|
use Friendica\Core\Renderer;
|
||||||
use Friendica\DI;
|
use Friendica\DI;
|
||||||
use Friendica\Module\BaseAdmin;
|
use Friendica\Module\BaseAdmin;
|
||||||
|
@ -57,7 +58,12 @@ class Index extends BaseAdmin
|
||||||
$addons = [];
|
$addons = [];
|
||||||
|
|
||||||
foreach ($addonHelper->getAvailableAddons() as $addonId) {
|
foreach ($addonHelper->getAvailableAddons() as $addonId) {
|
||||||
|
try {
|
||||||
$addonInfo = $addonHelper->getAddonInfo($addonId);
|
$addonInfo = $addonHelper->getAddonInfo($addonId);
|
||||||
|
} catch (InvalidAddonException $th) {
|
||||||
|
$this->logger->error('Invalid addon found: ' . $addonId, ['exception' => $th]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$info = [
|
$info = [
|
||||||
'name' => $addonInfo->getName(),
|
'name' => $addonInfo->getName(),
|
||||||
|
|
|
@ -43,7 +43,7 @@ return (function(string $basepath, array $getVars, array $serverVars, array $coo
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
\Friendica\Core\Addon\AddonHelper::class => [
|
\Friendica\Core\Addon\AddonHelper::class => [
|
||||||
'instanceOf' => \Friendica\Core\Addon\AddonProxy::class,
|
'instanceOf' => \Friendica\Core\Addon\AddonManagerHelper::class,
|
||||||
'constructParams' => [
|
'constructParams' => [
|
||||||
$basepath . '/addon',
|
$basepath . '/addon',
|
||||||
],
|
],
|
||||||
|
|
|
@ -14,19 +14,111 @@ use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class AddonInfoTest extends TestCase
|
class AddonInfoTest extends TestCase
|
||||||
{
|
{
|
||||||
|
public function testFromStringCreatesObject(): void
|
||||||
|
{
|
||||||
|
$this->assertInstanceOf(AddonInfo::class, AddonInfo::fromString('addonId', ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getStringData(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'minimal' => [
|
||||||
|
'test',
|
||||||
|
'',
|
||||||
|
['id' => 'test'],
|
||||||
|
],
|
||||||
|
'without-author' => [
|
||||||
|
'test',
|
||||||
|
<<<TEXT
|
||||||
|
/**
|
||||||
|
* Name: Test Addon
|
||||||
|
* Description: adds awesome features to friendica
|
||||||
|
* Version: 100.4.50-beta.5
|
||||||
|
* Maintainer: Robin
|
||||||
|
* Status: beta
|
||||||
|
* Ignore: The "ignore" key is unsupported and will be ignored
|
||||||
|
*/
|
||||||
|
TEXT,
|
||||||
|
[
|
||||||
|
'id' => 'test',
|
||||||
|
'name' => 'Test Addon',
|
||||||
|
'description' => 'adds awesome features to friendica',
|
||||||
|
|
||||||
|
'maintainers' => [
|
||||||
|
['name' => 'Robin'],
|
||||||
|
],
|
||||||
|
'version' => '100.4.50-beta.5',
|
||||||
|
'status' => 'beta',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'without-maintainer' => [
|
||||||
|
'test',
|
||||||
|
<<<TEXT
|
||||||
|
/*
|
||||||
|
* Name: Test Addon
|
||||||
|
* Description: adds awesome features to friendica
|
||||||
|
* Version: 100.4.50-beta.5
|
||||||
|
* Author: Sam
|
||||||
|
* Status: beta
|
||||||
|
* Ignore: The "ignore" key is unsupported and will be ignored
|
||||||
|
*/
|
||||||
|
TEXT,
|
||||||
|
[
|
||||||
|
'id' => 'test',
|
||||||
|
'name' => 'Test Addon',
|
||||||
|
'description' => 'adds awesome features to friendica',
|
||||||
|
'authors' => [
|
||||||
|
['name' => 'Sam'],
|
||||||
|
],
|
||||||
|
'version' => '100.4.50-beta.5',
|
||||||
|
'status' => 'beta',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'complete' => [
|
||||||
|
'test',
|
||||||
|
<<<TEXT
|
||||||
|
/*
|
||||||
|
* Name: Test Addon
|
||||||
|
* Description: adds awesome features to friendica
|
||||||
|
* Version: 100.4.50-beta.5
|
||||||
|
* Author: Sam
|
||||||
|
* Author: Sam With Mail <mail@example.org>
|
||||||
|
* Maintainer: Robin
|
||||||
|
* Maintainer: Robin With Profile <https://example.org/profile/robin>
|
||||||
|
* Status: beta
|
||||||
|
* Ignore: The "ignore" key is unsupported and will be ignored
|
||||||
|
*/
|
||||||
|
TEXT,
|
||||||
|
[
|
||||||
|
'id' => 'test',
|
||||||
|
'name' => 'Test Addon',
|
||||||
|
'description' => 'adds awesome features to friendica',
|
||||||
|
'authors' => [
|
||||||
|
['name' => 'Sam'],
|
||||||
|
['name' => 'Sam With Mail', 'link' => 'mail@example.org'],
|
||||||
|
],
|
||||||
|
'maintainers' => [
|
||||||
|
['name' => 'Robin'],
|
||||||
|
['name' => 'Robin With Profile', 'link' => 'https://example.org/profile/robin'],
|
||||||
|
],
|
||||||
|
'version' => '100.4.50-beta.5',
|
||||||
|
'status' => 'beta',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getStringData
|
||||||
|
*/
|
||||||
|
public function testFromStringReturnsCorrectValues(string $addonId, string $raw, array $expected): void
|
||||||
|
{
|
||||||
|
$this->assertAddonInfoData($expected, AddonInfo::fromString($addonId, $raw));
|
||||||
|
}
|
||||||
|
|
||||||
public function testFromArrayCreatesObject(): void
|
public function testFromArrayCreatesObject(): void
|
||||||
{
|
{
|
||||||
$data = [
|
$this->assertInstanceOf(AddonInfo::class, AddonInfo::fromArray([]));
|
||||||
'id' => '',
|
|
||||||
'name' => '',
|
|
||||||
'description' => '',
|
|
||||||
'authors' => [],
|
|
||||||
'maintainers' => [],
|
|
||||||
'version' => '',
|
|
||||||
'status' => '',
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->assertInstanceOf(AddonInfo::class, AddonInfo::fromArray($data));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetterReturningCorrectValues(): void
|
public function testGetterReturningCorrectValues(): void
|
||||||
|
@ -41,15 +133,34 @@ class AddonInfoTest extends TestCase
|
||||||
'status' => 'In Development',
|
'status' => 'In Development',
|
||||||
];
|
];
|
||||||
|
|
||||||
$info = AddonInfo::fromArray($data);
|
$this->assertAddonInfoData($data, AddonInfo::fromArray($data));
|
||||||
|
}
|
||||||
|
|
||||||
$this->assertSame($data['id'], $info->getId());
|
private function assertAddonInfoData(array $expected, AddonInfo $info): void
|
||||||
$this->assertSame($data['name'], $info->getName());
|
{
|
||||||
$this->assertSame($data['description'], $info->getDescription());
|
$expected = array_merge(
|
||||||
$this->assertSame($data['description'], $info->getDescription());
|
[
|
||||||
$this->assertSame($data['authors'], $info->getAuthors());
|
'id' => '',
|
||||||
$this->assertSame($data['maintainers'], $info->getMaintainers());
|
'name' => '',
|
||||||
$this->assertSame($data['version'], $info->getVersion());
|
'description' => '',
|
||||||
$this->assertSame($data['status'], $info->getStatus());
|
'authors' => [],
|
||||||
|
'maintainers' => [],
|
||||||
|
'version' => '',
|
||||||
|
'status' => '',
|
||||||
|
],
|
||||||
|
$expected
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'id' => $info->getId(),
|
||||||
|
'name' => $info->getName(),
|
||||||
|
'description' => $info->getDescription(),
|
||||||
|
'authors' => $info->getAuthors(),
|
||||||
|
'maintainers' => $info->getMaintainers(),
|
||||||
|
'version' => $info->getVersion(),
|
||||||
|
'status' => $info->getStatus(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertSame($expected, $data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
472
tests/Unit/Core/Addon/AddonManagerHelperTest.php
Normal file
472
tests/Unit/Core/Addon/AddonManagerHelperTest.php
Normal file
|
@ -0,0 +1,472 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Copyright (C) 2010-2024, the Friendica project
|
||||||
|
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Friendica\Test\Unit\Core\Addon;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Friendica\Core\Addon\AddonInfo;
|
||||||
|
use Friendica\Core\Addon\AddonManagerHelper;
|
||||||
|
use Friendica\Core\Addon\Exception\InvalidAddonException;
|
||||||
|
use Friendica\Core\Cache\Capability\ICanCache;
|
||||||
|
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||||
|
use Friendica\Database\Database;
|
||||||
|
use Friendica\Util\Profiler;
|
||||||
|
use org\bovigo\vfs\vfsStream;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class AddonManagerHelperTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetAddonInfoReturnsAddonInfo(): void
|
||||||
|
{
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
'helloaddon' => [
|
||||||
|
'helloaddon.php' => <<<PHP
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Name: Hello Addon
|
||||||
|
* Description: For testing purpose only
|
||||||
|
* Version: 1.0
|
||||||
|
* Author: Artur Weigandt <dont-mail-me@example.com>
|
||||||
|
*/
|
||||||
|
PHP,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$this->createStub(IManageConfigValues::class),
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$info = $addonManagerHelper->getAddonInfo('helloaddon');
|
||||||
|
|
||||||
|
$this->assertInstanceOf(AddonInfo::class, $info);
|
||||||
|
|
||||||
|
$this->assertEquals('Hello Addon', $info->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAddonInfoThrowsInvalidAddonException(): void
|
||||||
|
{
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
'helloaddon' => [
|
||||||
|
'helloaddon.php' => <<<PHP
|
||||||
|
<?php
|
||||||
|
// This is not a valid addon comment section
|
||||||
|
PHP,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$this->createStub(IManageConfigValues::class),
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(InvalidAddonException::class);
|
||||||
|
$this->expectExceptionMessage('Could not find valid comment block in addon file:');
|
||||||
|
|
||||||
|
$addonManagerHelper->getAddonInfo('helloaddon');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEnabledAddons(): void
|
||||||
|
{
|
||||||
|
$config = $this->createStub(IManageConfigValues::class);
|
||||||
|
$config->method('get')->willReturn([
|
||||||
|
'helloaddon' => [
|
||||||
|
'last_update' => 1738760499,
|
||||||
|
'admin' => false,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, []);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$config,
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame([], $addonManagerHelper->getEnabledAddons());
|
||||||
|
$this->assertFalse($addonManagerHelper->isAddonEnabled('helloaddon'));
|
||||||
|
|
||||||
|
$addonManagerHelper->loadAddons();
|
||||||
|
|
||||||
|
$this->assertSame(['helloaddon'], $addonManagerHelper->getEnabledAddons());
|
||||||
|
$this->assertTrue($addonManagerHelper->isAddonEnabled('helloaddon'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetVisibleEnabledAddons(): void
|
||||||
|
{
|
||||||
|
$config = $this->createStub(IManageConfigValues::class);
|
||||||
|
$config->method('get')->willReturn([
|
||||||
|
'helloaddon' => [
|
||||||
|
'last_update' => 1738760499,
|
||||||
|
'admin' => false,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, []);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$config,
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(['helloaddon'], $addonManagerHelper->getVisibleEnabledAddons());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetEnabledAddonsWithAdminSettings(): void
|
||||||
|
{
|
||||||
|
$config = $this->createStub(IManageConfigValues::class);
|
||||||
|
$config->method('get')->willReturn([
|
||||||
|
'helloaddon' => [
|
||||||
|
'last_update' => 1738760499,
|
||||||
|
'admin' => false,
|
||||||
|
],
|
||||||
|
'addonwithadminsettings' => [
|
||||||
|
'last_update' => 1738760499,
|
||||||
|
'admin' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, []);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$config,
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(['addonwithadminsettings'], $addonManagerHelper->getEnabledAddonsWithAdminSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetAvailableAddons(): void
|
||||||
|
{
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
'helloaddon' => [
|
||||||
|
'helloaddon.php' => <<<PHP
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Name: Hello Addon
|
||||||
|
* Description: For testing purpose only
|
||||||
|
* Version: 1.0
|
||||||
|
* Author: Artur Weigandt <dont-mail-me@example.com>
|
||||||
|
*/
|
||||||
|
PHP,
|
||||||
|
],
|
||||||
|
'invalidaddon' => [
|
||||||
|
'invalidaddon.php' => 'This addon should not be loaded, because it does not contain a valid comment section.',
|
||||||
|
],
|
||||||
|
'.hidden' => [
|
||||||
|
'.hidden.php' => 'This folder should be ignored',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$this->createStub(IManageConfigValues::class),
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(['helloaddon'], $addonManagerHelper->getAvailableAddons());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInstallAddonIncludesAddonFile(): void
|
||||||
|
{
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
'helloaddon' => [
|
||||||
|
'helloaddon.php' => '<?php throw new \Exception("Addon file loaded");',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$this->createStub(IManageConfigValues::class),
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(Exception::class);
|
||||||
|
$this->expectExceptionMessage('Addon file loaded');
|
||||||
|
|
||||||
|
$addonManagerHelper->installAddon('helloaddon');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInstallAddonCallsInstallFunction(): void
|
||||||
|
{
|
||||||
|
// We need a unique name for the addon to avoid conflicts
|
||||||
|
// with other tests that may define the same install function.
|
||||||
|
$addonName = __FUNCTION__;
|
||||||
|
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
$addonName => [
|
||||||
|
$addonName . '.php' => <<<PHP
|
||||||
|
<?php
|
||||||
|
function {$addonName}_install()
|
||||||
|
{
|
||||||
|
throw new \Exception("Addon installed");
|
||||||
|
}
|
||||||
|
PHP,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$this->createStub(IManageConfigValues::class),
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(Exception::class);
|
||||||
|
$this->expectExceptionMessage('Addon installed');
|
||||||
|
|
||||||
|
$addonManagerHelper->installAddon($addonName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInstallAddonUpdatesConfig(): void
|
||||||
|
{
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
'helloaddon' => [
|
||||||
|
'helloaddon.php' => '<?php',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root->getChild('helloaddon/helloaddon.php')->lastModified(1234567890);
|
||||||
|
|
||||||
|
$config = $this->createMock(IManageConfigValues::class);
|
||||||
|
$config->expects($this->once())->method('set')->with(
|
||||||
|
'addons',
|
||||||
|
'helloaddon',
|
||||||
|
['last_update' => 1234567890, 'admin' => false]
|
||||||
|
);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$config,
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$addonManagerHelper->installAddon('helloaddon');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInstallAddonEnablesAddon(): void
|
||||||
|
{
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
'helloaddon' => [
|
||||||
|
'helloaddon.php' => '<?php',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$this->createStub(IManageConfigValues::class),
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame([], $addonManagerHelper->getEnabledAddons());
|
||||||
|
|
||||||
|
$this->assertTrue($addonManagerHelper->installAddon('helloaddon'));
|
||||||
|
|
||||||
|
$this->assertSame(['helloaddon'], $addonManagerHelper->getEnabledAddons());
|
||||||
|
}
|
||||||
|
public function testUninstallAddonIncludesAddonFile(): void
|
||||||
|
{
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
'helloaddon' => [
|
||||||
|
'helloaddon.php' => '<?php throw new \Exception("Addon file loaded");',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$this->createStub(IManageConfigValues::class),
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(Exception::class);
|
||||||
|
$this->expectExceptionMessage('Addon file loaded');
|
||||||
|
|
||||||
|
$addonManagerHelper->uninstallAddon('helloaddon');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUninstallAddonCallsUninstallFunction(): void
|
||||||
|
{
|
||||||
|
// We need a unique name for the addon to avoid conflicts
|
||||||
|
// with other tests that may define the same install function.
|
||||||
|
$addonName = __FUNCTION__;
|
||||||
|
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
$addonName => [
|
||||||
|
$addonName . '.php' => <<<PHP
|
||||||
|
<?php
|
||||||
|
function {$addonName}_uninstall()
|
||||||
|
{
|
||||||
|
throw new \Exception("Addon uninstalled");
|
||||||
|
}
|
||||||
|
PHP,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$this->createStub(IManageConfigValues::class),
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(Exception::class);
|
||||||
|
$this->expectExceptionMessage('Addon uninstalled');
|
||||||
|
|
||||||
|
$addonManagerHelper->uninstallAddon($addonName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUninstallAddonRemovesHooksFromDatabase(): void
|
||||||
|
{
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
'helloaddon' => [
|
||||||
|
'helloaddon.php' => '<?php',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$database = $this->createMock(Database::class);
|
||||||
|
$database->expects($this->once())
|
||||||
|
->method('delete')
|
||||||
|
->with(
|
||||||
|
'hook',
|
||||||
|
['`file` LIKE ?', '%/helloaddon/helloaddon.php']
|
||||||
|
);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$database,
|
||||||
|
$this->createStub(IManageConfigValues::class),
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$addonManagerHelper->uninstallAddon('helloaddon');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUninstallAddonDisablesAddon(): void
|
||||||
|
{
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
'helloaddon' => [
|
||||||
|
'helloaddon.php' => '<?php',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$config = $this->createStub(IManageConfigValues::class);
|
||||||
|
$config->method('get')->willReturn([
|
||||||
|
'helloaddon' => [
|
||||||
|
'last_update' => 1234567890,
|
||||||
|
'admin' => false,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$config,
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$addonManagerHelper->loadAddons();
|
||||||
|
|
||||||
|
$this->assertSame(['helloaddon'], $addonManagerHelper->getEnabledAddons());
|
||||||
|
|
||||||
|
$addonManagerHelper->uninstallAddon('helloaddon');
|
||||||
|
|
||||||
|
$this->assertSame([], $addonManagerHelper->getEnabledAddons());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReloadAddonsInstallsAddon(): void
|
||||||
|
{
|
||||||
|
// We need a unique name for the addon to avoid conflicts
|
||||||
|
// with other tests that may define the same install function.
|
||||||
|
$addonName = __FUNCTION__;
|
||||||
|
|
||||||
|
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
|
||||||
|
$addonName => [
|
||||||
|
$addonName . '.php' => <<<PHP
|
||||||
|
<?php
|
||||||
|
function {$addonName}_install()
|
||||||
|
{
|
||||||
|
throw new \Exception("Addon reinstalled");
|
||||||
|
}
|
||||||
|
PHP,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$root->getChild($addonName . '/' . $addonName . '.php')->lastModified(1234567890);
|
||||||
|
|
||||||
|
$config = $this->createStub(IManageConfigValues::class);
|
||||||
|
$config->method('get')->willReturn([
|
||||||
|
$addonName => [
|
||||||
|
'last_update' => 0,
|
||||||
|
'admin' => false,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$addonManagerHelper = new AddonManagerHelper(
|
||||||
|
$root->url(),
|
||||||
|
$this->createStub(Database::class),
|
||||||
|
$config,
|
||||||
|
$this->createStub(ICanCache::class),
|
||||||
|
$this->createStub(LoggerInterface::class),
|
||||||
|
$this->createStub(Profiler::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$addonManagerHelper->loadAddons();
|
||||||
|
|
||||||
|
$this->assertSame([$addonName], $addonManagerHelper->getEnabledAddons());
|
||||||
|
|
||||||
|
$this->expectException(Exception::class);
|
||||||
|
$this->expectExceptionMessage('Addon reinstalled');
|
||||||
|
|
||||||
|
$addonManagerHelper->reloadAddons();
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 2025.02-dev\n"
|
"Project-Id-Version: 2025.02-dev\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-06-03 20:17+0200\n"
|
"POT-Creation-Date: 2025-06-04 09:41+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -2171,7 +2171,7 @@ msgstr ""
|
||||||
msgid "Manage other pages"
|
msgid "Manage other pages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Nav.php:323 src/Module/Admin/Addons/Details.php:131
|
#: src/Content/Nav.php:323 src/Module/Admin/Addons/Details.php:140
|
||||||
#: src/Module/Admin/Themes/Details.php:85 src/Module/BaseSettings.php:177
|
#: src/Module/Admin/Themes/Details.php:85 src/Module/BaseSettings.php:177
|
||||||
#: src/Module/Welcome.php:38 view/theme/frio/theme.php:231
|
#: src/Module/Welcome.php:38 view/theme/frio/theme.php:231
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
|
@ -3861,31 +3861,35 @@ msgstr ""
|
||||||
msgid "User with delegates can't be removed, please remove delegate users first"
|
msgid "User with delegates can't be removed, please remove delegate users first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Details.php:49
|
#: src/Module/Admin/Addons/Details.php:52
|
||||||
msgid "Addon not found."
|
msgid "Addon not found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Details.php:60 src/Module/Admin/Addons/Index.php:43
|
#: src/Module/Admin/Addons/Details.php:63 src/Module/Admin/Addons/Index.php:44
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Addon %s disabled."
|
msgid "Addon %s disabled."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Details.php:63 src/Module/Admin/Addons/Index.php:45
|
#: src/Module/Admin/Addons/Details.php:66 src/Module/Admin/Addons/Index.php:46
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Addon %s enabled."
|
msgid "Addon %s enabled."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Details.php:72
|
#: src/Module/Admin/Addons/Details.php:75
|
||||||
#: src/Module/Admin/Themes/Details.php:38
|
#: src/Module/Admin/Themes/Details.php:38
|
||||||
msgid "Disable"
|
msgid "Disable"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Details.php:75
|
#: src/Module/Admin/Addons/Details.php:78
|
||||||
#: src/Module/Admin/Themes/Details.php:41 src/Module/Settings/Display.php:341
|
#: src/Module/Admin/Themes/Details.php:41 src/Module/Settings/Display.php:341
|
||||||
msgid "Enable"
|
msgid "Enable"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Details.php:128 src/Module/Admin/Addons/Index.php:77
|
#: src/Module/Admin/Addons/Details.php:101
|
||||||
|
msgid "Invalid Addon found."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/Module/Admin/Addons/Details.php:137 src/Module/Admin/Addons/Index.php:83
|
||||||
#: src/Module/Admin/Federation.php:213 src/Module/Admin/Logs/Settings.php:74
|
#: src/Module/Admin/Federation.php:213 src/Module/Admin/Logs/Settings.php:74
|
||||||
#: src/Module/Admin/Logs/View.php:71 src/Module/Admin/Queue.php:59
|
#: src/Module/Admin/Logs/View.php:71 src/Module/Admin/Queue.php:59
|
||||||
#: src/Module/Admin/Site.php:446 src/Module/Admin/Storage.php:124
|
#: src/Module/Admin/Site.php:446 src/Module/Admin/Storage.php:124
|
||||||
|
@ -3896,36 +3900,36 @@ msgstr ""
|
||||||
msgid "Administration"
|
msgid "Administration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Details.php:129 src/Module/Admin/Addons/Index.php:78
|
#: src/Module/Admin/Addons/Details.php:138 src/Module/Admin/Addons/Index.php:84
|
||||||
#: src/Module/BaseAdmin.php:77 src/Module/BaseSettings.php:127
|
#: src/Module/BaseAdmin.php:77 src/Module/BaseSettings.php:127
|
||||||
msgid "Addons"
|
msgid "Addons"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Details.php:130
|
#: src/Module/Admin/Addons/Details.php:139
|
||||||
#: src/Module/Admin/Themes/Details.php:84
|
#: src/Module/Admin/Themes/Details.php:84
|
||||||
msgid "Toggle"
|
msgid "Toggle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Details.php:143
|
#: src/Module/Admin/Addons/Details.php:152
|
||||||
#: src/Module/Admin/Themes/Details.php:92
|
#: src/Module/Admin/Themes/Details.php:92
|
||||||
msgid "Author: "
|
msgid "Author: "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Details.php:144
|
#: src/Module/Admin/Addons/Details.php:153
|
||||||
#: src/Module/Admin/Themes/Details.php:93
|
#: src/Module/Admin/Themes/Details.php:93
|
||||||
msgid "Maintainer: "
|
msgid "Maintainer: "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Index.php:35
|
#: src/Module/Admin/Addons/Index.php:36
|
||||||
msgid "Addons reloaded"
|
msgid "Addons reloaded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Index.php:47
|
#: src/Module/Admin/Addons/Index.php:48
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Addon %s failed to install."
|
msgid "Addon %s failed to install."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Index.php:79 src/Module/Admin/Features.php:69
|
#: src/Module/Admin/Addons/Index.php:85 src/Module/Admin/Features.php:69
|
||||||
#: src/Module/Admin/Logs/Settings.php:76 src/Module/Admin/Site.php:449
|
#: src/Module/Admin/Logs/Settings.php:76 src/Module/Admin/Site.php:449
|
||||||
#: src/Module/Admin/Themes/Index.php:105 src/Module/Admin/Tos.php:72
|
#: src/Module/Admin/Themes/Index.php:105 src/Module/Admin/Tos.php:72
|
||||||
#: src/Module/Settings/Account.php:507 src/Module/Settings/Addons.php:64
|
#: src/Module/Settings/Account.php:507 src/Module/Settings/Addons.php:64
|
||||||
|
@ -3937,11 +3941,11 @@ msgstr ""
|
||||||
msgid "Save Settings"
|
msgid "Save Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Index.php:80
|
#: src/Module/Admin/Addons/Index.php:86
|
||||||
msgid "Reload active addons"
|
msgid "Reload active addons"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Addons/Index.php:84
|
#: src/Module/Admin/Addons/Index.php:90
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "There are currently no addons available on your node. You can find the official addon repository at %1$s."
|
msgid "There are currently no addons available on your node. You can find the official addon repository at %1$s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue