From d0b16b2fc15ac2b3d5db65586a7a94fda8f7224c Mon Sep 17 00:00:00 2001 From: Philipp Date: Fri, 25 Nov 2022 23:43:07 +0100 Subject: [PATCH] Move mod/fbrowser to src\Modules\Attachment|Photos\Browser --- src/App/Arguments.php | 2 + src/App/Page.php | 1 + src/App/Router.php | 2 +- src/Model/Photo.php | 53 ++++ src/Module/Attach.php | 1 - src/Module/Profile/Attachment/Browser.php | 81 +++++++ src/Module/Profile/Photos/Browser.php | 105 ++++++++ static/routes.config.php | 26 +- view/global.css | 12 +- view/js/filebrowser.js | 113 +++++---- view/js/main.js | 8 +- view/templates/jot-header.tpl | 4 +- view/templates/{ => profile}/filebrowser.tpl | 6 +- view/theme/frio/css/style.css | 4 +- view/theme/frio/js/filebrowser.js | 229 +++++++++--------- view/theme/frio/js/modal.js | 8 +- view/theme/frio/templates/jot-header.tpl | 4 +- view/theme/frio/templates/js_strings.tpl | 1 + .../templates/{ => profile}/filebrowser.tpl | 6 +- view/theme/quattro/dark/style.css | 12 +- view/theme/quattro/green/style.css | 12 +- view/theme/quattro/lilac/style.css | 12 +- view/theme/quattro/quattro.less | 12 +- view/theme/vier/style.css | 2 +- 24 files changed, 483 insertions(+), 233 deletions(-) create mode 100644 src/Module/Profile/Attachment/Browser.php create mode 100644 src/Module/Profile/Photos/Browser.php rename view/templates/{ => profile}/filebrowser.tpl (80%) rename view/theme/frio/templates/{ => profile}/filebrowser.tpl (85%) diff --git a/src/App/Arguments.php b/src/App/Arguments.php index 6dfdcb560..5d30f9bd2 100644 --- a/src/App/Arguments.php +++ b/src/App/Arguments.php @@ -85,6 +85,8 @@ class Arguments /** * @return string The module name based on the arguments + * @deprecated 2022.12 - With the new (sub-)routes, it's no more trustworthy, use the ModuleClass instead + * @see Router::getModuleClass() */ public function getModuleName(): string { diff --git a/src/App/Page.php b/src/App/Page.php index 37141426c..afc94fbdf 100644 --- a/src/App/Page.php +++ b/src/App/Page.php @@ -241,6 +241,7 @@ class Page implements ArrayAccess * being first */ $this->page['htmlhead'] = Renderer::replaceMacros($tpl, [ + '$local_nickname' => $app->getLoggedInUserNickname(), '$local_user' => $localUID, '$generator' => 'Friendica' . ' ' . App::VERSION, '$delitem' => $l10n->t('Delete this item?'), diff --git a/src/App/Router.php b/src/App/Router.php index 4e5f29521..b6fd1098f 100644 --- a/src/App/Router.php +++ b/src/App/Router.php @@ -266,7 +266,7 @@ class Router * @throws HTTPException\MethodNotAllowedException If a rule matched but the method didn't * @throws HTTPException\NotFoundException If no rule matched */ - private function getModuleClass(): string + public function getModuleClass(): string { $cmd = $this->args->getCommand(); $cmd = '/' . ltrim($cmd, '/'); diff --git a/src/Model/Photo.php b/src/Model/Photo.php index 7c620b0ad..a139ba715 100644 --- a/src/Model/Photo.php +++ b/src/Model/Photo.php @@ -173,6 +173,59 @@ class Photo return $photo; } + /** + * Returns all browsable albums for a given user + * + * @param int $uid The given user + * + * @return array An array of albums + * @throws \Exception + */ + public static function getBrowsableAlbumsForUser(int $uid): array + { + $photos = DBA::toArray( + DBA::p( + "SELECT DISTINCT(`album`) AS `albume` FROM `photo` WHERE `uid` = ? AND NOT `photo-type` IN (?, ?)", + $uid, + static::CONTACT_AVATAR, + static::CONTACT_BANNER + ) + ); + + return array_column($photos, 'album'); + } + + /** + * Returns browsable photos for a given user (optional and a given album) + * + * @param int $uid The given user id + * @param string|null $album (optional) The given album + * + * @return array All photos of the user/album + * @throws \Exception + */ + public static function getBrowsablePhotosForUser(int $uid, string $album = null): array + { + if (!empty($album)) { + $sqlExtra = sprintf("AND `album` = '%S' ", DBA::escape($album)); + $sqlExtra2 = ""; + } else { + $sqlExtra = ''; + $sqlExtra2 = ' ORDER BY created DESC LIMIT 0, 10'; + } + + return DBA::toArray( + DBA::p( + "SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`, ANY_VALUE(`type`) AS `type`, + min(`scale`) AS `hiq`, max(`scale`) AS `loq`, ANY_VALUE(`desc`) AS `desc`, ANY_VALUE(`created`) AS `created` + FROM `photo` WHERE `uid` = ? $sqlExtra AND NOT `photo-type` IN (?, ?) + GROUP BY `resource-id` $sqlExtra2", + $uid, + Photo::CONTACT_AVATAR, + Photo::CONTACT_BANNER + )); + } + /** * Check if photo with given conditions exists * diff --git a/src/Module/Attach.php b/src/Module/Attach.php index a73beb2b8..17b2d6e90 100644 --- a/src/Module/Attach.php +++ b/src/Module/Attach.php @@ -37,7 +37,6 @@ class Attach extends BaseModule */ protected function rawContent(array $request = []) { - $a = DI::app(); if (empty($this->parameters['item'])) { throw new \Friendica\Network\HTTPException\BadRequestException(); } diff --git a/src/Module/Profile/Attachment/Browser.php b/src/Module/Profile/Attachment/Browser.php new file mode 100644 index 000000000..09429bff8 --- /dev/null +++ b/src/Module/Profile/Attachment/Browser.php @@ -0,0 +1,81 @@ +session = $session; + $this->app = $app; + } + + protected function content(array $request = []): string + { + if (!$this->session->getLocalUserId()) { + $this->baseUrl->redirect(); + } + + // Needed to match the correct template in a module that uses a different theme than the user/site/default + $theme = Strings::sanitizeFilePathItem($request['theme'] ?? ''); + if ($theme && is_file("view/theme/$theme/config.php")) { + $this->app->setCurrentTheme($theme); + } + + $files = Attach::selectToArray(['id', 'filename', 'filetype'], ['uid' => $this->session->getLocalUserId()]); + + + $fileArray = array_map([$this, 'map_files'], $files); + + $tpl = Renderer::getMarkupTemplate('profile/filebrowser.tpl'); + $output = Renderer::replaceMacros($tpl, [ + '$type' => 'attachment', + '$path' => ['' => $this->t('Files')], + '$folders' => false, + '$files' => $fileArray, + '$cancel' => $this->t('Cancel'), + '$nickname' => $this->app->getLoggedInUserNickname(), + '$upload' => $this->t('Upload'), + ]); + + if (empty($request['mode'])) { + System::httpExit($output); + } + + return $output; + } + + protected function map_files(array $record): array + { + [$m1, $m2] = explode('/', $record['filetype']); + $filetype = file_exists(sprintf('images/icons/%s.png', $m1) ? $m1 : 'zip'); + + return [ + sprintf('%s/attach/%s', $this->baseUrl, $record['id']), + $record['filename'], + sprintf('%s/images/icon/16/%s.png', $this->baseUrl, $filetype), + ]; + } +} diff --git a/src/Module/Profile/Photos/Browser.php b/src/Module/Profile/Photos/Browser.php new file mode 100644 index 000000000..1e8a28b12 --- /dev/null +++ b/src/Module/Profile/Photos/Browser.php @@ -0,0 +1,105 @@ +session = $session; + $this->app = $app; + } + + protected function content(array $request = []): string + { + if (!$this->session->getLocalUserId()) { + $this->baseUrl->redirect(); + } + + // Needed to match the correct template in a module that uses a different theme than the user/site/default + $theme = Strings::sanitizeFilePathItem($request['theme'] ?? ''); + if ($theme && is_file("view/theme/$theme/config.php")) { + $this->app->setCurrentTheme($theme); + } + + $album = $this->parameters['album'] ?? null; + + $photos = Photo::getBrowsablePhotosForUser($this->session->getLocalUserId(), $album); + $albums = $album ? false : Photo::getBrowsableAlbumsForUser($this->session->getLocalUserId()); + + $path = [ + '' => $this->t('Photos'), + ]; + if (!empty($album)) { + $path[$album] = $album; + } + + $photosArray = array_map([$this, 'map_files'], $photos); + + $tpl = Renderer::getMarkupTemplate('profile/filebrowser.tpl'); + $output = Renderer::replaceMacros($tpl, [ + '$type' => 'photos', + '$path' => $path, + '$folders' => $albums, + '$files' => $photosArray, + '$cancel' => $this->t('Cancel'), + '$nickname' => $this->app->getLoggedInUserNickname(), + '$upload' => $this->t('Upload'), + ]); + + if (empty($request['mode'])) { + System::httpExit($output); + } + + return $output; + } + + protected function map_files(array $record): array + { + $types = Images::supportedTypes(); + $ext = $types[$record['type']]; + $filename_e = $record['filename']; + + // Take the largest picture that is smaller or equal 640 pixels + $photo = Photo::selectFirst( + ['scale'], + [ + "`resource-id` = ? AND `height` <= ? AND `width` <= ?", + $record['resource-id'], + 640, + 640 + ], + ['order' => ['scale']]); + $scale = $photo['scale'] ?? $record['loq']; + + return [ + sprintf('%s/photos/%s/image/%s', $this->baseUrl, $this->app->getLoggedInUserNickname(), $record['resource-id']), + $filename_e, + sprintf('%s/photo/%s-%s.%s', $this->baseUrl, $record['resource-id'], $scale, $ext), + $record['desc'], + ]; + } +} diff --git a/static/routes.config.php b/static/routes.config.php index ae5755711..794f705e4 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -31,18 +31,20 @@ use Friendica\App\Router as R; use Friendica\Module; $profileRoutes = [ - '' => [Module\Profile\Index::class, [R::GET]], - '/attachment/upload' => [Module\Profile\Attachment\Upload::class, [ R::POST]], - '/contacts/common' => [Module\Profile\Common::class, [R::GET]], - '/contacts[/{type}]' => [Module\Profile\Contacts::class, [R::GET]], - '/media' => [Module\Profile\Media::class, [R::GET]], - '/photos' => [Module\Profile\Photos\Index::class, [R::GET ]], - '/photos/upload' => [Module\Profile\Photos\Upload::class, [ R::POST]], - '/profile' => [Module\Profile\Profile::class, [R::GET]], - '/remote_follow' => [Module\Profile\RemoteFollow::class, [R::GET, R::POST]], - '/schedule' => [Module\Profile\Schedule::class, [R::GET, R::POST]], - '/status[/{category}[/{date1}[/{date2}]]]' => [Module\Profile\Status::class, [R::GET]], - '/unkmail' => [Module\Profile\UnkMail::class, [R::GET, R::POST]], + '' => [Module\Profile\Index::class, [R::GET]], + '/attachment/upload' => [Module\Profile\Attachment\Upload::class, [ R::POST]], + '/attachment/browser' => [Module\Profile\Attachment\Browser::class, [R::GET]], + '/contacts/common' => [Module\Profile\Common::class, [R::GET]], + '/contacts[/{type}]' => [Module\Profile\Contacts::class, [R::GET]], + '/media' => [Module\Profile\Media::class, [R::GET]], + '/photos' => [Module\Profile\Photos\Index::class, [R::GET ]], + '/photos/browser[/{album}]' => [Module\Profile\Photos\Browser::class, [R::GET]], + '/photos/upload' => [Module\Profile\Photos\Upload::class, [ R::POST]], + '/profile' => [Module\Profile\Profile::class, [R::GET]], + '/remote_follow' => [Module\Profile\RemoteFollow::class, [R::GET, R::POST]], + '/schedule' => [Module\Profile\Schedule::class, [R::GET, R::POST]], + '/status[/{category}[/{date1}[/{date2}]]]' => [Module\Profile\Status::class, [R::GET]], + '/unkmail' => [Module\Profile\UnkMail::class, [R::GET, R::POST]], ]; $apiRoutes = [ diff --git a/view/global.css b/view/global.css index 800a3ea34..f09d95939 100644 --- a/view/global.css +++ b/view/global.css @@ -345,12 +345,12 @@ img.acpopup-img { .fbrowser .path a:before, .fbrowser .path .btn-link:before { content: "/"; padding-right: 5px;} .fbrowser .folders ul { list-style-type: none; padding-left: 10px;} .fbrowser .list { height: auto; overflow-y: hidden; margin: 10px 0px; } -.fbrowser.image .photo-album-image-wrapper { float: left; } -.fbrowser.image a img, .fbrowser.image .btn-link img { height: 48px; } -.fbrowser.image a p, .fbrowser.image .btn-link p { display: none;} -.fbrowser.file .photo-album-image-wrapper { float:none; white-space: nowrap; } -.fbrowser.file img { display: inline; } -.fbrowser.file p { display: inline; white-space: nowrap; } +.fbrowser.photos .photo-album-image-wrapper { float: left; } +.fbrowser.photos a img, .fbrowser.photos .btn-link img { height: 48px; } +.fbrowser.photos a p, .fbrowser.photos .btn-link p { display: none;} +.fbrowser.attachment .photo-album-image-wrapper { float:none; white-space: nowrap; } +.fbrowser.attachment img { display: inline; } +.fbrowser.attachment p { display: inline; white-space: nowrap; } .fbrowser .upload { clear: both; padding-top: 1em;} .fbrowser .error { background: #ffeeee; border: 1px solid #994444; color: #994444; padding: 0.5em;} .fbrowser .error .close { float: right; font-weight: bold; } diff --git a/view/js/filebrowser.js b/view/js/filebrowser.js index e5bd2730c..1b8a4d51f 100644 --- a/view/js/filebrowser.js +++ b/view/js/filebrowser.js @@ -33,7 +33,7 @@ * will be one of "image" or "file", and the event handler will * get the following params: * - * filemane: filename of item choosed by user + * filename: filename of item chosen by user * embed: bbcode to embed element into posts * id: id from caller code * @@ -47,52 +47,51 @@ * $("body").on("fbrowser.image.example", function(event, filename, bbcode, id) { * // close colorbox * $.colorbox.close(); - * // replace textxarea text with bbcode + * // replace textarea text with bbcode * $(id).value = bbcode; * }); **/ +const FileBrowser = { + nickname: '', + type: '', + event: '', + id: null, -var FileBrowser = { - nickname : "", - type : "", - event: "", - id : null, - - init: function(nickname, type) { + init: function (nickname, type) { FileBrowser.nickname = nickname; FileBrowser.type = type; - FileBrowser.event = "fbrowser."+type; - if (location['hash']!=="") { - var h = location['hash'].replace("#",""); + FileBrowser.event = "fbrowser." + type; + if (location['hash'] !== "") { + const h = location['hash'].replace('#', ''); FileBrowser.event = FileBrowser.event + "." + h.split("-")[0]; FileBrowser.id = h.split("-")[1]; } - console.log("FileBrowser:", nickname, type,FileBrowser.event, FileBrowser.id ); + console.log('FileBrowser:', nickname, type, FileBrowser.event, FileBrowser.id); - $(".error a.close").on("click", function(e) { + $('.error a.close').on('click', function (e) { e.preventDefault(); - $(".error").addClass("hidden"); + $('.error').addClass('hidden'); }); - $(".folders a, .path a").on("click", function(e){ + $('.folders a, .path a').on('click', function (e) { e.preventDefault(); - location.href = baseurl + "/fbrowser/" + FileBrowser.type + "/" + encodeURIComponent(this.dataset.folder) + "?mode=minimal" + location['hash']; + location.href = FileBrowser._getUrl("minimal", location['hash'], this.dataset.folder); + location.reload(); }); - $(".photo-album-photo-link").on('click', function(e){ + $(".photo-album-photo-link").on('click', function (e) { e.preventDefault(); - var embed = ""; - if (FileBrowser.type == "image") { - embed = "[url="+this.dataset.link+"][img="+this.dataset.img+"]"+this.dataset.alt+"[/img][/url]"; + let embed = ''; + if (FileBrowser.type === "photos") { + embed = '[url=' + this.dataset.link + '][img=' + this.dataset.img + ']' + this.dataset.alt + '[/img][/url]'; } - if (FileBrowser.type=="file") { - // attachment links are "baseurl/attach/id"; we need id - embed = "[attachment]"+this.dataset.link.split("/").pop()+"[/attachment]"; + if (FileBrowser.type === "attachment") { + embed = '[attachment]' + this.dataset.link + '[/attachment]'; } console.log(FileBrowser.event, this.dataset.filename, embed, FileBrowser.id); - parent.$("body").trigger(FileBrowser.event, [ + parent.$('body').trigger(FileBrowser.event, [ this.dataset.filename, embed, FileBrowser.id @@ -100,45 +99,63 @@ var FileBrowser = { }); - if ($("#upload-image").length) - var image_uploader = new window.AjaxUpload( - 'upload-image', - { action: 'profile/' + FileBrowser.nickname + '/photos/upload?response=json', + if ($('#upload-photos').length) + { + new window.AjaxUpload( + 'upload-photos', + { + action: 'profile/' + FileBrowser.nickname + '/photos/upload?response=json', name: 'userfile', responseType: 'json', - onSubmit: function(file,ext) { $('#profile-rotator').show(); $(".error").addClass('hidden'); }, - onComplete: function(file,response) { - if (response['error']!= undefined) { - $(".error span").html(response['error']); - $(".error").removeClass('hidden'); + onSubmit: function (file, ext) { + $('#profile-rotator').show(); + $('.error').addClass('hidden'); + }, + onComplete: function (file, response) { + if (response['error'] !== undefined) { + $('.error span').html(response['error']); + $('.error').removeClass('hidden'); $('#profile-rotator').hide(); return; } - location = baseurl + "/fbrowser/image/?mode=minimal"+location['hash']; - location.reload(true); + location.href = FileBrowser._getUrl("minimal", location['hash']); + location.reload(); } } ); + } - if ($("#upload-file").length) - var file_uploader = new window.AjaxUpload( - 'upload-file', - { action: 'profile/' + FileBrowser.nickname + '/attachment/upload?response=json', + if ($('#upload-attachment').length) + { + new window.AjaxUpload( + 'upload-attachment', + { + action: 'profile/' + FileBrowser.nickname + '/attachment/upload?response=json', name: 'userfile', responseType: 'json', - onSubmit: function(file,ext) { $('#profile-rotator').show(); $(".error").addClass('hidden'); }, - onComplete: function(file,response) { - if (response['error']!= undefined) { - $(".error span").html(response['error']); - $(".error").removeClass('hidden'); + onSubmit: function (file, ext) { + $('#profile-rotator').show(); + $('.error').addClass('hidden'); + }, + onComplete: function (file, response) { + if (response['error'] !== undefined) { + $('.error span').html(response['error']); + $('.error').removeClass('hidden'); $('#profile-rotator').hide(); return; } - location = baseurl + "/fbrowser/file/?mode=minimal"+location['hash']; - location.reload(true); + location.href = FileBrowser._getUrl("minimal", location['hash']); + location.reload(); } } - ); + ); + } + }, + + _getUrl: function (mode, hash, folder) { + let folderValue = folder !== undefined ? folder : FileBrowser.folder; + let folderUrl = folderValue !== undefined ? '/' + encodeURIComponent(folderValue) : ''; + return 'profile/' + FileBrowser.nickname + '/' + FileBrowser.type + '/browser' + folderUrl + '?mode=' + mode + hash; } }; // @license-end diff --git a/view/js/main.js b/view/js/main.js index 93340dc37..3ba8240fe 100644 --- a/view/js/main.js +++ b/view/js/main.js @@ -166,7 +166,7 @@ $(function() { /* event from comment textarea button popups */ /* insert returned bbcode at cursor position or replace selected text */ - $("body").on("fbrowser.image.comment", function(e, filename, bbcode, id) { + $("body").on("fbrowser.photos.comment", function(e, filename, bbcode, id) { $.colorbox.close(); var textarea = document.getElementById("comment-edit-text-" +id); var start = textarea.selectionStart; @@ -1069,7 +1069,7 @@ var Dialog = { * to the event handler */ doImageBrowser : function (name, id) { - var url = Dialog._get_url("image",name,id); + var url = Dialog._get_url("photos",name,id); return Dialog.show(url); }, @@ -1086,7 +1086,7 @@ var Dialog = { * to the event handler */ doFileBrowser : function (name, id) { - var url = Dialog._get_url("file",name,id); + var url = Dialog._get_url("attachment",name,id); return Dialog.show(url); }, @@ -1095,7 +1095,7 @@ var Dialog = { if (id !== undefined) { hash = hash + "-" + id; } - return baseurl + "/fbrowser/"+type+"/?mode=minimal#"+hash; + return '/profile/' + localNickname + '/' + type + '/browser?mode=minimal#' + hash; }, _get_size: function() { diff --git a/view/templates/jot-header.tpl b/view/templates/jot-header.tpl index a67f9c3f0..2a81db6dd 100644 --- a/view/templates/jot-header.tpl +++ b/view/templates/jot-header.tpl @@ -61,11 +61,11 @@ function enableOnUser(){ **/ /* callback */ - $('body').on('fbrowser.image.main', function(e, filename, embedcode, id) { + $('body').on('fbrowser.photos.main', function(e, filename, embedcode, id) { $.colorbox.close(); addeditortext(embedcode); }); - $('body').on('fbrowser.file.main', function(e, filename, embedcode, id) { + $('body').on('fbrowser.photos.main', function(e, filename, embedcode, id) { $.colorbox.close(); addeditortext(embedcode); }); diff --git a/view/templates/filebrowser.tpl b/view/templates/profile/filebrowser.tpl similarity index 80% rename from view/templates/filebrowser.tpl rename to view/templates/profile/filebrowser.tpl index 09bf563ee..ff25741a9 100644 --- a/view/templates/filebrowser.tpl +++ b/view/templates/profile/filebrowser.tpl @@ -1,8 +1,8 @@ - - + +