Przeglądaj źródła

Improved user gallery
Improved public view

Sergio Brighenti 6 lat temu
rodzic
commit
7c4c02fac4

+ 12 - 19
app/Controllers/UploadController.php

@@ -5,6 +5,7 @@ namespace App\Controllers;
 
 use App\Exceptions\UnauthorizedException;
 use App\Web\Session;
+use Intervention\Image\ImageManagerStatic as Image;
 use League\Flysystem\FileExistsException;
 use League\Flysystem\FileNotFoundException;
 use League\Flysystem\Filesystem;
@@ -89,18 +90,15 @@ class UploadController extends Controller
 
 		$filesystem = $this->getStorage();
 
-		if (stristr($request->getHeaderLine('User-Agent'), 'TelegramBot') ||
-			stristr($request->getHeaderLine('User-Agent'), 'facebookexternalhit/') ||
-			stristr($request->getHeaderLine('User-Agent'), 'Discordbot/') ||
-			stristr($request->getHeaderLine('User-Agent'), 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0') || // The discord service bot?
-			stristr($request->getHeaderLine('User-Agent'), 'Facebot')) {
+		if (isBot($request->getHeaderLine('User-Agent'))) {
 			return $this->streamMedia($request, $response, $filesystem, $media);
 		} else {
 
 			try {
-				$mime = $filesystem->getMimetype($media->storage_path);
+				$media->mimetype = $filesystem->getMimetype($media->storage_path);
+				$media->size = humanFileSize($filesystem->getSize($media->storage_path));
 
-				$type = explode('/', $mime)[0];
+				$type = explode('/', $media->mimetype)[0];
 				if ($type === 'text') {
 					$media->text = $filesystem->read($media->storage_path);
 				} else if (in_array($type, ['image', 'video'])) {
@@ -119,7 +117,6 @@ class UploadController extends Controller
 			return $this->view->render($response, 'upload/public.twig', [
 				'delete_token' => isset($args['token']) ? $args['token'] : null,
 				'media' => $media,
-				'type' => $mime,
 				'extension' => pathinfo($media->filename, PATHINFO_EXTENSION),
 			]);
 		}
@@ -319,21 +316,17 @@ class UploadController extends Controller
 
 		if ($request->getParam('width') !== null && explode('/', $mime)[0] === 'image') {
 
-			$image = imagecreatefromstring($storage->read($media->storage_path));
-			$scaled = imagescale($image, $request->getParam('width'), $request->getParam('height') !== null ? $request->getParam('height') : -1);
-			imagedestroy($image);
-
-			ob_start();
-			imagepng($scaled, null, 9);
-
-			$imagedata = ob_get_contents();
-			ob_end_clean();
-			imagedestroy($scaled);
+			$image = Image::make($storage->readStream($media->storage_path))
+				->resizeCanvas(
+					$request->getParam('width'),
+					$request->getParam('height'),
+					'center')
+				->encode('png');
 
 			return $response
 				->withHeader('Content-Type', 'image/png')
 				->withHeader('Content-Disposition', $disposition . ';filename="scaled-' . pathinfo($media->filename)['filename'] . '.png"')
-				->write($imagedata);
+				->write($image);
 		} else {
 			ob_end_clean();
 			return $response

+ 63 - 4
app/helpers.php

@@ -13,7 +13,7 @@ if (!function_exists('humanFileSize')) {
 	{
 		for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {
 		}
-		return round($size, $precision) . ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i];
+		return round($size, $precision) . ' ' . ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i];
 	}
 }
 
@@ -77,7 +77,7 @@ if (!function_exists('urlFor')) {
 	 * @param string $path
 	 * @return string
 	 */
-	function urlFor(string $path)
+	function urlFor(string $path): string
 	{
 		global $app;
 		$baseUrl = $app->getContainer()->get('settings')['base_url'];
@@ -92,7 +92,7 @@ if (!function_exists('route')) {
 	 * @param array $args
 	 * @return string
 	 */
-	function route(string $path, array $args = [])
+	function route(string $path, array $args = []): string
 	{
 		global $app;
 		$uri = $app->getContainer()->get('router')->pathFor($path, $args);
@@ -106,8 +106,67 @@ if (!function_exists('lang')) {
 	 * @param array $args
 	 * @return string
 	 */
-	function lang(string $key, $args = [])
+	function lang(string $key, $args = []): string
 	{
 		return \App\Web\Lang::getInstance()->get($key, $args);
 	}
+}
+
+if (!function_exists('isBot')) {
+	/**
+	 * @param string $userAgent
+	 * @return boolean
+	 */
+	function isBot(string $userAgent)
+	{
+		$bots = [
+			'TelegramBot',
+			'facebookexternalhit/',
+			'Discordbot/',
+			'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0', // The discord service bot?
+			'Facebot',
+		];
+
+		foreach ($bots as $bot) {
+			if (stripos($userAgent, $bot) !== false) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+}
+
+if (!function_exists('mime2font')) {
+	function mime2font($mime)
+	{
+		$classes = [
+			'image' => 'fa-file-image',
+			'audio' => 'fa-file-audio',
+			'video' => 'fa-file-video',
+			'application/pdf' => 'fa-file-pdf',
+			'application/msword' => 'fa-file-word',
+			'application/vnd.ms-word' => 'fa-file-word',
+			'application/vnd.oasis.opendocument.text' => 'fa-file-word',
+			'application/vnd.openxmlformats-officedocument.wordprocessingml' => 'fa-file-word',
+			'application/vnd.ms-excel' => 'fa-file-excel',
+			'application/vnd.openxmlformats-officedocument.spreadsheetml' => 'fa-file-excel',
+			'application/vnd.oasis.opendocument.spreadsheet' => 'fa-file-excel',
+			'application/vnd.ms-powerpoint' => 'fa-file-powerpoint',
+			'application/vnd.openxmlformats-officedocument.presentationml' => 'fa-file-powerpoint',
+			'application/vnd.oasis.opendocument.presentation' => 'fa-file-powerpoint',
+			'text/plain' => 'fa-file-alt',
+			'text/html' => 'fa-file-code',
+			'application/json' => 'fa-file-code',
+			'application/gzip' => 'fa-file-archive',
+			'application/zip' => 'fa-file-archive',
+		];
+
+		foreach ($classes as $fullMime => $class) {
+			if (strpos($mime, $fullMime) === 0) {
+				return $class;
+			}
+		}
+		return 'fa-file';
+	}
 }

+ 1 - 0
bootstrap/app.php

@@ -83,6 +83,7 @@ $container['view'] = function ($container) use (&$config) {
 	$view->getEnvironment()->addFunction(new Twig_Function('route', 'route'));
 	$view->getEnvironment()->addFunction(new Twig_Function('lang', 'lang'));
 	$view->getEnvironment()->addFunction(new Twig_Function('urlFor', 'urlFor'));
+	$view->getEnvironment()->addFunction(new Twig_Function('mime2font', 'mime2font'));
 	return $view;
 };
 

+ 2 - 1
composer.json

@@ -1,6 +1,6 @@
 {
   "name": "sergix44/xbackbone",
-  "version": "2.2",
+  "version": "2.3",
   "description": "A lightweight ShareX PHP backend",
   "type": "project",
   "require": {
@@ -9,6 +9,7 @@
     "slim/twig-view": "^2.4",
     "league/flysystem": "^1.0.45",
     "monolog/monolog": "^1.23",
+    "intervention/image": "^2.4",
     "ext-json": "*",
     "ext-gd": "*",
     "ext-pdo": "*"

+ 136 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "0273ad770ec6dd004ae0d12f9db81ed4",
+    "content-hash": "61a3ac382bb0960dd8e44736c0be5178",
     "packages": [
         {
             "name": "container-interop/container-interop",
@@ -37,6 +37,141 @@
             "homepage": "https://github.com/container-interop/container-interop",
             "time": "2017-02-14T19:40:03+00:00"
         },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "1.4.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
+                "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "psr/http-message": "~1.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "time": "2017-03-20T17:10:46+00:00"
+        },
+        {
+            "name": "intervention/image",
+            "version": "2.4.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Intervention/image.git",
+                "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Intervention/image/zipball/e82d274f786e3d4b866a59b173f42e716f0783eb",
+                "reference": "e82d274f786e3d4b866a59b173f42e716f0783eb",
+                "shasum": ""
+            },
+            "require": {
+                "ext-fileinfo": "*",
+                "guzzlehttp/psr7": "~1.1",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "~0.9.2",
+                "phpunit/phpunit": "^4.8 || ^5.7"
+            },
+            "suggest": {
+                "ext-gd": "to use GD library based image processing.",
+                "ext-imagick": "to use Imagick based image processing.",
+                "intervention/imagecache": "Caching extension for the Intervention Image library"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.4-dev"
+                },
+                "laravel": {
+                    "providers": [
+                        "Intervention\\Image\\ImageServiceProvider"
+                    ],
+                    "aliases": {
+                        "Image": "Intervention\\Image\\Facades\\Image"
+                    }
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Intervention\\Image\\": "src/Intervention/Image"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Oliver Vogel",
+                    "email": "oliver@olivervogel.com",
+                    "homepage": "http://olivervogel.com/"
+                }
+            ],
+            "description": "Image handling and manipulation library with support for Laravel integration",
+            "homepage": "http://image.intervention.io/",
+            "keywords": [
+                "gd",
+                "image",
+                "imagick",
+                "laravel",
+                "thumbnail",
+                "watermark"
+            ],
+            "time": "2018-05-29T14:19:03+00:00"
+        },
         {
             "name": "league/flysystem",
             "version": "1.0.48",

+ 15 - 17
resources/templates/dashboard/home.twig

@@ -13,35 +13,33 @@
                     <div class="col-md-4" id="media_{{ media.id }}">
                         <div class="card mb-4 box-shadow">
                             {% if media.mimetype starts with 'image' %}
-                                <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}" target="_blank">
-                                    <img class="card-img-top user-img" src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension ~ '/raw?width=348&height=192') }}" alt="{{ media.filename }}">
-                                </a>
+                                <img class="card-img" src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension ~ '/raw?width=286&height=219') }}" alt="Card image">
                             {% else %}
-                                <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}" target="_blank">
-                                    <div class="card-header text-center"><i class="far fa-file fa-10x"></i></div>
-                                </a>
+                                <div class="text-center" style="font-size: 178px;"><i class="far {{ mime2font(media.mimetype) }} mb-4 mt-4"></i></div>
                             {% endif %}
-                            <div class="card-body">
-                                <p class="card-text">{{ media.filename }}
-                                    <small class="float-right">{{ media.size }}</small>
-                                </p>
-                                <div class="d-flex justify-content-between align-items-center">
-                                    <div class="btn-group">
-                                        <button type="button" class="btn btn-sm btn-outline-success btn-clipboard" data-toggle="tooltip" title="{{ lang('copy_link') }}" data-clipboard-text="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}">
+                            <div class="card-img-overlay" title="{{ media.filename }}">
+                                <div class="user-img-buttons">
+                                    <span class="badge badge-dark box-shadow-strong">{{ media.size }}</span>
+                                    <div class="btn-group box-shadow-strong float-right">
+                                        <a class="btn btn-sm btn-light" href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}" data-toggle="tooltip" title="{{ lang('open') }}" target="_blank"><i class="fas fa-external-link-alt"></i></a>
+                                        <button type="button" class="btn btn-sm btn-success btn-clipboard" data-toggle="tooltip" title="{{ lang('copy_link') }}" data-clipboard-text="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ media.extension) }}">
                                             <i class="fas fa-link"></i>
                                         </button>
                                         {% if media.published %}
-                                            <a href="javascript:void(0)" class="btn btn-sm btn-outline-warning publish-toggle" data-toggle="tooltip" title="{{ lang('hide') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-times-circle"></i></a>
+                                            <a href="javascript:void(0)" class="btn btn-sm btn-warning publish-toggle" data-toggle="tooltip" title="{{ lang('hide') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-times-circle"></i></a>
                                         {% else %}
-                                            <a href="javascript:void(0)" class="btn btn-sm btn-outline-info publish-toggle" data-toggle="tooltip" title="{{ lang('publish') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a>
+                                            <a href="javascript:void(0)" class="btn btn-sm btn-info publish-toggle" data-toggle="tooltip" title="{{ lang('publish') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a>
                                         {% endif %}
-                                        <button type="button" class="btn btn-sm btn-outline-danger media-delete" data-link="{{ route('upload.delete', {'id': media.id}) }}" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('delete') }}">
+                                        <button type="button" class="btn btn-sm btn-danger media-delete" data-link="{{ route('upload.delete', {'id': media.id}) }}" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('delete') }}">
                                             <i class="fas fa-trash"></i>
                                         </button>
                                     </div>
