refactoring various things.
This commit is contained in:
@@ -1,15 +1,21 @@
|
|||||||
<?php namespace Laravel;
|
<?php namespace Laravel;
|
||||||
|
|
||||||
use Closure;
|
|
||||||
|
|
||||||
class Arr {
|
class Arr {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an item from an array.
|
* Get an item from an array.
|
||||||
*
|
*
|
||||||
* If the specified key is null, the entire array will be returned. The array may
|
* This method supports accessing arrays through JavaScript "dot" style syntax
|
||||||
* also be accessed using JavaScript "dot" style notation. Retrieving items nested
|
* for conveniently digging deep into nested arrays. Like most other Laravel
|
||||||
* in multiple arrays is supported.
|
* "get" methods, a default value may be provided.
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* // Get the value of $array['user']['name']
|
||||||
|
* $value = Arr::get($array, 'user.name');
|
||||||
|
*
|
||||||
|
* // Get a value from the array, but return a default if it doesn't exist
|
||||||
|
* $value = Arr::get($array, 'user.name', 'Taylor');
|
||||||
|
* </code>
|
||||||
*
|
*
|
||||||
* @param array $array
|
* @param array $array
|
||||||
* @param string $key
|
* @param string $key
|
||||||
@@ -24,7 +30,7 @@ class Arr {
|
|||||||
{
|
{
|
||||||
if ( ! is_array($array) or ! array_key_exists($segment, $array))
|
if ( ! is_array($array) or ! array_key_exists($segment, $array))
|
||||||
{
|
{
|
||||||
return ($default instanceof Closure) ? call_user_func($default) : $default;
|
return ($default instanceof \Closure) ? call_user_func($default) : $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
$array = $array[$segment];
|
$array = $array[$segment];
|
||||||
@@ -36,9 +42,16 @@ class Arr {
|
|||||||
/**
|
/**
|
||||||
* Set an array item to a given value.
|
* Set an array item to a given value.
|
||||||
*
|
*
|
||||||
* This method is primarly helpful for setting the value in an array with
|
* This method supports accessing arrays through JavaScript "dot" style syntax
|
||||||
* a variable depth, such as configuration arrays. Like the Arr::get
|
* for conveniently digging deep into nested arrays.
|
||||||
* method, JavaScript "dot" syntax is supported.
|
*
|
||||||
|
* <code>
|
||||||
|
* // Set the $array['user']['name'] value in the array
|
||||||
|
* Arr::set($array, 'user.name', 'Taylor');
|
||||||
|
*
|
||||||
|
* // Set the $array['db']['driver']['name'] value in the array
|
||||||
|
* Arr::set($array, 'db.driver.name', 'SQLite');
|
||||||
|
* </code>
|
||||||
*
|
*
|
||||||
* @param array $array
|
* @param array $array
|
||||||
* @param string $key
|
* @param string $key
|
||||||
@@ -69,6 +82,20 @@ class Arr {
|
|||||||
/**
|
/**
|
||||||
* Return the first element in an array which passes a given truth test.
|
* Return the first element in an array which passes a given truth test.
|
||||||
*
|
*
|
||||||
|
* The truth test is passed as a closure, and simply returns true or false.
|
||||||
|
* The array key and value will be passed to the closure on each iteration.
|
||||||
|
*
|
||||||
|
* Like the "get" method, a default value may be specified, and will be
|
||||||
|
* returned if no matching array elements are found by the method.
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* // Get the first string from an array with a length of 3
|
||||||
|
* $value = Arr::first($array, function($k, $v) {return strlen($v) == 3;});
|
||||||
|
*
|
||||||
|
* // Return a default value if no matching array elements are found
|
||||||
|
* $value = Arr::first($array, function($k, $v) {return;}, 'Default');
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
* @param array $array
|
* @param array $array
|
||||||
* @param Closure $callback
|
* @param Closure $callback
|
||||||
* @return mixed
|
* @return mixed
|
||||||
@@ -80,7 +107,7 @@ class Arr {
|
|||||||
if (call_user_func($callback, $key, $value)) return $value;
|
if (call_user_func($callback, $key, $value)) return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ($default instanceof Closure) ? call_user_func($default) : $default;
|
return ($default instanceof \Closure) ? call_user_func($default) : $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -35,10 +35,10 @@ class Asset {
|
|||||||
* expressive code and a clean API.
|
* expressive code and a clean API.
|
||||||
*
|
*
|
||||||
* <code>
|
* <code>
|
||||||
* // Get the default asset container
|
* // Get an instance of the default asset container
|
||||||
* $container = Asset::container();
|
* $container = Asset::container();
|
||||||
*
|
*
|
||||||
* // Get the "footer" asset container
|
* // Get an instance of the "footer" container
|
||||||
* $container = Asset::container('footer');
|
* $container = Asset::container('footer');
|
||||||
* </code>
|
* </code>
|
||||||
*
|
*
|
||||||
@@ -58,12 +58,9 @@ class Asset {
|
|||||||
/**
|
/**
|
||||||
* Magic Method for calling methods on the default Asset container.
|
* Magic Method for calling methods on the default Asset container.
|
||||||
*
|
*
|
||||||
* This provides a convenient API, allowing the developer to skip the
|
|
||||||
* "container" method when using the default container.
|
|
||||||
*
|
|
||||||
* <code>
|
* <code>
|
||||||
* // Add a JavaScript file to the default container
|
* // Call the "add" method on the default asset container
|
||||||
* Asset::script('jquery', 'js/jquery.js');
|
* Asset::add('jquery', 'js/jquery.js');
|
||||||
*
|
*
|
||||||
* // Get all of the styles from the default container
|
* // Get all of the styles from the default container
|
||||||
* echo Asset::styles();
|
* echo Asset::styles();
|
||||||
@@ -125,10 +122,13 @@ class Asset_Container {
|
|||||||
*
|
*
|
||||||
* <code>
|
* <code>
|
||||||
* // Add an asset to the container
|
* // Add an asset to the container
|
||||||
* Asset::add('jquery', 'js/jquery.js');
|
* Asset::container()->add('style', 'style.css');
|
||||||
*
|
*
|
||||||
* // Add an asset that has dependencies
|
* // Add an asset to the container with attributes
|
||||||
* Asset::add('jquery', 'js/jquery.js', array('jquery-ui'));
|
* Asset::container()->add('style', 'style.css', array(), array('media' => 'print'));
|
||||||
|
*
|
||||||
|
* // Add an asset to the container with dependencies
|
||||||
|
* Asset::container()->add('jquery', 'jquery.js', array('jquery-ui'));
|
||||||
* </code>
|
* </code>
|
||||||
*
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
@@ -147,6 +147,17 @@ class Asset_Container {
|
|||||||
/**
|
/**
|
||||||
* Add a CSS file to the registered assets.
|
* Add a CSS file to the registered assets.
|
||||||
*
|
*
|
||||||
|
* <code>
|
||||||
|
* // Add a CSS file to the registered assets
|
||||||
|
* Asset::container()->style('common', 'common.css');
|
||||||
|
*
|
||||||
|
* // Add a CSS file with dependencies to the registered assets
|
||||||
|
* Asset::container()->style('common', 'common.css', array('reset'));
|
||||||
|
*
|
||||||
|
* // Add a CSS file with attributes to the registered assets
|
||||||
|
* Asset::container()->style('common', 'common.css', array(), array('media' => 'print'));
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param string $source
|
* @param string $source
|
||||||
* @param array $dependencies
|
* @param array $dependencies
|
||||||
@@ -168,6 +179,17 @@ class Asset_Container {
|
|||||||
/**
|
/**
|
||||||
* Add a JavaScript file to the registered assets.
|
* Add a JavaScript file to the registered assets.
|
||||||
*
|
*
|
||||||
|
* <code>
|
||||||
|
* // Add a CSS file to the registered assets
|
||||||
|
* Asset::container()->script('jquery', 'jquery.js');
|
||||||
|
*
|
||||||
|
* // Add a CSS file with dependencies to the registered assets
|
||||||
|
* Asset::container()->script('jquery', 'jquery.js', array('jquery-ui'));
|
||||||
|
*
|
||||||
|
* // Add a CSS file with attributes to the registered assets
|
||||||
|
* Asset::container()->script('loader', 'loader.js', array(), array('defer'));
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param string $source
|
* @param string $source
|
||||||
* @param array $dependencies
|
* @param array $dependencies
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ if (file_exists($path = CONFIG_PATH.'container'.EXT))
|
|||||||
$dependencies = array_merge($dependencies, require $path);
|
$dependencies = array_merge($dependencies, require $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_SERVER['LARAVEL_ENV']) and file_exists($path = CONFIG_PATH.$_SERVER['LARAVEL_ENV'].'/container'.EXT))
|
$env = (isset($_SERVER['LARAVEL_ENV'])) ? $_SERVER['LARAVEL_ENV'] : null;
|
||||||
|
|
||||||
|
if ( ! is_null($env) and file_exists($path = CONFIG_PATH.$env.'/container'.EXT))
|
||||||
{
|
{
|
||||||
$dependencies = array_merge($dependencies, require $path);
|
$dependencies = array_merge($dependencies, require $path);
|
||||||
}
|
}
|
||||||
@@ -61,4 +63,9 @@ IoC::$container = $container;
|
|||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
// Register the auto-loader on the auto-loader stack.
|
// Register the auto-loader on the auto-loader stack.
|
||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
spl_autoload_register(array($container->resolve('laravel.loader'), 'load'));
|
spl_autoload_register(array($container->resolve('laravel.loader'), 'load'));
|
||||||
|
|
||||||
|
// --------------------------------------------------------------
|
||||||
|
// Set the application environment configuration option.
|
||||||
|
// --------------------------------------------------------------
|
||||||
|
$container->resolve('laravel.config')->set('application.env', $env);
|
||||||
@@ -5,33 +5,32 @@ class Config {
|
|||||||
/**
|
/**
|
||||||
* All of the loaded configuration items.
|
* All of the loaded configuration items.
|
||||||
*
|
*
|
||||||
* The configuration arrays are keyed by their owning file name.
|
|
||||||
*
|
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $items = array();
|
protected $config = array();
|
||||||
|
|
||||||
/**
|
|
||||||
* The paths to the configuration files.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $paths = array();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new configuration manager instance.
|
* Create a new configuration manager instance.
|
||||||
*
|
*
|
||||||
* @param array $paths
|
* @param array $config
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct($paths)
|
public function __construct($config)
|
||||||
{
|
{
|
||||||
$this->paths = $paths;
|
$this->config = $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a configuration item or file exists.
|
* Determine if a configuration item or file exists.
|
||||||
*
|
*
|
||||||
|
* <code>
|
||||||
|
* // Determine if the "options" configuration file exists
|
||||||
|
* $options = Config::has('options');
|
||||||
|
*
|
||||||
|
* // Determine if a specific configuration item exists
|
||||||
|
* $timezone = Config::has('application.timezone');
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
* @param string $key
|
* @param string $key
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
@@ -43,18 +42,23 @@ class Config {
|
|||||||
/**
|
/**
|
||||||
* Get a configuration item.
|
* Get a configuration item.
|
||||||
*
|
*
|
||||||
* If the name of a configuration file is passed without specifying an item, the
|
* Configuration items are stored in the application/config directory, and provide
|
||||||
* entire configuration array will be returned.
|
* general configuration options for a wide range of Laravel facilities.
|
||||||
|
*
|
||||||
|
* The arrays may be accessed using JavaScript style "dot" notation to drill deep
|
||||||
|
* intot he configuration files. For example, asking for "database.connectors.sqlite"
|
||||||
|
* would return the connector closure for SQLite stored in the database configuration
|
||||||
|
* file. If no specific item is specfied, the entire configuration array is returned.
|
||||||
|
*
|
||||||
|
* Like most Laravel "get" functions, a default value may be provided, and it will
|
||||||
|
* be returned if the requested file or item doesn't exist.
|
||||||
*
|
*
|
||||||
* <code>
|
* <code>
|
||||||
* // Get the "timezone" option from the "application" file
|
* // Get the "timezone" option from the application config file
|
||||||
* $timezone = Config::get('application.timezone');
|
* $timezone = Config::get('application.timezone');
|
||||||
*
|
*
|
||||||
* // Get the SQLite connection configuration from the "database" file
|
* // Get an option, but return a default value if it doesn't exist
|
||||||
* $sqlite = Config::get('database.connections.sqlite');
|
* $value = Config::get('some.option', 'Default');
|
||||||
*
|
|
||||||
* // Get a configuration option and return "Fred" if it doesn't exist
|
|
||||||
* $option = Config::get('config.option', 'Fred');
|
|
||||||
* </code>
|
* </code>
|
||||||
*
|
*
|
||||||
* @param string $key
|
* @param string $key
|
||||||
@@ -63,30 +67,25 @@ class Config {
|
|||||||
*/
|
*/
|
||||||
public function get($key, $default = null)
|
public function get($key, $default = null)
|
||||||
{
|
{
|
||||||
list($file, $key) = $this->parse($key);
|
return Arr::get($this->items, $key, $default);
|
||||||
|
|
||||||
if ( ! $this->load($file))
|
|
||||||
{
|
|
||||||
return ($default instanceof \Closure) ? call_user_func($default) : $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_null($key)) return $this->items[$file];
|
|
||||||
|
|
||||||
return Arr::get($this->items[$file], $key, $default);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a configuration item.
|
* Set a configuration item.
|
||||||
*
|
*
|
||||||
* If a specific configuration item is not specified, the entire configuration
|
* Configuration items are stored in the application/config directory, and provide
|
||||||
* array will be replaced with the given value.
|
* general configuration options for a wide range of Laravel facilities.
|
||||||
|
*
|
||||||
|
* Like the "get" method, this method uses JavaScript style "dot" notation to access
|
||||||
|
* and manipulate the arrays in the configuration files. Also, like the "get" method,
|
||||||
|
* if no specific item is specified, the entire configuration array will be set.
|
||||||
*
|
*
|
||||||
* <code>
|
* <code>
|
||||||
* // Set the "timezone" option in the "application" file
|
* // Set the "timezone" option in the "application" array
|
||||||
* Config::set('application.timezone', 'America/Chicago');
|
* Config::set('application.timezone', 'America/Chicago');
|
||||||
*
|
*
|
||||||
* // Set the entire "session" array to an empty array
|
* // Set the entire "session" configuration array
|
||||||
* Config::set('session', array());
|
* Config::set('session', $array);
|
||||||
* </code>
|
* </code>
|
||||||
*
|
*
|
||||||
* @param string $key
|
* @param string $key
|
||||||
@@ -95,52 +94,7 @@ class Config {
|
|||||||
*/
|
*/
|
||||||
public function set($key, $value)
|
public function set($key, $value)
|
||||||
{
|
{
|
||||||
list($file, $key) = $this->parse($key);
|
Arr::set($this->items, $key, $value);
|
||||||
|
|
||||||
$this->load($file);
|
|
||||||
|
|
||||||
(is_null($key)) ? Arr::set($this->items, $file, $value) : Arr::set($this->items[$file], $key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a configuration key and return its file and key segments.
|
|
||||||
*
|
|
||||||
* Configuration keys follow a {file}.{key} convention.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function parse($key)
|
|
||||||
{
|
|
||||||
$segments = explode('.', $key);
|
|
||||||
|
|
||||||
$key = (count($segments) > 1) ? implode('.', array_slice($segments, 1)) : null;
|
|
||||||
|
|
||||||
return array($segments[0], $key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load all of the configuration items from a module configuration file.
|
|
||||||
*
|
|
||||||
* If the configuration file has already been loaded, it will not be loaded again.
|
|
||||||
*
|
|
||||||
* @param string $file
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
protected function load($file)
|
|
||||||
{
|
|
||||||
if (isset($this->items[$file])) return true;
|
|
||||||
|
|
||||||
$config = array();
|
|
||||||
|
|
||||||
foreach ($this->paths as $directory)
|
|
||||||
{
|
|
||||||
$config = (file_exists($path = $directory.$file.EXT)) ? array_merge($config, require $path) : $config;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($config) > 0) $this->items[$file] = $config;
|
|
||||||
|
|
||||||
return isset($this->items[$file]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -108,6 +108,8 @@ return array(
|
|||||||
($request->spoofed()) ? $input = $_POST : parse_str(file_get_contents('php://input'), $input);
|
($request->spoofed()) ? $input = $_POST : parse_str(file_get_contents('php://input'), $input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unset($input['_REQUEST_METHOD_']);
|
||||||
|
|
||||||
return new Input($container->resolve('laravel.file'), $container->resolve('laravel.cookie'), $input, $_FILES);
|
return new Input($container->resolve('laravel.file'), $container->resolve('laravel.cookie'), $input, $_FILES);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,9 @@ class Manager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a database connection.
|
* Get a database connection.
|
||||||
*
|
*
|
||||||
* If no database name is specified, the default connection will be returned as
|
* If no database name is specified, the default connection will be returned.
|
||||||
* defined in the database configuration file.
|
|
||||||
*
|
*
|
||||||
* Note: Database connections are managed as singletons.
|
* Note: Database connections are managed as singletons.
|
||||||
*
|
*
|
||||||
@@ -46,7 +45,9 @@ class Manager {
|
|||||||
// This provides the developer the maximum amount of freedom in establishing their
|
// This provides the developer the maximum amount of freedom in establishing their
|
||||||
// database connections, and allows the framework to remain agonstic to ugly database
|
// database connections, and allows the framework to remain agonstic to ugly database
|
||||||
// specific PDO connection details. Less code. Less bugs.
|
// specific PDO connection details. Less code. Less bugs.
|
||||||
$this->connections[$connection] = new Connection(call_user_func($this->config['connectors'][$connection], $this->config));
|
$pdo = call_user_func($this->config['connectors'][$connection]);
|
||||||
|
|
||||||
|
$this->connections[$connection] = new Connection($pdo, $this->config));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->connections[$connection];
|
return $this->connections[$connection];
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class Form {
|
|||||||
$attributes['accept-charset'] = $this->html->encoding;
|
$attributes['accept-charset'] = $this->html->encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
$append = ($method == 'PUT' or $method == 'DELETE') ? $this->hidden('REQUEST_METHOD', $method) : '';
|
$append = ($method == 'PUT' or $method == 'DELETE') ? $this->hidden('_REQUEST_METHOD', $method) : '';
|
||||||
|
|
||||||
return '<form'.$this->html->attributes($attributes).'>'.$append.PHP_EOL;
|
return '<form'.$this->html->attributes($attributes).'>'.$append.PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,6 @@ class Request {
|
|||||||
*/
|
*/
|
||||||
public $server;
|
public $server;
|
||||||
|
|
||||||
/**
|
|
||||||
* The route handling the current request.
|
|
||||||
*
|
|
||||||
* @var Routing\Route
|
|
||||||
*/
|
|
||||||
public $route;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The $_POST array for the request.
|
* The $_POST array for the request.
|
||||||
*
|
*
|
||||||
@@ -40,6 +33,13 @@ class Request {
|
|||||||
*/
|
*/
|
||||||
protected $uri;
|
protected $uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The route handling the current request.
|
||||||
|
*
|
||||||
|
* @var Routing\Route
|
||||||
|
*/
|
||||||
|
public $route;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new request instance.
|
* Create a new request instance.
|
||||||
*
|
*
|
||||||
@@ -84,7 +84,10 @@ class Request {
|
|||||||
throw new \Exception('Unable to determine the request URI.');
|
throw new \Exception('Unable to determine the request URI.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($uri === false) throw new \Exception('Malformed request URI. Request terminated.');
|
if ($uri === false)
|
||||||
|
{
|
||||||
|
throw new \Exception('Malformed request URI. Request terminated.');
|
||||||
|
}
|
||||||
|
|
||||||
foreach (array(parse_url($this->url, PHP_URL_PATH), '/index.php') as $value)
|
foreach (array(parse_url($this->url, PHP_URL_PATH), '/index.php') as $value)
|
||||||
{
|
{
|
||||||
@@ -117,7 +120,7 @@ class Request {
|
|||||||
*/
|
*/
|
||||||
public function method()
|
public function method()
|
||||||
{
|
{
|
||||||
return ($this->spoofed()) ? $this->post['REQUEST_METHOD'] : $this->server['REQUEST_METHOD'];
|
return ($this->spoofed()) ? $this->post['_REQUEST_METHOD'] : $this->server['REQUEST_METHOD'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -263,8 +263,9 @@ class Response {
|
|||||||
/**
|
/**
|
||||||
* Send the response to the browser.
|
* Send the response to the browser.
|
||||||
*
|
*
|
||||||
* All of the response header will be sent to the browser first, followed by the content
|
* All of the response header will be sent to the browser first, followed by
|
||||||
* of the response instance, which will be evaluated and rendered by the render method.
|
* the content of the response instance, which will be evaluated and rendered
|
||||||
|
* by the render method.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -49,3 +49,5 @@ $public = __DIR__;
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
require $laravel.'/laravel.php';
|
require $laravel.'/laravel.php';
|
||||||
|
|
||||||
|
echo elapsed();
|
||||||
Reference in New Issue
Block a user