mirror of
https://github.com/getgrav/grav.git
synced 2026-08-29 13:25:16 +02:00
Improve media index so it works with multiple external locations
This commit is contained in:
145
system/src/Grav/Common/Media/MediaIndex.php
Normal file
145
system/src/Grav/Common/Media/MediaIndex.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Media
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Media;
|
||||
|
||||
use Grav\Framework\File\Formatter\JsonFormatter;
|
||||
use Grav\Framework\File\JsonFile;
|
||||
|
||||
/**
|
||||
* Media Index class
|
||||
*/
|
||||
class MediaIndex
|
||||
{
|
||||
/** @var array */
|
||||
protected static $instances = [];
|
||||
|
||||
/** @var string */
|
||||
protected $filepath;
|
||||
/** @var array */
|
||||
protected $indexes;
|
||||
/** @var int */
|
||||
protected $modified = 0;
|
||||
/** @var JsonFile */
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* @param string $filepath
|
||||
* @return MediaIndex
|
||||
*/
|
||||
public static function getInstance(string $filepath): MediaIndex
|
||||
{
|
||||
if (!isset(static::$instances[$filepath])) {
|
||||
static::$instances[$filepath] = new static($filepath);
|
||||
}
|
||||
|
||||
return static::$instances[$filepath];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param bool $reload
|
||||
* @return array
|
||||
*/
|
||||
public function get(string $id, bool $reload = false): array
|
||||
{
|
||||
$indexes = $this->getIndexes($reload);
|
||||
|
||||
$index = $indexes[$id] ?? [];
|
||||
|
||||
return [$index, $this->modified];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $block
|
||||
* @return void
|
||||
*/
|
||||
public function lock(bool $block = true): void
|
||||
{
|
||||
$this->getFile()->lock($block);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $timestamp
|
||||
* @return void
|
||||
*/
|
||||
public function touch(string $id, ?int $timestamp = null): void
|
||||
{
|
||||
$this->getFile()->touch($timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function save(string $id, array $index): void
|
||||
{
|
||||
$file = $this->getFile();
|
||||
$file->lock();
|
||||
|
||||
$index = array_filter($index, static function($val) { return $val !== null; } );
|
||||
|
||||
$indexes = $file->exists() ? $file->load() : ['version' => 1];
|
||||
$version = $indexes['version'] ?? null;
|
||||
if ($version !== 1) {
|
||||
$indexes = [];
|
||||
}
|
||||
$indexes[$id] = $index;
|
||||
|
||||
$file->save($indexes);
|
||||
$file->unlock();
|
||||
|
||||
$this->indexes = $indexes;
|
||||
$this->modified = $file->getModificationTime();
|
||||
}
|
||||
|
||||
protected function getIndexes(bool $reload = true): array
|
||||
{
|
||||
if (!isset($this->indexes) || $reload) {
|
||||
// Read media index file.
|
||||
$file = $this->getFile();
|
||||
if ($file->exists()) {
|
||||
$this->indexes = $file->load();
|
||||
$version = $this->indexes['version'] ?? null;
|
||||
if ($version !== 1) {
|
||||
$this->indexes = [];
|
||||
}
|
||||
$this->modified = $file->getModificationTime();
|
||||
} else {
|
||||
$this->indexes = [];
|
||||
$this->modified = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get index file, which stores media file index.
|
||||
*
|
||||
* @return JsonFile
|
||||
*/
|
||||
protected function getFile(): JsonFile
|
||||
{
|
||||
if (!isset($this->file)) {
|
||||
$this->file = new JsonFile($this->filepath, new JsonFormatter(['encode_options' => JSON_PRETTY_PRINT]));
|
||||
}
|
||||
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filepath
|
||||
*/
|
||||
private function __construct(string $filepath)
|
||||
{
|
||||
$this->filepath = $filepath;
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,11 @@ use Grav\Common\Language\Language;
|
||||
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
|
||||
use Grav\Common\Media\Interfaces\MediaObjectInterface;
|
||||
use Grav\Common\Media\Interfaces\MediaUploadInterface;
|
||||
use Grav\Common\Media\MediaIndex;
|
||||
use Grav\Common\Media\Traits\MediaUploadTrait;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Compat\Serializable;
|
||||
use Grav\Framework\File\Formatter\JsonFormatter;
|
||||
use Grav\Framework\File\JsonFile;
|
||||
use InvalidArgumentException;
|
||||
use PHPExif\Reader\Reader;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
@@ -78,6 +77,19 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
|
||||
/** @var bool Hack to make Iterator work together with unset(). */
|
||||
private $iteratorUnset = false;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return md5($this->getType() . ':' . $this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getType(): string;
|
||||
|
||||
/**
|
||||
* Return media path.
|
||||
*
|
||||
@@ -358,10 +370,23 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
|
||||
*/
|
||||
public function updateIndex(array $files): void
|
||||
{
|
||||
[$index,] = $this->loadIndex();
|
||||
$files = array_merge($files, $index['files']);
|
||||
ksort($files, SORT_NATURAL);
|
||||
$this->saveIndex($files);
|
||||
$mediaIndex = $this->getIndex();
|
||||
if (!$mediaIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mediaIndex->lock();
|
||||
|
||||
$id = $this->getId();
|
||||
$index = $mediaIndex->get($id, true);
|
||||
|
||||
// Add new files and remove the old ones.
|
||||
$files += $index['files'] ?? [];
|
||||
$files = array_filter($files, static function($val) { return $val !== null; } );
|
||||
|
||||
$index = $this->generateIndex($files);
|
||||
|
||||
$mediaIndex->save($id, $index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -803,18 +828,39 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
|
||||
return $medium;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
* @param string|null $checksum
|
||||
* @param int|null $timestamp
|
||||
* @return array
|
||||
*/
|
||||
protected function generateIndex(array $files, string $checksum = null, ?int $timestamp = null): array
|
||||
{
|
||||
ksort($files, SORT_NATURAL);
|
||||
|
||||
return [
|
||||
'type' => $this->getType(),
|
||||
'version' => static::VERSION,
|
||||
'checksum' => $checksum ?? md5(serialize($files)),
|
||||
'timestamp' => $timestamp ?? time(),
|
||||
'folder' => $this->path,
|
||||
'url' => $this->url,
|
||||
'files' => $files,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get index file, which stores media file index.
|
||||
*
|
||||
* @return JsonFile|null
|
||||
* @return MediaIndex|null
|
||||
*/
|
||||
protected function getIndexFile(): ?JsonFile
|
||||
protected function getIndex(): ?MediaIndex
|
||||
{
|
||||
if (null === $this->indexFolder || null === $this->indexFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new JsonFile($this->indexFolder . '/' . $this->indexFile, new JsonFormatter(['encode_options' => JSON_PRETTY_PRINT]));
|
||||
return MediaIndex::getInstance($this->indexFolder . '/' . $this->indexFile);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -823,12 +869,14 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
|
||||
protected function loadIndex(): array
|
||||
{
|
||||
// Read media index file.
|
||||
$indexFile = $this->getIndexFile();
|
||||
if (!($indexFile && $indexFile->exists())) {
|
||||
$mediaIndex = $this->getIndex();
|
||||
if (!$mediaIndex || !$this->exists) {
|
||||
return [[], 0];
|
||||
}
|
||||
|
||||
$index = $indexFile->load();
|
||||
$id = $this->getId();
|
||||
[$index, $modified] = $mediaIndex->get($id);
|
||||
|
||||
$version = $index['version'] ?? null;
|
||||
$folder = $index['folder'] ?? null;
|
||||
$type = $index['type'] ?? null;
|
||||
@@ -836,7 +884,7 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
|
||||
return [[], 0];
|
||||
}
|
||||
|
||||
return [$index, $indexFile->getModificationTime()];
|
||||
return [$index, $modified];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -845,12 +893,13 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
|
||||
*/
|
||||
protected function touchIndex(?int $timestamp = null): void
|
||||
{
|
||||
$index = $this->getIndexFile();
|
||||
if (!$index || !$this->exists) {
|
||||
$mediaIndex = $this->getIndex();
|
||||
if (!$mediaIndex || !$this->exists) {
|
||||
return;
|
||||
}
|
||||
$id = $this->getId();
|
||||
|
||||
$index->touch($timestamp);
|
||||
$mediaIndex->touch($id, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -861,22 +910,15 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
|
||||
*/
|
||||
protected function saveIndex(array $files, string $checksum = null, ?int $timestamp = null): void
|
||||
{
|
||||
$index = $this->getIndexFile();
|
||||
if (!$index || !$this->exists) {
|
||||
$mediaIndex = $this->getIndex();
|
||||
if (!$mediaIndex || !$this->exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'type' => $this->config['type'] ?? 'local',
|
||||
'version' => static::VERSION,
|
||||
'checksum' => $checksum ?? md5(serialize($files)),
|
||||
'timestamp' => $timestamp ?? time(),
|
||||
'folder' => $this->path,
|
||||
'url' => $this->url,
|
||||
'files' => $files,
|
||||
];
|
||||
$id = $this->getId();
|
||||
$index = $this->generateIndex($files, $checksum, $timestamp);
|
||||
|
||||
$index->save($data);
|
||||
$mediaIndex->save($id, $index);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,8 +13,6 @@ use FilesystemIterator;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Media\Interfaces\MediaObjectInterface;
|
||||
use Grav\Framework\File\Formatter\JsonFormatter;
|
||||
use Grav\Framework\File\JsonFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
use function is_array;
|
||||
@@ -25,6 +23,14 @@ use function is_array;
|
||||
*/
|
||||
abstract class LocalMedia extends AbstractMedia
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return 'local';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return media path.
|
||||
*
|
||||
|
||||
@@ -24,6 +24,7 @@ use Grav\Framework\Filesystem\Filesystem;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Form\FormFlashFile;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
use function array_key_exists;
|
||||
@@ -46,6 +47,8 @@ trait FlexMediaTrait
|
||||
|
||||
/** @var array */
|
||||
protected $_uploads = [];
|
||||
/** @var array */
|
||||
protected $_mediaSettings = [];
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
@@ -103,7 +106,9 @@ trait FlexMediaTrait
|
||||
/** @var MediaFactory $factory */
|
||||
$factory = Grav::instance()['media_factory'];
|
||||
|
||||
$params = [
|
||||
$params = $settings['media'] ?? [];
|
||||
$params += [
|
||||
'object' => $this,
|
||||
'path' => $settings[$var],
|
||||
'order' => [],
|
||||
'load' => true
|
||||
@@ -128,6 +133,10 @@ trait FlexMediaTrait
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($this->_mediaSettings[$field])) {
|
||||
return $this->_mediaSettings[$field];
|
||||
}
|
||||
|
||||
// Load settings for the field.
|
||||
$schema = $this->getBlueprint()->schema();
|
||||
$settings = (array)$schema->getProperty($field);
|
||||
@@ -151,13 +160,27 @@ trait FlexMediaTrait
|
||||
|
||||
// Set media folder for media fields.
|
||||
if (isset($var)) {
|
||||
$folder = $settings[$var] ?? '';
|
||||
if (in_array(rtrim($folder, '/'), ['', '@self', 'self@', '@self@'], true)) {
|
||||
$settings['type'] = 'local';
|
||||
$token = $settings[$var] ?? '';
|
||||
if (in_array(rtrim($token, '/'), ['', '@self', 'self@', '@self@'], true)) {
|
||||
$settings[$var] = $this->getMediaFolder();
|
||||
$settings['self'] = true;
|
||||
} else {
|
||||
$settings[$var] = Utils::getPathFromToken($folder, $this);
|
||||
$event = new Event([
|
||||
'token' => $token,
|
||||
'object' => $this,
|
||||
'field' => $field,
|
||||
'type' => $type,
|
||||
'settings' => &$settings, // Value can be changed.
|
||||
'uri' => &$uri // Value will be set to here.
|
||||
]);
|
||||
|
||||
Grav::instance()->fireEvent('onGetFlexMediaFieldSettings', $event);
|
||||
|
||||
$settings[$var] = $uri ?? Utils::getPathFromToken($token, $this);
|
||||
$settings['self'] = false;
|
||||
|
||||
$this->_mediaSettings[$field] = $settings;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user