-                                    <small class="text-muted">{{ media.timestamp|date("d/m/Y H:i:s") }}</small>
                                 </div>
                             </div>
+                            <div class="card-footer d-flex justify-content-between">
+                                <span class="user-title">{{ media.filename }}</span>
+                                <small>{{ media.timestamp|date("d/m/Y H:i") }}</small>
+                            </div>
                         </div>
                     </div>
                 {% endfor %}

+ 22 - 9
resources/templates/upload/public.twig

@@ -37,7 +37,7 @@
                         </div>
                     </form>
                 {% endif %}
-                {% if type starts with 'image' %}
+                {% if media.mimetype starts with 'image' %}
                     <div class="row mb-2">
                         <div class="col-md-12">
                             <img src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" class="img-thumbnail rounded mx-auto d-block" alt="{{ media.filename }}">
@@ -48,7 +48,7 @@
                             {{ media.filename }}
                         </div>
                     </div>
-                {% elseif type starts with 'text' %}
+                {% elseif media.mimetype starts with 'text' %}
                     <div class="row mb-2">
                         <div class="col-md-12">
                             <pre><code>{{ media.text }}</code></pre>
@@ -59,24 +59,37 @@
                             {{ media.filename }}
                         </div>
                     </div>
-                {% elseif type starts with 'video' %}
-                    <div class="video-content">
-                        <video class="video-js vjs-fluid vjs-big-play-centered" data-setup='{"controls": true, "autoplay": true, "preload": "auto"}'>
-                            <source src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" type="{{ type }}">
+                {% elseif media.mimetype starts with 'video' %}
+                    <div class="media-player">
+                        <video class="video-js vjs-fluid vjs-big-play-centered" data-setup='{"controls": true, "autoplay": true, "preload": "auto", "responsive": true}'>
+                            <source src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" type="{{ media.mimetype }}">
                             Your browser does not support HTML5 video.
                             <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/download') }}" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
                         </video>
                     </div>
