forked from GNUsocial/gnu-social
[AVATAR] Fixed avatar upload, added avatar inline download and updated template and base controller
This commit is contained in:
parent
d5e41ec099
commit
a5c97762e0
60
components/Media/Controller/Avatar.php
Normal file
60
components/Media/Controller/Avatar.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
// {{{ License
|
||||
|
||||
// This file is part of GNU social - https://www.gnu.org/software/social
|
||||
//
|
||||
// GNU social is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// GNU social is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// }}}
|
||||
|
||||
namespace Component\Media\Controller;
|
||||
|
||||
use App\Core\Controller;
|
||||
use App\Core\DB\DB;
|
||||
use function App\Core\I18n\_m;
|
||||
use App\Core\Log;
|
||||
use App\Entity\Avatar as EAvatar;
|
||||
use Component\Media\Media;
|
||||
use Exception;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class Avatar extends Controller
|
||||
{
|
||||
public function send(Request $request, string $nickname, string $size)
|
||||
{
|
||||
switch ($size) {
|
||||
case 'full':
|
||||
$result = DB::createQuery('select f.file_hash, f.mimetype, f.title from ' .
|
||||
'App\\Entity\\File f join App\\Entity\\Avatar a with f.id = a.file_id ' .
|
||||
'join App\\Entity\\Profile p with p.id = a.profile_id ' .
|
||||
'where p.nickname = :nickname')
|
||||
->setParameter('nickname', $nickname)
|
||||
->getResult();
|
||||
|
||||
if (count($result) != 1) {
|
||||
Log::error('Avatar query returned more than one result for nickname ' . $nickname);
|
||||
throw new Exception(_m('Internal server error'));
|
||||
}
|
||||
|
||||
$res = $result[0];
|
||||
Media::sendFile(EAvatar::getFilePath($res['file_hash']), $res['mimetype'], $res['title']);
|
||||
die();
|
||||
// TODO FIX THIS
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Not implemented');
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ namespace Component\Media;
|
||||
|
||||
use App\Core\Module;
|
||||
use App\Entity\File;
|
||||
use App\Util\Common;
|
||||
use App\Util\Nickname;
|
||||
use Symfony\Component\HttpFoundation\File\File as SymfonyFile;
|
||||
|
||||
class Media extends Module
|
||||
@ -42,4 +44,50 @@ class Media extends Module
|
||||
// TODO Normalize file types
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include $filepath in the response, for viewing and downloading.
|
||||
*
|
||||
* @throws ServerException
|
||||
*/
|
||||
public static function sendFile(string $filepath, string $mimetype, string $output_filename, string $disposition = 'inline'): void
|
||||
{
|
||||
$x_delivery = Common::config('site', 'x_static_delivery');
|
||||
if (is_string($x_delivery)) {
|
||||
$tmp = explode(INSTALLDIR, $filepath);
|
||||
$relative_path = end($tmp);
|
||||
Log::debug("Using Static Delivery with header for: {$relative_path}");
|
||||
header("{$x_delivery}: {$relative_path}");
|
||||
} else {
|
||||
if (file_exists($filepath)) {
|
||||
header('Content-Description: File Transfer');
|
||||
header("Content-Type: {$mimetype}");
|
||||
header("Content-Disposition: {$disposition}; filename=\"{$output_filename}\"");
|
||||
header('Expires: 0');
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
|
||||
$filesize = filesize($filepath);
|
||||
|
||||
http_response_code(200);
|
||||
header("Content-Length: {$filesize}");
|
||||
// header('Cache-Control: private, no-transform, no-store, must-revalidate');
|
||||
|
||||
$ret = @readfile($filepath);
|
||||
|
||||
if ($ret === false) {
|
||||
http_response_code(404);
|
||||
Log::error("Couldn't read file at {$filepath}.");
|
||||
} elseif ($ret !== $filesize) {
|
||||
http_response_code(500);
|
||||
Log::error('The lengths of the file as recorded on the DB (or on disk) for the file ' .
|
||||
"{$filepath} differ from what was sent to the user ({$filesize} vs {$ret}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function onAddRoute($r)
|
||||
{
|
||||
$r->connect('avatar', '/{nickname<' . Nickname::DISPLAY_FMT . '>}/avatar/{size<full|big|medium|small>?full}', [Controller\Avatar::class, 'send']);
|
||||
}
|
||||
}
|
||||
|
35
public/assets/default-avatar.svg
Normal file
35
public/assets/default-avatar.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="141.11111mm" height="141.11111mm"
|
||||
viewBox="0 0 499.99998 500.00005" id="svg2" version="1.1" inkscape:version="0.91 r13725"
|
||||
inkscape:export-filename="/home/alberto/NewGS edited.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90"
|
||||
sodipodi:docname="GS avatar Final.svg">
|
||||
<defs id="defs4"/>
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.35355339" inkscape:cx="-611.42805" inkscape:cy="426.03658" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" inkscape:window-width="1366" inkscape:window-height="716" inkscape:window-x="0" inkscape:window-y="29" inkscape:window-maximized="1" showguides="false">
|
||||
<inkscape:grid type="xygrid" id="grid4146"/>
|
||||
</sodipodi:namedview>
|
||||
<metadata id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g inkscape:label="Capa 1" inkscape:groupmode="layer" id="layer1" transform="translate(-157.14286,-79.50503)">
|
||||
<rect style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="rect4685" width="500.00009" height="500.00009" x="157.14281" y="79.50502"/>
|
||||
<g id="g5206" transform="translate(5.0010184,16.878838)">
|
||||
<path id="path5202" transform="translate(157.14281,79.50503)" d="m 156.0957,58.869141 c -2.68025,0.0382 -5.40172,0.373422 -8.12109,0.976562 -4.27851,0.94895 -8.60777,2.588306 -12.59766,4.853516 -4.01887,2.281669 -7.73517,5.2222 -10.96484,8.724609 -3.23012,3.50289 -5.98354,7.539503 -8.00391,12.039063 -2.01857,4.495529 -3.38338,9.436829 -3.87695,14.667969 -0.50485,5.68236 -0.0729,12.79202 2.70703,20.16016 1.36562,3.61951 3.29345,7.34391 5.90625,10.94531 2.61146,3.59956 6.03849,7.0071 10.35742,10.1289 9.53878,6.89481 23.71189,11.49816 41.58399,13.83985 -5.91744,3.3848 -10.93941,6.88738 -14.68555,10.24804 -5.021,-4.706 -14.55133,-11.82251 -27.82031,-22.85937 -23.77204,-19.77309 -82.709577,-23.05953 -82.734377,-14.14063 -0.0278,10.01879 15.25625,50.60502 63.740237,43.14649 20.31295,-3.12485 31.72368,-0.31868 39.21484,1.29492 -19.37347,20.97225 -43.38563,52.45365 -31.125,87.50977 32.5947,93.19612 56.11218,146.97265 125.32617,146.97265 69.214,0 94.53219,-53.33948 125.32032,-146.97265 11.60063,-35.28002 -11.74959,-66.53752 -31.12305,-87.50977 7.49115,-1.6136 18.90189,-4.41977 39.21484,-1.29492 48.48399,7.45853 63.76615,-33.1277 63.73828,-43.14649 -0.0248,-8.9189 -58.96038,-5.63246 -82.73242,14.14063 -13.26898,11.03686 -22.80127,18.15337 -27.82226,22.85937 -2.58848,-3.42164 -7.1092,-7.00722 -12.95313,-10.4707 17.06416,-2.40798 30.62176,-6.9443 39.85352,-13.61719 4.31893,-3.1218 7.74401,-6.52934 10.35547,-10.1289 2.6128,-3.6014 4.54258,-7.3258 5.9082,-10.94531 2.77992,-7.36814 3.20993,-14.4778 2.70508,-20.16016 -0.49357,-5.23114 -1.85643,-10.17244 -3.875,-14.667969 -2.02037,-4.49956 -4.77379,-8.536173 -8.00391,-12.039063 -3.22967,-3.502409 -6.94793,-6.44294 -10.9668,-8.724609 -3.98989,-2.26521 -8.31719,-3.904566 -12.5957,-4.853516 -17.40396,-3.860099 -34.92622,3.313482 -40.78711,28.380859 1.6307,-4.282299 8.33903,-17.124026 24.52539,-17.878906 9.97855,-0.46537 17.73729,4.163357 22.68555,10.416016 4.89527,6.185699 6.47977,14.299742 5.63281,20.169918 -0.97936,6.7878 -5.99826,25.42473 -34.45508,24.58789 -18.69599,-0.5498 -35.3627,-18.64648 -58.92383,-18.64648 -7.42488,0 -12.3863,2.29126 -15.70312,5.42773 -3.31682,-3.13647 -8.27824,-5.42773 -15.70312,-5.42773 -23.56113,0 -40.22784,18.09668 -58.92383,18.64648 -28.45682,0.83684 -33.47572,-17.80009 -34.45508,-24.58789 -0.84696,-5.870176 0.73754,-13.984219 5.63281,-20.169918 4.94826,-6.252659 12.707,-10.881386 22.68555,-10.416016 16.18636,0.75488 22.89469,13.596607 24.52539,17.878906 C 183.81659,67.075965 170.56906,58.662871 156.0957,58.869141 Z" style="display:inline;fill:#a22430;fill-opacity:0.85098039;stroke:none" inkscape:connector-curvature="0"/>
|
||||
<path inkscape:connector-curvature="0" id="path5157" d="m 313.23851,138.37417 c -2.68025,0.0382 -5.40172,0.37342 -8.12109,0.97656 -4.27851,0.94895 -8.60777,2.58831 -12.59766,4.85352 -4.01887,2.28167 -7.73517,5.2222 -10.96484,8.72461 -3.23012,3.50289 -5.98354,7.5395 -8.00391,12.03906 -2.01857,4.49553 -3.38338,9.43683 -3.87695,14.66797 -0.50485,5.68236 -0.0729,12.79202 2.70703,20.16016 1.36562,3.61951 3.29345,7.34391 5.90625,10.94531 2.61146,3.59956 6.03849,7.0071 10.35742,10.1289 14.838,10.72518 40.83724,15.93497 74.64063,15.65039 21.51239,-0.1811 32.86693,-6.81751 38.85742,-13.4746 5.99049,6.65709 17.34503,13.2935 38.85742,13.4746 33.80339,0.28458 59.80263,-4.92521 74.64063,-15.65039 4.31893,-3.1218 7.74401,-6.52934 10.35547,-10.1289 2.6128,-3.6014 4.54258,-7.3258 5.9082,-10.94531 2.77992,-7.36814 3.20993,-14.4778 2.70508,-20.16016 -0.49357,-5.23114 -1.85643,-10.17244 -3.875,-14.66797 -2.02037,-4.49956 -4.77379,-8.53617 -8.00391,-12.03906 -3.22967,-3.50241 -6.94793,-6.44294 -10.9668,-8.72461 -3.98989,-2.26521 -8.31719,-3.90457 -12.5957,-4.85352 -17.40396,-3.8601 -34.92622,3.31348 -40.78711,28.38086 1.6307,-4.2823 8.33903,-17.12402 24.52539,-17.8789 9.97855,-0.46537 17.73729,4.16335 22.68555,10.41601 4.89527,6.1857 6.47977,14.29974 5.63281,20.16992 -0.97936,6.7878 -5.99826,25.42473 -34.45508,24.58789 -18.696,-0.5498 -35.3627,-18.64648 -58.92383,-18.64648 -7.42489,0 -12.3863,2.29126 -15.70312,5.42773 -3.31682,-3.13647 -8.27823,-5.42773 -15.70312,-5.42773 -23.56113,0 -40.22783,18.09668 -58.92383,18.64648 -28.45682,0.83684 -33.47572,-17.80009 -34.45508,-24.58789 -0.84696,-5.87018 0.73754,-13.98422 5.63281,-20.16992 4.94826,-6.25266 12.707,-10.88138 22.68555,-10.41601 16.18636,0.75488 22.89469,13.5966 24.52539,17.8789 -4.94513,-21.1506 -18.19266,-29.56369 -32.66602,-29.35742 z" style="display:inline;fill:#a22430;fill-opacity:0.85098039;stroke:none"/>
|
||||
<path sodipodi:nodetypes="csccc" style="opacity:1;fill:#a22430;fill-opacity:0.85098039;fill-rule:nonzero;stroke:none;stroke-width:3.67872214;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 316.40417,339.90855 c 32.59469,93.19613 16.53711,145.08383 85.75112,145.08383 69.214,0 54.93762,-51.45065 85.72574,-145.08383 3.04616,-57.94864 -4.43738,-62.72475 -85.73722,-62.35823 -76.96659,-0.36652 -88.47651,3.8032 -85.73964,62.35823 z" id="path4152" inkscape:connector-curvature="0"/>
|
||||
<ellipse ry="41.798668" rx="40.000877" cy="318.71783" cx="451.22238" id="path4139" style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
|
||||
<ellipse ry="41.798668" rx="40.000877" cy="318.71783" cx="353.06296" id="ellipse4143" style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
|
||||
<circle r="13.983542" cy="318.71783" cx="355.13037" id="path4671" style="opacity:1;fill:#a22430;fill-opacity:0.85022027;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
|
||||
<circle style="opacity:1;fill:#a22430;fill-opacity:0.85022027;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="circle4673" cx="449.15485" cy="318.71783" r="13.983542"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.1 KiB |
@ -38,6 +38,7 @@ use App\Core\Event;
|
||||
use App\Core\Form;
|
||||
use function App\Core\I18n\_m;
|
||||
use App\Core\Log;
|
||||
use App\Entity\Avatar;
|
||||
use App\Entity\File;
|
||||
use App\Util\ClientException;
|
||||
use App\Util\Common;
|
||||
@ -102,16 +103,16 @@ class UserPanel extends AbstractController
|
||||
|
||||
public function avatar(Request $request)
|
||||
{
|
||||
$avatar = Form::create([
|
||||
$form = Form::create([
|
||||
['avatar', FileType::class, ['label' => _m('Avatar'), 'help' => _m('You can upload your personal avatar. The maximum file size is 2MB.')]],
|
||||
['hidden', HiddenType::class, []],
|
||||
['save', SubmitType::class, ['label' => _m('Submit')]],
|
||||
]);
|
||||
|
||||
$avatar->handleRequest($request);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($avatar->isSubmitted() && $avatar->isValid()) {
|
||||
$data = $avatar->getData();
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$data = $form->getData();
|
||||
$sfile = $file_title = null;
|
||||
if (isset($data['hidden'])) {
|
||||
// Cropped client side
|
||||
@ -120,13 +121,13 @@ class UserPanel extends AbstractController
|
||||
list(, $mimetype_user, , $encoding_user, $data_user) = $matches;
|
||||
if ($encoding_user == 'base64') {
|
||||
$data_user = base64_decode($data_user);
|
||||
$tmp_file = tmpfile();
|
||||
fwrite($tmp_file, $data_user);
|
||||
$filename = tempnam('/tmp/', 'avatar');
|
||||
file_put_contents($filename, $data_user);
|
||||
try {
|
||||
$sfile = new SymfonyFile(stream_get_meta_data($tmp_file)['uri']);
|
||||
$sfile = new SymfonyFile($filename);
|
||||
$file_title = $data['avatar']->getFilename();
|
||||
} finally {
|
||||
fclose($tmp_file);
|
||||
// fclose($tmp_file);
|
||||
}
|
||||
} else {
|
||||
Log::info('Avatar upload got an invalid encoding, something\'s fishy and/or wrong');
|
||||
@ -138,21 +139,25 @@ class UserPanel extends AbstractController
|
||||
} else {
|
||||
throw new ClientException('Invalid form');
|
||||
}
|
||||
$profile_id = Common::profile()->getId();
|
||||
$file = Media::validateAndStoreFile($sfile, Common::config('avatar', 'dir'), $file_title);
|
||||
$avatar = null;
|
||||
try {
|
||||
$profile_id = Common::user()->getProfile()->getId();
|
||||
$file = Media::validateAndStoreFile($sfile, Common::config('avatar', 'dir'), $file_title);
|
||||
$fs_files_to_delete = DB::find('avatar', ['profile_id' => $profile_id])->delete();
|
||||
DB::persist(Avatar::create(['profile_id' => $profile_id, 'file_id' => $file->getId()]));
|
||||
DB::persist($file);
|
||||
DB::flush();
|
||||
// Only delete files if the commit went through
|
||||
File::deleteFiles($fs_files_to_delete);
|
||||
$avatar = DB::find('avatar', ['profile_id' => $profile_id]);
|
||||
} catch (Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
if ($avatar != null) {
|
||||
$avatar->delete();
|
||||
} else {
|
||||
DB::persist($file);
|
||||
DB::persist(Avatar::create(['profile_id' => $profile_id, 'file_id' => $file->getId()]));
|
||||
}
|
||||
DB::flush();
|
||||
// Only delete files if the commit went through
|
||||
File::deleteFiles($fs_files_to_delete ?? []);
|
||||
}
|
||||
|
||||
return ['_template' => 'settings/avatar.html.twig', 'avatar' => $avatar->createView()];
|
||||
return ['_template' => 'settings/avatar.html.twig', 'avatar' => $form->createView()];
|
||||
}
|
||||
|
||||
public function notifications(Request $request)
|
||||
|
@ -32,6 +32,8 @@
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use App\Core\DB\DB;
|
||||
use App\Util\Common;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
@ -60,7 +62,13 @@ class Controller extends AbstractController implements EventSubscriberInterface
|
||||
$controller = $event->getController();
|
||||
$request = $event->getRequest();
|
||||
|
||||
$this->vars = ['controler' => $controller, 'request' => $request];
|
||||
if (($avatar = DB::find('avatar', ['profile_id' => Common::profile()->getId()])) != null) {
|
||||
$avatar_filename = $avatar->getUrl();
|
||||
} else {
|
||||
$avatar_filename = '/public/assets/default_avatar.svg';
|
||||
}
|
||||
|
||||
$this->vars = ['controler' => $controller, 'request' => $request, 'user_avatar' => $avatar_filename];
|
||||
Event::handle('StartTwigPopulateVars', [&$this->vars]);
|
||||
|
||||
return $event;
|
||||
|
@ -23,6 +23,7 @@ namespace App\Core;
|
||||
|
||||
use App\Core\DB\DB;
|
||||
use App\Util\Formatting;
|
||||
use DateTime;
|
||||
|
||||
class Entity
|
||||
{
|
||||
@ -33,7 +34,8 @@ class Entity
|
||||
$args['created'] = $args['modified'] = new DateTime();
|
||||
foreach ($args as $prop => $val) {
|
||||
if (property_exists($class, $prop)) {
|
||||
$obj->{$prop} = $val;
|
||||
$set = 'set' . Formatting::snakeCaseToCamelCase($prop);
|
||||
$obj->{$set}($val);
|
||||
} else {
|
||||
Log::error("Property {$class}::{$prop} doesn't exist");
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Security\Core\Security as SSecurity;
|
||||
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
||||
@ -72,6 +73,7 @@ class GNUsocial implements EventSubscriberInterface
|
||||
protected TranslatorInterface $translator;
|
||||
protected EntityManagerInterface $entity_manager;
|
||||
protected RouterInterface $router;
|
||||
protected UrlGeneratorInterface $url_generator;
|
||||
protected FormFactoryInterface $form_factory;
|
||||
protected MessageBusInterface $message_bus;
|
||||
protected EventDispatcherInterface $event_dispatcher;
|
||||
@ -87,6 +89,7 @@ class GNUsocial implements EventSubscriberInterface
|
||||
TranslatorInterface $trans,
|
||||
EntityManagerInterface $em,
|
||||
RouterInterface $router,
|
||||
UrlGeneratorInterface $url_gen,
|
||||
FormFactoryInterface $ff,
|
||||
MessageBusInterface $mb,
|
||||
EventDispatcherInterface $ed,
|
||||
@ -99,6 +102,7 @@ class GNUsocial implements EventSubscriberInterface
|
||||
$this->translator = $trans;
|
||||
$this->entity_manager = $em;
|
||||
$this->router = $router;
|
||||
$this->url_generator = $url_gen;
|
||||
$this->form_factory = $ff;
|
||||
$this->message_bus = $mb;
|
||||
$this->event_dispatcher = $ed;
|
||||
@ -126,6 +130,7 @@ class GNUsocial implements EventSubscriberInterface
|
||||
Queue::setMessageBus($this->message_bus);
|
||||
Security::setHelper($this->security);
|
||||
Mailer::setMailer($this->mailer);
|
||||
Router::setRouter($this->router, $this->url_generator);
|
||||
|
||||
DefaultSettings::setDefaults();
|
||||
|
||||
@ -134,8 +139,6 @@ class GNUsocial implements EventSubscriberInterface
|
||||
// Events are proloaded on compilation, but set at runtime
|
||||
$this->module_manager->loadModules();
|
||||
|
||||
Router::setRouter($this->router);
|
||||
|
||||
$this->initialized = true;
|
||||
}
|
||||
}
|
||||
|
@ -30,15 +30,46 @@
|
||||
|
||||
namespace App\Core\Router;
|
||||
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Routing\Router as SRouter;
|
||||
|
||||
abstract class Router
|
||||
{
|
||||
public static ?SRouter $router = null;
|
||||
/**
|
||||
* Generates an absolute URL, e.g. "http://example.com/dir/file".
|
||||
*/
|
||||
const ABSOLUTE_URL = UrlGeneratorInterface::ABSOLUTE_URL;
|
||||
|
||||
public static function setRouter($rtr): void
|
||||
/**
|
||||
* Generates an absolute path, e.g. "/dir/file".
|
||||
*/
|
||||
const ABSOLUTE_PATH = UrlGeneratorInterface::ABSOLUTE_PATH;
|
||||
|
||||
/**
|
||||
* Generates a relative path based on the current request path, e.g. "../parent-file".
|
||||
*
|
||||
* @see UrlGenerator::getRelativePath()
|
||||
*/
|
||||
const RELATIVE_PATH = UrlGeneratorInterface::RELATIVE_PATH;
|
||||
|
||||
/**
|
||||
* Generates a network path, e.g. "//example.com/dir/file".
|
||||
* Such reference reuses the current scheme but specifies the host.
|
||||
*/
|
||||
const NETWORK_PATH = UrlGeneratorInterface::NETWORK_PATH;
|
||||
|
||||
public static ?SRouter $router = null;
|
||||
public static ?UrlGeneratorInterface $url_gen = null;
|
||||
|
||||
public static function setRouter($rtr, $gen): void
|
||||
{
|
||||
self::$router = $rtr;
|
||||
self::$router = $rtr;
|
||||
self::$url_gen = $gen;
|
||||
}
|
||||
|
||||
public static function url(string $id, array $args, int $type = self::ABSOLUTE_PATH): string
|
||||
{
|
||||
return self::$url_gen->generate($id, $args, $type);
|
||||
}
|
||||
|
||||
public static function __callStatic(string $name, array $args)
|
||||
|
@ -21,6 +21,8 @@ namespace App\Entity;
|
||||
|
||||
use App\Core\DB\DB;
|
||||
use App\Core\Entity;
|
||||
use App\Core\Router\Router;
|
||||
use App\Util\Common;
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
@ -42,9 +44,6 @@ class Avatar extends Entity
|
||||
// {{{ Autocode
|
||||
|
||||
private int $profile_id;
|
||||
private int $width;
|
||||
private int $height;
|
||||
private ?bool $is_original;
|
||||
private int $file_id;
|
||||
private \DateTimeInterface $created;
|
||||
private \DateTimeInterface $modified;
|
||||
@ -54,46 +53,18 @@ class Avatar extends Entity
|
||||
$this->profile_id = $profile_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProfileId(): int
|
||||
{
|
||||
return $this->profile_id;
|
||||
}
|
||||
|
||||
public function setWidth(int $width): self
|
||||
{
|
||||
$this->width = $width;
|
||||
return $this;
|
||||
}
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
public function setHeight(int $height): self
|
||||
{
|
||||
$this->height = $height;
|
||||
return $this;
|
||||
}
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
public function setIsOriginal(?bool $is_original): self
|
||||
{
|
||||
$this->is_original = $is_original;
|
||||
return $this;
|
||||
}
|
||||
public function getIsOriginal(): ?bool
|
||||
{
|
||||
return $this->is_original;
|
||||
}
|
||||
|
||||
public function setFileId(int $file_id): self
|
||||
{
|
||||
$this->file_id = $file_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFileId(): int
|
||||
{
|
||||
return $this->file_id;
|
||||
@ -104,6 +75,7 @@ class Avatar extends Entity
|
||||
$this->created = $created;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreated(): DateTimeInterface
|
||||
{
|
||||
return $this->created;
|
||||
@ -114,6 +86,7 @@ class Avatar extends Entity
|
||||
$this->modified = $modified;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getModified(): DateTimeInterface
|
||||
{
|
||||
return $this->modified;
|
||||
@ -123,18 +96,20 @@ class Avatar extends Entity
|
||||
|
||||
private ?File $file = null;
|
||||
|
||||
public function getUrl(): string
|
||||
{
|
||||
return Router::url('avatar', ['nickname' => Profile::getNicknameFromId($this->profile_id)]);
|
||||
}
|
||||
|
||||
public function getFile(): File
|
||||
{
|
||||
$this->file = $this->file ?: DB::find('file', ['id' => $this->file_id]);
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
public function getFilePath(): string
|
||||
public function getFilePath(?string $filename = null): string
|
||||
{
|
||||
$file_name = $this->getFile()->getFileName();
|
||||
if ($this->is_original) {
|
||||
return Common::config('avatar', 'dir') . '/' . $file_name;
|
||||
}
|
||||
return Common::config('avatar', 'dir') . '/' . $filename ?: $this->getFile()->getFileName();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,7 +121,7 @@ class Avatar extends Entity
|
||||
if (!$cascading) {
|
||||
$files = $this->getFile()->delete($cascade = true, $file_flush = false, $delete_files_now);
|
||||
} else {
|
||||
DB::remove(DB::getReference('avatar', ['profile_id' => $this->profile_id, 'width' => $this->width, 'height' => $this->height]));
|
||||
DB::remove(DB::getReference('avatar', ['profile_id' => $this->profile_id]));
|
||||
$file_path = $this->getFilePath();
|
||||
$files[] = $file_path;
|
||||
if ($flush) {
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Core\DB\DB;
|
||||
use App\Core\Entity;
|
||||
use DateTimeInterface;
|
||||
|
||||
@ -57,6 +58,7 @@ class File extends Entity
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
@ -67,6 +69,7 @@ class File extends Entity
|
||||
$this->url = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUrl(): ?string
|
||||
{
|
||||
return $this->url;
|
||||
@ -77,6 +80,7 @@ class File extends Entity
|
||||
$this->is_url_protected = $is_url_protected;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsUrlProtected(): ?bool
|
||||
{
|
||||
return $this->is_url_protected;
|
||||
@ -87,6 +91,7 @@ class File extends Entity
|
||||
$this->url_hash = $url_hash;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUrlHash(): ?string
|
||||
{
|
||||
return $this->url_hash;
|
||||
@ -97,6 +102,7 @@ class File extends Entity
|
||||
$this->file_hash = $file_hash;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFileHash(): ?string
|
||||
{
|
||||
return $this->file_hash;
|
||||
@ -107,6 +113,7 @@ class File extends Entity
|
||||
$this->mimetype = $mimetype;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMimetype(): ?string
|
||||
{
|
||||
return $this->mimetype;
|
||||
@ -117,6 +124,7 @@ class File extends Entity
|
||||
$this->size = $size;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSize(): ?int
|
||||
{
|
||||
return $this->size;
|
||||
@ -127,6 +135,7 @@ class File extends Entity
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
@ -137,6 +146,7 @@ class File extends Entity
|
||||
$this->timestamp = $timestamp;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTimestamp(): ?int
|
||||
{
|
||||
return $this->timestamp;
|
||||
@ -147,6 +157,7 @@ class File extends Entity
|
||||
$this->is_local = $is_local;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsLocal(): ?bool
|
||||
{
|
||||
return $this->is_local;
|
||||
@ -157,6 +168,7 @@ class File extends Entity
|
||||
$this->modified = $modified;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getModified(): DateTimeInterface
|
||||
{
|
||||
return $this->modified;
|
||||
@ -180,9 +192,11 @@ class File extends Entity
|
||||
$files = [];
|
||||
if ($cascade) {
|
||||
// An avatar can own a file, and it becomes invalid if the file is deleted
|
||||
$avatar = DB::find('avatar', ['file_id' => $this->id]);
|
||||
$files[] = $avatar->getFilePath();
|
||||
$avatar->delete($flush, $delete_files_now, $cascading = true);
|
||||
$avatar = DB::findBy('avatar', ['file_id' => $this->id]);
|
||||
foreach ($avatar as $a) {
|
||||
$files[] = $a->getFilePath();
|
||||
$a->delete($flush, $delete_files_now, $cascading = true);
|
||||
}
|
||||
foreach (DB::findBy('file_thumbnail', ['file_id' => $this->id]) as $ft) {
|
||||
$files[] = $ft->delete($flush, $delete_files_now, $cascading);
|
||||
}
|
||||
|
@ -20,8 +20,8 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Core\DB\DB;
|
||||
use App\Core\Entity;
|
||||
use App\Core\UserRoles;
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
use Functional as F;
|
||||
|
||||
@ -39,7 +39,7 @@ use Functional as F;
|
||||
* @copyright 2020 Free Software Foundation, Inc http://www.fsf.org
|
||||
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
|
||||
*/
|
||||
class Profile
|
||||
class Profile extends Entity
|
||||
{
|
||||
// {{{ Autocode
|
||||
|
||||
@ -62,6 +62,7 @@ class Profile
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
@ -72,6 +73,7 @@ class Profile
|
||||
$this->nickname = $nickname;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNickname(): string
|
||||
{
|
||||
return $this->nickname;
|
||||
@ -82,6 +84,7 @@ class Profile
|
||||
$this->fullname = $fullname;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFullname(): ?string
|
||||
{
|
||||
return $this->fullname;
|
||||
@ -92,6 +95,7 @@ class Profile
|
||||
$this->roles = $roles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRoles(): int
|
||||
{
|
||||
return $this->roles;
|
||||
@ -102,6 +106,7 @@ class Profile
|
||||
$this->homepage = $homepage;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHomepage(): ?string
|
||||
{
|
||||
return $this->homepage;
|
||||
@ -112,6 +117,7 @@ class Profile
|
||||
$this->bio = $bio;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBio(): ?string
|
||||
{
|
||||
return $this->bio;
|
||||
@ -122,6 +128,7 @@ class Profile
|
||||
$this->location = $location;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLocation(): ?string
|
||||
{
|
||||
return $this->location;
|
||||
@ -132,6 +139,7 @@ class Profile
|
||||
$this->lat = $lat;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLat(): ?float
|
||||
{
|
||||
return $this->lat;
|
||||
@ -142,6 +150,7 @@ class Profile
|
||||
$this->lon = $lon;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLon(): ?float
|
||||
{
|
||||
return $this->lon;
|
||||
@ -152,6 +161,7 @@ class Profile
|
||||
$this->location_id = $location_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLocationId(): ?int
|
||||
{
|
||||
return $this->location_id;
|
||||
@ -162,6 +172,7 @@ class Profile
|
||||
$this->location_service = $location_service;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLocationService(): ?int
|
||||
{
|
||||
return $this->location_service;
|
||||
@ -172,6 +183,7 @@ class Profile
|
||||
$this->created = $created;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreated(): DateTimeInterface
|
||||
{
|
||||
return $this->created;
|
||||
@ -182,6 +194,7 @@ class Profile
|
||||
$this->modified = $modified;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getModified(): DateTimeInterface
|
||||
{
|
||||
return $this->modified;
|
||||
@ -189,13 +202,39 @@ class Profile
|
||||
|
||||
// }}} Autocode
|
||||
|
||||
public function __construct(string $nickname)
|
||||
public function getFromId(int $id): ?self
|
||||
{
|
||||
$this->nickname = $nickname;
|
||||
return DB::find('profile', ['id' => $id]);
|
||||
}
|
||||
|
||||
// TODO auto update created and modified
|
||||
$this->created = new DateTime();
|
||||
$this->modified = new DateTime();
|
||||
public function getFromNickname(string $nickname): ?self
|
||||
{
|
||||
return DB::findOneBy('profile', ['nickname' => $nickname]);
|
||||
}
|
||||
|
||||
public static function getNicknameFromId(int $id): string
|
||||
{
|
||||
return self::getFromId($id)->getNickname();
|
||||
}
|
||||
|
||||
public function getSelfTags(): array
|
||||
{
|
||||
return DB::findBy('profile_tag', ['tagger' => $this->id, 'tagged' => $this->id]);
|
||||
}
|
||||
|
||||
public function setSelfTags(array $tags, array $pt_existing): void
|
||||
{
|
||||
$tag_existing = F\map($pt_existing, function ($pt) { return $pt->getTag(); });
|
||||
$tag_to_add = array_diff($tags, $tag_existing);
|
||||
$tag_to_remove = array_diff($tag_existing, $tags);
|
||||
$pt_to_remove = F\filter($pt_existing, function ($pt) use ($tag_to_remove) { return in_array($pt->getTag(), $tag_to_remove); });
|
||||
foreach ($tag_to_add as $tag) {
|
||||
$pt = new ProfileTag($this->id, $this->id, $tag);
|
||||
DB::persist($pt);
|
||||
}
|
||||
foreach ($pt_to_remove as $pt) {
|
||||
DB::remove($pt);
|
||||
}
|
||||
}
|
||||
|
||||
public static function schemaDef(): array
|
||||
@ -230,24 +269,4 @@ class Profile
|
||||
|
||||
return $def;
|
||||
}
|
||||
|
||||
public function getSelfTags(): array
|
||||
{
|
||||
return DB::findBy('profile_tag', ['tagger' => $this->id, 'tagged' => $this->id]);
|
||||
}
|
||||
|
||||
public function setSelfTags(array $tags, array $pt_existing): void
|
||||
{
|
||||
$tag_existing = F\map($pt_existing, function ($pt) { return $pt->getTag(); });
|
||||
$tag_to_add = array_diff($tags, $tag_existing);
|
||||
$tag_to_remove = array_diff($tag_existing, $tags);
|
||||
$pt_to_remove = F\filter($pt_existing, function ($pt) use ($tag_to_remove) { return in_array($pt->getTag(), $tag_to_remove); });
|
||||
foreach ($tag_to_add as $tag) {
|
||||
$pt = new ProfileTag($this->id, $this->id, $tag);
|
||||
DB::persist($pt);
|
||||
}
|
||||
foreach ($pt_to_remove as $pt) {
|
||||
DB::remove($pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,8 +35,9 @@ namespace App\Util;
|
||||
use App\Core\DB\DB;
|
||||
use App\Core\Router;
|
||||
use App\Core\Security;
|
||||
use App\Entity\LocalUser;
|
||||
use App\Entity\Profile;
|
||||
use Functional as F;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
abstract class Common
|
||||
{
|
||||
@ -69,11 +70,16 @@ abstract class Common
|
||||
DB::flush();
|
||||
}
|
||||
|
||||
public static function user(): UserInterface
|
||||
public static function user(): LocalUser
|
||||
{
|
||||
return Security::getUser();
|
||||
}
|
||||
|
||||
public static function profile(): Profile
|
||||
{
|
||||
return self::user()->getProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given string identical to a system path or route?
|
||||
* This could probably be put in some other class, but at
|
||||
|
@ -17,7 +17,7 @@
|
||||
<div class='navbar'>
|
||||
<div class="left-nav">
|
||||
<div class='profile'>
|
||||
<img src='{{ asset('assets/image.png') }}' alt="Your avatar." class="icon icon-avatar">
|
||||
<img src='{{ user_avatar }}' alt="Your avatar." class="icon icon-avatar">
|
||||
<div class="info">
|
||||
<b id="nick">{{ app.user.username }}</b>
|
||||
<div class="tags">
|
||||
|
Loading…
Reference in New Issue
Block a user