diff --git a/Admin/Install/db.json b/Admin/Install/db.json index f870e60..7c1089a 100755 --- a/Admin/Install/db.json +++ b/Admin/Install/db.json @@ -179,8 +179,8 @@ "type": "INT", "default": null, "null": true, - "foreignTable": "organization_unit", - "foreignKey": "organization_unit_id" + "foreignTable": "unit", + "foreignKey": "unit_id" }, "media_language": { "name": "media_language", @@ -263,5 +263,31 @@ "foreignKey": "tag_id" } } + }, + "media_type_rel": { + "name": "media_type_rel", + "fields": { + "media_type_rel_id": { + "name": "media_type_rel_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "media_type_rel_src": { + "name": "media_type_rel_src", + "type": "INT", + "null": false, + "foreignTable": "media", + "foreignKey": "media_id" + }, + "media_type_rel_dst": { + "name": "media_type_rel_dst", + "type": "INT", + "null": false, + "foreignTable": "type", + "foreignKey": "type_id" + } + } } } \ No newline at end of file diff --git a/Admin/Installer.php b/Admin/Installer.php index 8b52c6b..80661e7 100755 --- a/Admin/Installer.php +++ b/Admin/Installer.php @@ -116,7 +116,7 @@ final class Installer extends InstallerAbstract }; $apiApp->dbPool = $app->dbPool; - $apiApp->orgId = $app->orgId; + $apiApp->unitId = $app->unitId; $apiApp->accountManager = $app->accountManager; $apiApp->appSettings = $app->appSettings; $apiApp->moduleManager = $app->moduleManager; @@ -144,6 +144,9 @@ final class Installer extends InstallerAbstract case 'type': $result['type'][] = self::createType($apiApp, $media); break; + case 'reference': + $result['reference'][] = self::createReference($apiApp, $media); + break; default: } } @@ -151,6 +154,21 @@ final class Installer extends InstallerAbstract return $result; } + /** + * Create collection. + * + * @param ApplicationAbstract $app Application + * @param array{path?:string, name?:string, virtualPath?:string, create_directory?:bool} $data Media info + * + * @return array + * + * @since 1.0.0 + */ + private static function createReference(ApplicationAbstract $app, array $data) : array + { + return []; + } + /** * Create collection. * diff --git a/Controller/ApiController.php b/Controller/ApiController.php index 9f45860..1197598 100755 --- a/Controller/ApiController.php +++ b/Controller/ApiController.php @@ -30,11 +30,11 @@ use Modules\Media\Models\NullMedia; use Modules\Media\Models\NullMediaType; use Modules\Media\Models\PathSettings; use Modules\Media\Models\PermissionCategory; +use Modules\Media\Models\Reference; +use Modules\Media\Models\ReferenceMapper; use Modules\Media\Models\UploadFile; use Modules\Media\Models\UploadStatus; use Modules\Tag\Models\NullTag; -use PhpOffice\PhpWord\IOFactory; -use PhpOffice\PhpWord\Writer\HTML; use phpOMS\Account\PermissionType; use phpOMS\Application\ApplicationAbstract; use phpOMS\Asset\AssetType; @@ -86,7 +86,6 @@ final class ApiController extends Controller account: $request->header->account, basePath: __DIR__ . '/../../../Modules/Media/Files' . \urldecode((string) ($request->getData('path') ?? '')), virtualPath: \urldecode((string) ($request->getData('virtualpath') ?? '')), - type: $request->getData('type', 'int'), password: (string) ($request->getData('password') ?? ''), encryptionKey: (string) ($request->getData('encrypt') ?? ''), pathSettings: (int) ($request->getData('pathsettings') ?? PathSettings::RANDOM_PATH), // IMPORTANT!!! @@ -98,6 +97,39 @@ final class ApiController extends Controller $ids = []; foreach ($uploads as $file) { $ids[] = $file->getId(); + + // add media types + if (!empty($types = $request->getDataJson('types'))) { + foreach ($types as $type) { + if (!isset($type['id'])) { + $request->setData('name', $type['name'], true); + $request->setData('title', $type['title'], true); + $request->setData('lang', $type['lang'] ?? null, true); + + $internalResponse = new HttpResponse(); + $this->apiMediaTypeCreate($request, $internalResponse, null); + + if (!\is_array($data = $internalResponse->get($request->uri->__toString()))) { + continue; + } + + $file->addMediaType($tId = $data['response']); + } else { + $file->addMediaType(new NullMediaType($tId = (int) $type['id'])); + } + + $this->createModelRelation( + $request->header->account, + $file->getId(), + $tId, + MediaMapper::class, + 'types', + '', + $request->getOrigin() + ); + } + } + // add tags if (!empty($tags = $request->getDataJson('tags'))) { foreach ($tags as $tag) { @@ -144,7 +176,6 @@ final class ApiController extends Controller * @param int $account Uploader * @param string $basePath Base path. The path which is used for the upload. * @param string $virtualPath virtual path The path which is used to visually structure the files, like directories - * @param int $type Media type (internal/custom media categorization) * The file storage on the system can be different * @param string $password File password. The password to protect the file (only database) * @param string $encryptionKey Encryption key. Used to encrypt the file on the local file storage. @@ -164,7 +195,6 @@ final class ApiController extends Controller int $account, string $basePath = '/Modules/Media/Files', string $virtualPath = '', - int $type = null, string $password = '', string $encryptionKey = '', int $pathSettings = PathSettings::RANDOM_PATH, @@ -211,7 +241,6 @@ final class ApiController extends Controller $stat, $account, $virtualPath, - $type, app: $hasAccountRelation ? $this->app : null, readContent: $readContent, unit: $unit @@ -268,7 +297,6 @@ final class ApiController extends Controller * @param array $status Files * @param int $account Uploader * @param string $virtualPath Virtual path (not on the hard-drive) - * @param null|int $type Media type (internal categorization) * @param string $ip Ip of the origin * @param null|ApplicationAbstract $app Should create relation to uploader * @param bool $readContent Should the content of the file be stored in the db @@ -281,7 +309,6 @@ final class ApiController extends Controller array $status, int $account, string $virtualPath = '', - int $type = null, string $ip = '127.0.0.1', ApplicationAbstract $app = null, bool $readContent = false, @@ -299,7 +326,6 @@ final class ApiController extends Controller $media->size = $status['size']; $media->createdBy = new NullAccount($account); $media->extension = $status['extension']; - $media->type = $type === null ? null : new NullMediaType($type); $media->unit = $unit; $media->setVirtualPath($virtualPath); @@ -318,7 +344,7 @@ final class ApiController extends Controller $app->moduleManager->get('Admin')->createAccountModelPermission( new AccountPermission( $account, - $app->orgId, + $app->unitId, $app->appName, self::NAME, self::NAME, @@ -476,6 +502,85 @@ final class ApiController extends Controller return $media; } + public function apiReferenceCreate(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void + { + if (!empty($val = $this->validateReferenceCreate($request))) { + $response->set('collection_create', new FormValidation($val)); + $response->header->status = RequestStatusCode::R_400; + + return; + } + + $reference = $this->createReferenceFromRequest($request); + $this->createModel($request->header->account, $reference, ReferenceMapper::class, 'reference', $request->getOrigin()); + + // get parent collection + // create relation + $parentCollectionId = (int) $request->getData('parent'); + if ($parentCollectionId === 0) { + $parentCollection = CollectionMapper::get() + ->where('virtualPath', (string) ($request->getData('virtualpath') ?? '')) + ->where('name', (string) ($request->getData('name') ?? '')) + ->execute(); + + $parentCollectionId = $parentCollection->getId(); + } + + CollectionMapper::writer()->createRelationTable('sources', [$parentCollectionId], $reference->getId()); + + $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Reference', 'Reference successfully created.', $reference); + } + + /** + * Method to create collection from request. + * + * @param RequestAbstract $request Request + * + * @return Reference Returns the collection from the request + * + * @since 1.0.0 + */ + private function createReferenceFromRequest(RequestAbstract $request) : Reference + { + $mediaReference = new Reference(); + $mediaReference->name = (string) $request->getData('name'); + $mediaReference->source = new NullMedia((int) $request->getData('source')); + $mediaReference->createdBy = new NullAccount($request->header->account); + $mediaReference->setVirtualPath($request->getData('virtualpath')); + + return $mediaReference; + } + + /** + * Validate collection create request + * + * @param RequestAbstract $request Request + * + * @return array Returns the validation array of the request + * + * @since 1.0.0 + */ + private function validateReferenceCreate(RequestAbstract $request) : array + { + $val = []; + if (($val['name'] = empty($request->getData('name'))) + || ($val['virtualpath'] = empty($request->getData('virtualpath'))) + || ($val['source'] = empty($request->getData('source'))) + ) { + return $val; + } + + return []; + } + + // Very similar to create Reference + // Reference = it's own media element which points to another element (disadvantage = additional step) + // Collection add = directly pointing to other media element (disadvantage = we don't know if we are allowed to modify/delete) + public function apiCollectionAdd(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void + { + + } + /** * Api method to create a collection. * @@ -590,7 +695,7 @@ final class ApiController extends Controller { if (empty($media) || !$this->app->accountManager->get($account)->hasPermission( - PermissionType::CREATE, $this->app->orgId, null, self::NAME, PermissionCategory::COLLECTION, null) + PermissionType::CREATE, $this->app->unitId, null, self::NAME, PermissionCategory::COLLECTION, null) ) { return new NullCollection(); } @@ -731,7 +836,6 @@ final class ApiController extends Controller $stat, $request->header->account, $virtualPath, - $request->getData('type', 'int'), $request->getOrigin(), $this->app, unit: $request->getData('unit', 'int') @@ -775,6 +879,15 @@ final class ApiController extends Controller } } + if ($media->hasPassword() + && !$media->comparePassword((string) $request->getData('password')) + ) { + $view = new View($this->app->l11nManager, $request, $response); + $view->setTemplate('/Modules/Media/Theme/Api/invalidPassword'); + + return; + } + $this->setMediaResponseHeader($media, $request, $response); $view = $this->createView($media, $request, $response); $view->setData('path', __DIR__ . '/../../../'); diff --git a/Controller/BackendController.php b/Controller/BackendController.php index 0b644f1..522ead2 100755 --- a/Controller/BackendController.php +++ b/Controller/BackendController.php @@ -81,7 +81,7 @@ final class BackendController extends Controller $hasPermission = $this->app->accountManager->get($request->header->account) ->hasPermission( PermissionType::READ, - $this->app->orgId, + $this->app->unitId, $this->app->appName, self::NAME, PermissionCategory::MEDIA, @@ -93,7 +93,7 @@ final class BackendController extends Controller $permWhere = PermissionAbstractMapper::helper($this->app->dbPool->get('select')) ->groups($this->app->accountManager->get($request->header->account)->getGroupIds()) ->account($request->header->account) - ->units([null, $this->app->orgId]) + ->units([null, $this->app->unitId]) ->apps([null, 'Api', $this->app->appName]) ->modules([null, self::NAME]) ->categories([null, PermissionCategory::MEDIA]) @@ -112,7 +112,7 @@ final class BackendController extends Controller $permWhere = PermissionAbstractMapper::helper($this->app->dbPool->get('select')) ->groups($this->app->accountManager->get($request->header->account)->getGroupIds()) ->account($request->header->account) - ->units([null, $this->app->orgId]) + ->units([null, $this->app->unitId]) ->apps([null, 'Api', $this->app->appName]) ->modules([null, self::NAME]) ->categories([null, PermissionCategory::MEDIA]) @@ -236,6 +236,14 @@ final class BackendController extends Controller ->where('tags/title/language', $request->getLanguage()) ->execute(); + if ($media->hasPassword() + && !$media->comparePassword((string) $request->getData('password')) + ) { + $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/invalidPassword'); + + return $view; + } + if ($media->class === MediaClass::COLLECTION) { /** @var \Modules\Media\Models\Media[] $files */ $files = MediaMapper::getByVirtualPath( @@ -293,6 +301,15 @@ final class BackendController extends Controller private function createMediaView(Media $media, RequestAbstract $request, ResponseAbstract $response) : View { $view = new ElementView($this->app->l11nManager, $request, $response); + + if ($media->hasPassword() + && !$media->comparePassword((string) $request->getData('password')) + ) { + $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/invalidPassword'); + + return $view; + } + switch (\strtolower($media->extension)) { case 'pdf': $view->setTemplate('/Modules/Media/Theme/Backend/Components/Media/pdf'); diff --git a/Models/CollectionMapper.php b/Models/CollectionMapper.php index a2c724e..503f75c 100755 --- a/Models/CollectionMapper.php +++ b/Models/CollectionMapper.php @@ -74,7 +74,8 @@ final class CollectionMapper extends MediaMapper */ public static function getByVirtualPath(string $virtualPath = '/', int $status = MediaStatus::NORMAL) : ReadMapper { - return self::getAll()->where('virtualPath', $virtualPath) + return self::getAll() + ->where('virtualPath', $virtualPath) ->with('createdBy') ->with('tags') ->where('status', $status) diff --git a/Models/Media.php b/Models/Media.php index 5fe396c..b09596b 100755 --- a/Models/Media.php +++ b/Models/Media.php @@ -56,10 +56,10 @@ class Media implements \JsonSerializable /** * Type. * - * @var null|int|MediaType + * @var MediaType[] * @since 1.0.0 */ - public null | int | MediaType $type = null; + public array $types = []; /** * Extension. @@ -290,6 +290,11 @@ class Media implements \JsonSerializable return $this->nonce !== null; } + public function hasPassword() : bool + { + return !empty($password); + } + /** * Set encryption password * @@ -392,6 +397,72 @@ class Media implements \JsonSerializable $this->virtualPath = \str_replace('\\', '/', $path); } + /** + * Adding new type. + * + * @param MediaType $type MediaType + * + * @return int + * + * @since 1.0.0 + */ + public function addMediaType(MediaType $type) : int + { + $this->types[] = $type; + + \end($this->types); + $key = (int) \key($this->types); + \reset($this->types); + + return $key; + } + + /** + * Remove MediaType from list. + * + * @param int $id MediaType + * + * @return bool + * + * @since 1.0.0 + */ + public function removeMediaType($id) : bool + { + if (isset($this->types[$id])) { + unset($this->types[$id]); + + return true; + } + + return false; + } + + /** + * Get media types. + * + * @return MediaType[] + * + * @since 1.0.0 + */ + public function getMediaTypes() : array + { + return $this->types; + } + + /** + * Get media type. + * + * @param int $id Element id + * + * @return MediaType + * + * @since 1.0.0 + */ + public function getMediaType(int $id) : MediaType + { + return $this->types[$id] ?? new NullMediaType(); + } + /** * Adding new tag. * @@ -433,7 +504,7 @@ class Media implements \JsonSerializable } /** - * Get task elements. + * Get tags. * * @return Tag[] * @@ -445,7 +516,7 @@ class Media implements \JsonSerializable } /** - * Get task elements. + * Get tag. * * @param int $id Element id * diff --git a/Models/MediaMapper.php b/Models/MediaMapper.php index 663d32d..0bb7018 100755 --- a/Models/MediaMapper.php +++ b/Models/MediaMapper.php @@ -38,7 +38,6 @@ class MediaMapper extends DataMapperFactory public const COLUMNS = [ 'media_id' => ['name' => 'media_id', 'type' => 'int', 'internal' => 'id'], 'media_name' => ['name' => 'media_name', 'type' => 'string', 'internal' => 'name', 'autocomplete' => true], - 'media_type' => ['name' => 'media_type', 'type' => 'int', 'internal' => 'type'], 'media_description' => ['name' => 'media_description', 'type' => 'string', 'internal' => 'description', 'autocomplete' => true], 'media_description_raw' => ['name' => 'media_description_raw', 'type' => 'string', 'internal' => 'descriptionRaw'], 'media_content' => ['name' => 'media_content', 'type' => 'int', 'internal' => 'content'], @@ -107,6 +106,12 @@ class MediaMapper extends DataMapperFactory 'external' => 'media_tag_dst', 'self' => 'media_tag_src', ], + 'types' => [ + 'mapper' => MediaTypeMapper::class, + 'table' => 'media_type_rel', + 'external' => 'media_type_rel_dst', + 'self' => 'media_type_rel_src', + ], ]; /** @@ -169,7 +174,7 @@ class MediaMapper extends DataMapperFactory ->with('createdBy') ->with('source') ->with('tags') - ->with('tags/title') + ->with('tags/content') ->where('virtualPath', $virtualPath) ->where('status', $status); } diff --git a/Models/MediaTypeMapper.php b/Models/MediaTypeMapper.php index afcd820..0c62db7 100755 --- a/Models/MediaTypeMapper.php +++ b/Models/MediaTypeMapper.php @@ -49,7 +49,7 @@ final class MediaTypeMapper extends DataMapperFactory 'mapper' => MediaTypeL11nMapper::class, 'table' => 'media_type_l11n', 'self' => 'media_type_l11n_type', - 'column' => 'title', + 'column' => 'content', 'external'=> null, ], ]; diff --git a/Theme/Api/invalidPassword.tpl.php b/Theme/Api/invalidPassword.tpl.php new file mode 100644 index 0000000..129373a --- /dev/null +++ b/Theme/Api/invalidPassword.tpl.php @@ -0,0 +1 @@ +Invalid Password \ No newline at end of file diff --git a/Theme/Backend/Components/Media/invalidPassword.tpl.php b/Theme/Backend/Components/Media/invalidPassword.tpl.php new file mode 100644 index 0000000..a581fcd --- /dev/null +++ b/Theme/Backend/Components/Media/invalidPassword.tpl.php @@ -0,0 +1,29 @@ + + +
+
+
+
+ + +
+ + +
+
+
\ No newline at end of file diff --git a/tests/Controller/ApiControllerTest.php b/tests/Controller/ApiControllerTest.php index fa8ac56..3af6776 100755 --- a/tests/Controller/ApiControllerTest.php +++ b/tests/Controller/ApiControllerTest.php @@ -48,7 +48,7 @@ final class ApiControllerTest extends \PHPUnit\Framework\TestCase }; $this->app->dbPool = $GLOBALS['dbpool']; - $this->app->orgId = 1; + $this->app->unitId = 1; $this->app->appName = 'Api'; $this->app->accountManager = new AccountManager($GLOBALS['session']); $this->app->appSettings = new CoreSettings();