diff --git a/demos/_demo-data/create-db.php b/demos/_demo-data/create-db.php index dd086ef..d9da2e1 100644 --- a/demos/_demo-data/create-db.php +++ b/demos/_demo-data/create-db.php @@ -23,9 +23,7 @@ $fileModel->addField('token', ['required' => true]); $fileModel->addField('location', ['type' => 'text']); $fileModel->addField('url', ['type' => 'text']); -$fileModel->addField('storage'); $fileModel->addField('status'); -$fileModel->addField('source_file_id', ['type' => 'integer']); $fileModel->addField('meta_filename'); $fileModel->addField('meta_extension'); $fileModel->addField('meta_md5'); diff --git a/demos/index.php b/demos/index.php index 7af78c1..966ce44 100644 --- a/demos/index.php +++ b/demos/index.php @@ -9,7 +9,10 @@ use Atk4\Filestore\Model\File; use Atk4\Ui\Callback; use Atk4\Ui\Columns; +use Atk4\Ui\Crud; use Atk4\Ui\Form; +use Atk4\Ui\Grid; +use Atk4\Ui\Header; use Atk4\Ui\Js\JsExpression; use Atk4\Ui\View; use League\Flysystem\Filesystem; @@ -34,34 +37,41 @@ $adapter = new \League\Flysystem\Local\LocalFilesystemAdapter(__DIR__ . '/_demo-data/localfiles'); $filesystem = new Filesystem($adapter); -$col = Columns::addTo($app); +$columnsLayout = Columns::addTo($app); -$form = Form::addTo($col->addColumn()); +// new friend form +$c1 = $columnsLayout->addColumn(); +Header::addTo($c1, ['Add New Friend']); +$form = Form::addTo($c1); $form->setModel( (new Friend($app->db, [ 'filesystem' => $filesystem, ]))->createEntity() ); -$gr = \Atk4\Ui\Grid::addTo($col->addColumn(), [ - 'menu' => false, - 'paginator' => false, -]); -$gr->setModel(new File($app->db)); - -$form->onSubmit(function (Form $form) use ($gr) { +$form->onSubmit(function (Form $form) use ($app) { $form->model->save(); - return $gr->jsReload(); + return $app->layout->jsReload(); }); +// list all filestore files +$c2 = $columnsLayout->addColumn(); +Header::addTo($c2, ['All Filestore Files']); +$gr = Grid::addTo($c2, [ + 'paginator' => false, +]); +$files = new File($app->db, ['flysystem' => $filesystem]); +$gr->setModel($files); + View::addTo($app, ['ui' => 'divider']); -$crud = \Atk4\Ui\Crud::addTo($app); +// CRUD with all Friends records +Header::addTo($app, ['All Friends']); +$crud = Crud::addTo($app); $crud->setModel(new Friend($app->db, ['filesystem' => $filesystem])); -View::addTo($app, ['ui' => 'divider']); - +// custom actions $callbackDownload = Callback::addTo($app); $callbackDownload->set(function () use ($crud) { $id = $crud->getApp()->stickyGet('row_id'); diff --git a/src/Field/FileField.php b/src/Field/FileField.php index 020de64..2c74e03 100644 --- a/src/Field/FileField.php +++ b/src/Field/FileField.php @@ -59,7 +59,8 @@ protected function init(): void $this->fieldNameBase = preg_replace('~_id$~', '', $this->shortName); $this->importFields(); - $this->onHookToOwnerEntity(Model::HOOK_AFTER_SAVE, function (Model $m) { + // on insert/update delete old file and mark new one as linked + $fx = function (Model $m) { if ($m->isDirty($this->shortName)) { $old = $m->getDirtyRef()[$this->shortName]; $new = $m->get($this->shortName); @@ -71,10 +72,12 @@ protected function init(): void // mark new file as linked if ($new) { - $m->refModel($this->shortName)->loadBy('token', $new)->save(['status' => 'linked']); + $m->refModel($this->shortName)->loadBy('token', $new)->save(['status' => File::STATUS_LINKED]); } } - }); + }; + $this->onHookToOwnerEntity(Model::HOOK_AFTER_INSERT, $fx); + $this->onHookToOwnerEntity(Model::HOOK_AFTER_UPDATE, $fx); $this->onHookToOwnerEntity(Model::HOOK_AFTER_DELETE, function (Model $m) { $token = $m->get($this->shortName); diff --git a/src/Form/Control/Upload.php b/src/Form/Control/Upload.php index 61e3f5d..bec2206 100644 --- a/src/Form/Control/Upload.php +++ b/src/Form/Control/Upload.php @@ -67,7 +67,7 @@ protected function deleted(string $token): ?JsExpressionable $model = $this->entityField->getField()->fileModel; $entity = $model->loadBy('token', $token); - if ($entity->get('status') === 'draft') { + if ($entity->get('status') === $entity::STATUS_DRAFT) { $entity->delete(); } diff --git a/src/Helper.php b/src/Helper.php index f491560..1999dec 100644 --- a/src/Helper.php +++ b/src/Helper.php @@ -13,30 +13,30 @@ class Helper /** * @return never */ - public static function download(File $model, App $app): void + protected static function terminate(File $model, App $app): void { - $app->setResponseHeader('Content-Description', 'File Transfer'); - $app->setResponseHeader('Content-Type', 'application/octet-stream'); - $app->setResponseHeader('Cache-Control', 'must-revalidate'); - $app->setResponseHeader('Expires', '-1'); - $app->setResponseHeader('Content-Disposition', 'attachment; filename="' . $model->get('meta_filename') . '"'); - $app->setResponseHeader('Content-Length', (string) $model->get('meta_size')); - $app->setResponseHeader('Pragma', 'public'); - $app->setResponseHeader('Accept-Ranges', 'bytes'); + $path = $model->get('location'); + $resource = $model->flysystem->readStream($path); + $stream = (new Psr17Factory())->createStreamFromResource($resource); - static::output($model, $app); + $app->terminate($stream); } /** * @return never */ - protected static function output(File $model, App $app): void + public static function download(File $model, App $app): void { - $path = $model->get('location'); - $resource = $model->flysystem->readStream($path); - $stream = (new Psr17Factory())->createStreamFromResource($resource); + $app->setResponseHeader('Content-Description', 'Download File'); + $app->setResponseHeader('Content-Type', $model->get('meta_mime_type')); + $app->setResponseHeader('Cache-Control', 'must-revalidate'); + $app->setResponseHeader('Expires', '-1'); + $app->setResponseHeader('Content-Disposition', 'attachment; filename="' . $model->get('meta_filename') . '"'); + $app->setResponseHeader('Content-Length', (string) $model->get('meta_size')); + $app->setResponseHeader('Pragma', 'public'); + $app->setResponseHeader('Accept-Ranges', 'bytes'); - $app->terminate($stream); + static::terminate($model, $app); } /** @@ -44,7 +44,7 @@ protected static function output(File $model, App $app): void */ public static function view(File $model, App $app): void { - $app->setResponseHeader('Content-Description', 'File Transfer'); + $app->setResponseHeader('Content-Description', 'View File'); $app->setResponseHeader('Content-Type', $model->get('meta_mime_type')); $app->setResponseHeader('Cache-Control', 'must-revalidate'); $app->setResponseHeader('Expires', '-1'); @@ -53,6 +53,6 @@ public static function view(File $model, App $app): void $app->setResponseHeader('Pragma', 'public'); $app->setResponseHeader('Accept-Ranges', 'bytes'); - static::output($model, $app); + static::terminate($model, $app); } } diff --git a/src/Model/File.php b/src/Model/File.php index e4073b5..6885aef 100644 --- a/src/Model/File.php +++ b/src/Model/File.php @@ -13,19 +13,19 @@ class File extends Model public ?string $titleField = 'meta_filename'; + /** All uploaded files first get this status */ + public const STATUS_DRAFT = 'draft'; + /** When file is linked to some other model */ + public const STATUS_LINKED = 'linked'; + /** @const list */ + public const ALL_STATUSES = [ + self::STATUS_DRAFT, + self::STATUS_LINKED, + ]; + /** @var Filesystem */ public $flysystem; - public function newFile(): Model - { - $entity = $this->createEntity(); - - $entity->set('token', uniqid('token-')); - $entity->set('location', uniqid('file-') . '.bin'); - - return $entity; - } - protected function init(): void { parent::init(); @@ -33,14 +33,10 @@ protected function init(): void $this->addField('token', ['system' => true, 'type' => 'string', 'required' => true]); $this->addField('location'); $this->addField('url'); - $this->addField('storage'); - $this->hasOne('source_file_id', [ - 'model' => [self::class], - ]); $this->addField('status', [ - 'enum' => ['draft', 'uploaded', 'thumbok', 'normalok', 'ready', 'linked'], - 'default' => 'draft', + 'enum' => self::ALL_STATUSES, + 'default' => self::STATUS_DRAFT, ]); $this->addField('meta_filename'); @@ -52,10 +48,26 @@ protected function init(): void $this->addField('meta_image_width', ['type' => 'integer']); $this->addField('meta_image_height', ['type' => 'integer']); - $this->onHook(Model::HOOK_BEFORE_DELETE, function (self $m) { - if ($m->flysystem) { // @phpstan-ignore-line - $m->flysystem->delete($m->get('location')); + // delete physical file from storage after we delete DB record + $this->onHook(Model::HOOK_AFTER_DELETE, function (self $m) { + $path = $m->get('location'); + if ($path && $m->flysystem->fileExists($path)) { + $m->flysystem->delete($path); } }); } + + /** + * @return static + */ + public function newFile(): Model + { + $this->assertIsModel(); + + $entity = $this->createEntity(); + $entity->set('token', uniqid('token-')); + $entity->set('location', uniqid('file-') . '.bin'); + + return $entity; + } }