+                {% elseif media.mimetype starts with 'audio' %}
+                    <div class="media-player">
+                        <audio class="video-js vjs-fluid vjs-big-play-centered" data-setup='{"controls": true, "autoplay": true, "preload": "auto", "responsive": true}'>
+                            <source src="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/raw') }}" type="{{ media.mimetype }}">
+                            Your browser does not support HTML5 audio.
+                            <a href="{{ urlFor('/' ~ media.user_code ~ '/' ~ media.code ~ '.' ~ extension ~ '/download') }}" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
+                        </audio>
+                    </div>
                 {% else %}
                     <div class="text-center">
-                        <div class="row mb-2">
+                        <div class="row mb-3">
+                            <div class="col-md-12">
+                                <i class="far {{ mime2font(media.mimetype) }} fa-10x"></i>
+                            </div>
+                        </div>
+                        <div class="row">
                             <div class="col-md-12">
-                                <i class="far fa-file-archive fa-10x"></i>
+                                <b>{{ media.filename }}</b>
                             </div>
                         </div>
                         <div class="row mb-4">
                             <div class="col-md-12">
-                                {{ media.filename }}
+                                {{ media.size }}
                             </div>
                         </div>
                         <div class="row">

+ 26 - 1
src/css/app.css

@@ -45,6 +45,10 @@ body {
     box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
 }
 
