Skip to content
Snippets Groups Projects
Commit 1ddfe70c authored by Ron Lucke's avatar Ron Lucke Committed by David Siegfried
Browse files

TIC Avatar Modernisierung

Closes #4055

Merge request studip/studip!2877
parent 7c6ddb32
No related branches found
No related tags found
No related merge requests found
Showing
with 342 additions and 117 deletions
<?php
class Course_AvatarController extends AuthenticatedController
{
public function index_action()
{
$this->course_id = Context::getId();
if (!$GLOBALS['perm']->have_studip_perm('tutor', $this->course_id)) {
throw new AccessDeniedException(_("Sie haben keine Berechtigung diese " .
"Veranstaltung zu verändern."));
}
PageLayout::setTitle(Context::getHeaderLine() . ' - ' . _('Veranstaltungsbild ändern'));
Navigation::activateItem('/course/admin/avatar');
$avatar = CourseAvatar::getAvatar($this->course_id);
$this->avatar_url = $avatar->getURL(Avatar::NORMAL);
}
}
\ No newline at end of file
......@@ -379,10 +379,7 @@ class Course_BasicdataController extends AuthenticatedController
);
}
}
$widget->addLink(_('Bild ändern'),
$this->url_for('avatar/update/course', $this->course_id),
Icon::create('edit')
);
if ($GLOBALS['perm']->have_perm('admin')) {
$is_locked = $course->lock_rule;
$widget->addLink(
......
......@@ -383,13 +383,7 @@ class Course_StudygroupController extends AuthenticatedController
$this->url_for('course/wizard?studygroup=1'),
Icon::create('add')
);
if ($GLOBALS['perm']->have_studip_perm('tutor', $id)) {
$actions->addLink(
_('Bild ändern'),
$this->url_for('avatar/update/course/' . $id),
Icon::create('edit')
);
}
$actions->addLink(
_('Diese Studiengruppe löschen'),
$this->deleteURL(),
......@@ -977,6 +971,12 @@ class Course_StudygroupController extends AuthenticatedController
$this->redirect($this->url_for('messages/write', ['course_id' => $id, 'default_subject' => $subject, 'filter' => 'all', 'emailrequest' => 1]));
}
public function avatar_action()
{
Navigation::activateItem('/course/admin/avatar');
$this->studygroup_id = Context::getId();
$avatar = StudygroupAvatar::getAvatar($this->studygroup_id);
$this->avatar_url = $avatar->getURL(Avatar::NORMAL);
}
}
<?php
class Institute_AvatarController extends AuthenticatedController
{
public function before_filter(&$action, &$args)
{
parent::before_filter($action, $args);
// Ensure only admins gain access to this page
if (!$GLOBALS['perm']->have_perm("admin")) {
throw new AccessDeniedException();
}
}
public function index_action($i_id = false)
{
//get ID from an open Institut
$i_view = $i_id ?: Request::option('i_view', Context::getId());
if (!$i_view) {
Navigation::activateItem('/admin/institute/avatar');
require_once 'lib/admin_search.inc.php';
// This search just died a little inside, so it should be safe to
// continue here but we nevertheless return just to be sure
return;
} elseif ($i_view === 'new') {
closeObject();
Navigation::activateItem('/admin/institute/create');
} else {
Navigation::activateItem('/admin/institute/avatar');
}
// allow only inst-admin and root to view / edit
if ($i_view && !$GLOBALS['perm']->have_studip_perm('admin', $i_view) && $i_view !== 'new') {
throw new AccessDeniedException();
}
PageLayout::setTitle(Context::getHeaderLine() . ' - ' . _('Einrichtungsbild ändern'));
$this->institute_id = Context::getId();
$avatar = InstituteAvatar::getAvatar($this->institute_id);
$this->avatar_url = $avatar->getURL(Avatar::NORMAL);
}
}
\ No newline at end of file
......@@ -35,8 +35,6 @@ class Institute_BasicdataController extends AuthenticatedController
{
PageLayout::setTitle(_('Verwaltung der Grunddaten'));
PageLayout::addSqueezePackage('avatar');
//get ID from an open Institut
$i_view = $i_id ?: Request::option('i_view', Context::getId());
......
<?php
class Settings_AvatarController extends AuthenticatedController
{
public function before_filter(&$action, &$args)
{
parent::before_filter($action, $args);
// Ensure user is logged in
$GLOBALS['auth']->login_if($action !== 'logout' && $GLOBALS['auth']->auth['uid'] === 'nobody');
if (!$GLOBALS['perm']->have_profile_perm('user', User::findCurrent()->id)) {
throw new AccessDeniedException(_('Sie dürfen dieses Profil nicht bearbeiten'));
}
}
public function index_action()
{
PageLayout::setTitle(_('Profilbild anpassen'));
Navigation::activateItem('/profile/edit/avatar');
$this->user_id = User::findCurrent()->id;
$avatar = Avatar::getAvatar($this->user_id);
$this->avatar_url = $avatar->getURL(Avatar::NORMAL);
}
}
\ No newline at end of file
<?php
/**
* @var AvatarController $controller
* @var string $type
* @var string $id
* @var string $avatar
* @var bool $customized
* @var string $cancel_link
*/
?>
<form class="default settings-avatar" enctype="multipart/form-data"
action="<?= $controller->link_for('avatar/upload', $type, $id) ?>" method="post">
<fieldset>
<legend>
<?= $type == 'user' ? _('Profilbild bearbeiten und zuschneiden') :
($type == 'course' ? _('Veranstaltungsbild bearbeiten und zuschneiden') :
_('Einrichtungsbild bearbeiten und zuschneiden')) ?>
</legend>
<div class="form-group">
<div id="avatar-preview">
<img class="avatar-normal" id="new-avatar" src="<?= htmlReady($avatar) ?>"
data-message-too-small="<?= _('Das Bild ist kleiner als 250 x 250 Pixel. Wollen Sie wirklich fortfahren?') ?>">
</div>
<label class="file-upload">
<?= _('Wählen Sie ein Bild von Ihrer Festplatte aus.') ?>
<input type="file" id="avatar-upload" accept="image/gif,image/png,image/jpeg,image/webp"
capture="camera"
data-max-size="<?= Avatar::MAX_FILE_SIZE ?>"
data-message-too-large="<?= _('Die hochgeladene Datei ist zu groß. Bitte wählen Sie ein anderes Bild.') ?>">
<p class="form-text">
<?= sprintf(
_('Die Bilddatei darf max. %s groß sein, es sind nur Dateien mit den Endungen .jpg, .png, .gif und .webp erlaubt!'),
relsize(Avatar::MAX_FILE_SIZE)
) ?>
</p>
<a class="button" tabindex="0"><?= _('Auswählen') ?></a>
</label>
<input type="hidden" name="cropped-image" id="cropped-image" value="">
<div id="avatar-buttons" class="hidden-js">
<a href="" id="avatar-zoom-in" title="<?= _('Vergrößern') ?>">
<?= Icon::create('zoom-in')->asImg(24) ?>
<?= _('Vergrößern') ?>
</a>
<a href="" id="avatar-zoom-out" title="<?= _('Verkleinern') ?>">
<?= Icon::create('zoom-out')->asImg(24) ?>
<?= _('Verkleinern') ?>
</a>
<a href="" id="avatar-rotate-clockwise" title="<?= _('Nach rechts drehen') ?>">
<?= Icon::create('rotate-right')->asImg(24) ?>
<?= _('Nach rechts drehen') ?>
</a>
<a href="" id="avatar-rotate-counter-clockwise" title="<?= _('Nach links drehen') ?>">
<?= Icon::create('rotate-left')->asImg(24) ?>
<?= _('Nach links drehen') ?>
</a>
</div>
</div>
<?= CSRFProtection::tokenTag() ?>
</fieldset>
<footer data-dialog-button>
<?= Studip\Button::createAccept(_('Absenden'), 'upload', ['id' => 'submit-avatar']) ?>
<? if ($customized): ?>
<?= Studip\LinkButton::create(
_('Aktuelles Bild löschen'),
$controller->url_for('avatar/delete', $type, $id)
) ?>
<? endif ?>
<?= Studip\LinkButton::createCancel(_('Abbrechen'), $cancel_link) ?>
</footer>
</form>
<div
id="avatar-courses-app"
entry-type="courses"
entry-id="<?= $course_id ?>"
avatar-url="<?= $avatar_url ?>"
>
</div>
\ No newline at end of file
<div
id="avatar-studygroups-app"
entry-type="courses"
entry-id="<?= $studygroup_id ?>"
avatar-url="<?= $avatar_url ?>"
>
</div>
\ No newline at end of file
<div
id="avatar-institutes-app"
entry-type="institutes"
entry-id="<?= $institute_id ?>"
avatar-url="<?= $avatar_url ?>"
>
</div>
\ No newline at end of file
......@@ -142,23 +142,3 @@
<input type="hidden" name="i_view" value="<?= $i_view ?>">
</footer>
</form>
<?php
$sidebar = Sidebar::get();
if (!$institute->isNew()) {
$widget = new ActionsWidget();
$widget->addLink(
_('Infobild ändern'),
URLHelper::getURL('dispatch.php/avatar/update/institute/' . $institute->id),
Icon::create('edit')
)->asDialog();
if (InstituteAvatar::getAvatar($institute->id)->is_customized()) {
$widget->addLink(
_('Infobild löschen'),
URLHelper::getURL('dispatch.php/avatar/delete/institute/' . $institute->id),
Icon::create('trash')
);
}
$sidebar->addWidget($widget);
}
<div class="avatar-widget">
<? if ($GLOBALS['perm']->have_profile_perm('user', $current_user)) : ?>
<a class="profile-avatar"
accept="image/gif,image/png,image/jpeg" capture="camera"
data-max-size="<?= Avatar::MAX_FILE_SIZE ?>"
data-message-too-large="<?= _('Die hochgeladene Datei ist zu groß. Bitte wählen Sie ein anderes Bild.') ?>"
data-message-unaccepted="<?= _('Die hochgeladene Datei hat falsche Typ. Bitte wählen Sie ein anderes Bild.') ?>"
href="<?= URLHelper::getURL('dispatch.php/avatar/update/user/' . $current_user) ?>" data-dialog>
href="<?= URLHelper::getURL('dispatch.php/settings/avatar/') ?>">
<?= $avatar->getImageTag(Avatar::NORMAL) ?>
<div id="avatar-overlay" class="avatar-overlay">
<div class="text">
<?= _('Bild hochladen oder löschen.') ?>
<br>
<?= _('Drag & Drop oder Klicken') ?>
<?= _('Profilbild ändern') ?>
</div>
</div>
</a>
......
<div
id="avatar-users-app"
entry-type="users"
entry-id="<?= $user_id ?>"
avatar-url="<?= $avatar_url ?>"
>
</div>
\ No newline at end of file
......@@ -632,4 +632,9 @@ class Avatar
imagedestroy($img);
}
}
public function getId()
{
return $this->user_id;
}
}
......@@ -125,6 +125,7 @@ class RouteMap
$this->addAuthenticatedCoursewareRoutes($group);
}
$this->addAuthenticatedAvatarRoutes($group);
$this->addAuthenticatedEventsRoutes($group);
$this->addAuthenticatedFeedbackRoutes($group);
$this->addAuthenticatedFilesRoutes($group);
......@@ -650,6 +651,14 @@ class RouteMap
$group->post('/stock-images/{id}/blob', Routes\StockImages\StockImagesUpload::class);
}
private function addAuthenticatedAvatarRoutes(RouteCollectorProxy $group): void
{
$group->get('/{type:courses|institutes|users}/{id}/avatar', Routes\Avatar\AvatarOfRangeShow::class);
$group->delete('/{type:courses|institutes|users}/{id}/avatar', Routes\Avatar\AvatarofRangeDelete::class);
$group->post('/{type:courses|institutes|users}/{id}/avatar', Routes\Avatar\AvatarUpload::class);
}
private function addRelationship(RouteCollectorProxy $group, string $url, string $handler): void
{
$group->map(['GET', 'PATCH', 'POST', 'DELETE'], $url, $handler);
......
<?php
namespace JsonApi\Routes\Avatar;
use Avatar;
use User;
use Course;
use Institute;
class Authority
{
public static function canShowAvatarOfRange(User $user, Avatar $resource): bool
{
return true;
}
public static function canUpdateAvatarOfUser(User $user): bool
{
return $user->hasPermissionLevel('user', $user);
}
public static function canUpdateAvatarOfInstitute(User $user, Institute $institute): bool
{
return $user->hasPermissionLevel('admin', $institute);
}
public static function canUpdateAvatarOfSeminar(User $user, Course $course): bool
{
return $user->hasPermissionLevel('tutor', $course);
}
}
\ No newline at end of file
<?php
namespace JsonApi\Routes\Avatar;
use JsonApi\Errors\RecordNotFoundException;
trait AvatarHelpers
{
protected static function getAvatarClass(String $range_id, String $range_type, \User $user): Array
{
$has_perm = false;
$class = null;
if ($range_type === 'users') {
$has_perm = Authority::canUpdateAvatarOfUser($user);
$class = \Avatar::class;
} else if ($range_type === 'institutes') {
$inst = \Institute::find($range_id);
if ($inst) {
$has_perm = Authority::canUpdateAvatarOfInstitute($user, $inst);
$class = \InstituteAvatar::class;
}
} else if ($range_type === 'courses') {
$course = \Course::find($range_id);
if ($course) {
$has_perm = Authority::canUpdateAvatarOfSeminar($user, $course);
if ($course->isStudygroup()) {
$class = \StudygroupAvatar::class;
} else {
$class = \CourseAvatar::class;
}
}
} else {
throw new RecordNotFoundException();
}
return ['class' => $class, 'has_perm' => $has_perm];
}
}
\ No newline at end of file
<?php
namespace JsonApi\Routes\Avatar;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class AvatarOfRangeShow extends JsonApiController
{
use AvatarHelpers;
public function __invoke(Request $request, Response $response, $args): Response
{
$range_id = $args['id'];
$range_type = $args['type'];
$user = $this->getUser($request);
['class' => $class] = self::getAvatarClass($range_id, $range_type, $user);
$resource = $class::getAvatar($range_id);
if (!$resource) {
throw new RecordNotFoundException();
}
if (!Authority::canShowAvatarOfRange($this->getUser($request), $resource)) {
throw new AuthorizationFailedException();
}
return $this->getContentResponse($resource);
}
}
\ No newline at end of file
<?php
namespace JsonApi\Routes\Avatar;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\BadRequestException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\Routes\ValidationTrait;
use JsonApi\NonJsonApiController;
use JsonApi\Routes\Files\RoutesHelperTrait as FilesRoutesHelper;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Psr7\UploadedFile;
/**
* Create an Avatar.
*/
class AvatarUpload extends NonJsonApiController
{
use ValidationTrait;
use AvatarHelpers;
public function __invoke(Request $request, Response $response, $args): Response
{
$user = $this->getUser($request);
$json = $this->validate($request);
$range_id = self::arrayGet($json, 'data.range-id');
$range_type = self::arrayGet($json, 'data.range-type');
['class' => $class, 'has_perm' => $has_perm] = self::getAvatarClass($range_id, $range_type, $user);
if (!$has_perm) {
throw new AuthorizationFailedException();
}
$avatar = $class::getAvatar($range_id);
$imgdata_string = self::arrayGet($json, 'data.image');
[$type, $imgdata_part] = explode(';', $imgdata_string);
[$base, $imgdata_base64] = explode(',', $imgdata_part);
$imgdata = base64_decode($imgdata_base64);
// Write data to file.
$filename = $GLOBALS['TMP_PATH'] . '/avatar-' . $range_id . '.webp';
file_put_contents($filename, $imgdata);
// Use new image file for avatar creation.
$avatar->createFrom($filename);
return $response->withStatus(201);
}
protected function validateResourceDocument($json, $data)
{
if (!self::arrayHas($json, 'data')) {
return 'Missing `data` member at document´s top level.';
}
if (!self::arrayHas($json, 'data.range-id')) {
return 'New avatar must have an `range-id`.';
}
if (!self::arrayHas($json, 'data.range-type')) {
return 'New avatar must have a `range-type`.';
}
if (!self::arrayHas($json, 'data.image')) {
return 'New avatar must have a `image`.';
}
}
}
\ No newline at end of file
<?php
namespace JsonApi\Routes\Avatar;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
/**
* Delete one Avatar.
*/
class AvatarofRangeDelete extends JsonApiController
{
use AvatarHelpers;
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __invoke(Request $request, Response $response, $args)
{
$range_id = $args['id'];
$range_type = $args['type'];
$user = $this->getUser($request);
['class' => $class, 'has_perm' => $has_perm] = self::getAvatarClass($range_id, $range_type, $user);
if (!$has_perm) {
throw new AuthorizationFailedException();
}
$class::getAvatar($range_id)->reset();
return $this->getCodeResponse(204);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment