mirror of
https://github.com/Pathduck/gallery3.git
synced 2026-04-26 15:39:13 -04:00
First iteration of REST controller refactoring. RESTful controllers that refer to collections should now have plural names and there should be only one controller per resource. Updated existing classes that implement REST_Controller. The routing now works like this:
GET /controller -> controller::_index() POST /controller -> controller::_create() GET /controller/id -> controller::_show() PUT /controller/id -> controller::_update() DELETE /controller/id -> controller::_delete() GET /form/edit/controller/resource_id -> controller::_form() GET /form/add/controller/data -> controller::_form()
This commit is contained in:
@@ -24,7 +24,9 @@
|
||||
$config['^rest'] = null;
|
||||
$config['^rest/.*'] = null;
|
||||
$config['^(\w+)/(\d+)$'] = '$1/dispatch/$2';
|
||||
$config['^form/(\w+)/(.*)$'] = '$1/form/$2';
|
||||
// @todo The following will need to support query strings.
|
||||
$config['^(\w+)$'] = '$1/index';
|
||||
$config['^form/(\w+)/(\w+)/(.*)$'] = '$2/form/$3/$1';
|
||||
|
||||
// For now our default page is the scaffolding.
|
||||
$config['_default'] = 'welcome';
|
||||
|
||||
@@ -20,16 +20,9 @@
|
||||
class Album_Controller extends Item_Controller {
|
||||
|
||||
/**
|
||||
* @see Rest_Controller::_form($resource)
|
||||
* @see Rest_Controller::_show($resource, $output_format)
|
||||
*/
|
||||
public function _form($comment) {
|
||||
throw new Exception("@todo Album_Controller::_form NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Rest_Controller::_get($resource, $output_format)
|
||||
*/
|
||||
public function _get($item, $output_format) {
|
||||
public function _show($item, $output_format) {
|
||||
// @todo: these need to be pulled from the database
|
||||
$theme_name = "default";
|
||||
$page_size = 9;
|
||||
|
||||
@@ -20,21 +20,28 @@
|
||||
class Item_Controller extends REST_Controller {
|
||||
protected $resource_type = "item";
|
||||
|
||||
/**
|
||||
* @see Rest_Controller::_index($query)
|
||||
*/
|
||||
public function _index($query) {
|
||||
throw new Exception("@todo Item_Controller::_index NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Rest_Controller::_form($resource)
|
||||
*/
|
||||
public function _form($item) {
|
||||
public function _form($item, $form_type) {
|
||||
throw new Exception("@todo Comment_Controller::_form NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
public function _get($item, $format) {
|
||||
public function _show($item, $format) {
|
||||
// Redirect to the more specific resource type, since it will render
|
||||
// differently. We could also just delegate here, but it feels more appropriate
|
||||
// to have a single canonical resource mapping.
|
||||
return url::redirect("{$item->type}/$item->id");
|
||||
}
|
||||
|
||||
public function _put($item) {
|
||||
public function _update($item) {
|
||||
// @todo Productionize this code
|
||||
// 1) Add security checks
|
||||
// 2) Support owner_ids properly
|
||||
@@ -92,7 +99,7 @@ class Item_Controller extends REST_Controller {
|
||||
url::redirect("{$parent->type}/{$parent->id}");
|
||||
}
|
||||
|
||||
public function _post($item) {
|
||||
public function _create($item) {
|
||||
// @todo Productionize this
|
||||
// 1) Figure out how to do the right validation here. Validate the form input and apply it to
|
||||
// the model as appropriate.
|
||||
|
||||
@@ -20,16 +20,9 @@
|
||||
class Photo_Controller extends Item_Controller {
|
||||
|
||||
/**
|
||||
* @see Rest_Controller::_form($resource)
|
||||
* @see Rest_Controller::_show($resource, $output_format)
|
||||
*/
|
||||
public function _form($comment) {
|
||||
throw new Exception("@todo Comment_Controller::_form NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Rest_Controller::_get($resource, $output_format)
|
||||
*/
|
||||
public function _get($item, $output_format) {
|
||||
public function _show($item, $output_format) {
|
||||
$template = new View("page.html");
|
||||
|
||||
// @todo: this needs to be data-driven
|
||||
|
||||
@@ -24,16 +24,20 @@
|
||||
* class Comment_Controller extends REST_Controller {
|
||||
* protected $resource_type = "comment"; // this tells REST which model to use
|
||||
*
|
||||
* public function _get(ORM $comment, $output_format) {
|
||||
* public function _index($query) {
|
||||
* // Handle GET request to controller root
|
||||
* }
|
||||
*
|
||||
* public function _show(ORM $comment, $output_format) {
|
||||
* // Handle GET request
|
||||
* }
|
||||
*
|
||||
* public function _put(ORM $comment, $output_format) {
|
||||
* public function _update(ORM $comment, $output_format) {
|
||||
* // Handle PUT request
|
||||
* }
|
||||
*
|
||||
* public function _post(ORM $comment, $output_format) {
|
||||
* // Handle POST request
|
||||
* public function _create(ORM $comment, $output_format) {
|
||||
* // Handle POST request to controller root
|
||||
* }
|
||||
*
|
||||
* public function _delete(ORM $comment, $output_format) {
|
||||
@@ -45,7 +49,7 @@
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* A request to http://example.com/gallery3/comment/3 will result in a call to
|
||||
* A request to http://example.com/gallery3/comments/3 will result in a call to
|
||||
* REST_Controller::dispatch(3) which will load up the comment associated with id 3. If there's
|
||||
* no such comment, it returns a 404. Otherwise, it will then delegate to
|
||||
* Comment_Controller::get() with the ORM instance as an argument.
|
||||
@@ -60,7 +64,7 @@ abstract class REST_Controller extends Controller {
|
||||
|
||||
// @todo this needs security checks
|
||||
$resource = ORM::factory($this->resource_type, $id);
|
||||
if (!$resource->loaded) {
|
||||
if (!$resource->loaded && !$this->request_method() == "post") {
|
||||
return Kohana::show_404();
|
||||
}
|
||||
/**
|
||||
@@ -68,8 +72,8 @@ abstract class REST_Controller extends Controller {
|
||||
* PUT/DELETE through POST.
|
||||
*/
|
||||
$output_format = $this->input->get("_format", $this->input->post("_format", "html"));
|
||||
if (request::method() == "get") {
|
||||
$this->_get($resource, $output_format);
|
||||
if ($this->request_method() == "get") {
|
||||
$this->_show($resource, $output_format);
|
||||
|
||||
if (Session::instance()->get("use_profiler", false)) {
|
||||
$profiler = new Profiler();
|
||||
@@ -78,49 +82,88 @@ abstract class REST_Controller extends Controller {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($this->input->post("_method", $this->input->get("_method"))) {
|
||||
switch ($this->request_method()) {
|
||||
case "put":
|
||||
return $this->_put($resource);
|
||||
return $this->_update($resource);
|
||||
|
||||
case "delete":
|
||||
return $this->_delete($resource);
|
||||
|
||||
default:
|
||||
return $this->_post($resource);
|
||||
case "post":
|
||||
return $this->_create($resource);
|
||||
}
|
||||
}
|
||||
|
||||
public function form($id) {
|
||||
// @todo Get rid of $form_type, move to add_form() and edit_form().
|
||||
public function form($data, $form_type) {
|
||||
if ($this->resource_type == null) {
|
||||
throw new Exception("@todo ERROR_MISSING_RESOURCE_TYPE");
|
||||
}
|
||||
|
||||
// @todo this needs security checks
|
||||
$resource = ORM::factory($this->resource_type, $id);
|
||||
if (!$resource->loaded) {
|
||||
return Kohana::show_404();
|
||||
}
|
||||
if ($form_type == "edit") {
|
||||
/* We're editing an existing item, load it from the database. */
|
||||
$resource = ORM::factory($this->resource_type, $data);
|
||||
if (!$resource->loaded) {
|
||||
return Kohana::show_404();
|
||||
}
|
||||
|
||||
return $this->_form($resource);
|
||||
return $this->_form($resource, $form_type);
|
||||
} else {
|
||||
/* We're adding a new item, pass along any additional parameters. */
|
||||
return $this->_form($data, $form_type);
|
||||
}
|
||||
}
|
||||
|
||||
public function index($query_string=null) {
|
||||
// @todo Convert query string to an array and pass it along to _index()
|
||||
if (request::method() == "post") {
|
||||
return $this->dispatch(null);
|
||||
}
|
||||
return $this->_index(array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a GET request on this resource
|
||||
* @param ORM $resource the instance of this resource type
|
||||
* Return HTTP request method taking into consideration PUT and DELETE tunneling through POST.
|
||||
* @todo Move this to a MY_request helper?
|
||||
* @return string HTTP request method
|
||||
*/
|
||||
abstract public function _get($resource, $output_format);
|
||||
protected function request_method() {
|
||||
if (request::method() == "get") {
|
||||
return "get";
|
||||
} else {
|
||||
switch ($this->input->post("_method", $this->input->get("_method"))) {
|
||||
case "put": return "put";
|
||||
case "delete": return "delete";
|
||||
default: return "post";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a PUT request on this resource
|
||||
* @param ORM $resource the instance of this resource type
|
||||
* Perform a GET request on the controller root
|
||||
* (e.g. http://www.example.com/gallery3/comments)
|
||||
* @param array $query name-value pairs from the query string, if any
|
||||
*/
|
||||
abstract public function _put($resource);
|
||||
abstract public function _index($query);
|
||||
|
||||
/**
|
||||
* Perform a POST request on this resource
|
||||
* @param ORM $resource the instance of this resource type
|
||||
*/
|
||||
abstract public function _post($resource);
|
||||
abstract public function _create($resource);
|
||||
|
||||
/**
|
||||
* Perform a GET request on this resource
|
||||
* @param ORM $resource the instance of this resource type
|
||||
*/
|
||||
abstract public function _show($resource, $output_format);
|
||||
|
||||
/**
|
||||
* Perform a PUT request on this resource
|
||||
* @param ORM $resource the instance of this resource type
|
||||
*/
|
||||
abstract public function _update($resource);
|
||||
|
||||
/**
|
||||
* Perform a DELETE request on this resource
|
||||
@@ -132,5 +175,5 @@ abstract class REST_Controller extends Controller {
|
||||
* Present a form for adding a new resource
|
||||
* @param ORM $resource the resource container for instances of this resource type
|
||||
*/
|
||||
abstract public function _form($resource);
|
||||
abstract public function _form($resource, $form_type);
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
<?php defined("SYSPATH") or die("No direct script access.");
|
||||
/**
|
||||
* Gallery - a web based photo album viewer and editor
|
||||
* Copyright (C) 2000-2008 Bharat Mediratta
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* This program 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
class Comment_Controller extends REST_Controller {
|
||||
protected $resource_type = "comment";
|
||||
|
||||
/**
|
||||
* Present a form for editing a comment
|
||||
* @see Rest_Controller::form($resource)
|
||||
*/
|
||||
public function _form($comment) {
|
||||
$form = comment::get_edit_form($comment);
|
||||
print $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an existing comment.
|
||||
* @see Rest_Controller::_get($resource, $output_format)
|
||||
*/
|
||||
public function _get($comment, $output_format) {
|
||||
switch ($output_format) {
|
||||
case "xml":
|
||||
print xml::to_xml($comment->as_array(), array("comment"));
|
||||
break;
|
||||
|
||||
case "json":
|
||||
print json_encode($comment->as_array());
|
||||
break;
|
||||
|
||||
default:
|
||||
$v = new View("comment.$output_format");
|
||||
$v->comment = $comment;
|
||||
print $v;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update existing comment.
|
||||
* @see Rest_Controller::_put($resource)
|
||||
*/
|
||||
public function _put($comment) {
|
||||
$form = comment::get_edit_form($comment);
|
||||
if ($form->validate()) {
|
||||
$comment->author = $this->input->post('author');
|
||||
$comment->email = $this->input->post('email');
|
||||
$comment->text = $this->input->post('text');
|
||||
$comment->save();
|
||||
return;
|
||||
}
|
||||
print $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new comment
|
||||
* @see Rest_Controller::_post($resource)
|
||||
*/
|
||||
public function _post($comment) {
|
||||
throw new Exception("@todo Comment_Controller::_post NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete existing comment.
|
||||
* @see Rest_Controller::_delete($resource)
|
||||
*/
|
||||
public function _delete($resource) {
|
||||
throw new Exception("@todo Comment_Controller::_delete NOT IMPLEMENTED");
|
||||
}
|
||||
}
|
||||
@@ -18,59 +18,94 @@
|
||||
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
class Comments_Controller extends REST_Controller {
|
||||
protected $resource_type = "item";
|
||||
protected $resource_type = "comment";
|
||||
|
||||
/**
|
||||
* Present a form for adding a new comment to this item
|
||||
* @see Rest_Controller::form($resource)
|
||||
* Display comments based on criteria.
|
||||
* @see Rest_Controller::_delete($resource)
|
||||
*/
|
||||
public function _form($item) {
|
||||
$form = comment::get_add_form($item);
|
||||
print $form;
|
||||
public function _index($query) {
|
||||
throw new Exception("@todo Comment_Controller::_index NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the comment collection
|
||||
* @see Rest_Controller::_get($resource, $format)
|
||||
* Add a new comment to the collection.
|
||||
* @see Rest_Controller::_create($resource)
|
||||
*/
|
||||
public function _get($item, $output_format) {
|
||||
print comment::get_comments($item, $output_format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing comment collection.
|
||||
* @see Rest_Controller::_put($resource)
|
||||
*/
|
||||
public function _put($item) {
|
||||
throw new Exception("@todo Comment_Controller::_put NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new comment to the collection
|
||||
* @see Rest_Controller::_post($resource)
|
||||
*/
|
||||
public function _post($item) {
|
||||
$form = comment::get_add_form($item);
|
||||
public function _create($comment) {
|
||||
$form = comment::get_add_form($this->input->post('item_id'));
|
||||
if ($form->validate()) {
|
||||
$comment = ORM::factory('comment');
|
||||
$comment->author = $this->input->post('author');
|
||||
$comment->email = $this->input->post('email');
|
||||
$comment->text = $this->input->post('text');
|
||||
$comment->datetime = time();
|
||||
$comment->item_id = $item->id;
|
||||
$comment->item_id = $this->input->post('item_id');
|
||||
$comment->save();
|
||||
|
||||
header("HTTP/1.1 201 Created");
|
||||
header("Location: " . url::site("comment/{$comment->id}"));
|
||||
header("Location: " . url::site("comments/{$comment->id}"));
|
||||
}
|
||||
// @todo Return appropriate HTTP status code indicating error.
|
||||
print $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete existing comment collection.
|
||||
* Display an existing comment.
|
||||
* @see Rest_Controller::_show($resource, $format)
|
||||
*/
|
||||
public function _show($comment, $output_format) {
|
||||
switch ($output_format) {
|
||||
case "xml":
|
||||
print xml::to_xml($comment->as_array(), array("comment"));
|
||||
break;
|
||||
|
||||
case "json":
|
||||
print json_encode($comment->as_array());
|
||||
break;
|
||||
|
||||
default:
|
||||
$v = new View("comment.$output_format");
|
||||
$v->comment = $comment;
|
||||
print $v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change an existing comment.
|
||||
* @see Rest_Controller::_update($resource)
|
||||
*/
|
||||
public function _update($comment) {
|
||||
$form = comment::get_edit_form($comment);
|
||||
if ($form->validate()) {
|
||||
$comment->author = $this->input->post('author');
|
||||
$comment->email = $this->input->post('email');
|
||||
$comment->text = $this->input->post('text');
|
||||
$comment->save();
|
||||
return;
|
||||
}
|
||||
// @todo Return appropriate HTTP status code indicating error.
|
||||
print $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete existing comment.
|
||||
* @see Rest_Controller::_delete($resource)
|
||||
*/
|
||||
public function _delete($item) {
|
||||
public function _delete($comment) {
|
||||
throw new Exception("@todo Comment_Controller::_delete NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* Present a form for adding a new comment to this item or editing an existing comment.
|
||||
* @see Rest_Controller::form($resource)
|
||||
*/
|
||||
public function _form($resource, $form_type) {
|
||||
// This code will be clearer if we split form() into two functions.
|
||||
if ($form_type == "edit") {
|
||||
$form = comment::get_edit_form($resource);
|
||||
} else {
|
||||
$form = comment::get_add_form($resource);
|
||||
}
|
||||
print $form;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +54,13 @@ class Comment_Core {
|
||||
return $comment->save();
|
||||
}
|
||||
|
||||
static function get_add_form($item) {
|
||||
$form = new Forge(url::site("comments/{$item->id}"), "", "post", array("id" => "gCommentForm"));
|
||||
static function get_add_form($item_id) {
|
||||
$form = new Forge(url::site("comments"), "", "post", array("id" => "gCommentForm"));
|
||||
$group = $form->group(_("Add Comment"));
|
||||
$group->input("author") ->label(_("Author")) ->id("gAuthor");
|
||||
$group->input("email") ->label(_("Email")) ->id("gEmail");
|
||||
$group->textarea("text")->label(_("Text")) ->id("gText");
|
||||
$group->hidden("item_id")->value($item_id);
|
||||
$group->submit(_("Add"));
|
||||
$form->add_rules_from(ORM::factory("comment"));
|
||||
return $form;
|
||||
@@ -67,7 +68,7 @@ class Comment_Core {
|
||||
|
||||
static function get_edit_form($comment) {
|
||||
$form = new Forge(
|
||||
url::site("comment/{$comment->id}?_method=put"), "", "post", array("id" => "gCommentForm"));
|
||||
url::site("comments/{$comment->id}?_method=put"), "", "post", array("id" => "gCommentForm"));
|
||||
$group = $form->group(_("Edit Comment"));
|
||||
$group->input("author") ->label(_("Author")) ->id("gAuthor") ->value($comment->author);
|
||||
$group->input("email") ->label(_("Email")) ->id("gEmail") ->value($comment->email);
|
||||
|
||||
@@ -21,25 +21,31 @@ class Users_Controller extends REST_Controller {
|
||||
protected $resource_type = "user";
|
||||
|
||||
/**
|
||||
* Present a form for editing a user
|
||||
* @see Rest_Controller::form($resource)
|
||||
* Display comments based on criteria.
|
||||
* @see Rest_Controller::_delete($resource)
|
||||
*/
|
||||
public function _form($user) {
|
||||
$form = user::get_edit_form($user);
|
||||
print $form;
|
||||
public function _index($query) {
|
||||
throw new Exception("@todo Comment_Controller::_index NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Rest_Controller::_get($resource, $format)
|
||||
* @see Rest_Controller::_create($resource)
|
||||
*/
|
||||
public function _get($user, $format) {
|
||||
throw new Exception("@todo User_Controller::_get NOT IMPLEMENTED");
|
||||
public function _create($user) {
|
||||
throw new Exception("@todo User_Controller::_create NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Rest_Controller::_put($resource)
|
||||
* @see Rest_Controller::_show($resource, $format)
|
||||
*/
|
||||
public function _put($user) {
|
||||
public function _show($user, $format) {
|
||||
throw new Exception("@todo User_Controller::_show NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Rest_Controller::_update($resource)
|
||||
*/
|
||||
public function _update($user) {
|
||||
$form = user::get_edit_form($user);
|
||||
if ($form->validate()) {
|
||||
foreach ($form->as_array() as $key => $value) {
|
||||
@@ -54,17 +60,23 @@ class Users_Controller extends REST_Controller {
|
||||
print $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Rest_Controller::_post($resource)
|
||||
*/
|
||||
public function _post($user) {
|
||||
throw new Exception("@todo User_Controller::_post NOT IMPLEMENTED");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Rest_Controller::_delete($resource)
|
||||
*/
|
||||
public function _delete($user) {
|
||||
throw new Exception("@todo User_Controller::_delete NOT IMPLEMENTED");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Present a form for editing a user
|
||||
* @see Rest_Controller::form($resource)
|
||||
*/
|
||||
public function _form($user, $form_type) {
|
||||
if ($form_type == "edit") {
|
||||
$form = user::get_edit_form($user);
|
||||
print $form;
|
||||
} else {
|
||||
return Kohana::show_404();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class user {
|
||||
*/
|
||||
public static function get_edit_form($user) {
|
||||
$form = new Forge(
|
||||
url::site("user/{$user->id}?_method=put"), "", "post", array("id" => "gUserForm"));
|
||||
url::site("users/{$user->id}?_method=put"), "", "post", array("id" => "gUserForm"));
|
||||
$group = $form->group(_("User Info"));
|
||||
$group->input("name") ->label(_("Name")) ->id("gName") ->value($user->name);
|
||||
$group->input("display_name") ->label(_("Display Name")) ->id("gDisplayName") ->value($user->display_name);
|
||||
|
||||
@@ -8,11 +8,10 @@ function ajaxify_comment_form() {
|
||||
$("#gCommentForm").replaceWith(xhr.responseText);
|
||||
if (xhr.status == 201) {
|
||||
$.get(xhr.getResponseHeader("Location"), function(data, textStatus) {
|
||||
$("#gComment div.gBlockContent ul:first").append(data);
|
||||
$("#gComment div.gBlockContent ul:first li:last").hide().slideDown();
|
||||
}
|
||||
);
|
||||
$("#gCommentForm").clearForm();
|
||||
$("#gComment div.gBlockContent ul:first").append(data);
|
||||
$("#gComment div.gBlockContent ul:first li:last").hide().slideDown();
|
||||
});
|
||||
$("#gCommentForm").clearForm();
|
||||
}
|
||||
ajaxify_comment_form();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user