+.box-shadow-strong {
+    box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .15);
+}
+
 .admin-img {
     max-height: 25px;
 }
@@ -53,6 +57,27 @@ body {
     max-height: 192px;
 }
 
+.user-title {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    max-width: 200px;
+}
+
+.user-img-buttons {
+    opacity: 0;
+    transition: opacity .5s ease-in-out;
+    -moz-transition: opacity .5s ease-in-out;
+    -webkit-transition: opacity .5s ease-in-out;
+}
+
+.card-img-overlay:hover .user-img-buttons {
+    opacity: 1;
+    transition: opacity .2s ease-in-out;
+    -moz-transition: opacity .2s ease-in-out;
+    -webkit-transition: opacity .2s ease-in-out;
+}
+
 .footer {
     position: absolute;
     bottom: 0;
@@ -62,7 +87,7 @@ body {
     text-align: right;
 }
 
-.video-content {
+.media-player{
     width: 80%;
     margin-right: auto;
     margin-left: auto;

+ 4 - 4
src/js/app.js

@@ -31,8 +31,8 @@ var app = {
                     .tooltip('dispose')
                     .attr('title', 'Publish')
                     .tooltip()
-                    .removeClass('btn-outline-warning')
-                    .addClass('btn-outline-info')
+                    .removeClass('btn-warning')
+                    .addClass('btn-info')
                     .html('<i class="fas fa-check-circle"></i>');
                 $('#published_' + id).html('<span class="badge badge-danger"><i class="fas fa-times"></i></span>');
             });
@@ -43,8 +43,8 @@ var app = {
                     .tooltip('dispose')
                     .attr('title', 'Unpublish')
                     .tooltip()
-                    .removeClass('btn-outline-info')
-                    .addClass('btn-outline-warning')
+                    .removeClass('btn-info')
+                    .addClass('btn-warning')
                     .html('<i class="fas fa-times-circle"></i>');
                 $('#published_' + id).html('<span class="badge badge-success"><i class="fas fa-check"></i></span>');
             });