forked from Public/pics
Changes the ConfirmDelete page and updates database code.
The ConfirmDelete page now uses parts of the photopage. The Confirmation dialog is its own template which is based on Alert. The database now updates the album thumb to the most recent addition to the album, when the album thumb asset is being deleted. When there are no pictures left in the album 0 will be set.
This commit is contained in:
parent
e40c05c1f8
commit
16ec547064
@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
/*****************************************************************************
|
|
||||||
* ConfirmDelete.php
|
|
||||||
* Contains the ConfirmDelete controller
|
|
||||||
*
|
|
||||||
* Kabuki CMS (C) 2013-2016, Aaron van Geffen
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
class ConfirmDelete extends HTMLController
|
|
||||||
{
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
// Ensure we're logged in at this point.
|
|
||||||
$user = Registry::get('user');
|
|
||||||
if (!$user->isLoggedIn())
|
|
||||||
throw new NotAllowedException();
|
|
||||||
|
|
||||||
$photo = Asset::fromSlug($_GET['slug']);
|
|
||||||
if (empty($photo))
|
|
||||||
throw new NotFoundException();
|
|
||||||
|
|
||||||
$author = $photo->getAuthor();
|
|
||||||
if (!($user->isAdmin() || $user->getUserId() === $author->getUserId()))
|
|
||||||
throw new NotAllowedException();
|
|
||||||
|
|
||||||
if (isset($_REQUEST['confirmed']))
|
|
||||||
$this->handleDelete($photo);
|
|
||||||
|
|
||||||
parent::__construct('Confirm deletion' . ' - ' . SITE_TITLE);
|
|
||||||
$page = new ConfirmDeletePage($photo->getImage());
|
|
||||||
|
|
||||||
$this->page->adopt($page);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleDelete(Asset $photo) {
|
|
||||||
$album_url = $photo->getSubdir();
|
|
||||||
|
|
||||||
$photo->delete();
|
|
||||||
|
|
||||||
header('Location: ' . BASEURL . '/' . $album_url);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,17 +11,53 @@ class ViewPhoto extends HTMLController
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
// Ensure we're logged in at this point.
|
// Ensure we're logged in at this point.
|
||||||
if (!Registry::get('user')->isLoggedIn())
|
$user = Registry::get('user');
|
||||||
|
if (!$user->isLoggedIn())
|
||||||
throw new NotAllowedException();
|
throw new NotAllowedException();
|
||||||
|
|
||||||
$photo = Asset::fromSlug($_GET['slug']);
|
$photo = Asset::fromSlug($_GET['slug']);
|
||||||
if (empty($photo))
|
if (empty($photo))
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
|
|
||||||
|
parent::__construct($photo->getTitle() . ' - ' . SITE_TITLE);
|
||||||
|
|
||||||
|
$author = $photo->getAuthor();
|
||||||
|
|
||||||
|
if (isset($_REQUEST['confirm_delete']) || isset($_REQUEST['delete_confirmed']))
|
||||||
|
$this->handleConfirmDelete($user, $author, $photo);
|
||||||
|
else
|
||||||
|
$this->handleViewPhoto($user, $author, $photo);
|
||||||
|
|
||||||
|
// Add an edit button to the admin bar.
|
||||||
|
if ($user->isAdmin())
|
||||||
|
$this->admin_bar->appendItem(BASEURL . '/editasset/?id=' . $photo->getId(), 'Edit this photo');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleConfirmDelete(User $user, User $author, Asset $photo)
|
||||||
|
{
|
||||||
|
if (!($user->isAdmin() || $user->getUserId() === $author->getUserId()))
|
||||||
|
throw new NotAllowedException();
|
||||||
|
|
||||||
|
if (isset($_REQUEST['confirm_delete']))
|
||||||
|
{
|
||||||
|
$page = new ConfirmDeletePage($photo->getImage());
|
||||||
|
$this->page->adopt($page);
|
||||||
|
}
|
||||||
|
else if (isset($_REQUEST['delete_confirmed']))
|
||||||
|
{
|
||||||
|
$album_url = $photo->getSubdir();
|
||||||
|
$photo->delete();
|
||||||
|
|
||||||
|
header('Location: ' . BASEURL . '/' . $album_url);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleViewPhoto(User $user, User $author, Asset $photo)
|
||||||
|
{
|
||||||
if (!empty($_POST))
|
if (!empty($_POST))
|
||||||
$this->handleTagging($photo->getImage());
|
$this->handleTagging($photo->getImage());
|
||||||
|
|
||||||
parent::__construct($photo->getTitle() . ' - ' . SITE_TITLE);
|
|
||||||
$page = new PhotoPage($photo->getImage());
|
$page = new PhotoPage($photo->getImage());
|
||||||
|
|
||||||
// Exif data?
|
// Exif data?
|
||||||
@ -43,17 +79,11 @@ class ViewPhoto extends HTMLController
|
|||||||
if ($next_url)
|
if ($next_url)
|
||||||
$page->setNextPhotoUrl($next_url);
|
$page->setNextPhotoUrl($next_url);
|
||||||
|
|
||||||
$user = Registry::get('user');
|
|
||||||
$author = $photo->getAuthor();
|
|
||||||
if ($user->isAdmin() || $user->getUserId() === $author->getUserId())
|
if ($user->isAdmin() || $user->getUserId() === $author->getUserId())
|
||||||
$page->setIsAssetOwner(true);
|
$page->setIsAssetOwner(true);
|
||||||
|
|
||||||
$this->page->adopt($page);
|
$this->page->adopt($page);
|
||||||
$this->page->setCanonicalUrl($photo->getPageUrl());
|
$this->page->setCanonicalUrl($photo->getPageUrl());
|
||||||
|
|
||||||
// Add an edit button to the admin bar.
|
|
||||||
if (Registry::get('user')->isAdmin())
|
|
||||||
$this->admin_bar->appendItem(BASEURL . '/editasset/?id=' . $photo->getId(), 'Edit this photo');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleTagging(Image $photo)
|
private function handleTagging(Image $photo)
|
||||||
|
@ -485,14 +485,6 @@ class Asset
|
|||||||
if (!unlink(ASSETSDIR . '/' . $this->subdir . '/' . $this->filename))
|
if (!unlink(ASSETSDIR . '/' . $this->subdir . '/' . $this->filename))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
$db->query('
|
|
||||||
UPDATE tags
|
|
||||||
SET id_asset_thumb = 0
|
|
||||||
WHERE id_asset_thumb = {int:id_asset} AND kind = "Album"',
|
|
||||||
[
|
|
||||||
'id_asset' => $this->id_asset,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$db->query('
|
$db->query('
|
||||||
DELETE FROM assets_meta
|
DELETE FROM assets_meta
|
||||||
WHERE id_asset = {int:id_asset}',
|
WHERE id_asset = {int:id_asset}',
|
||||||
@ -500,12 +492,53 @@ class Asset
|
|||||||
'id_asset' => $this->id_asset,
|
'id_asset' => $this->id_asset,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $db->query('
|
$rows = $db->query('
|
||||||
|
SELECT id_tag
|
||||||
|
FROM assets_tags
|
||||||
|
WHERE id_asset = {int:id_asset}',
|
||||||
|
[
|
||||||
|
'id_asset' => $this->id_asset,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$recount_tags = [];
|
||||||
|
if(!empty($rows))
|
||||||
|
foreach($rows as $row)
|
||||||
|
$recount_tags[] = $row['id_tag'];
|
||||||
|
|
||||||
|
$db->query('
|
||||||
|
DELETE FROM assets_tags
|
||||||
|
WHERE id_asset = {int:id_asset}',
|
||||||
|
[
|
||||||
|
'id_asset' => $this->id_asset,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Tag::recount($recount_tags);
|
||||||
|
|
||||||
|
$return = $db->query('
|
||||||
DELETE FROM assets
|
DELETE FROM assets
|
||||||
WHERE id_asset = {int:id_asset}',
|
WHERE id_asset = {int:id_asset}',
|
||||||
[
|
[
|
||||||
'id_asset' => $this->id_asset,
|
'id_asset' => $this->id_asset,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$rows = $db->query('
|
||||||
|
SELECT id_tag
|
||||||
|
FROM tags
|
||||||
|
WHERE id_asset_thumb = {int:id_asset} AND kind = "Album"',
|
||||||
|
[
|
||||||
|
'id_asset' => $this->id_asset,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($rows))
|
||||||
|
{
|
||||||
|
foreach ($rows as $row)
|
||||||
|
{
|
||||||
|
$tag = Tag::fromId($row['id_tag']);
|
||||||
|
$tag->resetIdAsset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function linkTags(array $id_tags)
|
public function linkTags(array $id_tags)
|
||||||
|
@ -28,7 +28,6 @@ class Dispatcher
|
|||||||
'suggest' => 'ProvideAutoSuggest',
|
'suggest' => 'ProvideAutoSuggest',
|
||||||
'timeline' => 'ViewTimeline',
|
'timeline' => 'ViewTimeline',
|
||||||
'uploadmedia' => 'UploadMedia',
|
'uploadmedia' => 'UploadMedia',
|
||||||
'confirmdelete' => 'ConfirmDelete',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Work around PHP's FPM not always providing PATH_INFO.
|
// Work around PHP's FPM not always providing PATH_INFO.
|
||||||
|
@ -289,6 +289,34 @@ class Tag
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function resetIdAsset()
|
||||||
|
{
|
||||||
|
$db = Registry::get('db');
|
||||||
|
|
||||||
|
$row = $db->query('
|
||||||
|
SELECT MAX(id_asset) as new_id
|
||||||
|
FROM assets_tags
|
||||||
|
WHERE id_tag = {int:id_tag}',
|
||||||
|
[
|
||||||
|
'id_tag' => $this->id_tag,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$new_id = 0;
|
||||||
|
if(!empty($row))
|
||||||
|
{
|
||||||
|
$new_id = $row->fetch_assoc()['new_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $db->query('
|
||||||
|
UPDATE tags
|
||||||
|
SET id_asset_thumb = {int:new_id}
|
||||||
|
WHERE id_tag = {int:id_tag}',
|
||||||
|
[
|
||||||
|
'new_id' => $new_id,
|
||||||
|
'id_tag' => $this->id_tag,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public static function match($tokens)
|
public static function match($tokens)
|
||||||
{
|
{
|
||||||
if (!is_array($tokens))
|
if (!is_array($tokens))
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
@import url(//fonts.googleapis.com/css?family=Open+Sans:400,400italic,700,700italic);
|
@import url(//fonts.googleapis.com/css?family=Open+Sans:400,400italic,700,700italic);
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Invaders';
|
font-family: 'Invaders';
|
||||||
src: url('fonts/invaders.ttf') format('truetype');
|
src: url('fonts/invaders.ttf') format('truetype');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -135,7 +135,7 @@ ul#nav li a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pagination .page-padding {
|
.pagination .page-padding {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -565,7 +565,7 @@ a#previous_photo:hover, a#next_photo:hover {
|
|||||||
content: '→';
|
content: '→';
|
||||||
}
|
}
|
||||||
|
|
||||||
#sub_photo h2, #sub_photo h3, #photo_exif_box h3 {
|
#sub_photo h2, #sub_photo h3, #photo_exif_box h3, #user_actions_box h3 {
|
||||||
font: 600 20px/30px "Open Sans", sans-serif;
|
font: 600 20px/30px "Open Sans", sans-serif;
|
||||||
margin: 0 0 10px;
|
margin: 0 0 10px;
|
||||||
}
|
}
|
||||||
@ -622,22 +622,13 @@ a#previous_photo:hover, a#next_photo:hover {
|
|||||||
#user_actions_box {
|
#user_actions_box {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
float: left;
|
||||||
margin: 25px 0 25px 0;
|
margin: 25px 0 25px 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 2%;
|
padding: 2%;
|
||||||
float: right;
|
|
||||||
width: 20%;
|
width: 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#confirm_box {
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
|
||||||
padding: 2%;
|
|
||||||
margin: 25px 0 25px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Responsive: smartphone in portrait
|
/* Responsive: smartphone in portrait
|
||||||
---------------------------------------*/
|
---------------------------------------*/
|
||||||
@media only screen and (max-width: 895px) {
|
@media only screen and (max-width: 895px) {
|
||||||
|
@ -19,6 +19,13 @@ class Alert extends SubTemplate
|
|||||||
{
|
{
|
||||||
echo '
|
echo '
|
||||||
<div class="alert', $this->_type != 'alert' ? ' alert-' . $this->_type : '', '">', (!empty($this->_title) ? '
|
<div class="alert', $this->_type != 'alert' ? ' alert-' . $this->_type : '', '">', (!empty($this->_title) ? '
|
||||||
<strong>' . $this->_title . '</strong><br>' : ''), $this->_message, '</div>';
|
<strong>' . $this->_title . '</strong><br>' : ''), '<p>', $this->_message, '</p>';
|
||||||
|
|
||||||
|
$this->additional_alert_content();
|
||||||
|
|
||||||
|
echo '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function additional_alert_content()
|
||||||
|
{}
|
||||||
}
|
}
|
||||||
|
27
templates/Button.php
Normal file
27
templates/Button.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
/*****************************************************************************
|
||||||
|
* Button.php
|
||||||
|
* Defines the Button template.
|
||||||
|
*
|
||||||
|
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
class Button extends SubTemplate
|
||||||
|
{
|
||||||
|
private $content = '';
|
||||||
|
private $href = '';
|
||||||
|
private $class = '';
|
||||||
|
|
||||||
|
public function __construct($content = '', $href = '', $class = '')
|
||||||
|
{
|
||||||
|
$this->content = $content;
|
||||||
|
$this->href = $href;
|
||||||
|
$this->class = $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function html_content()
|
||||||
|
{
|
||||||
|
echo '
|
||||||
|
<a class="', $this->class, '" href="', $this->href, '">', $this->content, '</a>';
|
||||||
|
}
|
||||||
|
}
|
@ -6,13 +6,11 @@
|
|||||||
* Kabuki CMS (C) 2013-2016, Aaron van Geffen
|
* Kabuki CMS (C) 2013-2016, Aaron van Geffen
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
class ConfirmDeletePage extends SubTemplate
|
class ConfirmDeletePage extends PhotoPage
|
||||||
{
|
{
|
||||||
private $photo;
|
|
||||||
|
|
||||||
public function __construct(Image $photo)
|
public function __construct(Image $photo)
|
||||||
{
|
{
|
||||||
$this->photo = $photo;
|
parent::__construct($photo);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function html_content()
|
protected function html_content()
|
||||||
@ -23,30 +21,15 @@ class ConfirmDeletePage extends SubTemplate
|
|||||||
|
|
||||||
private function confirm()
|
private function confirm()
|
||||||
{
|
{
|
||||||
echo '
|
$buttons = [];
|
||||||
<div id=confirm_box>
|
$buttons[] = new Button("Delete", BASEURL . '/' . $this->photo->getSlug() . '?delete_confirmed', "btn btn-red");
|
||||||
<h1>Confirm deletion</h1>
|
$buttons[] = new Button("Cancel", $this->photo->getPageUrl(), "btn");
|
||||||
<p>You are about to permanently delete the following photo.</p>
|
|
||||||
<a class="btn btn-red" href="', BASEURL, '/confirmdelete?slug=', $this->photo->getSlug(), '&confirmed">Delete</a>
|
|
||||||
<a class="btn" href="', $this->photo->getPageUrl(), '"> Cancel</a>
|
|
||||||
</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function photo()
|
$alert = new WarningDialog(
|
||||||
{
|
"Confirm deletion.",
|
||||||
echo '
|
"You are about to permanently delete the following photo.",
|
||||||
<div id="photo_frame">
|
$buttons
|
||||||
<a href="', $this->photo->getUrl(), '">';
|
);
|
||||||
|
$alert->html_content();
|
||||||
if ($this->photo->isPortrait())
|
|
||||||
echo '
|
|
||||||
<img src="', $this->photo->getThumbnailUrl(null, 960), '" alt="">';
|
|
||||||
else
|
|
||||||
echo '
|
|
||||||
<img src="', $this->photo->getThumbnailUrl(1280, null), '" alt="">';
|
|
||||||
|
|
||||||
echo '
|
|
||||||
</a>
|
|
||||||
</div>';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ class EditAssetForm extends SubTemplate
|
|||||||
<form id="asset_form" action="" method="post" enctype="multipart/form-data">
|
<form id="asset_form" action="" method="post" enctype="multipart/form-data">
|
||||||
<div class="boxed_content" style="margin-bottom: 2%">
|
<div class="boxed_content" style="margin-bottom: 2%">
|
||||||
<div style="float: right">
|
<div style="float: right">
|
||||||
<a class="btn btn-red" href="', BASEURL, '/confirmdelete?slug=', $this->asset->getSlug(), '>Delete asset</a>
|
<a class="btn btn-red" href="', BASEURL, '/', $this->asset->getSlug(), '?delete_confirmed">Delete asset</a>
|
||||||
<input type="submit" value="Save asset data">
|
<input type="submit" value="Save asset data">
|
||||||
</div>
|
</div>
|
||||||
<h2>Edit asset \'', $this->asset->getTitle(), '\' (', $this->asset->getFilename(), ')</h2>
|
<h2>Edit asset \'', $this->asset->getTitle(), '\' (', $this->asset->getFilename(), ')</h2>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
class PhotoPage extends SubTemplate
|
class PhotoPage extends SubTemplate
|
||||||
{
|
{
|
||||||
private $photo;
|
protected $photo;
|
||||||
private $exif;
|
private $exif;
|
||||||
private $previous_photo_url = '';
|
private $previous_photo_url = '';
|
||||||
private $next_photo_url = '';
|
private $next_photo_url = '';
|
||||||
@ -29,7 +29,8 @@ class PhotoPage extends SubTemplate
|
|||||||
$this->next_photo_url = $url;
|
$this->next_photo_url = $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setIsAssetOwner($flag) {
|
public function setIsAssetOwner($flag)
|
||||||
|
{
|
||||||
$this->is_asset_owner = $flag;
|
$this->is_asset_owner = $flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,15 +51,14 @@ class PhotoPage extends SubTemplate
|
|||||||
|
|
||||||
$this->photoMeta();
|
$this->photoMeta();
|
||||||
|
|
||||||
if($this->is_asset_owner) {
|
if($this->is_asset_owner)
|
||||||
$this->addUserActions();
|
$this->addUserActions();
|
||||||
}
|
|
||||||
|
|
||||||
echo '
|
echo '
|
||||||
<script type="text/javascript" src="', BASEURL, '/js/photonav.js"></script>';
|
<script type="text/javascript" src="', BASEURL, '/js/photonav.js"></script>';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function photo()
|
protected function photo()
|
||||||
{
|
{
|
||||||
echo '
|
echo '
|
||||||
<div id="photo_frame">
|
<div id="photo_frame">
|
||||||
@ -198,7 +198,7 @@ class PhotoPage extends SubTemplate
|
|||||||
echo '
|
echo '
|
||||||
<div id=user_actions_box>
|
<div id=user_actions_box>
|
||||||
<h3>Actions</h3>
|
<h3>Actions</h3>
|
||||||
<a class="btn btn-red" href="', BASEURL, '/confirmdelete?slug=', $this->photo->getSlug(), '">Delete</a>
|
<a class="btn btn-red" href="', BASEURL, '/', $this->photo->getSlug(), '?confirm_delete">Delete</a>
|
||||||
</div>';
|
</div>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
templates/WarningDialog.php
Normal file
29
templates/WarningDialog.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
/*****************************************************************************
|
||||||
|
* WarningDialog.php
|
||||||
|
* Defines the WarningDialog template.
|
||||||
|
*
|
||||||
|
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
class WarningDialog extends Alert
|
||||||
|
{
|
||||||
|
protected $buttons;
|
||||||
|
|
||||||
|
public function __construct($title = '', $message = '', $buttons = [])
|
||||||
|
{
|
||||||
|
parent::__construct($title, $message);
|
||||||
|
$this->buttons = $buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function additional_alert_content()
|
||||||
|
{
|
||||||
|
$this->addButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addButtons()
|
||||||
|
{
|
||||||
|
foreach ($this->buttons as $button)
|
||||||
|
$button->html_content();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user