193 lines
5.5 KiB
PHP
193 lines
5.5 KiB
PHP
<?php
|
|
/**
|
|
* Holds the file controller.
|
|
* @package Sakura
|
|
*/
|
|
|
|
namespace Sakura\Controllers;
|
|
|
|
use Phroute\Phroute\Exception\HttpMethodNotAllowedException;
|
|
use Phroute\Phroute\Exception\HttpRouteNotFoundException;
|
|
use Sakura\Config;
|
|
use Sakura\CurrentSession;
|
|
use Sakura\DB;
|
|
use Sakura\Exceptions\FileException;
|
|
use Sakura\File;
|
|
use Sakura\Template;
|
|
use Sakura\User;
|
|
|
|
/**
|
|
* File controller, handles user uploads like avatars.
|
|
* @package Sakura
|
|
* @author Julian van de Groep <me@flash.moe>
|
|
*/
|
|
class FileController extends Controller
|
|
{
|
|
/**
|
|
* Possible modes.
|
|
*/
|
|
private const MODES = [
|
|
'avatar',
|
|
'background',
|
|
'header',
|
|
];
|
|
|
|
/**
|
|
* The base for serving a file.
|
|
* @param string $data
|
|
* @param string $mime
|
|
* @param string $name
|
|
* @return string
|
|
*/
|
|
private function serve(string $data, string $mime, string $name): string
|
|
{
|
|
header("Content-Disposition: inline; filename={$name}");
|
|
header("Content-Type: {$mime}");
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Handles file uploads.
|
|
* @param string $mode
|
|
* @param array $file
|
|
* @param User $user
|
|
* @throws FileException
|
|
*/
|
|
private function upload(string $mode, array $file, User $user): void
|
|
{
|
|
switch ($file['error']) {
|
|
case UPLOAD_ERR_OK:
|
|
break;
|
|
|
|
case UPLOAD_ERR_INI_SIZE:
|
|
case UPLOAD_ERR_FORM_SIZE:
|
|
throw new FileException("Your file was too large!");
|
|
|
|
case UPLOAD_ERR_PARTIAL:
|
|
throw new FileException("The upload failed!");
|
|
|
|
case UPLOAD_ERR_NO_TMP_DIR:
|
|
case UPLOAD_ERR_CANT_WRITE:
|
|
throw new FileException("Wasn't able to save the file, contact a staff member!");
|
|
|
|
case UPLOAD_ERR_EXTENSION:
|
|
default:
|
|
throw new FileException("Something prevented the file upload!");
|
|
}
|
|
|
|
$tmpName = $file['tmp_name'];
|
|
$meta = getimagesize($tmpName);
|
|
|
|
if (!$meta || !in_array($meta[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG], true)) {
|
|
throw new FileException("Please upload a valid image!");
|
|
}
|
|
|
|
$maxWidth = config("file.{$mode}.max_width");
|
|
$maxHeight = config("file.{$mode}.max_height");
|
|
|
|
if ($meta[0] > $maxWidth
|
|
|| $meta[1] > $maxHeight) {
|
|
throw new FileException("Your image can't be bigger than {$maxWidth}x{$maxHeight}" .
|
|
", yours was {$meta[0]}x{$meta[1]}!");
|
|
}
|
|
|
|
// Check file size
|
|
$maxFileSize = config("file.{$mode}.max_file_size");
|
|
|
|
if (filesize($tmpName) > $maxFileSize) {
|
|
$maxSizeFmt = byte_symbol($maxFileSize);
|
|
|
|
throw new FileException("Your image is not allowed to be larger than {$maxSizeFmt}!");
|
|
}
|
|
|
|
$ext = image_type_to_extension($meta[2]);
|
|
$filename = "{$mode}_{$user->id}{$ext}";
|
|
$file = File::create(file_get_contents($tmpName), $filename, $user);
|
|
$this->delete($mode, $user);
|
|
$column = "user_{$mode}";
|
|
|
|
DB::table('users')
|
|
->where('user_id', $user->id)
|
|
->update([
|
|
$column => $file->id,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Deletes a file.
|
|
* @param string $mode
|
|
* @param User $user
|
|
*/
|
|
public function delete(string $mode, User $user): void
|
|
{
|
|
$fileId = $user->{$mode};
|
|
|
|
if ($fileId) {
|
|
(new File($fileId))->delete();
|
|
}
|
|
|
|
DB::table('users')
|
|
->where('user_id', $user->id)
|
|
->update(["user_{$mode}" => 0]);
|
|
}
|
|
|
|
/**
|
|
* Catchall serve.
|
|
* @param string $method
|
|
* @param array $params
|
|
* @return string
|
|
*/
|
|
public function __call(string $method, array $params): ?string
|
|
{
|
|
if (!in_array($method, self::MODES)) {
|
|
throw new HttpRouteNotFoundException;
|
|
}
|
|
|
|
$user = User::construct($params[0] ?? 0);
|
|
|
|
if (session_check()) {
|
|
$perm_var = "change" . ucfirst(strtolower($method));
|
|
|
|
if (($user->id !== CurrentSession::$user->id || !$user->activated
|
|
|| $user->restricted || !$user->perms->{$perm_var})
|
|
&& !CurrentSession::$user->perms->manageProfileImages
|
|
) {
|
|
throw new HttpMethodNotAllowedException;
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$error = null;
|
|
|
|
try {
|
|
$this->upload($method, $_FILES['file'] ?? null, $user);
|
|
} catch (FileException $e) {
|
|
$error = $e->getMessage();
|
|
}
|
|
|
|
return $this->json(compact('error'));
|
|
} elseif ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
|
|
$this->delete($method, $user);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
$no_file = path('public/' . str_replace('%tplname%', Template::$name, config("user.{$method}_none")));
|
|
$none = [
|
|
'name' => basename($no_file),
|
|
'data' => file_get_contents($no_file),
|
|
'mime' => getimagesize($no_file)['mime'],
|
|
];
|
|
|
|
if (!$user->activated || $user->restricted || !$user->{$method}) {
|
|
return $this->serve($none['data'], $none['mime'], $none['name']);
|
|
}
|
|
|
|
$serve = new File($user->{$method});
|
|
|
|
if (!$serve->id) {
|
|
return $this->serve($none['data'], $none['mime'], $none['name']);
|
|
}
|
|
|
|
return $this->serve($serve->data, $serve->mime, $serve->name);
|
|
}
|
|
}
|