"ezyang/htmlpurifier": "^4.7",
"friendica/json-ld": "^1.0",
"league/html-to-markdown": "^4.8",
- "level-2/dice": "^4",
+ "level-2/dice": "dev-master",
"lightopenid/lightopenid": "dev-master",
"michelf/php-markdown": "^1.7",
"mobiledetect/mobiledetectlib": "^2.8",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "34ad225ce21474eb84ce78047d9f2c01",
+ "content-hash": "bf05cd52bc7307f45aff80f1d1fd8214",
"packages": [
{
"name": "asika/simple-console",
"jsonld.php"
]
},
+ "notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "A JSON-LD Processor and API implementation in PHP.",
"homepage": "https://git.friendi.ca/friendica/php-json-ld",
"keywords": [
- "JSON",
"JSON-LD",
"Linked Data",
"RDF",
"Semantic Web",
+ "json",
"jsonld"
],
"time": "2018-10-08T20:41:00+00:00"
},
{
"name": "level-2/dice",
- "version": "4.0.1",
+ "version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/Level-2/Dice.git",
- "reference": "e631f110f0520294fec902814c61cac26566023c"
+ "reference": "2fea2749a625c3adcc29c402218b0dcaed11586f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Level-2/Dice/zipball/e631f110f0520294fec902814c61cac26566023c",
- "reference": "e631f110f0520294fec902814c61cac26566023c",
+ "url": "https://api.github.com/repos/Level-2/Dice/zipball/2fea2749a625c3adcc29c402218b0dcaed11586f",
+ "reference": "2fea2749a625c3adcc29c402218b0dcaed11586f",
"shasum": ""
},
"require": {
"di",
"ioc"
],
- "time": "2019-05-01T12:55:36+00:00"
+ "time": "2019-10-03T16:08:46+00:00"
},
{
"name": "lightopenid/lightopenid",
"require": {
"npm-asset/ev-emitter": ">=1.0.0,<2.0.0"
},
+ "require-dev": {
+ "npm-asset/chalk": ">=1.1.1,<2.0.0",
+ "npm-asset/cheerio": ">=0.19.0,<0.20.0",
+ "npm-asset/gulp": ">=3.9.0,<4.0.0",
+ "npm-asset/gulp-jshint": ">=1.11.2,<2.0.0",
+ "npm-asset/gulp-json-lint": ">=0.1.0,<0.2.0",
+ "npm-asset/gulp-rename": ">=1.2.2,<2.0.0",
+ "npm-asset/gulp-replace": ">=0.5.4,<0.6.0",
+ "npm-asset/gulp-requirejs-optimize": "dev-github:metafizzy/gulp-requirejs-optimize",
+ "npm-asset/gulp-uglify": ">=1.4.2,<2.0.0",
+ "npm-asset/gulp-util": ">=3.0.7,<4.0.0",
+ "npm-asset/highlight.js": ">=8.9.1,<9.0.0",
+ "npm-asset/marked": ">=0.3.5,<0.4.0",
+ "npm-asset/minimist": ">=1.2.0,<2.0.0",
+ "npm-asset/transfob": ">=1.0.0,<2.0.0"
+ },
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
"url": "https://registry.npmjs.org/jgrowl/-/jgrowl-1.4.6.tgz",
"shasum": "2736e332aaee73ccf0a14a5f0066391a0a13f4a3"
},
+ "require-dev": {
+ "npm-asset/grunt": "~0.4.2",
+ "npm-asset/grunt-contrib-cssmin": "~0.9.0",
+ "npm-asset/grunt-contrib-jshint": "~0.6.3",
+ "npm-asset/grunt-contrib-less": "~0.11.0",
+ "npm-asset/grunt-contrib-uglify": "~0.4.0",
+ "npm-asset/grunt-contrib-watch": "~0.6.1"
+ },
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
"url": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz",
"shasum": "2c89d6889b5eac522a7eea32c14521559c6cbf02"
},
+ "require-dev": {
+ "npm-asset/commitplease": "2.0.0",
+ "npm-asset/core-js": "0.9.17",
+ "npm-asset/grunt": "0.4.5",
+ "npm-asset/grunt-babel": "5.0.1",
+ "npm-asset/grunt-cli": "0.1.13",
+ "npm-asset/grunt-compare-size": "0.4.0",
+ "npm-asset/grunt-contrib-jshint": "0.11.2",
+ "npm-asset/grunt-contrib-uglify": "0.9.2",
+ "npm-asset/grunt-contrib-watch": "0.6.1",
+ "npm-asset/grunt-git-authors": "2.0.1",
+ "npm-asset/grunt-jscs": "2.1.0",
+ "npm-asset/grunt-jsonlint": "1.0.4",
+ "npm-asset/grunt-npmcopy": "0.1.0",
+ "npm-asset/gzip-js": "0.3.2",
+ "npm-asset/jsdom": "5.6.1",
+ "npm-asset/load-grunt-tasks": "1.0.0",
+ "npm-asset/qunit-assert-step": "1.0.3",
+ "npm-asset/qunitjs": "1.17.1",
+ "npm-asset/requirejs": "2.1.17",
+ "npm-asset/sinon": "1.10.3",
+ "npm-asset/sizzle": "2.2.1",
+ "npm-asset/strip-json-comments": "1.0.3",
+ "npm-asset/testswarm": "1.1.0",
+ "npm-asset/win-spawn": "2.0.0"
+ },
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
"url": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz",
"shasum": "06f0335f16e353a695e7206bf50503cb523a6ee5"
},
+ "require-dev": {
+ "npm-asset/grunt": "~0.4.1",
+ "npm-asset/grunt-contrib-connect": "~0.5.0",
+ "npm-asset/grunt-contrib-jshint": "~0.7.1",
+ "npm-asset/grunt-contrib-uglify": "~0.2.7"
+ },
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
"authors": [
{
"name": "Sebastian Bergmann",
- "role": "lead",
- "email": "sb@sebastian-bergmann.de"
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
"authors": [
{
"name": "Sebastian Bergmann",
- "role": "lead",
- "email": "sebastian@phpunit.de"
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
}
],
"description": "The PHP Unit Testing framework.",
}
],
"description": "Provides the functionality to compare PHP values for equality",
- "homepage": "http://www.github.com/sebastianbergmann/comparator",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
- "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "homepage": "https://github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
}
],
"description": "Provides the functionality to export PHP variables for visualization",
- "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "homepage": "https://github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
}
],
"description": "Snapshotting of global state",
- "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "homepage": "https://github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
}
],
"description": "Provides functionality to recursively process PHP variables",
- "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
"time": "2016-11-19T07:33:16+00:00"
},
{
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
+ "level-2/dice": 20,
"lightopenid/lightopenid": 20
},
"prefer-stable": false,
* Author: Alice <https://alice.social/~alice>
*/
-use Friendica\Core\StorageManager;
use Friendica\Addon\samplestorage\SampleStorageBackend;
+use Friendica\DI;
function samplestorage_install()
{
// on addon install, we register our class with name "Sample Storage".
// note: we use `::class` property, which returns full class name as string
// this save us the problem of correctly escape backslashes in class name
- StorageManager::register("Sample Storage", SampleStorageBackend::class);
+ DI::facStorage()->register("Sample Storage", SampleStorageBackend::class);
}
function samplestorage_unistall()
{
// when the plugin is uninstalled, we unregister the backend.
- StorageManager::unregister("Sample Storage");
+ DI::facStorage()->unregister("Sample Storage");
}
```
{
protected $helpOptions = ['h', 'help', '?'];
+ /** @var StorageManager */
+ private $storageManager;
+
+ /**
+ * @param StorageManager $storageManager
+ */
+ public function __construct(StorageManager $storageManager, array $argv = [])
+ {
+ parent::__construct($argv);
+
+ $this->storageManager = $storageManager;
+ }
+
protected function getHelp()
{
$help = <<<HELP
protected function doList()
{
$rowfmt = ' %-3s | %-20s';
- $current = StorageManager::getBackend();
+ $current = $this->storageManager->getBackend();
$this->out(sprintf($rowfmt, 'Sel', 'Name'));
$this->out('-----------------------');
$isregisterd = false;
- foreach (StorageManager::listBackends() as $name => $class) {
+ foreach ($this->storageManager->listBackends() as $name => $class) {
$issel = ' ';
if ($current === $class) {
$issel = '*';
}
$name = $this->args[1];
- $class = StorageManager::getByName($name);
+ $class = $this->storageManager->getByName($name);
if ($class === '') {
$this->out($name . ' is not a registered backend.');
return -1;
}
- if (!StorageManager::setBackend($class)) {
+ if (!$this->storageManager->setBackend($class)) {
$this->out($class . ' is not a valid backend storage class.');
return -1;
}
$tables = [$table];
}
- $current = StorageManager::getBackend();
+ $current = $this->storageManager->getBackend();
$total = 0;
do {
- $moved = StorageManager::move($current, $tables, $this->getOption('n', 5000));
+ $moved = $this->storageManager->move($current, $tables, $this->getOption('n', 5000));
if ($moved) {
$this->out(date('[Y-m-d H:i:s] ') . sprintf('Moved %d files', $moved));
}
namespace Friendica\Core;
-use Friendica\Database\DBA;
-use Friendica\Model\Storage\IStorage;
+use Dice\Dice;
+use Exception;
+use Friendica\Core\Config\IConfiguration;
+use Friendica\Database\Database;
+use Friendica\Model\Storage;
+use Psr\Log\LoggerInterface;
/**
*/
class StorageManager
{
- private static $default_backends = [
- 'Filesystem' => \Friendica\Model\Storage\Filesystem::class,
- 'Database' => \Friendica\Model\Storage\Database::class,
+ // Default tables to look for data
+ const TABLES = ['photo', 'attach'];
+
+ // Default storage backends
+ const DEFAULT_BACKENDS = [
+ Storage\Filesystem::NAME => Storage\Filesystem::class,
+ Storage\Database::NAME => Storage\Database::class,
];
- private static $backends = [];
+ private $backends = [];
+
+ /** @var Database */
+ private $dba;
+ /** @var IConfiguration */
+ private $config;
+ /** @var LoggerInterface */
+ private $logger;
+ /** @var Dice */
+ private $dice;
+
+ /** @var Storage\IStorage */
+ private $currentBackend;
- private static function setup()
+ /**
+ * @param Database $dba
+ * @param IConfiguration $config
+ * @param LoggerInterface $logger
+ */
+ public function __construct(Database $dba, IConfiguration $config, LoggerInterface $logger, Dice $dice)
{
- if (count(self::$backends) == 0) {
- self::$backends = Config::get('storage', 'backends', self::$default_backends);
+ $this->dba = $dba;
+ $this->config = $config;
+ $this->logger = $logger;
+ $this->dice = $dice;
+ $this->backends = $config->get('storage', 'backends', self::DEFAULT_BACKENDS);
+
+ $currentName = $this->config->get('storage', 'name', '');
+
+ if ($this->isValidBackend($currentName)) {
+ $this->currentBackend = $this->dice->create($this->backends[$currentName]);
+ } else {
+ $this->currentBackend = null;
}
}
/**
* @brief Return current storage backend class
*
- * @return string
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ * @return Storage\IStorage|null
*/
- public static function getBackend()
+ public function getBackend()
{
- return Config::get('storage', 'class', '');
+ return $this->currentBackend;
}
/**
* @brief Return storage backend class by registered name
*
- * @param string $name Backend name
- * @return string Empty if no backend registered at $name exists
+ * @param string $name Backend name
+ *
+ * @return Storage\IStorage|null null if no backend registered at $name
+ */
+ public function getByName(string $name)
+ {
+ if (!$this->isValidBackend($name)) {
+ return null;
+ }
+
+ return $this->dice->create($this->backends[$name]);
+ }
+
+ /**
+ * Checks, if the storage is a valid backend
+ *
+ * @param string $name The name or class of the backend
+ *
+ * @return boolean True, if the backend is a valid backend
*/
- public static function getByName($name)
+ public function isValidBackend(string $name)
{
- self::setup();
- return self::$backends[$name] ?? '';
+ return array_key_exists($name, $this->backends);
}
/**
* @brief Set current storage backend class
*
- * @param string $class Backend class name
- * @return bool
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ * @param string $name Backend class name
+ *
+ * @return boolean True, if the set was successful
*/
- public static function setBackend($class)
+ public function setBackend(string $name)
{
- if (!in_array('Friendica\Model\Storage\IStorage', class_implements($class))) {
+ if (!$this->isValidBackend($name)) {
return false;
}
- Config::set('storage', 'class', $class);
-
- return true;
+ if ($this->config->set('storage', 'name', $name)) {
+ $this->currentBackend = $this->dice->create($this->backends[$name]);
+ return true;
+ } else {
+ return false;
+ }
}
/**
*
* @return array
*/
- public static function listBackends()
+ public function listBackends()
{
- self::setup();
- return self::$backends;
+ return $this->backends;
}
-
/**
* @brief Register a storage backend class
*
* @param string $name User readable backend name
* @param string $class Backend class name
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ *
+ * @return boolean True, if the registration was successful
*/
- public static function register($name, $class)
+ public function register(string $name, string $class)
{
- /// @todo Check that $class implements IStorage
- self::setup();
- self::$backends[$name] = $class;
- Config::set('storage', 'backends', self::$backends);
- }
+ if (!is_subclass_of($class, Storage\IStorage::class)) {
+ return false;
+ }
+ $backends = $this->backends;
+ $backends[$name] = $class;
+
+ if ($this->config->set('storage', 'backends', $this->backends)) {
+ $this->backends = $backends;
+ return true;
+ } else {
+ return false;
+ }
+ }
/**
* @brief Unregister a storage backend class
*
* @param string $name User readable backend name
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ *
+ * @return boolean True, if unregistering was successful
*/
- public static function unregister($name)
+ public function unregister(string $name)
{
- self::setup();
- unset(self::$backends[$name]);
- Config::set('storage', 'backends', self::$backends);
+ unset($this->backends[$name]);
+ return $this->config->set('storage', 'backends', $this->backends);
}
-
/**
* @brief 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 string $destination Storage class name
- * @param array|null $tables Tables to look in for resources. Optional, defaults to ['photo', 'attach']
- * @param int $limit Limit of the process batch size, defaults to 5000
+ * @param Storage\IStorage $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 \Exception
+ * @throws Storage\StorageException
+ * @throws Exception
*/
- public static function move($destination, $tables = null, $limit = 5000)
+ public function move(Storage\IStorage $destination, array $tables = self::TABLES, int $limit = 5000)
{
- if (empty($destination)) {
- throw new \Exception('Can\'t move to NULL storage backend');
- }
-
- if (is_null($tables)) {
- $tables = ['photo', 'attach'];
+ if ($destination === null) {
+ throw new Storage\StorageException('Can\'t move to NULL storage backend');
}
$moved = 0;
foreach ($tables as $table) {
// Get the rows where backend class is not the destination backend class
- $resources = DBA::select(
- $table,
+ $resources = $this->dba->select(
+ $table,
['id', 'data', 'backend-class', 'backend-ref'],
['`backend-class` IS NULL or `backend-class` != ?', $destination],
['limit' => $limit]
);
- while ($resource = DBA::fetch($resources)) {
- $id = $resource['id'];
- $data = $resource['data'];
- /** @var IStorage $backendClass */
- $backendClass = $resource['backend-class'];
- $backendRef = $resource['backend-ref'];
- if (!empty($backendClass)) {
- Logger::log("get data from old backend " . $backendClass . " : " . $backendRef);
- $data = $backendClass::get($backendRef);
+ while ($resource = $this->dba->fetch($resources)) {
+ $id = $resource['id'];
+ $data = $resource['data'];
+ $source = $this->getByName($resource['backend-class']);
+ $sourceRef = $resource['backend-ref'];
+
+ if (!empty($source)) {
+ $this->logger->info('Get data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
+ $data = $source->get($sourceRef);
}
- Logger::log("save data to new backend " . $destination);
- /** @var IStorage $destination */
- $ref = $destination::put($data);
- Logger::log("saved data as " . $ref);
-
- if ($ref !== '') {
- Logger::log("update row");
- if (DBA::update($table, ['backend-class' => $destination, 'backend-ref' => $ref, 'data' => ''], ['id' => $id])) {
- if (!empty($backendClass)) {
- Logger::log("delete data from old backend " . $backendClass . " : " . $backendRef);
- $backendClass::delete($backendRef);
+ $this->logger->info('Save data to new backend.', ['newBackend' => $destination]);
+ $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, '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++;
}
}
}
- DBA::close($resources);
+ $this->dba->close($resources);
}
return $moved;
}
}
-
* @method static Core\L10n\L10n l10n()
* @method static Core\Process process()
* @method static Core\Session\ISession session()
+ * @method static Core\StorageManager facStorage()
* @method static Database\Database dba()
* @method static Factory\Mastodon\Account mstdnAccount()
* @method static Factory\Mastodon\FollowRequest mstdnFollowRequest()
* @method static Model\User\Cookie cookie()
* @method static Model\Notify notify()
* @method static Repository\Introduction intro()
+ * @method static Model\Storage\IStorage storage()
* @method static Protocol\Activity activity()
* @method static Util\ACLFormatter aclFormatter()
* @method static Util\DateTimeFormat dtFormat()
'lock' => Core\Lock\ILock::class,
'process' => Core\Process::class,
'session' => Core\Session\ISession::class,
+ 'facStorage' => Core\StorageManager::class,
'dba' => Database\Database::class,
'mstdnAccount' => Factory\Mastodon\Account::class,
'mstdnFollowRequest' => Factory\Mastodon\FollowRequest::class,
'mstdnRelationship' => Factory\Mastodon\Relationship::class,
'cookie' => Model\User\Cookie::class,
'notify' => Model\Notify::class,
+ 'storage' => Model\Storage\IStorage::class,
'intro' => Repository\Introduction::class,
'activity' => Protocol\Activity::class,
'aclFormatter' => Util\ACLFormatter::class,
$filesize = strlen($data);
}
- /** @var IStorage $backend_class */
- $backend_class = StorageManager::getBackend();
- $backend_ref = '';
- if ($backend_class !== '') {
- $backend_ref = $backend_class::put($data);
- $data = '';
- }
+ $backend_ref = DI::storage()->put($data);
+ $data = '';
$hash = System::createGUID(64);
$created = DateTimeFormat::utcNow();
'allow_gid' => $allow_gid,
'deny_cid' => $deny_cid,
'deny_gid' => $deny_gid,
- 'backend-class' => $backend_class,
+ 'backend-class' => (string)DI::storage(),
'backend-ref' => $backend_ref
];
$data = "";
$backend_ref = "";
- /** @var IStorage $backend_class */
if (DBA::isResult($existing_photo)) {
$backend_ref = (string)$existing_photo["backend-ref"];
- $backend_class = (string)$existing_photo["backend-class"];
+ $storage = DI::facStorage()->getByName((string)$existing_photo["backend-class"]);
} else {
- $backend_class = StorageManager::getBackend();
+ $storage = DI::storage();
}
- if ($backend_class === "") {
+ if ($storage === null) {
$data = $Image->asString();
} else {
- $backend_ref = $backend_class::put($Image->asString(), $backend_ref);
+ $backend_ref = $storage->put($Image->asString(), $backend_ref);
}
"deny_cid" => $deny_cid,
"deny_gid" => $deny_gid,
"desc" => $desc,
- "backend-class" => $backend_class,
+ "backend-class" => (string)$storage,
"backend-ref" => $backend_ref
];
namespace Friendica\Model\Storage;
-use Friendica\Core\Logger;
use Friendica\Core\L10n;
-use Friendica\Database\DBA;
+use Psr\Log\LoggerInterface;
/**
* @brief Database based storage system
*/
class Database implements IStorage
{
- public static function get($ref)
+ const NAME = 'Database';
+
+ /** @var \Friendica\Database\Database */
+ private $dba;
+ /** @var LoggerInterface */
+ private $logger;
+ /** @var L10n\L10n */
+ private $l10n;
+
+ /**
+ * @param \Friendica\Database\Database $dba
+ * @param LoggerInterface $logger
+ * @param L10n\L10n $l10n
+ */
+ public function __construct(\Friendica\Database\Database $dba, LoggerInterface $logger, L10n\L10n $l10n)
+ {
+ $this->dba = $dba;
+ $this->logger = $logger;
+ $this->l10n = $l10n;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function get(string $reference)
{
- $r = DBA::selectFirst('storage', ['data'], ['id' => $ref]);
- if (!DBA::isResult($r)) {
+ $result = $this->dba->selectFirst('storage', ['data'], ['id' => $reference]);
+ if (!$this->dba->isResult($result)) {
return '';
}
- return $r['data'];
+ return $result['data'];
}
- public static function put($data, $ref = '')
+ /**
+ * @inheritDoc
+ */
+ public function put(string $data, string $reference = '')
{
- if ($ref !== '') {
- $r = DBA::update('storage', ['data' => $data], ['id' => $ref]);
- if ($r === false) {
- Logger::log('Failed to update data with id ' . $ref . ': ' . DBA::errorNo() . ' : ' . DBA::errorMessage());
- throw new StorageException(L10n::t('Database storage failed to update %s', $ref));
+ if ($reference !== '') {
+ $result = $this->dba->update('storage', ['data' => $data], ['id' => $reference]);
+ if ($result === false) {
+ $this->logger->warning('Failed to update data.', ['id' => $reference, 'errorCode' => $this->dba->errorNo(), 'errorMessage' => $this->dba->errorMessage()]);
+ throw new StorageException($this->l10n->t('Database storage failed to update %s', $reference));
}
- return $ref;
+
+ return $reference;
} else {
- $r = DBA::insert('storage', ['data' => $data]);
- if ($r === false) {
- Logger::log('Failed to insert data: ' . DBA::errorNo() . ' : ' . DBA::errorMessage());
- throw new StorageException(L10n::t('Database storage failed to insert data'));
+ $result = $this->dba->insert('storage', ['data' => $data]);
+ if ($result === false) {
+ $this->logger->warning('Failed to insert data.', ['errorCode' => $this->dba->errorNo(), 'errorMessage' => $this->dba->errorMessage()]);
+ throw new StorageException($this->l10n->t('Database storage failed to insert data'));
}
- return DBA::lastInsertId();
+
+ return $this->dba->lastInsertId();
}
}
- public static function delete($ref)
+ /**
+ * @inheritDoc
+ */
+ public function delete(string $reference)
{
- return DBA::delete('storage', ['id' => $ref]);
+ return $this->dba->delete('storage', ['id' => $reference]);
}
- public static function getOptions()
+ /**
+ * @inheritDoc
+ */
+ public function getOptions()
{
return [];
}
- public static function saveOptions($data)
+ /**
+ * @inheritDoc
+ */
+ public function saveOptions(array $data)
{
return [];
}
+
+ /**
+ * @inheritDoc
+ */
+ public function __toString()
+ {
+ return self::NAME;
+ }
}
namespace Friendica\Model\Storage;
-use Friendica\Core\Config;
-use Friendica\Core\L10n;
-use Friendica\Core\Logger;
+use Friendica\Core\Config\IConfiguration;
+use Friendica\Core\L10n\L10n;
use Friendica\Util\Strings;
+use Psr\Log\LoggerInterface;
/**
* @brief Filesystem based storage backend
*/
class Filesystem implements IStorage
{
+ const NAME = 'Filesystem';
+
// Default base folder
const DEFAULT_BASE_FOLDER = 'storage';
- private static function getBasePath()
+ /** @var IConfiguration */
+ private $config;
+ /** @var LoggerInterface */
+ private $logger;
+ /** @var L10n */
+ private $l10n;
+
+ /** @var string */
+ private $basePath;
+
+ /**
+ * Filesystem constructor.
+ *
+ * @param IConfiguration $config
+ * @param LoggerInterface $logger
+ * @param L10n $l10n
+ */
+ public function __construct(IConfiguration $config, LoggerInterface $logger, L10n $l10n)
{
- $path = Config::get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER);
- return rtrim($path, '/');
+ $this->config = $config;
+ $this->logger = $logger;
+ $this->l10n = $l10n;
+
+ $path = $this->config->get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER);
+ $this->basePath = rtrim($path, '/');
}
/**
* @brief Split data ref and return file path
- * @param string $ref Data reference
+ *
+ * @param string $reference Data reference
+ *
* @return string
*/
- private static function pathForRef($ref)
+ private function pathForRef(string $reference)
{
- $base = self::getBasePath();
- $fold1 = substr($ref, 0, 2);
- $fold2 = substr($ref, 2, 2);
- $file = substr($ref, 4);
+ $fold1 = substr($reference, 0, 2);
+ $fold2 = substr($reference, 2, 2);
+ $file = substr($reference, 4);
- return implode('/', [$base, $fold1, $fold2, $file]);
+ return implode('/', [$this->basePath, $fold1, $fold2, $file]);
}
/**
* @brief Create dirctory tree to store file, with .htaccess and index.html files
+ *
* @param string $file Path and filename
+ *
* @throws StorageException
*/
- private static function createFoldersForFile($file)
+ private function createFoldersForFile(string $file)
{
$path = dirname($file);
if (!is_dir($path)) {
if (!mkdir($path, 0770, true)) {
- Logger::log('Failed to create dirs ' . $path);
- throw new StorageException(L10n::t('Filesystem storage failed to create "%s". Check you write permissions.', $path));
+ $this->logger->warning('Failed to create dir.', ['path' => $path]);
+ throw new StorageException($this->l10n->t('Filesystem storage failed to create "%s". Check you write permissions.', $path));
}
}
- $base = self::getBasePath();
-
- while ($path !== $base) {
+ while ($path !== $this->basePath) {
if (!is_file($path . '/index.html')) {
file_put_contents($path . '/index.html', '');
}
}
}
- public static function get($ref)
+ /**
+ * @inheritDoc
+ */
+ public function get(string $reference)
{
- $file = self::pathForRef($ref);
+ $file = $this->pathForRef($reference);
if (!is_file($file)) {
return '';
}
return file_get_contents($file);
}
- public static function put($data, $ref = '')
+ /**
+ * @inheritDoc
+ */
+ public function put(string $data, string $reference = '')
{
- if ($ref === '') {
- $ref = Strings::getRandomHex();
+ if ($reference === '') {
+ $reference = Strings::getRandomHex();
}
- $file = self::pathForRef($ref);
+ $file = $this->pathForRef($reference);
- self::createFoldersForFile($file);
+ $this->createFoldersForFile($file);
- $r = file_put_contents($file, $data);
- if ($r === FALSE) {
- Logger::log('Failed to write data to ' . $file);
- throw new StorageException(L10n::t('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
+ if ((file_exists($file) && !is_writable($file)) || !file_put_contents($file, $data)) {
+ $this->logger->warning('Failed to write data.', ['file' => $file]);
+ throw new StorageException($this->l10n->t('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
}
+
chmod($file, 0660);
- return $ref;
+ return $reference;
}
- public static function delete($ref)
+ /**
+ * @inheritDoc
+ */
+ public function delete(string $reference)
{
- $file = self::pathForRef($ref);
+ $file = $this->pathForRef($reference);
// return true if file doesn't exists. we want to delete it: success with zero work!
if (!is_file($file)) {
return true;
return unlink($file);
}
- public static function getOptions()
+ /**
+ * @inheritDoc
+ */
+ public function getOptions()
{
return [
'storagepath' => [
'input',
- L10n::t('Storage base path'),
- self::getBasePath(),
- L10n::t('Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree')
+ $this->l10n->t('Storage base path'),
+ $this->basePath,
+ $this->l10n->t('Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree')
]
];
}
-
- public static function saveOptions($data)
+
+ /**
+ * @inheritDoc
+ */
+ public function saveOptions(array $data)
{
- $storagepath = $data['storagepath'] ?? '';
- if ($storagepath === '' || !is_dir($storagepath)) {
+ $storagePath = $data['storagepath'] ?? '';
+ if ($storagePath === '' || !is_dir($storagePath)) {
return [
- 'storagepath' => L10n::t('Enter a valid existing folder')
+ 'storagepath' => $this->l10n->t('Enter a valid existing folder')
];
};
- Config::set('storage', 'filesystem_path', $storagepath);
+ $this->config->set('storage', 'filesystem_path', $storagePath);
+ $this->basePath = $storagePath;
return [];
}
+ /**
+ * @inheritDoc
+ */
+ public function __toString()
+ {
+ return self::NAME;
+ }
}
{
/**
* @brief Get data from backend
- * @param string $ref Data reference
+ *
+ * @param string $reference Data reference
+ *
* @return string
- */
- public static function get($ref);
+ */
+ public function get(string $reference);
/**
* @brief Put data in backend as $ref. If $ref is not defined a new reference is created.
- * @param string $data Data to save
- * @param string $ref Data referece. Optional.
- * @return string Saved data referece
+ *
+ * @param string $data Data to save
+ * @param string $reference Data reference. Optional.
+ *
+ * @return string Saved data reference
*/
- public static function put($data, $ref = "");
+ public function put(string $data, string $reference = "");
/**
* @brief Remove data from backend
- * @param string $ref Data referece
+ *
+ * @param string $reference Data reference
+ *
* @return boolean True on success
*/
- public static function delete($ref);
-
+ public function delete(string $reference);
+
/**
* @brief Get info about storage options
*
*
* See https://github.com/friendica/friendica/wiki/Quick-Template-Guide
*/
- public static function getOptions();
-
+ public function getOptions();
+
/**
* @brief Validate and save options
*
- * @param array $data Array [optionname => value] to be saved
+ * @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 static function saveOptions($data);
-
-}
-
+ public function saveOptions(array $data);
+ /**
+ * The name of the backend
+ *
+ * @return string
+ */
+ public function __toString();
+}
$relay_user_tags = !empty($_POST['relay_user_tags']);
$active_panel = (!empty($_POST['active_panel']) ? "#" . Strings::escapeTags(trim($_POST['active_panel'])) : '');
- /**
- * @var $storagebackend \Friendica\Model\Storage\IStorage
- */
$storagebackend = Strings::escapeTags(trim($_POST['storagebackend'] ?? ''));
// save storage backend form
- if (!is_null($storagebackend) && $storagebackend != "") {
- if (StorageManager::setBackend($storagebackend)) {
- $storage_opts = $storagebackend::getOptions();
- $storage_form_prefix = preg_replace('|[^a-zA-Z0-9]|', '', $storagebackend);
- $storage_opts_data = [];
- foreach ($storage_opts as $name => $info) {
- $fieldname = $storage_form_prefix . '_' . $name;
- switch ($info[0]) { // type
- case 'checkbox':
- case 'yesno':
- $value = !empty($_POST[$fieldname]);
- break;
- default:
- $value = $_POST[$fieldname] ?? '';
- }
- $storage_opts_data[$name] = $value;
+ if (DI::facStorage()->setBackend($storagebackend)) {
+ $storage_opts = DI::storage()->getOptions();
+ $storage_form_prefix = preg_replace('|[^a-zA-Z0-9]|', '', $storagebackend);
+ $storage_opts_data = [];
+ foreach ($storage_opts as $name => $info) {
+ $fieldname = $storage_form_prefix . '_' . $name;
+ switch ($info[0]) { // type
+ case 'checkbox':
+ case 'yesno':
+ $value = !empty($_POST[$fieldname]);
+ break;
+ default:
+ $value = $_POST[$fieldname] ?? '';
}
- unset($name);
- unset($info);
-
- $storage_form_errors = $storagebackend::saveOptions($storage_opts_data);
- if (count($storage_form_errors)) {
- foreach ($storage_form_errors as $name => $err) {
- notice('Storage backend, ' . $storage_opts[$name][1] . ': ' . $err);
- }
- DI::baseUrl()->redirect('admin/site' . $active_panel);
+ $storage_opts_data[$name] = $value;
+ }
+ unset($name);
+ unset($info);
+
+ $storage_form_errors = DI::storage()->saveOptions($storage_opts_data);
+ if (count($storage_form_errors)) {
+ foreach ($storage_form_errors as $name => $err) {
+ notice('Storage backend, ' . $storage_opts[$name][1] . ': ' . $err);
}
- } else {
- info(L10n::t('Invalid storage backend setting value.'));
+ DI::baseUrl()->redirect('admin/site' . $active_panel);
}
+ } else {
+ info(L10n::t('Invalid storage backend setting value.'));
}
// Has the directory url changed? If yes, then resubmit the existing profiles there
$optimize_max_tablesize = -1;
}
- $storage_backends = StorageManager::listBackends();
- /** @var $current_storage_backend \Friendica\Model\Storage\IStorage */
- $current_storage_backend = StorageManager::getBackend();
-
+ $current_storage_backend = DI::storage();
$available_storage_backends = [];
// show legacy option only if it is the current backend:
// once changed can't be selected anymore
- if ($current_storage_backend == '') {
+ if ($current_storage_backend == null) {
$available_storage_backends[''] = L10n::t('Database (legacy)');
}
- foreach ($storage_backends as $name => $class) {
- $available_storage_backends[$class] = $name;
+ foreach (DI::facStorage()->listBackends() as $name => $class) {
+ $available_storage_backends[$name] = $name;
}
- unset($storage_backends);
// build storage config form,
$storage_form_prefix = preg_replace('|[^a-zA-Z0-9]|' ,'', $current_storage_backend);
$storage_form = [];
if (!is_null($current_storage_backend) && $current_storage_backend != '') {
- foreach ($current_storage_backend::getOptions() as $name => $info) {
+ foreach ($current_storage_backend->getOptions() as $name => $info) {
$type = $info[0];
$info[0] = $storage_form_prefix . '_' . $name;
$info['type'] = $type;
*/
private static function moveStorage()
{
- $current = StorageManager::getBackend();
- $moved = StorageManager::move($current);
+ $current = DI::storage();
+ $moved = DI::facStorage()->move($current);
if ($moved) {
Worker::add(PRIORITY_LOW, "CronJobs", "move_storage");
use Friendica\Core\Lock\ILock;
use Friendica\Core\Process;
use Friendica\Core\Session\ISession;
+use Friendica\Core\StorageManager;
use Friendica\Database\Database;
use Friendica\Factory;
+use Friendica\Model\Storage\IStorage;
use Friendica\Model\User\Cookie;
use Friendica\Util;
use Psr\Log\LoggerInterface;
'constructParams' => [
$_SERVER, $_COOKIE
],
- ]
+ ],
+ StorageManager::class => [
+ 'constructParams' => [
+ [Dice::INSTANCE => Dice::SELF],
+ ]
+ ],
+ IStorage::class => [
+ // Don't share this class with other creations, because it's possible to switch the backend
+ // and so we wouldn't be possible to update it
+ 'shared' => false,
+ 'instanceOf' => StorageManager::class,
+ 'call' => [
+ ['getBackend', [], Dice::CHAIN_CALL],
+ ],
+ ],
];
--- /dev/null
+<?php
+
+namespace Friendica\Test\src\Model\Storage;
+
+use Friendica\Core\L10n\L10n;
+use Friendica\Factory\ConfigFactory;
+use Friendica\Model\Storage\Database;
+use Friendica\Model\Storage\IStorage;
+use Friendica\Test\DatabaseTestTrait;
+use Friendica\Test\Util\Database\StaticDatabase;
+use Friendica\Test\Util\VFSTrait;
+use Friendica\Util\ConfigFileLoader;
+use Friendica\Util\Profiler;
+use Mockery\MockInterface;
+use Psr\Log\NullLogger;
+
+class DatabaseStorageTest extends StorageTest
+{
+ use DatabaseTestTrait;
+ use VFSTrait;
+
+ protected function setUp()
+ {
+ $this->setUpVfsDir();
+
+ parent::setUp();
+ }
+
+ protected function getInstance()
+ {
+ $logger = new NullLogger();
+ $profiler = \Mockery::mock(Profiler::class);
+ $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
+
+ // load real config to avoid mocking every config-entry which is related to the Database class
+ $configFactory = new ConfigFactory();
+ $loader = new ConfigFileLoader($this->root->url());
+ $configCache = $configFactory->createCache($loader);
+
+ $dba = new StaticDatabase($configCache, $profiler, $logger);
+
+ /** @var MockInterface|L10n $l10n */
+ $l10n = \Mockery::mock(L10n::class)->makePartial();
+
+ return new Database($dba, $logger, $l10n);
+ }
+
+ protected function assertOption(IStorage $storage)
+ {
+ $this->assertEmpty($storage->getOptions());
+ }
+}
--- /dev/null
+<?php
+
+namespace Friendica\Test\src\Model\Storage;
+
+use Friendica\Core\Config\IConfiguration;
+use Friendica\Core\L10n\L10n;
+use Friendica\Model\Storage\Filesystem;
+use Friendica\Model\Storage\IStorage;
+use Friendica\Test\Util\VFSTrait;
+use Friendica\Util\Profiler;
+use Mockery\MockInterface;
+use org\bovigo\vfs\vfsStream;
+use Psr\Log\NullLogger;
+use function GuzzleHttp\Psr7\uri_for;
+
+class FilesystemStorageTest extends StorageTest
+{
+ use VFSTrait;
+
+ /** @var MockInterface|IConfiguration */
+ protected $config;
+
+ protected function setUp()
+ {
+ $this->setUpVfsDir();
+
+ vfsStream::create(['storage' => []], $this->root);
+
+ parent::setUp();
+ }
+
+ protected function getInstance()
+ {
+ $logger = new NullLogger();
+ $profiler = \Mockery::mock(Profiler::class);
+ $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
+
+ /** @var MockInterface|L10n $l10n */
+ $l10n = \Mockery::mock(L10n::class)->makePartial();
+ $this->config = \Mockery::mock(IConfiguration::class);
+ $this->config->shouldReceive('get')
+ ->with('storage', 'filesystem_path', Filesystem::DEFAULT_BASE_FOLDER)
+ ->andReturn($this->root->getChild('storage')->url());
+
+ return new Filesystem($this->config, $logger, $l10n);
+ }
+
+ protected function assertOption(IStorage $storage)
+ {
+ $this->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());
+ }
+
+ /**
+ * Test the exception in case of missing directorsy permissions
+ *
+ * @expectedException \Friendica\Model\Storage\StorageException
+ * @expectedExceptionMessageRegExp /Filesystem storage failed to create \".*\". Check you write permissions./
+ */
+ public function testMissingDirPermissions()
+ {
+ $this->root->getChild('storage')->chmod(000);
+
+ $instance = $this->getInstance();
+ $instance->put('test');
+ }
+
+ /**
+ * Test the exception in case of missing file permissions
+ *
+ * @expectedException \Friendica\Model\Storage\StorageException
+ * @expectedExceptionMessageRegExp /Filesystem storage failed to save data to \".*\". Check your write permissions/
+ */
+ public function testMissingFilePermissions()
+ {
+ 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();
+
+ $this->assertDirectoryExists($dir);
+ $this->assertFileExists($file);
+
+ $this->assertDirectoryIsWritable($dir);
+ $this->assertFileIsWritable($file);
+
+ $this->assertEquals('test', file_get_contents($file));
+ }
+}
--- /dev/null
+<?php
+
+namespace Friendica\Test\src\Model\Storage;
+
+use Friendica\Model\Storage\IStorage;
+use Friendica\Test\MockedTest;
+
+abstract class StorageTest extends MockedTest
+{
+ /** @return IStorage */
+ abstract protected function getInstance();
+
+ abstract protected function assertOption(IStorage $storage);
+
+ /**
+ * Test if the instance is "really" implementing the interface
+ */
+ public function testInstance()
+ {
+ $instance = $this->getInstance();
+ $this->assertInstanceOf(IStorage::class, $instance);
+ }
+
+ /**
+ * Test if the "getOption" is asserted
+ */
+ public function testGetOptions()
+ {
+ $instance = $this->getInstance();
+
+ $this->assertOption($instance);
+ }
+
+ /**
+ * Test basic put, get and delete operations
+ */
+ public function testPutGetDelete()
+ {
+ $instance = $this->getInstance();
+
+ $ref = $instance->put('data12345');
+ $this->assertNotEmpty($ref);
+
+ $this->assertEquals('data12345', $instance->get($ref));
+
+ $this->assertTrue($instance->delete($ref));
+ }
+
+ /**
+ * Test a delete with an invalid reference
+ */
+ public function testInvalidDelete()
+ {
+ $instance = $this->getInstance();
+
+ // Even deleting not existing references should return "true"
+ $this->assertTrue($instance->delete(-1234456));
+ }
+
+ /**
+ * Test a get with an invalid reference
+ */
+ public function testInvalidGet()
+ {
+ $instance = $this->getInstance();
+
+ // Invalid references return an empty string
+ $this->assertEmpty($instance->get(-123456));
+ }
+
+ /**
+ * Test an update with a given reference
+ */
+ public function testUpdateReference()
+ {
+ $instance = $this->getInstance();
+
+ $ref = $instance->put('data12345');
+ $this->assertNotEmpty($ref);
+
+ $this->assertEquals('data12345', $instance->get($ref));
+
+ $this->assertEquals($ref, $instance->put('data5432', $ref));
+ $this->assertEquals('data5432', $instance->get($ref));
+ }
+
+ /**
+ * Test that an invalid update results in an insert
+ */
+ public function testInvalidUpdate()
+ {
+ $instance = $this->getInstance();
+
+ $this->assertEquals(-123, $instance->put('data12345', -123));
+ }
+}
return Update::SUCCESS;
}
+function update_1329()
+{
+ $currStorage = Config::get('storage', 'class', '');
+
+ if (!empty($currStorage)) {
+ $storageName = array_key_first(\Friendica\Core\StorageManager::DEFAULT_BACKENDS, $currStorage);
+ Config::set('storage', 'name', $storageName);
+ Config::delete('storage', 'class');
+ }
+
+ return Update::SUCCESS;
+}