* [Home](help)
Storage backends can be added via addons.
-A storage backend is implemented as a class, and the plugin register the class to make it avaiable to the system.
+A storage backend is implemented as a class, and the plugin register the class to make it available to the system.
## The Storage Backend Class
There are two different interfaces you need to implement.
-### `IWritableStorage`
+### `ICanWriteToStorage`
-The class must implement `Friendica\Model\Storage\IWritableStorage` interface. All method in the interface must be implemented:
+The class must implement `Friendica\Core\Storage\Capability\ICanWriteToStorage` interface. All method in the interface must be implemented:
```php
-namespace Friendica\Model\Storage\IWritableStorage;
+namespace Friendica\Core\Storage\Capability\ICanWriteToStorage;
-interface IWritableStorage
+interface ICanWriteToStorage
{
public function get(string $reference);
public function put(string $data, string $reference = '');
- `put(string $data, string $reference)` saves data in `$data` to position `$reference`, or a new position if `$reference` is empty.
- `delete(string $reference)` delete data pointed by `$reference`
-### `IStorageConfiguration`
+### `ICanConfigureStorage`
Each storage backend can have options the admin can set in admin page.
-To make the options possible, you need to implement the `Friendica\Model\Storage\IStorageConfiguration` interface.
+To make the options possible, you need to implement the `Friendica\Core\Storage\Capability\ICanConfigureStorage` interface.
All methods in the interface must be implemented:
```php
-namespace Friendica\Model\Storage\IStorageConfiguration;
+namespace Friendica\Core\Storage\Capability\ICanConfigureStorage;
-interface IStorageConfiguration
+interface ICanConfigureStorage
{
public function getOptions();
public function saveOptions(array $data);
`DI::facStorage()->unregister(string $class)`.
You have to register a new hook in your addon, listening on `storage_instance(App $a, array $data)`.
-In case `$data['name']` is your storage class name, you have to instance a new instance of your `Friendica\Model\Storage\IStorage` class.
+In case `$data['name']` is your storage class name, you have to instance a new instance of your `Friendica\Core\Storage\Capability\ICanReadFromStorage` class.
Set the instance of your class as `$data['storage']` to pass it back to the backend.
This is necessary because it isn't always clear, if you need further construction arguments.
Override the two necessary instances:
```php
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
abstract class StorageTest
{
abstract protected function getInstance();
// Assertion for the option array you return for your new StorageClass
- abstract protected function assertOption(IWritableStorage $storage);
+ abstract protected function assertOption(ICanWriteToStorage $storage);
}
```
Example:
```php
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
-class ExampleStorage implements IWritableStorage
+class ExampleStorage implements ICanWriteToStorage
{
public function get(string $reference) : string
{
try {
throw new Exception('a real bad exception');
} catch (Exception $exception) {
- throw new \Friendica\Model\Storage\StorageException(sprintf('The Example Storage throws an exception for reference %s', $reference), 500, $exception);
+ throw new \Friendica\Core\Storage\Exception\StorageException(sprintf('The Example Storage throws an exception for reference %s', $reference), 500, $exception);
}
}
}
<?php
namespace Friendica\Addon\samplestorage;
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
-class SampleStorageBackend implements IWritableStorage
+class SampleStorageBackend implements ICanWriteToStorage
{
const NAME = 'Sample Storage';
<?php
namespace Friendica\Addon\samplestorage;
-use Friendica\Model\Storage\IStorageConfiguration;
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
-class SampleStorageBackendConfig implements IStorageConfiguration
+class SampleStorageBackendConfig implements ICanConfigureStorage
{
/** @var \Friendica\Core\Config\Capability\IManageConfigValues */
private $config;
**Theoretically - until tests for Addons are enabled too - create a test class with the name `addon/tests/SampleStorageTest.php`:
```php
-use Friendica\Model\Storage\IWritableStorage;
-use Friendica\Test\src\Model\Storage\StorageTest;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Test\src\Core\Storage\StorageTest;
class SampleStorageTest extends StorageTest
{
}
// Assertion for the option array you return for your new StorageClass
- protected function assertOption(IWritableStorage $storage)
+ protected function assertOption(ICanWriteToStorage $storage)
{
$this->assertEquals([
'filename' => [
Hook data:
- **name** (input): the name of the used storage backend
-- **data['storage']** (output): the storage instance to use (**must** implement `\Friendica\Model\Storage\IWritableStorage`)
+- **data['storage']** (output): the storage instance to use (**must** implement `\Friendica\Core\Storage\IWritableStorage`)
### storage_config
Hook data:
- **name** (input): the name of the used storage backend
-- **data['storage_config']** (output): the storage configuration instance to use (**must** implement `\Friendica\Model\Storage\IStorageConfiguration`)
+- **data['storage_config']** (output): the storage configuration instance to use (**must** implement `\Friendica\Core\Storage\Capability\IConfigureStorage`)
## Complete list of hook callbacks
namespace Friendica\Console;
use Asika\SimpleConsole\CommandArgsException;
-use Friendica\Core\StorageManager;
-use Friendica\Model\Storage\ReferenceStorageException;
-use Friendica\Model\Storage\StorageException;
+use Friendica\Core\Storage\Repository\StorageManager;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
/**
* tool to manage storage backend and stored data from CLI
{
protected $helpOptions = ['h', 'help', '?'];
- /** @var StorageManager */
+ /** @var \Friendica\Core\Storage\Repository\StorageManager */
private $storageManager;
/**
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Capability;
+
+/**
+ * The interface to use for configurable storage backends
+ */
+interface ICanConfigureStorage
+{
+ /**
+ * Get info about storage options
+ *
+ * @return array
+ *
+ * This method return an array with information about storage options
+ * from which the form presented to the user is build.
+ *
+ * The returned array is:
+ *
+ * [
+ * 'option1name' => [ ..info.. ],
+ * 'option2name' => [ ..info.. ],
+ * ...
+ * ]
+ *
+ * An empty array can be returned if backend doesn't have any options
+ *
+ * The info array for each option MUST be as follows:
+ *
+ * [
+ * 'type', // define the field used in form, and the type of data.
+ * // one of 'checkbox', 'combobox', 'custom', 'datetime',
+ * // 'input', 'intcheckbox', 'password', 'radio', 'richtext'
+ * // 'select', 'select_raw', 'textarea'
+ *
+ * 'label', // Translatable label of the field
+ * 'value', // Current value
+ * 'help text', // Translatable description for the field
+ * extra data // Optional. Depends on 'type':
+ * // select: array [ value => label ] of choices
+ * // intcheckbox: value of input element
+ * // select_raw: prebuild html string of < option > tags
+ * ]
+ *
+ * See https://github.com/friendica/friendica/wiki/Quick-Template-Guide
+ */
+ public function getOptions(): array;
+
+ /**
+ * Validate and save options
+ *
+ * @param array $data Array [optionname => value] to be saved
+ *
+ * @return array Validation errors: [optionname => error message]
+ *
+ * Return array must be empty if no error.
+ */
+ public function saveOptions(array $data): array;
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Capability;
+
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+
+/**
+ * Interface for basic storage backends
+ */
+interface ICanReadFromStorage
+{
+ /**
+ * Get data from backend
+ *
+ * @param string $reference Data reference
+ *
+ * @return string
+ *
+ * @throws StorageException in case there's an unexpected error
+ * @throws ReferenceStorageException in case the reference doesn't exist
+ */
+ public function get(string $reference): string;
+
+ /**
+ * The name of the backend
+ *
+ * @return string
+ */
+ public function __toString(): string;
+
+ /**
+ * The name of the backend
+ *
+ * @return string
+ */
+ public static function getName(): string;
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Capability;
+
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+
+/**
+ * Interface for writable storage backends
+ *
+ * Used for storages with CRUD functionality, mainly used for user data (e.g. photos, attachements).
+ * There's only one active writable storage possible. This type of storage is selectable by the current administrator.
+ */
+interface ICanWriteToStorage extends ICanReadFromStorage
+{
+ /**
+ * Put data in backend as $ref. If $ref is not defined a new reference is created.
+ *
+ * @param string $data Data to save
+ * @param string $reference Data reference. Optional.
+ *
+ * @return string Saved data reference
+ *
+ * @throws StorageException in case there's an unexpected error
+ */
+ public function put(string $data, string $reference = ""): string;
+
+ /**
+ * Remove data from backend
+ *
+ * @param string $reference Data reference
+ *
+ * @throws StorageException in case there's an unexpected error
+ * @throws ReferenceStorageException in case the reference doesn't exist
+ */
+ public function delete(string $reference);
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Exception;
+
+/**
+ * Storage Exception in case of invalid storage class
+ */
+class InvalidClassStorageException extends StorageException
+{
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Exception;
+
+/**
+ * Storage Exception in case of invalid references
+ */
+class ReferenceStorageException extends StorageException
+{
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Exception;
+
+use Exception;
+
+/**
+ * Storage Exception for unexpected failures
+ */
+class StorageException extends Exception
+{
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Repository;
+
+use Exception;
+use Friendica\Core\Config\Capability\IManageConfigValues;
+use Friendica\Core\Hook;
+use Friendica\Core\L10n;
+use Friendica\Core\Storage\Exception\InvalidClassStorageException;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Database\Database;
+use Friendica\Core\Storage\Type;
+use Friendica\Network\HTTPException\InternalServerErrorException;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Manage storage backends
+ *
+ * Core code uses this class to get and set current storage backend class.
+ * Addons use this class to register and unregister additional backends.
+ */
+class StorageManager
+{
+ // Default tables to look for data
+ const TABLES = ['photo', 'attach'];
+
+ // Default storage backends
+ /** @var string[] */
+ const DEFAULT_BACKENDS = [
+ Type\Filesystem::NAME,
+ Type\Database::NAME,
+ ];
+
+ /** @var string[] List of valid backend classes */
+ private $validBackends;
+
+ /**
+ * @var ICanReadFromStorage[] A local cache for storage instances
+ */
+ private $backendInstances = [];
+
+ /** @var Database */
+ private $dba;
+ /** @var IManageConfigValues */
+ private $config;
+ /** @var LoggerInterface */
+ private $logger;
+ /** @var L10n */
+ private $l10n;
+
+ /** @var ICanWriteToStorage */
+ private $currentBackend;
+
+ /**
+ * @param Database $dba
+ * @param IManageConfigValues $config
+ * @param LoggerInterface $logger
+ * @param L10n $l10n
+ *
+ * @throws InvalidClassStorageException in case the active backend class is invalid
+ * @throws StorageException in case of unexpected errors during the active backend class loading
+ */
+ public function __construct(Database $dba, IManageConfigValues $config, LoggerInterface $logger, L10n $l10n)
+ {
+ $this->dba = $dba;
+ $this->config = $config;
+ $this->logger = $logger;
+ $this->l10n = $l10n;
+ $this->validBackends = $config->get('storage', 'backends', self::DEFAULT_BACKENDS);
+
+ $currentName = $this->config->get('storage', 'name');
+
+ // you can only use user backends as a "default" backend, so the second parameter is true
+ $this->currentBackend = $this->getWritableStorageByName($currentName);
+ }
+
+ /**
+ * Return current storage backend class
+ *
+ * @return ICanWriteToStorage
+ */
+ public function getBackend(): ICanWriteToStorage
+ {
+ return $this->currentBackend;
+ }
+
+ /**
+ * Returns a writable storage backend class by registered name
+ *
+ * @param string $name Backend name
+ *
+ * @return ICanWriteToStorage
+ *
+ * @throws InvalidClassStorageException in case there's no backend class for the name
+ * @throws StorageException in case of an unexpected failure during the hook call
+ */
+ public function getWritableStorageByName(string $name): ICanWriteToStorage
+ {
+ $storage = $this->getByName($name, $this->validBackends);
+ if (!$storage instanceof ICanWriteToStorage) {
+ throw new InvalidClassStorageException(sprintf('Backend %s is not writable', $name));
+ }
+
+ return $storage;
+ }
+
+ /**
+ * Return storage backend configuration by registered name
+ *
+ * @param string $name Backend name
+ *
+ * @return ICanConfigureStorage|false
+ *
+ * @throws InvalidClassStorageException in case there's no backend class for the name
+ * @throws StorageException in case of an unexpected failure during the hook call
+ */
+ public function getConfigurationByName(string $name)
+ {
+ switch ($name) {
+ // Try the filesystem backend
+ case Type\Filesystem::getName():
+ return new Type\FilesystemConfig($this->config, $this->l10n);
+ // try the database backend
+ case Type\Database::getName():
+ return false;
+ default:
+ $data = [
+ 'name' => $name,
+ 'storage_config' => null,
+ ];
+ try {
+ Hook::callAll('storage_config', $data);
+ if (!($data['storage_config'] ?? null) instanceof ICanConfigureStorage) {
+ throw new InvalidClassStorageException(sprintf('Configuration for backend %s was not found', $name));
+ }
+
+ return $data['storage_config'];
+ } catch (InternalServerErrorException $exception) {
+ throw new StorageException(sprintf('Failed calling hook::storage_config for backend %s', $name), $exception);
+ }
+ }
+ }
+
+ /**
+ * Return storage backend class by registered name
+ *
+ * @param string $name Backend name
+ * @param string[]|null $validBackends possible, manual override of the valid backends
+ *
+ * @return ICanReadFromStorage
+ *
+ * @throws InvalidClassStorageException in case there's no backend class for the name
+ * @throws StorageException in case of an unexpected failure during the hook call
+ */
+ public function getByName(string $name, array $validBackends = null): ICanReadFromStorage
+ {
+ // If there's no cached instance create a new instance
+ if (!isset($this->backendInstances[$name])) {
+ // If the current name isn't a valid backend (or the SystemResource instance) create it
+ if (!$this->isValidBackend($name, $validBackends)) {
+ throw new InvalidClassStorageException(sprintf('Backend %s is not valid', $name));
+ }
+
+ switch ($name) {
+ // Try the filesystem backend
+ case Type\Filesystem::getName():
+ $storageConfig = new Type\FilesystemConfig($this->config, $this->l10n);
+ $this->backendInstances[$name] = new Type\Filesystem($storageConfig->getStoragePath());
+ break;
+ // try the database backend
+ case Type\Database::getName():
+ $this->backendInstances[$name] = new Type\Database($this->dba);
+ break;
+ // at least, try if there's an addon for the backend
+ case \Friendica\Core\Storage\Type\SystemResource::getName():
+ $this->backendInstances[$name] = new \Friendica\Core\Storage\Type\SystemResource();
+ break;
+ case \Friendica\Core\Storage\Type\ExternalResource::getName():
+ $this->backendInstances[$name] = new \Friendica\Core\Storage\Type\ExternalResource();
+ break;
+ default:
+ $data = [
+ 'name' => $name,
+ 'storage' => null,
+ ];
+ try {
+ Hook::callAll('storage_instance', $data);
+ if (!($data['storage'] ?? null) instanceof ICanReadFromStorage) {
+ throw new InvalidClassStorageException(sprintf('Backend %s was not found', $name));
+ }
+
+ $this->backendInstances[$data['name'] ?? $name] = $data['storage'];
+ } catch (InternalServerErrorException $exception) {
+ throw new StorageException(sprintf('Failed calling hook::storage_instance for backend %s', $name), $exception);
+ }
+ break;
+ }
+ }
+
+ return $this->backendInstances[$name];
+ }
+
+ /**
+ * Checks, if the storage is a valid backend
+ *
+ * @param string|null $name The name or class of the backend
+ * @param string[]|null $validBackends Possible, valid backends to check
+ *
+ * @return boolean True, if the backend is a valid backend
+ */
+ public function isValidBackend(string $name = null, array $validBackends = null): bool
+ {
+ $validBackends = $validBackends ?? array_merge($this->validBackends,
+ [
+ Type\SystemResource::getName(),
+ Type\ExternalResource::getName(),
+ ]);
+ return in_array($name, $validBackends);
+ }
+
+ /**
+ * Set current storage backend class
+ *
+ * @param ICanWriteToStorage $storage The storage class
+ *
+ * @return boolean True, if the set was successful
+ */
+ public function setBackend(ICanWriteToStorage $storage): bool
+ {
+ if ($this->config->set('storage', 'name', $storage::getName())) {
+ $this->currentBackend = $storage;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get registered backends
+ *
+ * @return string[]
+ */
+ public function listBackends(): array
+ {
+ return $this->validBackends;
+ }
+
+ /**
+ * Register a storage backend class
+ *
+ * You have to register the hook "storage_instance" as well to make this class work!
+ *
+ * @param string $class Backend class name
+ *
+ * @return boolean True, if the registration was successful
+ */
+ public function register(string $class): bool
+ {
+ if (is_subclass_of($class, ICanReadFromStorage::class)) {
+ /** @var ICanReadFromStorage $class */
+ if ($this->isValidBackend($class::getName(), $this->validBackends)) {
+ return true;
+ }
+
+ $backends = $this->validBackends;
+ $backends[] = $class::getName();
+
+ if ($this->config->set('storage', 'backends', $backends)) {
+ $this->validBackends = $backends;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Unregister a storage backend class
+ *
+ * @param string $class Backend class name
+ *
+ * @return boolean True, if unregistering was successful
+ *
+ * @throws StorageException
+ */
+ public function unregister(string $class): bool
+ {
+ if (is_subclass_of($class, ICanReadFromStorage::class)) {
+ /** @var ICanReadFromStorage $class */
+ if ($this->currentBackend::getName() == $class::getName()) {
+ throw new StorageException(sprintf('Cannot unregister %s, because it\'s currently active.', $class::getName()));
+ }
+
+ $key = array_search($class::getName(), $this->validBackends);
+
+ if ($key !== false) {
+ $backends = $this->validBackends;
+ unset($backends[$key]);
+ $backends = array_values($backends);
+ if ($this->config->set('storage', 'backends', $backends)) {
+ $this->validBackends = $backends;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Move up to 5000 resources to storage $dest
+ *
+ * Copy existing data to destination storage and delete from source.
+ * This method cannot move to legacy in-table `data` field.
+ *
+ * @param ICanWriteToStorage $destination Destination storage class name
+ * @param array $tables Tables to look in for resources. Optional, defaults to ['photo', 'attach']
+ * @param int $limit Limit of the process batch size, defaults to 5000
+ *
+ * @return int Number of moved resources
+ * @throws StorageException
+ * @throws Exception
+ */
+ public function move(ICanWriteToStorage $destination, array $tables = self::TABLES, int $limit = 5000): int
+ {
+ if (!$this->isValidBackend($destination, $this->validBackends)) {
+ throw new StorageException(sprintf("Can't move to storage backend '%s'", $destination::getName()));
+ }
+
+ $moved = 0;
+ foreach ($tables as $table) {
+ // Get the rows where backend class is not the destination backend class
+ $resources = $this->dba->select(
+ $table,
+ ['id', 'data', 'backend-class', 'backend-ref'],
+ ['`backend-class` IS NULL or `backend-class` != ?', $destination::getName()],
+ ['limit' => $limit]
+ );
+
+ while ($resource = $this->dba->fetch($resources)) {
+ $id = $resource['id'];
+ $sourceRef = $resource['backend-ref'];
+ $source = null;
+
+ try {
+ $source = $this->getWritableStorageByName($resource['backend-class'] ?? '');
+ $this->logger->info('Get data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
+ $data = $source->get($sourceRef);
+ } catch (InvalidClassStorageException $exception) {
+ $this->logger->info('Get data from DB resource field.', ['oldReference' => $sourceRef]);
+ $data = $resource['data'];
+ } catch (ReferenceStorageException $exception) {
+ $this->logger->info('Invalid source reference.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
+ continue;
+ }
+
+ $this->logger->info('Save data to new backend.', ['newBackend' => $destination::getName()]);
+ $destinationRef = $destination->put($data);
+ $this->logger->info('Saved data.', ['newReference' => $destinationRef]);
+
+ if ($destinationRef !== '') {
+ $this->logger->info('update row');
+ if ($this->dba->update($table, ['backend-class' => $destination::getName(), 'backend-ref' => $destinationRef, 'data' => ''], ['id' => $id])) {
+ if (!empty($source)) {
+ $this->logger->info('Deleted data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
+ $source->delete($sourceRef);
+ }
+ $moved++;
+ }
+ }
+ }
+
+ $this->dba->close($resources);
+ }
+
+ return $moved;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Type;
+
+use Exception;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Database\Database as DBA;
+
+/**
+ * Database based storage system
+ *
+ * This class manage data stored in database table.
+ */
+class Database implements ICanWriteToStorage
+{
+ const NAME = 'Database';
+
+ /** @var DBA */
+ private $dba;
+
+ /**
+ * @param DBA $dba
+ */
+ public function __construct(DBA $dba)
+ {
+ $this->dba = $dba;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function get(string $reference): string
+ {
+ try {
+ $result = $this->dba->selectFirst('storage', ['data'], ['id' => $reference]);
+ if (!$this->dba->isResult($result)) {
+ throw new ReferenceStorageException(sprintf('Database storage cannot find data for reference %s', $reference));
+ }
+
+ return $result['data'];
+ } catch (Exception $exception) {
+ if ($exception instanceof ReferenceStorageException) {
+ throw $exception;
+ } else {
+ throw new StorageException(sprintf('Database storage failed to get %s', $reference), $exception->getCode(), $exception);
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function put(string $data, string $reference = ''): string
+ {
+ if ($reference !== '') {
+ try {
+ $result = $this->dba->update('storage', ['data' => $data], ['id' => $reference]);
+ } catch (Exception $exception) {
+ throw new StorageException(sprintf('Database storage failed to update %s', $reference), $exception->getCode(), $exception);
+ }
+ if ($result === false) {
+ throw new StorageException(sprintf('Database storage failed to update %s', $reference), 500, new Exception($this->dba->errorMessage(), $this->dba->errorNo()));
+ }
+
+ return $reference;
+ } else {
+ try {
+ $result = $this->dba->insert('storage', ['data' => $data]);
+ } catch (Exception $exception) {
+ throw new StorageException(sprintf('Database storage failed to insert %s', $reference), $exception->getCode(), $exception);
+ }
+ if ($result === false) {
+ throw new StorageException(sprintf('Database storage failed to update %s', $reference), 500, new Exception($this->dba->errorMessage(), $this->dba->errorNo()));
+ }
+
+ return $this->dba->lastInsertId();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function delete(string $reference)
+ {
+ try {
+ if (!$this->dba->delete('storage', ['id' => $reference]) || $this->dba->affectedRows() === 0) {
+ throw new ReferenceStorageException(sprintf('Database storage failed to delete %s', $reference));
+ }
+ } catch (Exception $exception) {
+ if ($exception instanceof ReferenceStorageException) {
+ throw $exception;
+ } else {
+ throw new StorageException(sprintf('Database storage failed to delete %s', $reference), $exception->getCode(), $exception);
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function getName(): string
+ {
+ return self::NAME;
+ }
+
+ public function __toString(): string
+ {
+ return self::getName();
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Type;
+
+use Exception;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
+use Friendica\Util\HTTPSignature;
+
+/**
+ * External resource storage class
+ *
+ * This class is used to load external resources, like images.
+ * Is not intended to be selectable by admins as default storage class.
+ */
+class ExternalResource implements ICanReadFromStorage
+{
+ const NAME = 'ExternalResource';
+
+ /**
+ * @inheritDoc
+ */
+ public function get(string $reference): string
+ {
+ $data = json_decode($reference);
+ if (empty($data->url)) {
+ throw new ReferenceStorageException(sprintf('Invalid reference %s, cannot retrieve URL', $reference));
+ }
+
+ $parts = parse_url($data->url);
+ if (empty($parts['scheme']) || empty($parts['host'])) {
+ throw new ReferenceStorageException(sprintf('Invalid reference %s, cannot extract scheme and host', $reference));
+ }
+
+ try {
+ $fetchResult = HTTPSignature::fetchRaw($data->url, $data->uid, ['accept_content' => []]);
+ } catch (Exception $exception) {
+ throw new ReferenceStorageException(sprintf('External resource failed to get %s', $reference), $exception->getCode(), $exception);
+ }
+ if ($fetchResult->isSuccess()) {
+ return $fetchResult->getBody();
+ } else {
+ throw new ReferenceStorageException(sprintf('External resource failed to get %s', $reference), $fetchResult->getReturnCode(), new Exception($fetchResult->getBody()));
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function __toString(): string
+ {
+ return self::NAME;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function getName(): string
+ {
+ return self::NAME;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Type;
+
+use Exception;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Util\Strings;
+
+/**
+ * Filesystem based storage backend
+ *
+ * This class manage data on filesystem.
+ * Base folder for storage is set in storage.filesystem_path.
+ * Best would be for storage folder to be outside webserver folder, we are using a
+ * folder relative to code tree root as default to ease things for users in shared hostings.
+ * Each new resource gets a value as reference and is saved in a
+ * folder tree stucture created from that value.
+ */
+class Filesystem implements ICanWriteToStorage
+{
+ const NAME = 'Filesystem';
+
+ /** @var string */
+ private $basePath;
+
+ /**
+ * Filesystem constructor.
+ *
+ * @param string $filesystemPath
+ *
+ * @throws StorageException in case the path doesn't exist or isn't writeable
+ */
+ public function __construct(string $filesystemPath = FilesystemConfig::DEFAULT_BASE_FOLDER)
+ {
+ $path = $filesystemPath;
+ $this->basePath = rtrim($path, '/');
+
+ if (!is_dir($this->basePath) || !is_writable($this->basePath)) {
+ throw new StorageException(sprintf('Path "%s" does not exist or is not writeable.', $this->basePath));
+ }
+ }
+
+ /**
+ * Split data ref and return file path
+ *
+ * @param string $reference Data reference
+ *
+ * @return string
+ */
+ private function pathForRef(string $reference): string
+ {
+ $fold1 = substr($reference, 0, 2);
+ $fold2 = substr($reference, 2, 2);
+ $file = substr($reference, 4);
+
+ return implode('/', [$this->basePath, $fold1, $fold2, $file]);
+ }
+
+
+ /**
+ * Create directory tree to store file, with .htaccess and index.html files
+ *
+ * @param string $file Path and filename
+ *
+ * @throws StorageException
+ */
+ private function createFoldersForFile(string $file)
+ {
+ $path = dirname($file);
+
+ if (!is_dir($path)) {
+ if (!mkdir($path, 0770, true)) {
+ throw new StorageException(sprintf('Filesystem storage failed to create "%s". Check you write permissions.', $path));
+ }
+ }
+
+ while ($path !== $this->basePath) {
+ if (!is_file($path . '/index.html')) {
+ file_put_contents($path . '/index.html', '');
+ }
+ chmod($path . '/index.html', 0660);
+ chmod($path, 0770);
+ $path = dirname($path);
+ }
+ if (!is_file($path . '/index.html')) {
+ file_put_contents($path . '/index.html', '');
+ chmod($path . '/index.html', 0660);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function get(string $reference): string
+ {
+ $file = $this->pathForRef($reference);
+ if (!is_file($file)) {
+ throw new ReferenceStorageException(sprintf('Filesystem storage failed to get the file %s, The file is invalid', $reference));
+ }
+
+ $result = file_get_contents($file);
+
+ if ($result === false) {
+ throw new StorageException(sprintf('Filesystem storage failed to get data to "%s". Check your write permissions', $file));
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function put(string $data, string $reference = ''): string
+ {
+ if ($reference === '') {
+ try {
+ $reference = Strings::getRandomHex();
+ } catch (Exception $exception) {
+ throw new StorageException('Filesystem storage failed to generate a random hex', $exception->getCode(), $exception);
+ }
+ }
+ $file = $this->pathForRef($reference);
+
+ $this->createFoldersForFile($file);
+
+ $result = file_put_contents($file, $data);
+
+ // just in case the result is REALLY false, not zero or empty or anything else, throw the exception
+ if ($result === false) {
+ throw new StorageException(sprintf('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
+ }
+
+ chmod($file, 0660);
+ return $reference;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function delete(string $reference)
+ {
+ $file = $this->pathForRef($reference);
+ if (!is_file($file)) {
+ throw new ReferenceStorageException(sprintf('File with reference "%s" doesn\'t exist', $reference));
+ }
+
+ if (!unlink($file)) {
+ throw new StorageException(sprintf('Cannot delete with file with reference "%s"', $reference));
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function getName(): string
+ {
+ return self::NAME;
+ }
+
+ public function __toString(): string
+ {
+ return self::getName();
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Type;
+
+use Friendica\Core\Config\Capability\IManageConfigValues;
+use Friendica\Core\L10n;
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
+
+/**
+ * Filesystem based storage backend configuration
+ */
+class FilesystemConfig implements ICanConfigureStorage
+{
+ // Default base folder
+ const DEFAULT_BASE_FOLDER = 'storage';
+
+ /** @var IManageConfigValues */
+ private $config;
+
+ /** @var string */
+ private $storagePath;
+
+ /** @var L10n */
+ private $l10n;
+
+ /**
+ * Returns the current storage path
+ *
+ * @return string
+ */
+ public function getStoragePath(): string
+ {
+ return $this->storagePath;
+ }
+
+ /**
+ * Filesystem constructor.
+ *
+ * @param IManageConfigValues $config
+ * @param L10n $l10n
+ */
+ public function __construct(IManageConfigValues $config, L10n $l10n)
+ {
+ $this->config = $config;
+ $this->l10n = $l10n;
+
+ $path = $this->config->get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER);
+ $this->storagePath = rtrim($path, '/');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getOptions(): array
+ {
+ return [
+ 'storagepath' => [
+ 'input',
+ $this->l10n->t('Storage base path'),
+ $this->storagePath,
+ $this->l10n->t('Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree')
+ ]
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function saveOptions(array $data): array
+ {
+ $storagePath = $data['storagepath'] ?? '';
+ if ($storagePath === '' || !is_dir($storagePath)) {
+ return [
+ 'storagepath' => $this->l10n->t('Enter a valid existing folder')
+ ];
+ };
+ $this->config->set('storage', 'filesystem_path', $storagePath);
+ $this->storagePath = $storagePath;
+ return [];
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Type;
+
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
+
+/**
+ * System resource storage class
+ *
+ * This class is used to load system resources, like images.
+ * Is not intended to be selectable by admins as default storage class.
+ */
+class SystemResource implements ICanReadFromStorage
+{
+ const NAME = 'SystemResource';
+
+ // Valid folders to look for resources
+ const VALID_FOLDERS = ["images"];
+
+ /**
+ * @inheritDoc
+ */
+ public function get(string $reference): string
+ {
+ $folder = dirname($reference);
+ if (!in_array($folder, self::VALID_FOLDERS)) {
+ throw new ReferenceStorageException(sprintf('System Resource is invalid for reference %s, no valid folder found', $reference));
+ }
+ if (!file_exists($reference)) {
+ throw new StorageException(sprintf('System Resource is invalid for reference %s, the file doesn\'t exist', $reference));
+ }
+ $content = file_get_contents($reference);
+
+ if ($content === false) {
+ throw new StorageException(sprintf('Cannot get content for reference %s', $reference));
+ }
+
+ return $content;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function __toString(): string
+ {
+ return self::NAME;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function getName(): string
+ {
+ return self::NAME;
+ }
+}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Core;
-
-use Exception;
-use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Database\Database;
-use Friendica\Model\Storage;
-use Friendica\Network\HTTPException\InternalServerErrorException;
-use Psr\Log\LoggerInterface;
-
-/**
- * Manage storage backends
- *
- * Core code uses this class to get and set current storage backend class.
- * Addons use this class to register and unregister additional backends.
- */
-class StorageManager
-{
- // Default tables to look for data
- const TABLES = ['photo', 'attach'];
-
- // Default storage backends
- /** @var string[] */
- const DEFAULT_BACKENDS = [
- Storage\Filesystem::NAME,
- Storage\Database::NAME,
- ];
-
- /** @var string[] List of valid backend classes */
- private $validBackends;
-
- /**
- * @var Storage\IStorage[] A local cache for storage instances
- */
- private $backendInstances = [];
-
- /** @var Database */
- private $dba;
- /** @var IManageConfigValues */
- private $config;
- /** @var LoggerInterface */
- private $logger;
- /** @var L10n */
- private $l10n;
-
- /** @var Storage\IWritableStorage */
- private $currentBackend;
-
- /**
- * @param Database $dba
- * @param IManageConfigValues $config
- * @param LoggerInterface $logger
- * @param L10n $l10n
- *
- * @throws Storage\InvalidClassStorageException in case the active backend class is invalid
- * @throws Storage\StorageException in case of unexpected errors during the active backend class loading
- */
- public function __construct(Database $dba, IManageConfigValues $config, LoggerInterface $logger, L10n $l10n)
- {
- $this->dba = $dba;
- $this->config = $config;
- $this->logger = $logger;
- $this->l10n = $l10n;
- $this->validBackends = $config->get('storage', 'backends', self::DEFAULT_BACKENDS);
-
- $currentName = $this->config->get('storage', 'name');
-
- // you can only use user backends as a "default" backend, so the second parameter is true
- $this->currentBackend = $this->getWritableStorageByName($currentName);
- }
-
- /**
- * Return current storage backend class
- *
- * @return Storage\IWritableStorage
- */
- public function getBackend()
- {
- return $this->currentBackend;
- }
-
- /**
- * Returns a writable storage backend class by registered name
- *
- * @param string $name Backend name
- *
- * @return Storage\IWritableStorage
- *
- * @throws Storage\InvalidClassStorageException in case there's no backend class for the name
- * @throws Storage\StorageException in case of an unexpected failure during the hook call
- */
- public function getWritableStorageByName(string $name): Storage\IWritableStorage
- {
- $storage = $this->getByName($name, $this->validBackends);
- if (!$storage instanceof Storage\IWritableStorage) {
- throw new Storage\InvalidClassStorageException(sprintf('Backend %s is not writable', $name));
- }
-
- return $storage;
- }
-
- /**
- * Return storage backend configuration by registered name
- *
- * @param string $name Backend name
- *
- * @return Storage\IStorageConfiguration|false
- *
- * @throws Storage\InvalidClassStorageException in case there's no backend class for the name
- * @throws Storage\StorageException in case of an unexpected failure during the hook call
- */
- public function getConfigurationByName(string $name)
- {
- switch ($name) {
- // Try the filesystem backend
- case Storage\Filesystem::getName():
- return new Storage\FilesystemConfig($this->config, $this->l10n);
- // try the database backend
- case Storage\Database::getName():
- return false;
- default:
- $data = [
- 'name' => $name,
- 'storage_config' => null,
- ];
- try {
- Hook::callAll('storage_config', $data);
- if (!($data['storage_config'] ?? null) instanceof Storage\IStorageConfiguration) {
- throw new Storage\InvalidClassStorageException(sprintf('Configuration for backend %s was not found', $name));
- }
-
- return $data['storage_config'];
- } catch (InternalServerErrorException $exception) {
- throw new Storage\StorageException(sprintf('Failed calling hook::storage_config for backend %s', $name), $exception);
- }
- }
- }
-
- /**
- * Return storage backend class by registered name
- *
- * @param string $name Backend name
- * @param string[]|null $validBackends possible, manual override of the valid backends
- *
- * @return Storage\IStorage
- *
- * @throws Storage\InvalidClassStorageException in case there's no backend class for the name
- * @throws Storage\StorageException in case of an unexpected failure during the hook call
- */
- public function getByName(string $name, array $validBackends = null): Storage\IStorage
- {
- // If there's no cached instance create a new instance
- if (!isset($this->backendInstances[$name])) {
- // If the current name isn't a valid backend (or the SystemResource instance) create it
- if (!$this->isValidBackend($name, $validBackends)) {
- throw new Storage\InvalidClassStorageException(sprintf('Backend %s is not valid', $name));
- }
-
- switch ($name) {
- // Try the filesystem backend
- case Storage\Filesystem::getName():
- $storageConfig = new Storage\FilesystemConfig($this->config, $this->l10n);
- $this->backendInstances[$name] = new Storage\Filesystem($storageConfig->getStoragePath());
- break;
- // try the database backend
- case Storage\Database::getName():
- $this->backendInstances[$name] = new Storage\Database($this->dba);
- break;
- // at least, try if there's an addon for the backend
- case Storage\SystemResource::getName():
- $this->backendInstances[$name] = new Storage\SystemResource();
- break;
- case Storage\ExternalResource::getName():
- $this->backendInstances[$name] = new Storage\ExternalResource();
- break;
- default:
- $data = [
- 'name' => $name,
- 'storage' => null,
- ];
- try {
- Hook::callAll('storage_instance', $data);
- if (!($data['storage'] ?? null) instanceof Storage\IStorage) {
- throw new Storage\InvalidClassStorageException(sprintf('Backend %s was not found', $name));
- }
-
- $this->backendInstances[$data['name'] ?? $name] = $data['storage'];
- } catch (InternalServerErrorException $exception) {
- throw new Storage\StorageException(sprintf('Failed calling hook::storage_instance for backend %s', $name), $exception);
- }
- break;
- }
- }
-
- return $this->backendInstances[$name];
- }
-
- /**
- * Checks, if the storage is a valid backend
- *
- * @param string|null $name The name or class of the backend
- * @param string[]|null $validBackends Possible, valid backends to check
- *
- * @return boolean True, if the backend is a valid backend
- */
- public function isValidBackend(string $name = null, array $validBackends = null): bool
- {
- $validBackends = $validBackends ?? array_merge($this->validBackends,
- [
- Storage\SystemResource::getName(),
- Storage\ExternalResource::getName(),
- ]);
- return in_array($name, $validBackends);
- }
-
- /**
- * Set current storage backend class
- *
- * @param Storage\IWritableStorage $storage The storage class
- *
- * @return boolean True, if the set was successful
- */
- public function setBackend(Storage\IWritableStorage $storage): bool
- {
- if ($this->config->set('storage', 'name', $storage::getName())) {
- $this->currentBackend = $storage;
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Get registered backends
- *
- * @return string[]
- */
- public function listBackends(): array
- {
- return $this->validBackends;
- }
-
- /**
- * Register a storage backend class
- *
- * You have to register the hook "storage_instance" as well to make this class work!
- *
- * @param string $class Backend class name
- *
- * @return boolean True, if the registration was successful
- */
- public function register(string $class): bool
- {
- if (is_subclass_of($class, Storage\IStorage::class)) {
- /** @var Storage\IStorage $class */
-
- if ($this->isValidBackend($class::getName(), $this->validBackends)) {
- return true;
- }
-
- $backends = $this->validBackends;
- $backends[] = $class::getName();
-
- if ($this->config->set('storage', 'backends', $backends)) {
- $this->validBackends = $backends;
- return true;
- } else {
- return false;
- }
- } else {
- return false;
- }
- }
-
- /**
- * Unregister a storage backend class
- *
- * @param string $class Backend class name
- *
- * @return boolean True, if unregistering was successful
- *
- * @throws Storage\StorageException
- */
- public function unregister(string $class): bool
- {
- if (is_subclass_of($class, Storage\IStorage::class)) {
- /** @var Storage\IStorage $class */
-
- if ($this->currentBackend::getName() == $class::getName()) {
- throw new Storage\StorageException(sprintf('Cannot unregister %s, because it\'s currently active.', $class::getName()));
- }
-
- $key = array_search($class::getName(), $this->validBackends);
-
- if ($key !== false) {
- $backends = $this->validBackends;
- unset($backends[$key]);
- $backends = array_values($backends);
- if ($this->config->set('storage', 'backends', $backends)) {
- $this->validBackends = $backends;
- return true;
- } else {
- return false;
- }
- } else {
- return true;
- }
- } else {
- return false;
- }
- }
-
- /**
- * Move up to 5000 resources to storage $dest
- *
- * Copy existing data to destination storage and delete from source.
- * This method cannot move to legacy in-table `data` field.
- *
- * @param Storage\IWritableStorage $destination Destination storage class name
- * @param array $tables Tables to look in for resources. Optional, defaults to ['photo', 'attach']
- * @param int $limit Limit of the process batch size, defaults to 5000
- *
- * @return int Number of moved resources
- * @throws Storage\StorageException
- * @throws Exception
- */
- public function move(Storage\IWritableStorage $destination, array $tables = self::TABLES, int $limit = 5000): int
- {
- if (!$this->isValidBackend($destination, $this->validBackends)) {
- throw new Storage\StorageException(sprintf("Can't move to storage backend '%s'", $destination::getName()));
- }
-
- $moved = 0;
- foreach ($tables as $table) {
- // Get the rows where backend class is not the destination backend class
- $resources = $this->dba->select(
- $table,
- ['id', 'data', 'backend-class', 'backend-ref'],
- ['`backend-class` IS NULL or `backend-class` != ?', $destination::getName()],
- ['limit' => $limit]
- );
-
- while ($resource = $this->dba->fetch($resources)) {
- $id = $resource['id'];
- $sourceRef = $resource['backend-ref'];
- $source = null;
-
- try {
- $source = $this->getWritableStorageByName($resource['backend-class'] ?? '');
- $this->logger->info('Get data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
- $data = $source->get($sourceRef);
- } catch (Storage\InvalidClassStorageException $exception) {
- $this->logger->info('Get data from DB resource field.', ['oldReference' => $sourceRef]);
- $data = $resource['data'];
- } catch (Storage\ReferenceStorageException $exception) {
- $this->logger->info('Invalid source reference.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
- continue;
- }
-
- $this->logger->info('Save data to new backend.', ['newBackend' => $destination::getName()]);
- $destinationRef = $destination->put($data);
- $this->logger->info('Saved data.', ['newReference' => $destinationRef]);
-
- if ($destinationRef !== '') {
- $this->logger->info('update row');
- if ($this->dba->update($table, ['backend-class' => $destination::getName(), 'backend-ref' => $destinationRef, 'data' => ''], ['id' => $id])) {
- if (!empty($source)) {
- $this->logger->info('Delete data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
- $source->delete($sourceRef);
- }
- $moved++;
- }
- }
- }
-
- $this->dba->close($resources);
- }
-
- return $moved;
- }
-}
}
/**
- * @return Core\StorageManager
+ * @return \Friendica\Core\Storage\Repository\StorageManager
*/
public static function storageManager()
{
- return self::$dice->create(Core\StorageManager::class);
+ return self::$dice->create(Core\Storage\Repository\StorageManager::class);
}
//
}
/**
- * @return Model\Storage\IWritableStorage
+ * @return Core\Storage\Capability\ICanWriteToStorage
*/
public static function storage()
{
- return self::$dice->create(Model\Storage\IWritableStorage::class);
+ return self::$dice->create(Core\Storage\Capability\ICanWriteToStorage::class);
}
/**
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
-use Friendica\Model\Storage\InvalidClassStorageException;
-use Friendica\Model\Storage\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\InvalidClassStorageException;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
use Friendica\Object\Image;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Mimetype;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
-use Friendica\Model\Storage\ExternalResource;
-use Friendica\Model\Storage\InvalidClassStorageException;
-use Friendica\Model\Storage\ReferenceStorageException;
-use Friendica\Model\Storage\StorageException;
-use Friendica\Model\Storage\SystemResource;
+use Friendica\Core\Storage\Type\ExternalResource;
+use Friendica\Core\Storage\Exception\InvalidClassStorageException;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Type\SystemResource;
use Friendica\Object\Image;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Images;
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-use Exception;
-use Friendica\Database\Database as DBA;
-
-/**
- * Database based storage system
- *
- * This class manage data stored in database table.
- */
-class Database implements IWritableStorage
-{
- const NAME = 'Database';
-
- /** @var DBA */
- private $dba;
-
- /**
- * @param DBA $dba
- */
- public function __construct(DBA $dba)
- {
- $this->dba = $dba;
- }
-
- /**
- * @inheritDoc
- */
- public function get(string $reference): string
- {
- try {
- $result = $this->dba->selectFirst('storage', ['data'], ['id' => $reference]);
- if (!$this->dba->isResult($result)) {
- throw new ReferenceStorageException(sprintf('Database storage cannot find data for reference %s', $reference));
- }
-
- return $result['data'];
- } catch (Exception $exception) {
- if ($exception instanceof ReferenceStorageException) {
- throw $exception;
- } else {
- throw new StorageException(sprintf('Database storage failed to get %s', $reference), $exception->getCode(), $exception);
- }
- }
- }
-
- /**
- * @inheritDoc
- */
- public function put(string $data, string $reference = ''): string
- {
- if ($reference !== '') {
- try {
- $result = $this->dba->update('storage', ['data' => $data], ['id' => $reference]);
- } catch (Exception $exception) {
- throw new StorageException(sprintf('Database storage failed to update %s', $reference), $exception->getCode(), $exception);
- }
- if ($result === false) {
- throw new StorageException(sprintf('Database storage failed to update %s', $reference), 500, new Exception($this->dba->errorMessage(), $this->dba->errorNo()));
- }
-
- return $reference;
- } else {
- try {
- $result = $this->dba->insert('storage', ['data' => $data]);
- } catch (Exception $exception) {
- throw new StorageException(sprintf('Database storage failed to insert %s', $reference), $exception->getCode(), $exception);
- }
- if ($result === false) {
- throw new StorageException(sprintf('Database storage failed to update %s', $reference), 500, new Exception($this->dba->errorMessage(), $this->dba->errorNo()));
- }
-
- return $this->dba->lastInsertId();
- }
- }
-
- /**
- * @inheritDoc
- */
- public function delete(string $reference)
- {
- try {
- if (!$this->dba->delete('storage', ['id' => $reference]) || $this->dba->affectedRows() === 0) {
- throw new ReferenceStorageException(sprintf('Database storage failed to delete %s', $reference));
- }
- } catch (Exception $exception) {
- if ($exception instanceof ReferenceStorageException) {
- throw $exception;
- } else {
- throw new StorageException(sprintf('Database storage failed to delete %s', $reference), $exception->getCode(), $exception);
- }
- }
- }
-
- /**
- * @inheritDoc
- */
- public static function getName(): string
- {
- return self::NAME;
- }
-
- public function __toString()
- {
- return self::getName();
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-use Exception;
-use Friendica\Util\HTTPSignature;
-
-/**
- * External resource storage class
- *
- * This class is used to load external resources, like images.
- * Is not intended to be selectable by admins as default storage class.
- */
-class ExternalResource implements IStorage
-{
- const NAME = 'ExternalResource';
-
- /**
- * @inheritDoc
- */
- public function get(string $reference): string
- {
- $data = json_decode($reference);
- if (empty($data->url)) {
- throw new ReferenceStorageException(sprintf('Invalid reference %s, cannot retrieve URL', $reference));
- }
-
- $parts = parse_url($data->url);
- if (empty($parts['scheme']) || empty($parts['host'])) {
- throw new ReferenceStorageException(sprintf('Invalid reference %s, cannot extract scheme and host', $reference));
- }
-
- try {
- $fetchResult = HTTPSignature::fetchRaw($data->url, $data->uid, ['accept_content' => []]);
- } catch (Exception $exception) {
- throw new ReferenceStorageException(sprintf('External resource failed to get %s', $reference), $exception->getCode(), $exception);
- }
- if ($fetchResult->isSuccess()) {
- return $fetchResult->getBody();
- } else {
- throw new ReferenceStorageException(sprintf('External resource failed to get %s', $reference), $fetchResult->getReturnCode(), new Exception($fetchResult->getBody()));
- }
- }
-
- /**
- * @inheritDoc
- */
- public function __toString()
- {
- return self::NAME;
- }
-
- /**
- * @inheritDoc
- */
- public static function getName(): string
- {
- return self::NAME;
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-use Exception;
-use Friendica\Util\Strings;
-
-/**
- * Filesystem based storage backend
- *
- * This class manage data on filesystem.
- * Base folder for storage is set in storage.filesystem_path.
- * Best would be for storage folder to be outside webserver folder, we are using a
- * folder relative to code tree root as default to ease things for users in shared hostings.
- * Each new resource gets a value as reference and is saved in a
- * folder tree stucture created from that value.
- */
-class Filesystem implements IWritableStorage
-{
- const NAME = 'Filesystem';
-
- /** @var string */
- private $basePath;
-
- /**
- * Filesystem constructor.
- *
- * @param string $filesystemPath
- *
- * @throws StorageException in case the path doesn't exist or isn't writeable
- */
- public function __construct(string $filesystemPath = FilesystemConfig::DEFAULT_BASE_FOLDER)
- {
- $path = $filesystemPath;
- $this->basePath = rtrim($path, '/');
-
- if (!is_dir($this->basePath) || !is_writable($this->basePath)) {
- throw new StorageException(sprintf('Path "%s" does not exist or is not writeable.', $this->basePath));
- }
- }
-
- /**
- * Split data ref and return file path
- *
- * @param string $reference Data reference
- *
- * @return string
- */
- private function pathForRef(string $reference): string
- {
- $fold1 = substr($reference, 0, 2);
- $fold2 = substr($reference, 2, 2);
- $file = substr($reference, 4);
-
- return implode('/', [$this->basePath, $fold1, $fold2, $file]);
- }
-
-
- /**
- * Create directory tree to store file, with .htaccess and index.html files
- *
- * @param string $file Path and filename
- *
- * @throws StorageException
- */
- private function createFoldersForFile(string $file)
- {
- $path = dirname($file);
-
- if (!is_dir($path)) {
- if (!mkdir($path, 0770, true)) {
- throw new StorageException(sprintf('Filesystem storage failed to create "%s". Check you write permissions.', $path));
- }
- }
-
- while ($path !== $this->basePath) {
- if (!is_file($path . '/index.html')) {
- file_put_contents($path . '/index.html', '');
- }
- chmod($path . '/index.html', 0660);
- chmod($path, 0770);
- $path = dirname($path);
- }
- if (!is_file($path . '/index.html')) {
- file_put_contents($path . '/index.html', '');
- chmod($path . '/index.html', 0660);
- }
- }
-
- /**
- * @inheritDoc
- */
- public function get(string $reference): string
- {
- $file = $this->pathForRef($reference);
- if (!is_file($file)) {
- throw new ReferenceStorageException(sprintf('Filesystem storage failed to get the file %s, The file is invalid', $reference));
- }
-
- $result = file_get_contents($file);
-
- if ($result === false) {
- throw new StorageException(sprintf('Filesystem storage failed to get data to "%s". Check your write permissions', $file));
- }
-
- return $result;
- }
-
- /**
- * @inheritDoc
- */
- public function put(string $data, string $reference = ''): string
- {
- if ($reference === '') {
- try {
- $reference = Strings::getRandomHex();
- } catch (Exception $exception) {
- throw new StorageException('Filesystem storage failed to generate a random hex', $exception->getCode(), $exception);
- }
- }
- $file = $this->pathForRef($reference);
-
- $this->createFoldersForFile($file);
-
- $result = file_put_contents($file, $data);
-
- // just in case the result is REALLY false, not zero or empty or anything else, throw the exception
- if ($result === false) {
- throw new StorageException(sprintf('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
- }
-
- chmod($file, 0660);
- return $reference;
- }
-
- /**
- * @inheritDoc
- */
- public function delete(string $reference)
- {
- $file = $this->pathForRef($reference);
- if (!is_file($file)) {
- throw new ReferenceStorageException(sprintf('File with reference "%s" doesn\'t exist', $reference));
- }
-
- if (!unlink($file)) {
- throw new StorageException(sprintf('Cannot delete with file with reference "%s"', $reference));
- }
- }
-
- /**
- * @inheritDoc
- */
- public static function getName(): string
- {
- return self::NAME;
- }
-
- public function __toString()
- {
- return self::getName();
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\L10n;
-
-/**
- * Filesystem based storage backend configuration
- */
-class FilesystemConfig implements IStorageConfiguration
-{
- // Default base folder
- const DEFAULT_BASE_FOLDER = 'storage';
-
- /** @var IManageConfigValues */
- private $config;
-
- /** @var string */
- private $storagePath;
-
- /** @var L10n */
- private $l10n;
-
- /**
- * Returns the current storage path
- *
- * @return string
- */
- public function getStoragePath(): string
- {
- return $this->storagePath;
- }
-
- /**
- * Filesystem constructor.
- *
- * @param IManageConfigValues $config
- * @param L10n $l10n
- */
- public function __construct(IManageConfigValues $config, L10n $l10n)
- {
- $this->config = $config;
- $this->l10n = $l10n;
-
- $path = $this->config->get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER);
- $this->storagePath = rtrim($path, '/');
- }
-
- /**
- * @inheritDoc
- */
- public function getOptions(): array
- {
- return [
- 'storagepath' => [
- 'input',
- $this->l10n->t('Storage base path'),
- $this->storagePath,
- $this->l10n->t('Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree')
- ]
- ];
- }
-
- /**
- * @inheritDoc
- */
- public function saveOptions(array $data): array
- {
- $storagePath = $data['storagepath'] ?? '';
- if ($storagePath === '' || !is_dir($storagePath)) {
- return [
- 'storagepath' => $this->l10n->t('Enter a valid existing folder')
- ];
- };
- $this->config->set('storage', 'filesystem_path', $storagePath);
- $this->storagePath = $storagePath;
- return [];
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * Interface for basic storage backends
- */
-interface IStorage
-{
- /**
- * Get data from backend
- *
- * @param string $reference Data reference
- *
- * @return string
- *
- * @throws StorageException in case there's an unexpected error
- * @throws ReferenceStorageException in case the reference doesn't exist
- */
- public function get(string $reference): string;
-
- /**
- * The name of the backend
- *
- * @return string
- */
- public function __toString();
-
- /**
- * The name of the backend
- *
- * @return string
- */
- public static function getName(): string;
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * The interface to use for configurable storage backends
- */
-interface IStorageConfiguration
-{
- /**
- * Get info about storage options
- *
- * @return array
- *
- * This method return an array with information about storage options
- * from which the form presented to the user is build.
- *
- * The returned array is:
- *
- * [
- * 'option1name' => [ ..info.. ],
- * 'option2name' => [ ..info.. ],
- * ...
- * ]
- *
- * An empty array can be returned if backend doesn't have any options
- *
- * The info array for each option MUST be as follows:
- *
- * [
- * 'type', // define the field used in form, and the type of data.
- * // one of 'checkbox', 'combobox', 'custom', 'datetime',
- * // 'input', 'intcheckbox', 'password', 'radio', 'richtext'
- * // 'select', 'select_raw', 'textarea'
- *
- * 'label', // Translatable label of the field
- * 'value', // Current value
- * 'help text', // Translatable description for the field
- * extra data // Optional. Depends on 'type':
- * // select: array [ value => label ] of choices
- * // intcheckbox: value of input element
- * // select_raw: prebuild html string of < option > tags
- * ]
- *
- * See https://github.com/friendica/friendica/wiki/Quick-Template-Guide
- */
- public function getOptions(): array;
-
- /**
- * Validate and save options
- *
- * @param array $data Array [optionname => value] to be saved
- *
- * @return array Validation errors: [optionname => error message]
- *
- * Return array must be empty if no error.
- */
- public function saveOptions(array $data): array;
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * Interface for writable storage backends
- *
- * Used for storages with CRUD functionality, mainly used for user data (e.g. photos, attachements).
- * There's only one active writable storage possible. This type of storage is selectable by the current administrator.
- */
-interface IWritableStorage extends IStorage
-{
- /**
- * Put data in backend as $ref. If $ref is not defined a new reference is created.
- *
- * @param string $data Data to save
- * @param string $reference Data reference. Optional.
- *
- * @return string Saved data reference
- *
- * @throws StorageException in case there's an unexpected error
- */
- public function put(string $data, string $reference = ""): string;
-
- /**
- * Remove data from backend
- *
- * @param string $reference Data reference
- *
- * @throws StorageException in case there's an unexpected error
- * @throws ReferenceStorageException in case the reference doesn't exist
- */
- public function delete(string $reference);
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * Storage Exception in case of invalid storage class
- */
-class InvalidClassStorageException extends StorageException
-{
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * Storage Exception in case of invalid references
- */
-class ReferenceStorageException extends StorageException
-{
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-use Exception;
-
-/**
- * Storage Exception for unexpected failures
- */
-class StorageException extends Exception
-{
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * System resource storage class
- *
- * This class is used to load system resources, like images.
- * Is not intended to be selectable by admins as default storage class.
- */
-class SystemResource implements IStorage
-{
- const NAME = 'SystemResource';
-
- // Valid folders to look for resources
- const VALID_FOLDERS = ["images"];
-
- /**
- * @inheritDoc
- */
- public function get(string $reference): string
- {
- $folder = dirname($reference);
- if (!in_array($folder, self::VALID_FOLDERS)) {
- throw new ReferenceStorageException(sprintf('System Resource is invalid for reference %s, no valid folder found', $reference));
- }
- if (!file_exists($reference)) {
- throw new StorageException(sprintf('System Resource is invalid for reference %s, the file doesn\'t exist', $reference));
- }
- $content = file_get_contents($reference);
-
- if ($content === false) {
- throw new StorageException(sprintf('Cannot get content for reference %s', $reference));
- }
-
- return $content;
- }
-
- /**
- * @inheritDoc
- */
- public function __toString()
- {
- return self::NAME;
- }
-
- /**
- * @inheritDoc
- */
- public static function getName(): string
- {
- return self::NAME;
- }
-}
use Friendica\Core\Renderer;
use Friendica\DI;
-use Friendica\Model\Storage\InvalidClassStorageException;
-use Friendica\Model\Storage\IStorageConfiguration;
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Exception\InvalidClassStorageException;
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
use Friendica\Module\BaseAdmin;
use Friendica\Util\Strings;
$storagebackend = Strings::escapeTags(trim($parameters['name'] ?? ''));
try {
- /** @var IStorageConfiguration|false $newStorageConfig */
+ /** @var \Friendica\Core\Storage\Capability\ICanConfigureStorage|false $newStorageConfig */
$newStorageConfig = DI::storageManager()->getConfigurationByName($storagebackend);
} catch (InvalidClassStorageException $storageException) {
notice(DI::l10n()->t('Storage backend, %s is invalid.', $storagebackend));
if (!empty($_POST['submit_save_set'])) {
try {
- /** @var IWritableStorage $newstorage */
+ /** @var \Friendica\Core\Storage\Capability\ICanWriteToStorage $newstorage */
$newstorage = DI::storageManager()->getWritableStorageByName($storagebackend);
if (!DI::storageManager()->setBackend($newstorage)) {
'name' => $name,
'prefix' => $storage_form_prefix,
'form' => $storage_form,
- 'active' => $current_storage_backend instanceof IWritableStorage && $name === $current_storage_backend::getName(),
+ 'active' => $current_storage_backend instanceof ICanWriteToStorage && $name === $current_storage_backend::getName(),
];
}
'$noconfig' => DI::l10n()->t('This backend doesn\'t have custom settings'),
'$baseurl' => DI::baseUrl()->get(true),
'$form_security_token' => self::getFormSecurityToken("admin_storage"),
- '$storagebackend' => $current_storage_backend instanceof IWritableStorage ? $current_storage_backend::getName() : DI::l10n()->t('Database (legacy)'),
+ '$storagebackend' => $current_storage_backend instanceof ICanWriteToStorage ? $current_storage_backend::getName() : DI::l10n()->t('Database (legacy)'),
'$availablestorageforms' => $available_storage_forms,
]);
}
use Friendica\Model\Photo as MPhoto;
use Friendica\Model\Post;
use Friendica\Model\Profile;
-use Friendica\Model\Storage\ExternalResource;
-use Friendica\Model\Storage\SystemResource;
+use Friendica\Core\Storage\Type\ExternalResource;
+use Friendica\Core\Storage\Type\SystemResource;
use Friendica\Model\User;
use Friendica\Network\HTTPException;
use Friendica\Object\Image;
use Friendica\Core\Lock;
use Friendica\Core\Process;
use Friendica\Core\Session\Capability\IHandleSessions;
-use Friendica\Core\StorageManager;
+use Friendica\Core\Storage\Repository\StorageManager;
use Friendica\Database\Database;
use Friendica\Factory;
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
use Friendica\Model\User\Cookie;
use Friendica\Model\Log\ParsedLogIterator;
use Friendica\Network;
$_SERVER, $_COOKIE
],
],
- IWritableStorage::class => [
+ ICanWriteToStorage::class => [
'instanceOf' => StorageManager::class,
'call' => [
['getBackend', [], Dice::CHAIN_CALL],
namespace Friendica\Test\Util;
use Friendica\Core\Hook;
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
use Friendica\Core\L10n;
/**
* A backend storage example class
*/
-class SampleStorageBackend implements IWritableStorage
+class SampleStorageBackend implements ICanWriteToStorage
{
const NAME = 'Sample Storage';
return $this->options;
}
- public function __toString()
+ public function __toString(): string
{
return self::NAME;
}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage;
+
+use Friendica\Core\Config\Factory\Config;
+use Friendica\Core\Storage\Type\Database;
+use Friendica\Test\DatabaseTestTrait;
+use Friendica\Test\Util\Database\StaticDatabase;
+use Friendica\Test\Util\VFSTrait;
+use Friendica\Util\Profiler;
+use Psr\Log\NullLogger;
+
+class DatabaseStorageTest extends StorageTest
+{
+ use DatabaseTestTrait;
+ use VFSTrait;
+
+ protected function setUp(): void
+ {
+ $this->setUpVfsDir();
+
+ $this->setUpDb();
+
+ parent::setUp();
+ }
+
+ protected function getInstance()
+ {
+ $logger = new NullLogger();
+ $profiler = \Mockery::mock(Profiler::class);
+ $profiler->shouldReceive('startRecording');
+ $profiler->shouldReceive('stopRecording');
+ $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
+
+ // load real config to avoid mocking every config-entry which is related to the Database class
+ $configFactory = new Config();
+ $loader = (new Config())->createConfigFileLoader($this->root->url(), []);
+ $configCache = $configFactory->createCache($loader);
+
+ $dba = new StaticDatabase($configCache, $profiler, $logger);
+
+ return new Database($dba);
+ }
+
+ protected function tearDown(): void
+ {
+ $this->tearDownDb();
+
+ parent::tearDown();
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage;
+
+use Friendica\Core\Config\Capability\IManageConfigValues;
+use Friendica\Core\L10n;
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
+use Friendica\Core\Storage\Type\FilesystemConfig;
+use Friendica\Test\Util\VFSTrait;
+use Mockery\MockInterface;
+use org\bovigo\vfs\vfsStream;
+
+class FilesystemStorageConfigTest extends StorageConfigTest
+{
+ use VFSTrait;
+
+ protected function setUp(): void
+ {
+ $this->setUpVfsDir();
+
+ vfsStream::create(['storage' => []], $this->root);
+
+ parent::setUp();
+ }
+
+ protected function getInstance()
+ {
+ /** @var MockInterface|L10n $l10n */
+ $l10n = \Mockery::mock(L10n::class)->makePartial();
+ $config = \Mockery::mock(IManageConfigValues::class);
+ $config->shouldReceive('get')
+ ->with('storage', 'filesystem_path', FilesystemConfig::DEFAULT_BASE_FOLDER)
+ ->andReturn($this->root->getChild('storage')->url());
+
+ return new FilesystemConfig($config, $l10n);
+ }
+
+ protected function assertOption(ICanConfigureStorage $storage)
+ {
+ self::assertEquals([
+ 'storagepath' => [
+ 'input', 'Storage base path',
+ $this->root->getChild('storage')->url(),
+ 'Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree'
+ ]
+ ], $storage->getOptions());
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage;
+
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Type\Filesystem;
+use Friendica\Core\Storage\Type\FilesystemConfig;
+use Friendica\Test\Util\VFSTrait;
+use org\bovigo\vfs\vfsStream;
+
+class FilesystemStorageTest extends StorageTest
+{
+ use VFSTrait;
+
+ protected function setUp(): void
+ {
+ $this->setUpVfsDir();
+
+ vfsStream::create(['storage' => []], $this->root);
+
+ parent::setUp();
+ }
+
+ protected function getInstance()
+ {
+ return new Filesystem($this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->url());
+ }
+
+ /**
+ * Test the exception in case of missing directory permissions during put new files
+ */
+ public function testMissingDirPermissionsDuringPut()
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionMessageMatches("/Filesystem storage failed to create \".*\". Check you write permissions./");
+ $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0777);
+
+ $instance = $this->getInstance();
+
+ $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0000);
+ $instance->put('test');
+ }
+
+ /**
+ * Test the exception in case the directory isn't writeable
+ */
+ public function testMissingDirPermissions()
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionMessageMatches("/Path \".*\" does not exist or is not writeable./");
+ $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0000);
+
+ $this->getInstance();
+ }
+
+ /**
+ * Test the exception in case of missing file permissions
+ *
+ */
+ public function testMissingFilePermissions()
+ {
+ static::markTestIncomplete("Cannot catch file_put_content() error due vfsStream failure");
+
+ $this->expectException(StorageException::class);
+ $this->expectExceptionMessageMatches("/Filesystem storage failed to save data to \".*\". Check your write permissions/");
+
+ vfsStream::create(['storage' => ['f0' => ['c0' => ['k0i0' => '']]]], $this->root);
+
+ $this->root->getChild('storage/f0/c0/k0i0')->chmod(000);
+
+ $instance = $this->getInstance();
+ $instance->put('test', 'f0c0k0i0');
+ }
+
+ /**
+ * Test the backend storage of the Filesystem Storage class
+ */
+ public function testDirectoryTree()
+ {
+ $instance = $this->getInstance();
+
+ $instance->put('test', 'f0c0d0i0');
+
+ $dir = $this->root->getChild('storage/f0/c0')->url();
+ $file = $this->root->getChild('storage/f0/c0/d0i0')->url();
+
+ self::assertDirectoryExists($dir);
+ self::assertFileExists($file);
+
+ self::assertDirectoryIsWritable($dir);
+ self::assertFileIsWritable($file);
+
+ self::assertEquals('test', file_get_contents($file));
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage\Repository;
+
+use Dice\Dice;
+use Friendica\Core\Config\Capability\IManageConfigValues;
+use Friendica\Core\Config\Type\PreloadConfig;
+use Friendica\Core\Hook;
+use Friendica\Core\L10n;
+use Friendica\Core\Session\Capability\IHandleSessions;
+use Friendica\Core\Session\Type\Memory;
+use Friendica\Core\Storage\Exception\InvalidClassStorageException;
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Core\Storage\Repository\StorageManager;
+use Friendica\Core\Storage\Type\Filesystem;
+use Friendica\Core\Storage\Type\SystemResource;
+use Friendica\Database\Database;
+use Friendica\DI;
+use Friendica\Core\Config\Factory\Config;
+use Friendica\Core\Config\Repository;
+use Friendica\Core\Storage\Type;
+use Friendica\Network\HTTPClient;
+use Friendica\Test\DatabaseTest;
+use Friendica\Test\Util\Database\StaticDatabase;
+use Friendica\Test\Util\VFSTrait;
+use Friendica\Util\Profiler;
+use org\bovigo\vfs\vfsStream;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Friendica\Test\Util\SampleStorageBackend;
+
+class StorageManagerTest extends DatabaseTest
+{
+ use VFSTrait;
+ /** @var Database */
+ private $dba;
+ /** @var IManageConfigValues */
+ private $config;
+ /** @var LoggerInterface */
+ private $logger;
+ /** @var L10n */
+ private $l10n;
+ /** @var HTTPClient */
+ private $httpRequest;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->setUpVfsDir();
+
+ vfsStream::newDirectory(Type\FilesystemConfig::DEFAULT_BASE_FOLDER, 0777)->at($this->root);
+
+ $this->logger = new NullLogger();
+
+ $profiler = \Mockery::mock(Profiler::class);
+ $profiler->shouldReceive('startRecording');
+ $profiler->shouldReceive('stopRecording');
+ $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
+
+ // load real config to avoid mocking every config-entry which is related to the Database class
+ $configFactory = new Config();
+ $loader = $configFactory->createConfigFileLoader($this->root->url(), []);
+ $configCache = $configFactory->createCache($loader);
+
+ $this->dba = new StaticDatabase($configCache, $profiler, $this->logger);
+
+ $configModel = new Repository\Config($this->dba);
+ $this->config = new PreloadConfig($configCache, $configModel);
+ $this->config->set('storage', 'name', 'Database');
+ $this->config->set('storage', 'filesystem_path', $this->root->getChild(Type\FilesystemConfig::DEFAULT_BASE_FOLDER)->url());
+
+ $this->l10n = \Mockery::mock(L10n::class);
+
+ $this->httpRequest = \Mockery::mock(HTTPClient::class);
+ }
+
+ protected function tearDown(): void
+ {
+ $this->root->removeChild(Type\FilesystemConfig::DEFAULT_BASE_FOLDER);
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test plain instancing first
+ */
+ public function testInstance()
+ {
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest);
+
+ self::assertInstanceOf(StorageManager::class, $storageManager);
+ }
+
+ public function dataStorages()
+ {
+ return [
+ 'empty' => [
+ 'name' => '',
+ 'valid' => false,
+ 'interface' => ICanReadFromStorage::class,
+ 'assert' => null,
+ 'assertName' => '',
+ ],
+ 'database' => [
+ 'name' => Type\Database::NAME,
+ 'valid' => true,
+ 'interface' => ICanWriteToStorage::class,
+ 'assert' => Type\Database::class,
+ 'assertName' => Type\Database::NAME,
+ ],
+ 'filesystem' => [
+ 'name' => Filesystem::NAME,
+ 'valid' => true,
+ 'interface' => ICanWriteToStorage::class,
+ 'assert' => Filesystem::class,
+ 'assertName' => Filesystem::NAME,
+ ],
+ 'systemresource' => [
+ 'name' => SystemResource::NAME,
+ 'valid' => true,
+ 'interface' => ICanReadFromStorage::class,
+ 'assert' => SystemResource::class,
+ 'assertName' => SystemResource::NAME,
+ ],
+ 'invalid' => [
+ 'name' => 'invalid',
+ 'valid' => false,
+ 'interface' => null,
+ 'assert' => null,
+ 'assertName' => '',
+ 'userBackend' => false,
+ ],
+ ];
+ }
+
+ /**
+ * Test the getByName() method
+ *
+ * @dataProvider dataStorages
+ */
+ public function testGetByName($name, $valid, $interface, $assert, $assertName)
+ {
+ if (!$valid) {
+ $this->expectException(InvalidClassStorageException::class);
+ }
+
+ if ($interface === ICanWriteToStorage::class) {
+ $this->config->set('storage', 'name', $name);
+ }
+
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+ if ($interface === ICanWriteToStorage::class) {
+ $storage = $storageManager->getWritableStorageByName($name);
+ } else {
+ $storage = $storageManager->getByName($name);
+ }
+
+ self::assertInstanceOf($interface, $storage);
+ self::assertInstanceOf($assert, $storage);
+ self::assertEquals($assertName, $storage);
+ }
+
+ /**
+ * Test the isValidBackend() method
+ *
+ * @dataProvider dataStorages
+ */
+ public function testIsValidBackend($name, $valid, $interface, $assert, $assertName)
+ {
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+ // true in every of the backends
+ self::assertEquals(!empty($assertName), $storageManager->isValidBackend($name));
+
+ // if it's a ICanWriteToStorage, the valid backend should return true, otherwise false
+ self::assertEquals($interface === ICanWriteToStorage::class, $storageManager->isValidBackend($name, StorageManager::DEFAULT_BACKENDS));
+ }
+
+ /**
+ * Test the method listBackends() with default setting
+ */
+ public function testListBackends()
+ {
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+ self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
+ }
+
+ /**
+ * Test the method getBackend()
+ *
+ * @dataProvider dataStorages
+ */
+ public function testGetBackend($name, $valid, $interface, $assert, $assertName)
+ {
+ if ($interface !== ICanWriteToStorage::class) {
+ static::markTestSkipped('only works for ICanWriteToStorage');
+ }
+
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+ $selBackend = $storageManager->getWritableStorageByName($name);
+ $storageManager->setBackend($selBackend);
+
+ self::assertInstanceOf($assert, $storageManager->getBackend());
+ }
+
+ /**
+ * Test the method getBackend() with a pre-configured backend
+ *
+ * @dataProvider dataStorages
+ */
+ public function testPresetBackend($name, $valid, $interface, $assert, $assertName)
+ {
+ $this->config->set('storage', 'name', $name);
+ if ($interface !== ICanWriteToStorage::class) {
+ $this->expectException(InvalidClassStorageException::class);
+ }
+
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+ self::assertInstanceOf($assert, $storageManager->getBackend());
+ }
+
+ /**
+ * Tests the register and unregister methods for a new backend storage class
+ *
+ * Uses a sample storage for testing
+ *
+ * @see SampleStorageBackend
+ */
+ public function testRegisterUnregisterBackends()
+ {
+ /// @todo Remove dice once "Hook" is dynamic and mockable
+ $dice = (new Dice())
+ ->addRules(include __DIR__ . '/../../../../../static/dependencies.config.php')
+ ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
+ ->addRule(IHandleSessions::class, ['instanceOf' => Session\Type\Memory::class, 'shared' => true, 'call' => null]);
+ DI::init($dice);
+
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+ self::assertTrue($storageManager->register(SampleStorageBackend::class));
+
+ self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
+ SampleStorageBackend::getName(),
+ ]), $storageManager->listBackends());
+ self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
+ SampleStorageBackend::getName()
+ ]), $this->config->get('storage', 'backends'));
+
+ self::assertTrue($storageManager->unregister(SampleStorageBackend::class));
+ self::assertEquals(StorageManager::DEFAULT_BACKENDS, $this->config->get('storage', 'backends'));
+ self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
+ }
+
+ /**
+ * tests that an active backend cannot get unregistered
+ */
+ public function testUnregisterActiveBackend()
+ {
+ /// @todo Remove dice once "Hook" is dynamic and mockable
+ $dice = (new Dice())
+ ->addRules(include __DIR__ . '/../../../../../static/dependencies.config.php')
+ ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
+ ->addRule(IHandleSessions::class, ['instanceOf' => Memory::class, 'shared' => true, 'call' => null]);
+ DI::init($dice);
+
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+ self::assertTrue($storageManager->register(SampleStorageBackend::class));
+
+ self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
+ SampleStorageBackend::getName(),
+ ]), $storageManager->listBackends());
+ self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
+ SampleStorageBackend::getName()
+ ]), $this->config->get('storage', 'backends'));
+
+ // inline call to register own class as hook (testing purpose only)
+ SampleStorageBackend::registerHook();
+ Hook::loadHooks();
+
+ self::assertTrue($storageManager->setBackend($storageManager->getWritableStorageByName(SampleStorageBackend::NAME)));
+ self::assertEquals(SampleStorageBackend::NAME, $this->config->get('storage', 'name'));
+
+ self::assertInstanceOf(SampleStorageBackend::class, $storageManager->getBackend());
+
+ self::expectException(\Friendica\Core\Storage\Exception\StorageException::class);
+ self::expectExceptionMessage('Cannot unregister Sample Storage, because it\'s currently active.');
+
+ $storageManager->unregister(SampleStorageBackend::class);
+ }
+
+ /**
+ * Test moving data to a new storage (currently testing db & filesystem)
+ *
+ * @dataProvider dataStorages
+ */
+ public function testMoveStorage($name, $valid, $interface, $assert, $assertName)
+ {
+ if ($interface !== ICanWriteToStorage::class) {
+ self::markTestSkipped("No user backend");
+ }
+
+ $this->loadFixture(__DIR__ . '/../../../../datasets/storage/database.fixture.php', $this->dba);
+
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+ $storage = $storageManager->getWritableStorageByName($name);
+ $storageManager->move($storage);
+
+ $photos = $this->dba->select('photo', ['backend-ref', 'backend-class', 'id', 'data']);
+
+ while ($photo = $this->dba->fetch($photos)) {
+ self::assertEmpty($photo['data']);
+
+ $storage = $storageManager->getByName($photo['backend-class']);
+ $data = $storage->get($photo['backend-ref']);
+
+ self::assertNotEmpty($data);
+ }
+ }
+
+ /**
+ * Test moving data to a WRONG storage
+ */
+ public function testWrongWritableStorage()
+ {
+ $this->expectException(InvalidClassStorageException::class);
+ $this->expectExceptionMessage('Backend SystemResource is not valid');
+
+ $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+ $storage = $storageManager->getWritableStorageByName(SystemResource::getName());
+ $storageManager->move($storage);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage;
+
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
+use Friendica\Test\MockedTest;
+
+abstract class StorageConfigTest extends MockedTest
+{
+ /** @return \Friendica\Core\Storage\Capability\ICanConfigureStorage */
+ abstract protected function getInstance();
+
+ abstract protected function assertOption(\Friendica\Core\Storage\Capability\ICanConfigureStorage $storage);
+
+ /**
+ * Test if the "getOption" is asserted
+ */
+ public function testGetOptions()
+ {
+ $instance = $this->getInstance();
+
+ $this->assertOption($instance);
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage;
+
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Test\MockedTest;
+
+abstract class StorageTest extends MockedTest
+{
+ /** @return ICanWriteToStorage */
+ abstract protected function getInstance();
+
+ /**
+ * Test if the instance is "really" implementing the interface
+ */
+ public function testInstance()
+ {
+ $instance = $this->getInstance();
+ self::assertInstanceOf(ICanReadFromStorage::class, $instance);
+ }
+
+ /**
+ * Test basic put, get and delete operations
+ */
+ public function testPutGetDelete()
+ {
+ $instance = $this->getInstance();
+
+ $ref = $instance->put('data12345');
+ self::assertNotEmpty($ref);
+
+ self::assertEquals('data12345', $instance->get($ref));
+
+ $instance->delete($ref);
+ }
+
+ /**
+ * Test a delete with an invalid reference
+ */
+ public function testInvalidDelete()
+ {
+ self::expectException(ReferenceStorageException::class);
+
+ $instance = $this->getInstance();
+
+ $instance->delete(-1234456);
+ }
+
+ /**
+ * Test a get with an invalid reference
+ */
+ public function testInvalidGet()
+ {
+ self::expectException(ReferenceStorageException::class);
+
+ $instance = $this->getInstance();
+
+ $instance->get(-123456);
+ }
+
+ /**
+ * Test an update with a given reference
+ */
+ public function testUpdateReference()
+ {
+ $instance = $this->getInstance();
+
+ $ref = $instance->put('data12345');
+ self::assertNotEmpty($ref);
+
+ self::assertEquals('data12345', $instance->get($ref));
+
+ self::assertEquals($ref, $instance->put('data5432', $ref));
+ self::assertEquals('data5432', $instance->get($ref));
+ }
+
+ /**
+ * Test that an invalid update results in an insert
+ */
+ public function testInvalidUpdate()
+ {
+ $instance = $this->getInstance();
+
+ self::assertEquals(-123, $instance->put('data12345', -123));
+ }
+}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Core;
-
-use Dice\Dice;
-use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\Config\Type\PreloadConfig;
-use Friendica\Core\Hook;
-use Friendica\Core\L10n;
-use Friendica\Core\Session\Capability\IHandleSessions;
-use Friendica\Core\Session\Type\Memory;
-use Friendica\Core\StorageManager;
-use Friendica\Database\Database;
-use Friendica\DI;
-use Friendica\Core\Config\Factory\Config;
-use Friendica\Core\Config\Repository;
-use Friendica\Model\Storage;
-use Friendica\Network\HTTPClient;
-use Friendica\Test\DatabaseTest;
-use Friendica\Test\Util\Database\StaticDatabase;
-use Friendica\Test\Util\VFSTrait;
-use Friendica\Util\Profiler;
-use org\bovigo\vfs\vfsStream;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-use Friendica\Test\Util\SampleStorageBackend;
-
-class StorageManagerTest extends DatabaseTest
-{
- use VFSTrait;
- /** @var Database */
- private $dba;
- /** @var IManageConfigValues */
- private $config;
- /** @var LoggerInterface */
- private $logger;
- /** @var L10n */
- private $l10n;
- /** @var HTTPClient */
- private $httpRequest;
-
- protected function setUp(): void
- {
- parent::setUp();
-
- $this->setUpVfsDir();
-
- vfsStream::newDirectory(Storage\FilesystemConfig::DEFAULT_BASE_FOLDER, 0777)->at($this->root);
-
- $this->logger = new NullLogger();
-
- $profiler = \Mockery::mock(Profiler::class);
- $profiler->shouldReceive('startRecording');
- $profiler->shouldReceive('stopRecording');
- $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
-
- // load real config to avoid mocking every config-entry which is related to the Database class
- $configFactory = new Config();
- $loader = $configFactory->createConfigFileLoader($this->root->url(), []);
- $configCache = $configFactory->createCache($loader);
-
- $this->dba = new StaticDatabase($configCache, $profiler, $this->logger);
-
- $configModel = new Repository\Config($this->dba);
- $this->config = new PreloadConfig($configCache, $configModel);
- $this->config->set('storage', 'name', 'Database');
- $this->config->set('storage', 'filesystem_path', $this->root->getChild(Storage\FilesystemConfig::DEFAULT_BASE_FOLDER)->url());
-
- $this->l10n = \Mockery::mock(L10n::class);
-
- $this->httpRequest = \Mockery::mock(HTTPClient::class);
- }
-
- protected function tearDown(): void
- {
- $this->root->removeChild(Storage\FilesystemConfig::DEFAULT_BASE_FOLDER);
-
- parent::tearDown();
- }
-
- /**
- * Test plain instancing first
- */
- public function testInstance()
- {
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest);
-
- self::assertInstanceOf(StorageManager::class, $storageManager);
- }
-
- public function dataStorages()
- {
- return [
- 'empty' => [
- 'name' => '',
- 'valid' => false,
- 'interface' => Storage\IStorage::class,
- 'assert' => null,
- 'assertName' => '',
- ],
- 'database' => [
- 'name' => Storage\Database::NAME,
- 'valid' => true,
- 'interface' => Storage\IWritableStorage::class,
- 'assert' => Storage\Database::class,
- 'assertName' => Storage\Database::NAME,
- ],
- 'filesystem' => [
- 'name' => Storage\Filesystem::NAME,
- 'valid' => true,
- 'interface' => Storage\IWritableStorage::class,
- 'assert' => Storage\Filesystem::class,
- 'assertName' => Storage\Filesystem::NAME,
- ],
- 'systemresource' => [
- 'name' => Storage\SystemResource::NAME,
- 'valid' => true,
- 'interface' => Storage\IStorage::class,
- 'assert' => Storage\SystemResource::class,
- 'assertName' => Storage\SystemResource::NAME,
- ],
- 'invalid' => [
- 'name' => 'invalid',
- 'valid' => false,
- 'interface' => null,
- 'assert' => null,
- 'assertName' => '',
- 'userBackend' => false,
- ],
- ];
- }
-
- /**
- * Test the getByName() method
- *
- * @dataProvider dataStorages
- */
- public function testGetByName($name, $valid, $interface, $assert, $assertName)
- {
- if (!$valid) {
- $this->expectException(Storage\InvalidClassStorageException::class);
- }
-
- if ($interface === Storage\IWritableStorage::class) {
- $this->config->set('storage', 'name', $name);
- }
-
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
- if ($interface === Storage\IWritableStorage::class) {
- $storage = $storageManager->getWritableStorageByName($name);
- } else {
- $storage = $storageManager->getByName($name);
- }
-
- self::assertInstanceOf($interface, $storage);
- self::assertInstanceOf($assert, $storage);
- self::assertEquals($assertName, $storage);
- }
-
- /**
- * Test the isValidBackend() method
- *
- * @dataProvider dataStorages
- */
- public function testIsValidBackend($name, $valid, $interface, $assert, $assertName)
- {
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
- // true in every of the backends
- self::assertEquals(!empty($assertName), $storageManager->isValidBackend($name));
-
- // if it's a IWritableStorage, the valid backend should return true, otherwise false
- self::assertEquals($interface === Storage\IWritableStorage::class, $storageManager->isValidBackend($name, StorageManager::DEFAULT_BACKENDS));
- }
-
- /**
- * Test the method listBackends() with default setting
- */
- public function testListBackends()
- {
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
- self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
- }
-
- /**
- * Test the method getBackend()
- *
- * @dataProvider dataStorages
- */
- public function testGetBackend($name, $valid, $interface, $assert, $assertName)
- {
- if ($interface !== Storage\IWritableStorage::class) {
- static::markTestSkipped('only works for IWritableStorage');
- }
-
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
- $selBackend = $storageManager->getWritableStorageByName($name);
- $storageManager->setBackend($selBackend);
-
- self::assertInstanceOf($assert, $storageManager->getBackend());
- }
-
- /**
- * Test the method getBackend() with a pre-configured backend
- *
- * @dataProvider dataStorages
- */
- public function testPresetBackend($name, $valid, $interface, $assert, $assertName)
- {
- $this->config->set('storage', 'name', $name);
- if ($interface !== Storage\IWritableStorage::class) {
- $this->expectException(Storage\InvalidClassStorageException::class);
- }
-
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
- self::assertInstanceOf($assert, $storageManager->getBackend());
- }
-
- /**
- * Tests the register and unregister methods for a new backend storage class
- *
- * Uses a sample storage for testing
- *
- * @see SampleStorageBackend
- */
- public function testRegisterUnregisterBackends()
- {
- /// @todo Remove dice once "Hook" is dynamic and mockable
- $dice = (new Dice())
- ->addRules(include __DIR__ . '/../../../static/dependencies.config.php')
- ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
- ->addRule(IHandleSessions::class, ['instanceOf' => Session\Type\Memory::class, 'shared' => true, 'call' => null]);
- DI::init($dice);
-
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
- self::assertTrue($storageManager->register(SampleStorageBackend::class));
-
- self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
- SampleStorageBackend::getName(),
- ]), $storageManager->listBackends());
- self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
- SampleStorageBackend::getName()
- ]), $this->config->get('storage', 'backends'));
-
- self::assertTrue($storageManager->unregister(SampleStorageBackend::class));
- self::assertEquals(StorageManager::DEFAULT_BACKENDS, $this->config->get('storage', 'backends'));
- self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
- }
-
- /**
- * tests that an active backend cannot get unregistered
- */
- public function testUnregisterActiveBackend()
- {
- /// @todo Remove dice once "Hook" is dynamic and mockable
- $dice = (new Dice())
- ->addRules(include __DIR__ . '/../../../static/dependencies.config.php')
- ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
- ->addRule(IHandleSessions::class, ['instanceOf' => Memory::class, 'shared' => true, 'call' => null]);
- DI::init($dice);
-
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
- self::assertTrue($storageManager->register(SampleStorageBackend::class));
-
- self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
- SampleStorageBackend::getName(),
- ]), $storageManager->listBackends());
- self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
- SampleStorageBackend::getName()
- ]), $this->config->get('storage', 'backends'));
-
- // inline call to register own class as hook (testing purpose only)
- SampleStorageBackend::registerHook();
- Hook::loadHooks();
-
- self::assertTrue($storageManager->setBackend($storageManager->getWritableStorageByName(SampleStorageBackend::NAME)));
- self::assertEquals(SampleStorageBackend::NAME, $this->config->get('storage', 'name'));
-
- self::assertInstanceOf(SampleStorageBackend::class, $storageManager->getBackend());
-
- self::expectException(Storage\StorageException::class);
- self::expectExceptionMessage('Cannot unregister Sample Storage, because it\'s currently active.');
-
- $storageManager->unregister(SampleStorageBackend::class);
- }
-
- /**
- * Test moving data to a new storage (currently testing db & filesystem)
- *
- * @dataProvider dataStorages
- */
- public function testMoveStorage($name, $valid, $interface, $assert, $assertName)
- {
- if ($interface !== Storage\IWritableStorage::class) {
- self::markTestSkipped("No user backend");
- }
-
- $this->loadFixture(__DIR__ . '/../../datasets/storage/database.fixture.php', $this->dba);
-
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
- $storage = $storageManager->getWritableStorageByName($name);
- $storageManager->move($storage);
-
- $photos = $this->dba->select('photo', ['backend-ref', 'backend-class', 'id', 'data']);
-
- while ($photo = $this->dba->fetch($photos)) {
- self::assertEmpty($photo['data']);
-
- $storage = $storageManager->getByName($photo['backend-class']);
- $data = $storage->get($photo['backend-ref']);
-
- self::assertNotEmpty($data);
- }
- }
-
- /**
- * Test moving data to a WRONG storage
- */
- public function testWrongWritableStorage()
- {
- $this->expectException(Storage\InvalidClassStorageException::class);
- $this->expectExceptionMessage('Backend SystemResource is not valid');
-
- $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
- $storage = $storageManager->getWritableStorageByName(Storage\SystemResource::getName());
- $storageManager->move($storage);
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Model\Storage;
-
-use Friendica\Core\Config\Factory\Config;
-use Friendica\Model\Storage\Database;
-use Friendica\Test\DatabaseTestTrait;
-use Friendica\Test\Util\Database\StaticDatabase;
-use Friendica\Test\Util\VFSTrait;
-use Friendica\Util\Profiler;
-use Psr\Log\NullLogger;
-
-class DatabaseStorageTest extends StorageTest
-{
- use DatabaseTestTrait;
- use VFSTrait;
-
- protected function setUp(): void
- {
- $this->setUpVfsDir();
-
- $this->setUpDb();
-
- parent::setUp();
- }
-
- protected function getInstance()
- {
- $logger = new NullLogger();
- $profiler = \Mockery::mock(Profiler::class);
- $profiler->shouldReceive('startRecording');
- $profiler->shouldReceive('stopRecording');
- $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
-
- // load real config to avoid mocking every config-entry which is related to the Database class
- $configFactory = new Config();
- $loader = (new Config())->createConfigFileLoader($this->root->url(), []);
- $configCache = $configFactory->createCache($loader);
-
- $dba = new StaticDatabase($configCache, $profiler, $logger);
-
- return new Database($dba);
- }
-
- protected function tearDown(): void
- {
- $this->tearDownDb();
-
- parent::tearDown();
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Model\Storage;
-
-use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\L10n;
-use Friendica\Model\Storage\FilesystemConfig;
-use Friendica\Model\Storage\IStorageConfiguration;
-use Friendica\Test\Util\VFSTrait;
-use Mockery\MockInterface;
-use org\bovigo\vfs\vfsStream;
-
-class FilesystemStorageConfigTest extends StorageConfigTest
-{
- use VFSTrait;
-
- protected function setUp(): void
- {
- $this->setUpVfsDir();
-
- vfsStream::create(['storage' => []], $this->root);
-
- parent::setUp();
- }
-
- protected function getInstance()
- {
- /** @var MockInterface|L10n $l10n */
- $l10n = \Mockery::mock(L10n::class)->makePartial();
- $config = \Mockery::mock(IManageConfigValues::class);
- $config->shouldReceive('get')
- ->with('storage', 'filesystem_path', FilesystemConfig::DEFAULT_BASE_FOLDER)
- ->andReturn($this->root->getChild('storage')->url());
-
- return new FilesystemConfig($config, $l10n);
- }
-
- protected function assertOption(IStorageConfiguration $storage)
- {
- self::assertEquals([
- 'storagepath' => [
- 'input', 'Storage base path',
- $this->root->getChild('storage')->url(),
- 'Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree'
- ]
- ], $storage->getOptions());
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Model\Storage;
-
-use Friendica\Model\Storage\Filesystem;
-use Friendica\Model\Storage\FilesystemConfig;
-use Friendica\Model\Storage\StorageException;
-use Friendica\Test\Util\VFSTrait;
-use org\bovigo\vfs\vfsStream;
-
-class FilesystemStorageTest extends StorageTest
-{
- use VFSTrait;
-
- protected function setUp(): void
- {
- $this->setUpVfsDir();
-
- vfsStream::create(['storage' => []], $this->root);
-
- parent::setUp();
- }
-
- protected function getInstance()
- {
- return new Filesystem($this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->url());
- }
-
- /**
- * Test the exception in case of missing directory permissions during put new files
- */
- public function testMissingDirPermissionsDuringPut()
- {
- $this->expectException(StorageException::class);
- $this->expectExceptionMessageMatches("/Filesystem storage failed to create \".*\". Check you write permissions./");
- $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0777);
-
- $instance = $this->getInstance();
-
- $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0000);
- $instance->put('test');
- }
-
- /**
- * Test the exception in case the directory isn't writeable
- */
- public function testMissingDirPermissions()
- {
- $this->expectException(StorageException::class);
- $this->expectExceptionMessageMatches("/Path \".*\" does not exist or is not writeable./");
- $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0000);
-
- $this->getInstance();
- }
-
- /**
- * Test the exception in case of missing file permissions
- *
- */
- public function testMissingFilePermissions()
- {
- static::markTestIncomplete("Cannot catch file_put_content() error due vfsStream failure");
-
- $this->expectException(StorageException::class);
- $this->expectExceptionMessageMatches("/Filesystem storage failed to save data to \".*\". Check your write permissions/");
-
- vfsStream::create(['storage' => ['f0' => ['c0' => ['k0i0' => '']]]], $this->root);
-
- $this->root->getChild('storage/f0/c0/k0i0')->chmod(000);
-
- $instance = $this->getInstance();
- $instance->put('test', 'f0c0k0i0');
- }
-
- /**
- * Test the backend storage of the Filesystem Storage class
- */
- public function testDirectoryTree()
- {
- $instance = $this->getInstance();
-
- $instance->put('test', 'f0c0d0i0');
-
- $dir = $this->root->getChild('storage/f0/c0')->url();
- $file = $this->root->getChild('storage/f0/c0/d0i0')->url();
-
- self::assertDirectoryExists($dir);
- self::assertFileExists($file);
-
- self::assertDirectoryIsWritable($dir);
- self::assertFileIsWritable($file);
-
- self::assertEquals('test', file_get_contents($file));
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Model\Storage;
-
-use Friendica\Model\Storage\IStorageConfiguration;
-use Friendica\Test\MockedTest;
-
-abstract class StorageConfigTest extends MockedTest
-{
- /** @return IStorageConfiguration */
- abstract protected function getInstance();
-
- abstract protected function assertOption(IStorageConfiguration $storage);
-
- /**
- * Test if the "getOption" is asserted
- */
- public function testGetOptions()
- {
- $instance = $this->getInstance();
-
- $this->assertOption($instance);
- }
-}
+++ /dev/null
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Model\Storage;
-
-use Friendica\Model\Storage\IWritableStorage;
-use Friendica\Model\Storage\IStorage;
-use Friendica\Model\Storage\ReferenceStorageException;
-use Friendica\Test\MockedTest;
-
-abstract class StorageTest extends MockedTest
-{
- /** @return IWritableStorage */
- abstract protected function getInstance();
-
- /**
- * Test if the instance is "really" implementing the interface
- */
- public function testInstance()
- {
- $instance = $this->getInstance();
- self::assertInstanceOf(IStorage::class, $instance);
- }
-
- /**
- * Test basic put, get and delete operations
- */
- public function testPutGetDelete()
- {
- $instance = $this->getInstance();
-
- $ref = $instance->put('data12345');
- self::assertNotEmpty($ref);
-
- self::assertEquals('data12345', $instance->get($ref));
-
- $instance->delete($ref);
- }
-
- /**
- * Test a delete with an invalid reference
- */
- public function testInvalidDelete()
- {
- self::expectException(ReferenceStorageException::class);
-
- $instance = $this->getInstance();
-
- $instance->delete(-1234456);
- }
-
- /**
- * Test a get with an invalid reference
- */
- public function testInvalidGet()
- {
- self::expectException(ReferenceStorageException::class);
-
- $instance = $this->getInstance();
-
- $instance->get(-123456);
- }
-
- /**
- * Test an update with a given reference
- */
- public function testUpdateReference()
- {
- $instance = $this->getInstance();
-
- $ref = $instance->put('data12345');
- self::assertNotEmpty($ref);
-
- self::assertEquals('data12345', $instance->get($ref));
-
- self::assertEquals($ref, $instance->put('data5432', $ref));
- self::assertEquals('data5432', $instance->get($ref));
- }
-
- /**
- * Test that an invalid update results in an insert
- */
- public function testInvalidUpdate()
- {
- $instance = $this->getInstance();
-
- self::assertEquals(-123, $instance->put('data12345', -123));
- }
-}
*/
use Friendica\Core\Logger;
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
use Friendica\Core\Update;
use Friendica\Core\Worker;
use Friendica\Database\Database;
use Friendica\Model\Photo;
use Friendica\Model\Post;
use Friendica\Model\Profile;
-use Friendica\Model\Storage;
use Friendica\Security\PermissionSet\Repository\PermissionSet;
use Friendica\Worker\Delivery;
// set the name of the storage instead of the classpath as config
if (!empty($currStorage)) {
- /** @var Storage\IStorage $currStorage */
+ /** @var ICanReadFromStorage $currStorage */
if (!DI::config()->set('storage', 'name', $currStorage::getName())) {
return Update::FAILED;
}
// in case of an empty config, set "Database" as default storage backend
if (empty($name)) {
- DI::config()->set('storage', 'name', Storage\Database::getName());
+ DI::config()->set('storage', 'name', \Friendica\Core\Storage\Type\Database::getName());
}
// In case of a Using deprecated storage class value, set the right name for it