Add avatar uploading.
This commit is contained in:
parent
a23b2729cc
commit
073389c964
21 changed files with 450 additions and 74 deletions
7
assets/less/mio/classes/input/upload.less
Normal file
7
assets/less/mio/classes/input/upload.less
Normal file
|
@ -0,0 +1,7 @@
|
|||
.mio__input__upload {
|
||||
display: none;
|
||||
|
||||
&__label {
|
||||
.mio__input__button();
|
||||
}
|
||||
}
|
20
assets/less/mio/classes/settings/avatar.less
Normal file
20
assets/less/mio/classes/settings/avatar.less
Normal file
|
@ -0,0 +1,20 @@
|
|||
.mio__settings__avatar {
|
||||
display: flex;
|
||||
min-height: 200px;
|
||||
justify-content: space-between;
|
||||
|
||||
&__form {
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&__forms {
|
||||
text-align: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__preview {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
|
@ -2,4 +2,8 @@
|
|||
&--account {
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&--avatar {
|
||||
margin: 2px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ body {
|
|||
@import "classes/input/button";
|
||||
@import "classes/input/text";
|
||||
@import "classes/input/textarea";
|
||||
@import "classes/input/upload";
|
||||
|
||||
// Base styles
|
||||
@import "classes/avatar";
|
||||
|
@ -52,6 +53,7 @@ body {
|
|||
@import "classes/settings/content";
|
||||
@import "classes/settings/errors";
|
||||
@import "classes/settings/account";
|
||||
@import "classes/settings/avatar";
|
||||
|
||||
// Forums
|
||||
@import "classes/forum/listing";
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
"require": {
|
||||
"php": ">=7.2",
|
||||
"ext-bcmath": "*",
|
||||
"ext-imagick": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-redis": "*",
|
||||
"twig/twig": "~2.4",
|
||||
"nesbot/carbon": "~1.22",
|
||||
"illuminate/database": "~5.5",
|
||||
|
|
62
composer.lock
generated
62
composer.lock
generated
|
@ -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": "bfc5b8cbdbf22514c4b51ae1af8c333b",
|
||||
"content-hash": "2a199e9d03d4a7caedecbe72d7ea6b35",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/ca-bundle",
|
||||
|
@ -696,7 +696,7 @@
|
|||
},
|
||||
{
|
||||
"name": "illuminate/container",
|
||||
"version": "v5.6.11",
|
||||
"version": "v5.6.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/container.git",
|
||||
|
@ -740,7 +740,7 @@
|
|||
},
|
||||
{
|
||||
"name": "illuminate/contracts",
|
||||
"version": "v5.6.11",
|
||||
"version": "v5.6.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/contracts.git",
|
||||
|
@ -784,16 +784,16 @@
|
|||
},
|
||||
{
|
||||
"name": "illuminate/database",
|
||||
"version": "v5.6.11",
|
||||
"version": "v5.6.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/database.git",
|
||||
"reference": "4d2fc3c816ed402fcac290e6ca7bc855d5313000"
|
||||
"reference": "104cd99c17d46e6f96eafd4f1469ea921a289279"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/database/zipball/4d2fc3c816ed402fcac290e6ca7bc855d5313000",
|
||||
"reference": "4d2fc3c816ed402fcac290e6ca7bc855d5313000",
|
||||
"url": "https://api.github.com/repos/illuminate/database/zipball/104cd99c17d46e6f96eafd4f1469ea921a289279",
|
||||
"reference": "104cd99c17d46e6f96eafd4f1469ea921a289279",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -839,11 +839,11 @@
|
|||
"orm",
|
||||
"sql"
|
||||
],
|
||||
"time": "2018-03-09T13:55:05+00:00"
|
||||
"time": "2018-03-14T12:21:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/filesystem",
|
||||
"version": "v5.6.11",
|
||||
"version": "v5.6.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/filesystem.git",
|
||||
|
@ -894,7 +894,7 @@
|
|||
},
|
||||
{
|
||||
"name": "illuminate/pagination",
|
||||
"version": "v5.6.11",
|
||||
"version": "v5.6.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/pagination.git",
|
||||
|
@ -938,30 +938,30 @@
|
|||
},
|
||||
{
|
||||
"name": "illuminate/support",
|
||||
"version": "v5.6.11",
|
||||
"version": "v5.6.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/support.git",
|
||||
"reference": "259f6f17a11b0379340ec5311fcba27bc2a04070"
|
||||
"reference": "f0776f5bbfeeb9d4c4cac8f64d96f8f0cbe4f3f7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/support/zipball/259f6f17a11b0379340ec5311fcba27bc2a04070",
|
||||
"reference": "259f6f17a11b0379340ec5311fcba27bc2a04070",
|
||||
"url": "https://api.github.com/repos/illuminate/support/zipball/f0776f5bbfeeb9d4c4cac8f64d96f8f0cbe4f3f7",
|
||||
"reference": "f0776f5bbfeeb9d4c4cac8f64d96f8f0cbe4f3f7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/inflector": "~1.1",
|
||||
"ext-mbstring": "*",
|
||||
"illuminate/contracts": "5.6.*",
|
||||
"nesbot/carbon": "^1.20",
|
||||
"nesbot/carbon": "^1.24.1",
|
||||
"php": "^7.1.3"
|
||||
},
|
||||
"conflict": {
|
||||
"tightenco/collect": "<5.5.33"
|
||||
},
|
||||
"suggest": {
|
||||
"illuminate/filesystem": "Required to use the composer class (5.2.*).",
|
||||
"illuminate/filesystem": "Required to use the composer class (5.6.*).",
|
||||
"symfony/process": "Required to use the composer class (~4.0).",
|
||||
"symfony/var-dumper": "Required to use the dd function (~4.0)."
|
||||
},
|
||||
|
@ -991,7 +991,7 @@
|
|||
],
|
||||
"description": "The Illuminate Support package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"time": "2018-03-09T16:52:54+00:00"
|
||||
"time": "2018-03-14T12:56:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maxmind-db/reader",
|
||||
|
@ -1097,16 +1097,16 @@
|
|||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "1.24.2",
|
||||
"version": "1.25.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/briannesbitt/Carbon.git",
|
||||
"reference": "bba6c6e410c6b4317e37a9474aeaa753808c3875"
|
||||
"reference": "cbcf13da0b531767e39eb86e9687f5deba9857b4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bba6c6e410c6b4317e37a9474aeaa753808c3875",
|
||||
"reference": "bba6c6e410c6b4317e37a9474aeaa753808c3875",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/cbcf13da0b531767e39eb86e9687f5deba9857b4",
|
||||
"reference": "cbcf13da0b531767e39eb86e9687f5deba9857b4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1146,7 +1146,7 @@
|
|||
"datetime",
|
||||
"time"
|
||||
],
|
||||
"time": "2018-03-10T10:10:14+00:00"
|
||||
"time": "2018-03-19T15:50:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
|
@ -1478,16 +1478,16 @@
|
|||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v2.4.6",
|
||||
"version": "v2.4.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "d2117ec118c1ff3d28ccddca8212d82787a4809f"
|
||||
"reference": "69aacd44dbbaa3199d5afb68605c996d577896fc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/d2117ec118c1ff3d28ccddca8212d82787a4809f",
|
||||
"reference": "d2117ec118c1ff3d28ccddca8212d82787a4809f",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/69aacd44dbbaa3199d5afb68605c996d577896fc",
|
||||
"reference": "69aacd44dbbaa3199d5afb68605c996d577896fc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1496,8 +1496,8 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"psr/container": "^1.0",
|
||||
"symfony/debug": "~2.7",
|
||||
"symfony/phpunit-bridge": "~3.3@dev"
|
||||
"symfony/debug": "^2.7",
|
||||
"symfony/phpunit-bridge": "^3.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
|
@ -1540,7 +1540,7 @@
|
|||
"keywords": [
|
||||
"templating"
|
||||
],
|
||||
"time": "2018-03-03T16:23:01+00:00"
|
||||
"time": "2018-03-20T04:31:17+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
|
@ -3010,7 +3010,9 @@
|
|||
"platform": {
|
||||
"php": ">=7.2",
|
||||
"ext-bcmath": "*",
|
||||
"ext-mbstring": "*"
|
||||
"ext-imagick": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-redis": "*"
|
||||
},
|
||||
"platform-dev": []
|
||||
}
|
||||
|
|
11
misuzu.php
11
misuzu.php
|
@ -10,11 +10,20 @@ $app = Application::start(
|
|||
$app->startDatabase();
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
$storage_dir = $app->getStoragePath();
|
||||
if (!$storage_dir->isReadable()
|
||||
|| !$storage_dir->isWritable()) {
|
||||
echo 'Cannot access storage directory.';
|
||||
exit;
|
||||
}
|
||||
|
||||
if (isset($_COOKIE['msz_uid'], $_COOKIE['msz_sid'])) {
|
||||
$app->startSession((int)$_COOKIE['msz_uid'], $_COOKIE['msz_sid']);
|
||||
}
|
||||
|
||||
//ob_start('ob_gzhandler');
|
||||
if (!$app->inDebugMode()) {
|
||||
ob_start('ob_gzhandler');
|
||||
}
|
||||
|
||||
$app->startTemplating();
|
||||
}
|
||||
|
|
BIN
public/images/no-avatar.png
Normal file
BIN
public/images/no-avatar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
|
@ -1,16 +1,56 @@
|
|||
<?php
|
||||
use Misuzu\IO\File;
|
||||
use Misuzu\IO\FileStream;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once __DIR__ . '/../misuzu.php';
|
||||
|
||||
$user_id = (int)($_GET['u'] ?? 0);
|
||||
$mode = (string)($_GET['m'] ?? 'view');
|
||||
$profile_user = User::find($user_id);
|
||||
|
||||
switch ($mode) {
|
||||
case 'avatar':
|
||||
$avatar_filename = $app->getPath(
|
||||
$app->config->get('Avatar', 'default_path', 'string', 'public/images/no-avatar.png')
|
||||
);
|
||||
|
||||
if ($profile_user !== null) {
|
||||
$user_avatar = "{$profile_user->user_id}.msz";
|
||||
$cropped_avatar = $app->getStore('avatars/200x200')->filename($user_avatar);
|
||||
|
||||
if (File::exists($cropped_avatar)) {
|
||||
$avatar_filename = $cropped_avatar;
|
||||
} else {
|
||||
$original_avatar = $app->getStore('avatars/original')->filename($user_avatar);
|
||||
|
||||
if (File::exists($original_avatar)) {
|
||||
try {
|
||||
$app->templating->vars(['profile' => User::findOrFail($user_id)]);
|
||||
File::writeAll(
|
||||
$cropped_avatar,
|
||||
crop_image_centred_path($original_avatar, 200, 200)->getImagesBlob()
|
||||
);
|
||||
|
||||
$avatar_filename = $cropped_avatar;
|
||||
} catch (Exception $ex) {
|
||||
http_response_code(404);
|
||||
echo $app->templating->render('user.notfound');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header('Content-Type: ' . mime_content_type($avatar_filename));
|
||||
echo File::readToEnd($avatar_filename);
|
||||
break;
|
||||
|
||||
case 'view':
|
||||
default:
|
||||
if ($profile_user === null) {
|
||||
http_response_code(404);
|
||||
echo $app->templating->render('user.notfound');
|
||||
break;
|
||||
}
|
||||
|
||||
$app->templating->var('profile', $profile_user);
|
||||
echo $app->templating->render('user.view');
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
use Misuzu\Application;
|
||||
use Misuzu\IO\File;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\Session;
|
||||
|
||||
|
@ -12,6 +13,8 @@ if ($settings_session === null) {
|
|||
return;
|
||||
}
|
||||
|
||||
$csrf_error_str = "Couldn't verify you, please refresh the page and retry.";
|
||||
|
||||
$settings_profile_fields = [
|
||||
'twitter' => [
|
||||
'name' => 'Twitter',
|
||||
|
@ -91,11 +94,17 @@ if (!array_key_exists($settings_mode, $settings_modes)) {
|
|||
|
||||
$settings_errors = [];
|
||||
|
||||
$avatar_filename = "{$settings_user->user_id}.msz";
|
||||
$avatar_max_width = $app->config->get('Avatar', 'max_width', 'int', 4000);
|
||||
$avatar_max_height = $app->config->get('Avatar', 'max_height', 'int', 4000);
|
||||
$avatar_max_filesize = $app->config->get('Avatar', 'max_filesize', 'int', 1000000);
|
||||
$avatar_max_filesize_human = byte_symbol($avatar_max_filesize, true);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
switch ($settings_mode) {
|
||||
case 'account':
|
||||
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
|
||||
$settings_errors[] = "Couldn't verify you, please refresh the page and retry.";
|
||||
$settings_errors[] = $csrf_error_str;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -193,6 +202,110 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
$settings_user->save();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'avatar':
|
||||
if (isset($_POST['import'])
|
||||
&& !File::exists($app->getStore('avatars/original')->filename($avatar_filename))) {
|
||||
if (!tmp_csrf_verify($_POST['import'])) {
|
||||
$settings_errors[] = $csrf_error_str;
|
||||
break;
|
||||
}
|
||||
|
||||
$old_avatar_url = trim(file_get_contents(
|
||||
"https://secret.flashii.net/avatar-serve.php?id={$settings_user->user_id}&r"
|
||||
));
|
||||
|
||||
if (empty($old_avatar_url)) {
|
||||
$settings_errors[] = 'No old avatar was found for you.';
|
||||
break;
|
||||
}
|
||||
|
||||
File::writeAll(
|
||||
$app->getStore('avatars/original')->filename($avatar_filename),
|
||||
file_get_contents($old_avatar_url)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($_POST['delete'])) {
|
||||
if (!tmp_csrf_verify($_POST['delete'])) {
|
||||
$settings_errors[] = $csrf_error_str;
|
||||
break;
|
||||
}
|
||||
|
||||
File::delete($app->getStore('avatars/original')->filename($avatar_filename));
|
||||
File::delete($app->getStore('avatars/200x200')->filename($avatar_filename));
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($_POST['upload'])) {
|
||||
if (!tmp_csrf_verify($_POST['upload'])) {
|
||||
$settings_errors[] = $csrf_error_str;
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($_FILES['avatar']['error']) {
|
||||
case UPLOAD_ERR_OK:
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
$settings_errors[] = 'The upload was interrupted, please try again!';
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
$settings_errors[] = "Your avatar is not allowed to be larger in filesize than {$avatar_max_filesize_human}!";
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
$settings_errors[] = 'Unable to save your avatar, contact an administator!';
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
default:
|
||||
$settings_errors[] = 'Something happened?';
|
||||
break;
|
||||
}
|
||||
|
||||
if (count($settings_errors) > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
$upload_path = $_FILES['avatar']['tmp_name'];
|
||||
$upload_meta = getimagesize($upload_path);
|
||||
|
||||
if (!$upload_meta
|
||||
|| !in_array($upload_meta[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG], true)
|
||||
|| $upload_meta[0] < 1
|
||||
|| $upload_meta[1] < 1) {
|
||||
$settings_errors[] = 'Please provide a valid image.';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($upload_meta[0] > $avatar_max_width || $upload_meta[1] > $avatar_max_height) {
|
||||
$settings_errors[] = "Your avatar can't be larger than {$avatar_max_width}x{$avatar_max_height}, yours was {$upload_meta[0]}x{$upload_meta[1]}";
|
||||
break;
|
||||
}
|
||||
|
||||
if (filesize($upload_path) > $avatar_max_filesize) {
|
||||
$settings_errors[] = "Your avatar is not allowed to be larger in filesize than {$avatar_max_filesize_human}!";
|
||||
break;
|
||||
}
|
||||
|
||||
$avatar_path = $app->getStore('avatars/original')->filename($avatar_filename);
|
||||
move_uploaded_file($upload_path, $avatar_path);
|
||||
|
||||
$crop_path = $app->getStore('avatars/200x200')->filename($avatar_filename);
|
||||
|
||||
if (File::exists($crop_path)) {
|
||||
File::delete($crop_path);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$settings_errors[] = "You shouldn't have done that.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,6 +317,13 @@ switch ($settings_mode) {
|
|||
$app->templating->vars(compact('settings_profile_fields'));
|
||||
break;
|
||||
|
||||
case 'avatar':
|
||||
$app->templating->var(
|
||||
'can_import_old_avatar',
|
||||
!File::exists($app->getStore('avatars/original')->filename($avatar_filename))
|
||||
);
|
||||
break;
|
||||
|
||||
case 'sessions':
|
||||
$app->templating->var('user_sessions', $settings_user->sessions->reverse());
|
||||
break;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Config\ConfigManager;
|
||||
use Misuzu\IO\Directory;
|
||||
use Misuzu\Users\Session;
|
||||
use UnexpectedValueException;
|
||||
use InvalidArgumentException;
|
||||
|
@ -48,6 +49,48 @@ class Application extends ApplicationBase
|
|||
ExceptionHandler::unregister();
|
||||
}
|
||||
|
||||
public function inDebugMode(): bool
|
||||
{
|
||||
return $this->debugMode;
|
||||
}
|
||||
|
||||
public function getPath(string $path): string
|
||||
{
|
||||
if (!starts_with($path, '/')) {
|
||||
$path = __DIR__ . '/../' . $path;
|
||||
}
|
||||
|
||||
return Directory::fixSlashes(rtrim($path, '/'));
|
||||
}
|
||||
|
||||
public function getStoragePath(string $append = ''): Directory
|
||||
{
|
||||
$path = '';
|
||||
|
||||
if (starts_with($append, '/')) {
|
||||
$path = $append;
|
||||
} else {
|
||||
$path = $this->config->get('Storage', 'path', 'string', __DIR__ . '/../store');
|
||||
|
||||
if (!empty($append)) {
|
||||
$path .= '/' . $append;
|
||||
}
|
||||
}
|
||||
|
||||
return Directory::createOrOpen($this->getPath($path));
|
||||
}
|
||||
|
||||
public function getStore(string $purpose): Directory
|
||||
{
|
||||
$override_key = "override_{$purpose}";
|
||||
|
||||
if ($this->config->contains('Storage', $override_key)) {
|
||||
return new Directory($this->config->get('Storage', $override_key));
|
||||
}
|
||||
|
||||
return $this->getStoragePath($purpose);
|
||||
}
|
||||
|
||||
public function startSession(int $user_id, string $session_key): void
|
||||
{
|
||||
$session = Session::where('session_key', $session_key)
|
||||
|
|
|
@ -12,7 +12,22 @@ class Directory
|
|||
* Path to this directory.
|
||||
* @var string
|
||||
*/
|
||||
public $path;
|
||||
private $path;
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return is_readable($this->getPath());
|
||||
}
|
||||
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return is_writable($this->getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes the path, sets proper slashes and checks if the directory exists.
|
||||
|
@ -21,11 +36,13 @@ class Directory
|
|||
*/
|
||||
public function __construct(string $path)
|
||||
{
|
||||
$this->path = static::fixSlashes(rtrim($path, '/\\'));
|
||||
$path = static::fixSlashes(rtrim($path, '/\\'));
|
||||
|
||||
if (!static::exists($this->path)) {
|
||||
if (!static::exists($path)) {
|
||||
throw new DirectoryDoesNotExistException;
|
||||
}
|
||||
|
||||
$this->path = realpath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,6 +61,11 @@ class Directory
|
|||
}, glob($this->path . '/' . $pattern));
|
||||
}
|
||||
|
||||
public function filename(string $filename): string
|
||||
{
|
||||
return $this->getPath() . '/' . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a directory if it doesn't already exist.
|
||||
* @param string $path
|
||||
|
@ -52,17 +74,33 @@ class Directory
|
|||
*/
|
||||
public static function create(string $path): Directory
|
||||
{
|
||||
$path = static::fixSlashes($path);
|
||||
|
||||
if (static::exists($path)) {
|
||||
throw new DirectoryExistsException;
|
||||
}
|
||||
|
||||
mkdir($path);
|
||||
$split_path = explode('/', $path);
|
||||
$existing_path = '/';
|
||||
|
||||
foreach ($split_path as $path_part) {
|
||||
$existing_path .= $path_part . '/';
|
||||
|
||||
if (!Directory::exists($existing_path)) {
|
||||
mkdir($existing_path);
|
||||
}
|
||||
}
|
||||
|
||||
return new static($path);
|
||||
}
|
||||
|
||||
public static function createOrOpen(string $path): Directory
|
||||
{
|
||||
if (static::exists($path)) {
|
||||
return new Directory($path);
|
||||
} else {
|
||||
return Directory::create($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a directory, recursively if requested. Use $purge with care!
|
||||
* @param string $path
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu\IO;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Static file meta functions.
|
||||
* @package Misuzu\IO
|
||||
|
@ -13,6 +15,27 @@ class File
|
|||
return new FileStream($filename, FileStream::MODE_READ_WRITE, true);
|
||||
}
|
||||
|
||||
public static function readToEnd(string $filename, bool $lock = false): string
|
||||
{
|
||||
$output = '';
|
||||
|
||||
try {
|
||||
$file = new FileStream($filename, FileStream::MODE_READ, $lock);
|
||||
$output = $file->read($file->getLength());
|
||||
$file->close();
|
||||
} catch (Exception $ex) {
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
public static function writeAll(string $filename, string $data): void
|
||||
{
|
||||
$file = new FileStream($filename, FileStream::MODE_TRUNCATE, true);
|
||||
$file->write($data);
|
||||
$file->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of a temporary file.
|
||||
* @param string $prefix
|
||||
|
|
|
@ -124,44 +124,44 @@ class FileStream extends Stream
|
|||
}
|
||||
}
|
||||
|
||||
protected function getCanRead(): bool
|
||||
public function getCanRead(): bool
|
||||
{
|
||||
return ($this->fileMode & static::MODE_READ) > 0 && is_readable($this->filePath);
|
||||
}
|
||||
|
||||
protected function getCanSeek(): bool
|
||||
public function getCanSeek(): bool
|
||||
{
|
||||
return ($this->fileMode & static::MODE_APPEND_RAW) == 0 && $this->getCanRead();
|
||||
}
|
||||
|
||||
protected function getCanTimeout(): bool
|
||||
public function getCanTimeout(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getCanWrite(): bool
|
||||
public function getCanWrite(): bool
|
||||
{
|
||||
return ($this->fileMode & static::MODE_WRITE) > 0 && is_writable($this->filePath);
|
||||
}
|
||||
|
||||
protected function getLength(): int
|
||||
public function getLength(): int
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
return fstat($this->fileHandle)['size'];
|
||||
}
|
||||
|
||||
protected function getPosition(): int
|
||||
public function getPosition(): int
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
return ftell($this->fileHandle);
|
||||
}
|
||||
|
||||
protected function getReadTimeout(): int
|
||||
public function getReadTimeout(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected function getWriteTimeout(): int
|
||||
public function getWriteTimeout(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -49,42 +49,42 @@ class NetworkStream extends Stream
|
|||
}
|
||||
}
|
||||
|
||||
protected function getCanRead(): bool
|
||||
public function getCanRead(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getCanSeek(): bool
|
||||
public function getCanSeek(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getCanTimeout(): bool
|
||||
public function getCanTimeout(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getCanWrite(): bool
|
||||
public function getCanWrite(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getLength(): int
|
||||
public function getLength(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected function getPosition(): int
|
||||
public function getPosition(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected function getReadTimeout(): int
|
||||
public function getReadTimeout(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected function getWriteTimeout(): int
|
||||
public function getWriteTimeout(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -20,14 +20,14 @@ abstract class Stream
|
|||
throw new InvalidArgumentException;
|
||||
}
|
||||
|
||||
abstract protected function getCanRead(): bool;
|
||||
abstract protected function getCanSeek(): bool;
|
||||
abstract protected function getCanTimeout(): bool;
|
||||
abstract protected function getCanWrite(): bool;
|
||||
abstract protected function getLength(): int;
|
||||
abstract protected function getPosition(): int;
|
||||
abstract protected function getReadTimeout(): int;
|
||||
abstract protected function getWriteTimeout(): int;
|
||||
abstract public function getCanRead(): bool;
|
||||
abstract public function getCanSeek(): bool;
|
||||
abstract public function getCanTimeout(): bool;
|
||||
abstract public function getCanWrite(): bool;
|
||||
abstract public function getLength(): int;
|
||||
abstract public function getPosition(): int;
|
||||
abstract public function getReadTimeout(): int;
|
||||
abstract public function getWriteTimeout(): int;
|
||||
|
||||
abstract public function flush(): void;
|
||||
abstract public function close(): void;
|
||||
|
|
47
utility.php
47
utility.php
|
@ -150,6 +150,53 @@ function tmp_csrf_token(?\Misuzu\Users\Session $session = null): string
|
|||
return md5($session->session_key);
|
||||
}
|
||||
|
||||
function crop_image_centred_path(string $filename, int $target_width, int $target_height): \Imagick
|
||||
{
|
||||
return crop_image_centred(new \Imagick($filename), $target_width, $target_height);
|
||||
}
|
||||
|
||||
function crop_image_centred(Imagick $image, int $target_width, int $target_height): Imagick
|
||||
{
|
||||
$image->setImageFormat($image->getNumberImages() > 1 ? 'gif' : 'png');
|
||||
$image = $image->coalesceImages();
|
||||
|
||||
$width = $image->getImageWidth();
|
||||
$height = $image->getImageHeight();
|
||||
|
||||
if ($width > $height) {
|
||||
$resize_width = $width * $target_height / $height;
|
||||
$resize_height = $target_height;
|
||||
} else {
|
||||
$resize_width = $target_width;
|
||||
$resize_height = $height * $target_width / $width;
|
||||
}
|
||||
|
||||
do {
|
||||
$image->resizeImage(
|
||||
$resize_width,
|
||||
$resize_height,
|
||||
Imagick::FILTER_LANCZOS,
|
||||
0.9
|
||||
);
|
||||
|
||||
$image->cropImage(
|
||||
$target_width,
|
||||
$target_height,
|
||||
($resize_width - $target_width) / 2,
|
||||
($resize_height - $target_height) / 2
|
||||
);
|
||||
|
||||
$image->setImagePage(
|
||||
$target_width,
|
||||
$target_height,
|
||||
0,
|
||||
0
|
||||
);
|
||||
} while ($image->nextImage());
|
||||
|
||||
return $image->deconstructImages();
|
||||
}
|
||||
|
||||
function is_int_ex($value, int $boundary_low, int $boundary_high): bool
|
||||
{
|
||||
return is_int($value) && $value >= $boundary_low && $value <= $boundary_high;
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<div class="mio__container mio__header__user">
|
||||
<div class="mio__container__title">Hey, {{ app.session.user.username }}!</div>
|
||||
<div class="mio__container__content mio__header__user__content">
|
||||
<div class="mio__avatar mio__header__user__avatar" style="background-image:url('https://secret.flashii.net/avatar-serve.php?id={{ app.session.user.user_id }}');"></div>
|
||||
<div class="mio__avatar mio__header__user__avatar" style="background-image:url('/profile.php?u={{ app.session.user.user_id }}&m=avatar');"></div>
|
||||
<div class="mio__header__user__links__container">
|
||||
<ul class="mio__header__user__links">
|
||||
<li class="mio__header__user__option"><a class="mio__header__user__link" href="/profile.php?u={{ app.session.user.user_id }}">Profile</a></li>
|
||||
|
|
|
@ -1,5 +1,24 @@
|
|||
{% extends '@mio/settings/master.twig' %}
|
||||
|
||||
{% block settings_content %}
|
||||
<p>Sorry for getting your hopes up with the button, but not yet! Read the front page while logged in.</p>
|
||||
<div class="mio__settings__avatar">
|
||||
<div class="mio__settings__avatar__forms">
|
||||
<form class="mio__settings__avatar__form" method="post" action="?m=avatar" enctype="multipart/form-data">
|
||||
<label class="mio__input__upload__label">
|
||||
<input type="file" name="avatar" class="mio__input__upload">
|
||||
Pick file...
|
||||
</label>
|
||||
<button class="mio__input__button" name="upload" value="{{ csrf_token() }}">Upload</button>
|
||||
</form>
|
||||
<form class="mio__settings__avatar__form" method="post" action="?m=avatar">
|
||||
<button class="mio__input__button" name="delete" value="{{ csrf_token() }}">Delete</button>
|
||||
</form>
|
||||
{% if can_import_old_avatar %}
|
||||
<form class="mio__settings__avatar__form" method="post" action="?m=avatar">
|
||||
<button class="mio__input__button" name="import" value="{{ csrf_token() }}">Import old avatar</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mio__avatar mio__settings__avatar__preview" style="background-image:url('/profile.php?u={{ settings_user.user_id }}&m=avatar')"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -115,7 +115,7 @@
|
|||
</div>
|
||||
{% endspaceless %}
|
||||
</div>
|
||||
<div class="mio__avatar mio__profile__avatar" style="background-image:url('https://secret.flashii.net/avatar-serve.php?id={{ profile.user_id }}');"></div>
|
||||
<div class="mio__avatar mio__profile__avatar" style="background-image:url('/profile.php?u={{ profile.user_id }}&m=avatar');"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
<div class="profile__content">
|
||||
{% spaceless %}
|
||||
<div class="profile__container profile__container--left">
|
||||
<div class="profile__avatar" style="background-image: url('https://secret.flashii.net/avatar-serve.php?id={{ profile.user_id }}');"></div>
|
||||
<div class="profile__avatar" style="background-image: url('/profile.php?u={{ profile.user_id }}&m=avatar');"></div>
|
||||
<div class="platform profile__platform profile__hierarchies">
|
||||
{% for id, data in hierarchies %}
|
||||
{% if data.display %}
|
||||
|
|
Loading…
Add table
Reference in a new issue