Enabled LDAP ORM operation
This commit is contained in:
parent
2997f6ada0
commit
83fcf55501
4
classes/Database/LDAP/Search/Result.php
Normal file
4
classes/Database/LDAP/Search/Result.php
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||||
|
|
||||||
|
class Database_LDAP_Search_Result extends Kohana_Database_LDAP_Search_Result {}
|
||||||
|
?>
|
@ -9,7 +9,7 @@
|
|||||||
* @copyright (c) 2013 phpLDAPadmin Development Team
|
* @copyright (c) 2013 phpLDAPadmin Development Team
|
||||||
* @license http://dev.phpldapadmin.org/license.html
|
* @license http://dev.phpldapadmin.org/license.html
|
||||||
*/
|
*/
|
||||||
class Kohana_Auth_LDAP extends Auth {
|
abstract class Kohana_Auth_LDAP extends Auth {
|
||||||
// Unnused required abstract functions
|
// Unnused required abstract functions
|
||||||
public function password($username) {}
|
public function password($username) {}
|
||||||
public function check_password($password) {}
|
public function check_password($password) {}
|
||||||
|
@ -21,10 +21,6 @@ abstract class Kohana_Database_LDAP extends Kohana_LDAP {
|
|||||||
* @defunct This required abstruct function is defunct for LDAP
|
* @defunct This required abstruct function is defunct for LDAP
|
||||||
*/
|
*/
|
||||||
public function commit() { throw HTTP_Exception::factory(501,'We shouldnt be here: :method',array(':method'=>__METHOD__)); }
|
public function commit() { throw HTTP_Exception::factory(501,'We shouldnt be here: :method',array(':method'=>__METHOD__)); }
|
||||||
/**
|
|
||||||
* @defunct This required abstruct function is defunct for LDAP
|
|
||||||
*/
|
|
||||||
public function list_columns($table, $like = NULL, $add_prefix = TRUE) { throw HTTP_Exception::factory(501,'We shouldnt be here: :method',array(':method'=>__METHOD__)); }
|
|
||||||
/**
|
/**
|
||||||
* @defunct This required abstruct function is defunct for LDAP
|
* @defunct This required abstruct function is defunct for LDAP
|
||||||
*/
|
*/
|
||||||
@ -45,6 +41,13 @@ abstract class Kohana_Database_LDAP extends Kohana_LDAP {
|
|||||||
/** REQUIRED ABSTRACT FUNCTIONS **/
|
/** REQUIRED ABSTRACT FUNCTIONS **/
|
||||||
public function escape($value) { return $value;}
|
public function escape($value) { return $value;}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override We provide the columns that are in all LDAP objects
|
||||||
|
*/
|
||||||
|
public function list_columns($table,$like=NULL,$add_prefix=TRUE) {
|
||||||
|
return array('dn'=>array(),'objectclass'=>array());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override We override this parent function, since LDAP doesnt quote columns
|
* @override We override this parent function, since LDAP doesnt quote columns
|
||||||
*/
|
*/
|
||||||
@ -166,7 +169,7 @@ abstract class Kohana_Database_LDAP extends Kohana_LDAP {
|
|||||||
$u = $x->search(NULL)
|
$u = $x->search(NULL)
|
||||||
->scope('sub')
|
->scope('sub')
|
||||||
->where($this->_config['login_attr'],'=',$user)
|
->where($this->_config['login_attr'],'=',$user)
|
||||||
->run();
|
->execute();
|
||||||
|
|
||||||
if (! $u)
|
if (! $u)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@ -178,10 +181,9 @@ abstract class Kohana_Database_LDAP extends Kohana_LDAP {
|
|||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($u as $base => $entries)
|
foreach ($u as $dn => $leaf)
|
||||||
foreach ($entries as $dn => $details)
|
if ($this->_bind($dn,$pass))
|
||||||
if ($this->_bind($dn,$pass))
|
return $this;
|
||||||
return $this;
|
|
||||||
|
|
||||||
// We didnt find an AUTH DN to bind with
|
// We didnt find an AUTH DN to bind with
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?php defined('SYSPATH') OR die('No direct script access.');
|
<?php defined('SYSPATH') OR die('No direct script access.');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LDAP database result. See [Results](/database/results) for usage and examples.
|
* LDAP database result. See [Results](/database/results) for usage and examples.
|
||||||
*
|
*
|
||||||
@ -8,9 +9,137 @@
|
|||||||
* @copyright (c) 2013 phpLDAPadmin Development Team
|
* @copyright (c) 2013 phpLDAPadmin Development Team
|
||||||
* @license http://dev.phpldapadmin.org/license.html
|
* @license http://dev.phpldapadmin.org/license.html
|
||||||
*/
|
*/
|
||||||
class Kohana_Database_LDAP_Result extends Database_Result {
|
abstract class Kohana_Database_LDAP_Result extends Database_Result {
|
||||||
private $_current_entry = NULL;
|
private $_current_entry = NULL;
|
||||||
|
|
||||||
|
/** SeekableIterator **/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements [Iterator::current], returns the current LDAP Leaf Entry.
|
||||||
|
*
|
||||||
|
* @return mixed LDAP leaf array of values
|
||||||
|
*/
|
||||||
|
public function current() {
|
||||||
|
if (! $this->_current_entry)
|
||||||
|
return array();
|
||||||
|
|
||||||
|
if ($this->_as_object === TRUE) {
|
||||||
|
// Return an stdClass
|
||||||
|
throw HTTP_Exception::factory(501,'Not implemented');
|
||||||
|
|
||||||
|
} elseif (is_string($this->_as_object)) {
|
||||||
|
// Return an object of given class name
|
||||||
|
throw HTTP_Exception::factory(501,'Not implemented');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Return an array of the row
|
||||||
|
// @todo We could probably cache this for optimisation
|
||||||
|
$attrs = $vals = array();
|
||||||
|
array_push($attrs,ldap_first_attribute($this->_result['l'],$this->_current_entry));
|
||||||
|
|
||||||
|
while ($x = ldap_next_attribute($this->_result['l'],$this->_current_entry))
|
||||||
|
array_push($attrs,$x);
|
||||||
|
|
||||||
|
foreach ($attrs as $attr) {
|
||||||
|
$vals[strtolower($attr)] = ldap_get_values($this->_result['l'],$this->_current_entry,$attr);
|
||||||
|
|
||||||
|
// We dont need the count entry
|
||||||
|
unset($vals[strtolower($attr)]['count']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$vals['dn'] = $this->key();
|
||||||
|
return $vals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements [Iterator::key], returns the dn.
|
||||||
|
*
|
||||||
|
* echo key($result);
|
||||||
|
*
|
||||||
|
* @return string LDAP Distringuised Name
|
||||||
|
*/
|
||||||
|
public function key() {
|
||||||
|
return ldap_get_dn($this->_result['l'],$this->_current_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements [Iterator::next], moves to the next row.
|
||||||
|
*
|
||||||
|
* next($result);
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function next() {
|
||||||
|
$this->_current_entry = ldap_next_entry($this->_result['l'],$this->_current_entry);
|
||||||
|
++$this->_current_row;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override We cant go backwards!
|
||||||
|
*/
|
||||||
|
public function prev() {
|
||||||
|
throw new Kohana_Exception('Cant go backwards');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements [Iterator::rewind], moves to the first entry.
|
||||||
|
*
|
||||||
|
* reset($result);
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function rewind() {
|
||||||
|
if ($this->_total_rows)
|
||||||
|
$this->_current_entry = ldap_first_entry($this->_result['l'],$this->_result['r']);
|
||||||
|
|
||||||
|
$this->_current_row = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements [SeekableIterator::seek], to seek to an entry.
|
||||||
|
*
|
||||||
|
* [!!] This method is only used internally.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function seek($dn) {
|
||||||
|
$this->_current_entry = ldap_first_entry($this->_result['l'],$this->_result['r']);
|
||||||
|
|
||||||
|
do {
|
||||||
|
if ($dn == $this->key())
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
// Increment internal row for optimization assuming rows are fetched in order
|
||||||
|
$this->_current_entry = ldap_next_entry($this->_result['l'],$this->_current_entry);
|
||||||
|
|
||||||
|
} while ($this->_current_entry);
|
||||||
|
|
||||||
|
// We didnt find it
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements [Iterator::valid], checks if the current row exists.
|
||||||
|
*
|
||||||
|
* [!!] This method is only used internally.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function valid() {
|
||||||
|
return (boolean)$this->_current_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ArrayAccess **/
|
||||||
|
|
||||||
|
public function offsetExists($offset) {
|
||||||
|
return $this->valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Custom Methods **/
|
||||||
|
|
||||||
public function __construct($result, $sql, $as_object = FALSE, array $params = NULL) {
|
public function __construct($result, $sql, $as_object = FALSE, array $params = NULL) {
|
||||||
parent::__construct($result, $sql, $as_object, $params);
|
parent::__construct($result, $sql, $as_object, $params);
|
||||||
|
|
||||||
@ -53,104 +182,5 @@ class Kohana_Database_LDAP_Result extends Database_Result {
|
|||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function current() {
|
|
||||||
if (! $this->_current_entry)
|
|
||||||
return array();
|
|
||||||
|
|
||||||
if ($this->_as_object === TRUE) {
|
|
||||||
// Return an stdClass
|
|
||||||
throw HTTP_Exception::factory(501,'Not implemented');
|
|
||||||
|
|
||||||
} elseif (is_string($this->_as_object)) {
|
|
||||||
// Return an object of given class name
|
|
||||||
throw HTTP_Exception::factory(501,'Not implemented');
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Return an array of the row
|
|
||||||
// @todo We could probably cache this for optimisation
|
|
||||||
$attrs = $vals = array();
|
|
||||||
array_push($attrs,ldap_first_attribute($this->_result['l'],$this->_current_entry));
|
|
||||||
|
|
||||||
while ($x = ldap_next_attribute($this->_result['l'],$this->_current_entry))
|
|
||||||
array_push($attrs,$x);
|
|
||||||
|
|
||||||
foreach ($attrs as $attr) {
|
|
||||||
$vals[strtolower($attr)] = ldap_get_values($this->_result['l'],$this->_current_entry,$attr);
|
|
||||||
|
|
||||||
// We dont need the count entry
|
|
||||||
unset($vals[strtolower($attr)]['count']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $vals;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements [Iterator::key], returns the dn.
|
|
||||||
*
|
|
||||||
* echo key($result);
|
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
|
||||||
public function key() {
|
|
||||||
return ldap_get_dn($this->_result['l'],$this->_current_entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements [Iterator::next], moves to the next row.
|
|
||||||
*
|
|
||||||
* next($result);
|
|
||||||
*
|
|
||||||
* @return $this
|
|
||||||
*/
|
|
||||||
public function next() {
|
|
||||||
$this->_current_entry = ldap_next_entry($this->_result['l'],$this->_current_entry);
|
|
||||||
++$this->_current_row;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function offsetExists($offset) {
|
|
||||||
return $this->valid();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function prev() {
|
|
||||||
throw new Kohana_Exception('Cant go backwards');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function seek($dn) {
|
|
||||||
$this->_current_entry = ldap_first_entry($this->_result['l'],$this->_result['r']);
|
|
||||||
|
|
||||||
do {
|
|
||||||
if ($dn == $this->key())
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
// Increment internal row for optimization assuming rows are fetched in order
|
|
||||||
$this->_current_entry = ldap_next_entry($this->_result['l'],$this->_current_entry);
|
|
||||||
|
|
||||||
} while ($this->_current_entry);
|
|
||||||
|
|
||||||
// We didnt find it
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function rewind() {
|
|
||||||
if ($this->_total_rows)
|
|
||||||
$this->_current_entry = ldap_first_entry($this->_result['l'],$this->_result['r']);
|
|
||||||
|
|
||||||
$this->_current_row = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements [Iterator::valid], checks if the current row exists.
|
|
||||||
*
|
|
||||||
* [!!] This method is only used internally.
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public function valid() {
|
|
||||||
return (boolean)$this->_current_entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // End Database_LDAP_Result
|
} // End Database_LDAP_Result
|
||||||
|
?>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<?php defined('SYSPATH') OR die('No direct script access.');
|
<?php defined('SYSPATH') OR die('No direct script access.');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object used for caching the results of select queries. See [Results](/database/results#select-cached) for usage and examples.
|
* Object used for caching the results of select queries. See [Results](/database/results#select-cached) for usage and examples.
|
||||||
*
|
*
|
||||||
@ -8,7 +9,7 @@
|
|||||||
* @copyright (c) 2013 phpLDAPadmin Development Team
|
* @copyright (c) 2013 phpLDAPadmin Development Team
|
||||||
* @license http://dev.phpldapadmin.org/license.html
|
* @license http://dev.phpldapadmin.org/license.html
|
||||||
*/
|
*/
|
||||||
class Kohana_Database_LDAP_Result_Cached extends Database_Result_Cached {
|
abstract class Kohana_Database_LDAP_Result_Cached extends Database_Result_Cached {
|
||||||
|
|
||||||
public function __construct(array $result, $sql, $as_object = NULL)
|
public function __construct(array $result, $sql, $as_object = NULL)
|
||||||
{
|
{
|
||||||
@ -65,5 +66,5 @@ class Kohana_Database_LDAP_Result_Cached extends Database_Result_Cached {
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // End Database_LDAP_Result_Cached
|
} // End Database_LDAP_Result_Cached
|
||||||
|
?>
|
||||||
|
@ -27,6 +27,9 @@ abstract class Kohana_Database_LDAP_Search {
|
|||||||
// Cache lifetime
|
// Cache lifetime
|
||||||
protected $_lifetime = NULL;
|
protected $_lifetime = NULL;
|
||||||
|
|
||||||
|
// Parameters for __construct when using object results
|
||||||
|
protected $_object_params = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callable database methods
|
* Callable database methods
|
||||||
* @var array
|
* @var array
|
||||||
@ -34,6 +37,14 @@ abstract class Kohana_Database_LDAP_Search {
|
|||||||
protected static $_db_methods = array(
|
protected static $_db_methods = array(
|
||||||
'where', 'and_where', 'or_where', 'where_open', 'and_where_open', 'or_where_open', 'where_close',
|
'where', 'and_where', 'or_where', 'where_open', 'and_where_open', 'or_where_open', 'where_close',
|
||||||
'and_where_close', 'or_where_close',
|
'and_where_close', 'or_where_close',
|
||||||
|
'limit',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ORM methods that are called, but dont do anything in LDAP
|
||||||
|
*/
|
||||||
|
protected static $_orm_ignore_methods = array(
|
||||||
|
'from',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,18 +85,19 @@ abstract class Kohana_Database_LDAP_Search {
|
|||||||
|
|
||||||
// Return the property
|
// Return the property
|
||||||
return $this->{'_'.$method};
|
return $this->{'_'.$method};
|
||||||
}
|
|
||||||
elseif (in_array($method,Database_LDAP_Search::$_db_methods))
|
} elseif (in_array($method,Database_LDAP_Search::$_db_methods)) {
|
||||||
{
|
|
||||||
// Add pending database call which is executed after query type is determined
|
// Add pending database call which is executed after query type is determined
|
||||||
$this->_db_pending[] = array('name' => $method,'args' => $args);
|
$this->_db_pending[] = array('name' => $method,'args' => $args);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
|
||||||
else
|
} elseif (in_array($method,Database_LDAP_Search::$_orm_ignore_methods)) {
|
||||||
{
|
return $this;
|
||||||
throw new Kohana_Exception('Invalid method :method called in :class',
|
|
||||||
array(':method' => $method,':class' => get_class($this)));
|
} else {
|
||||||
|
throw new Kohana_Exception('Invalid method :method called in :class (:args)',
|
||||||
|
array(':method' => $method,':class' => get_class($this),':args'=>print_r($args,TRUE)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,16 +111,37 @@ abstract class Kohana_Database_LDAP_Search {
|
|||||||
|
|
||||||
$this->_db_applied[$name] = $name;
|
$this->_db_applied[$name] = $name;
|
||||||
|
|
||||||
call_user_func_array(array($search,$name),$args);
|
switch ($name) {
|
||||||
|
case 'limit':
|
||||||
|
$this->_size_limit = $args[0];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
call_user_func_array(array($search,$name),$args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $search;
|
return $search;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function as_assoc() {
|
||||||
|
$this->_as_object = FALSE;
|
||||||
|
|
||||||
|
$this->_object_params = array();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Figure out the bases
|
* Figure out the bases
|
||||||
*/
|
*/
|
||||||
public function base() {
|
public function base(array $base=NULL) {
|
||||||
|
if (! is_null($base))
|
||||||
|
$this->_base = $base;
|
||||||
|
|
||||||
|
if ($this->_base)
|
||||||
|
return $this->_base;
|
||||||
|
|
||||||
// If the base is set in the configuration file, then just return that.
|
// If the base is set in the configuration file, then just return that.
|
||||||
if (! is_null($x=Kohana::$config->load('database.default.connection.database')))
|
if (! is_null($x=Kohana::$config->load('database.default.connection.database')))
|
||||||
return $x;
|
return $x;
|
||||||
@ -117,25 +150,22 @@ abstract class Kohana_Database_LDAP_Search {
|
|||||||
|
|
||||||
$u = $x->search(array(''))
|
$u = $x->search(array(''))
|
||||||
->scope('base')
|
->scope('base')
|
||||||
->run();
|
->execute();
|
||||||
|
|
||||||
// Remove the '' base
|
|
||||||
$u = array_pop($u);
|
|
||||||
|
|
||||||
// Quick validation
|
// Quick validation
|
||||||
if ($u->count() > 1)
|
if ($u['']->count() > 1)
|
||||||
throw HTTP_Exception::factory(501,'We got more than 1 null DN?');
|
throw HTTP_Exception::factory(501,'We got more than 1 null DN?');
|
||||||
|
|
||||||
return isset($u['']['namingcontexts']) ? $u['']['namingcontexts'] : array();
|
return isset($u['']['']['namingcontexts']) ? $u['']['']['namingcontexts'] : array();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables the query to be cached for a specified amount of time.
|
* Enables the query to be cached for a specified amount of time.
|
||||||
*
|
*
|
||||||
* @param integer $lifetime number of seconds to cache, 0 deletes it from the cache
|
* @param integer $lifetime number of seconds to cache, 0 deletes it from the cache
|
||||||
* @param boolean whether or not to execute the query during a cache hit
|
* @param boolean whether or not to execute the query during a cache hit
|
||||||
* @return $this
|
* @return $this
|
||||||
* @uses Kohana::$cache_life
|
* @uses Kohana::$cache_life
|
||||||
*/
|
*/
|
||||||
public function cached($lifetime=NULL,$force=FALSE) {
|
public function cached($lifetime=NULL,$force=FALSE) {
|
||||||
if ($lifetime === NULL) {
|
if ($lifetime === NULL) {
|
||||||
@ -158,9 +188,22 @@ abstract class Kohana_Database_LDAP_Search {
|
|||||||
/**
|
/**
|
||||||
* Search the LDAP database
|
* Search the LDAP database
|
||||||
*/
|
*/
|
||||||
public function run($as_object=FALSE,$object_params=NULL) {
|
public function execute($db=NULL,$as_object=FALSE,$object_params=NULL) {
|
||||||
$query = array();
|
$query = array();
|
||||||
|
|
||||||
|
if (! is_object($this->_db))
|
||||||
|
throw new Kohana_Exception('db must be an object');
|
||||||
|
|
||||||
|
// We'll override the DB if we have been given one
|
||||||
|
if (is_object($db))
|
||||||
|
$this->_db = $db;
|
||||||
|
|
||||||
|
if ($as_object === NULL)
|
||||||
|
$as_object = $this->_as_object;
|
||||||
|
|
||||||
|
if ($object_params === NULL)
|
||||||
|
$object_params = $this->_object_params;
|
||||||
|
|
||||||
// Query Defaults
|
// Query Defaults
|
||||||
$attrs_only = 0;
|
$attrs_only = 0;
|
||||||
|
|
||||||
@ -168,10 +211,12 @@ abstract class Kohana_Database_LDAP_Search {
|
|||||||
if ($this->_db_pending)
|
if ($this->_db_pending)
|
||||||
$this->_filter = $this->_build();
|
$this->_filter = $this->_build();
|
||||||
|
|
||||||
$result = array();
|
// Validation that we are connected, no point contining if we are not.
|
||||||
foreach ($this->_base as $base) {
|
if (! $this->_db->connection())
|
||||||
$result[$base] = NULL;
|
throw HTTP_Exception::factory(501,'Cant run a search without a connection (:type,:filter)',array(':type'=>$this->_db,':filter'=>$this->_filter));
|
||||||
|
|
||||||
|
$result = new Database_LDAP_Search_Result;
|
||||||
|
foreach ($this->_base as $base) {
|
||||||
if ($this->_lifetime !== NULL AND $this->_db->caching()) {
|
if ($this->_lifetime !== NULL AND $this->_db->caching()) {
|
||||||
// Set the cache key based on the database instance name and SQL
|
// Set the cache key based on the database instance name and SQL
|
||||||
$cache_key = 'Database::query("'.$this->_db.'","'.$base.'","'.$this->_scope.'","'.$this->_filter.'")';
|
$cache_key = 'Database::query("'.$this->_db.'","'.$base.'","'.$this->_scope.'","'.$this->_filter.'")';
|
||||||
@ -184,20 +229,26 @@ abstract class Kohana_Database_LDAP_Search {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search is not cached, OR caching is disabled, so we'll query
|
// Search is not cached, OR caching is disabled, so we'll query
|
||||||
if (! $result[$base]) {
|
if (! isset($result[$base])) {
|
||||||
switch ($this->_scope) {
|
try {
|
||||||
case 'base':
|
switch ($this->_scope) {
|
||||||
$search = ldap_read($this->_db->connection(),$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref);
|
case 'base':
|
||||||
break;
|
$search = ldap_read($this->_db->connection(),$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'one':
|
case 'one':
|
||||||
$search = ldap_list($this->_db->connection(),$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref);
|
$search = ldap_list($this->_db->connection(),$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'sub':
|
case 'sub':
|
||||||
default:
|
default:
|
||||||
$search = ldap_search($this->_db->connection(),$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref);
|
$search = ldap_search($this->_db->connection(),$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw HTTP_Exception::factory(501,'Error running a query (SCOPE::scope,TYPE::type,FILTER::filter,ATTRS::attrs) [:error]',
|
||||||
|
array(':scope'=>$this->_scope,':type'=>$this->_db,':filter'=>$this->_filter,':attrs'=>join('|',$this->_attrs),':error'=>$e->getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
$result[$base] = new Database_LDAP_Result(array('l'=>$this->_db->connection(),'r'=>$search),array('b'=>$base,'s'=>$this->_scope,'f'=>$this->_filter),$as_object,$object_params);
|
$result[$base] = new Database_LDAP_Result(array('l'=>$this->_db->connection(),'r'=>$search),array('b'=>$base,'s'=>$this->_scope,'f'=>$this->_filter),$as_object,$object_params);
|
||||||
|
@ -10,8 +10,12 @@
|
|||||||
* @license http://dev.phpldapadmin.org/license.html
|
* @license http://dev.phpldapadmin.org/license.html
|
||||||
*/
|
*/
|
||||||
abstract class Kohana_Database_LDAP_Search_Builder_Query extends Database_Query_Builder {
|
abstract class Kohana_Database_LDAP_Search_Builder_Query extends Database_Query_Builder {
|
||||||
|
// WHERE ...
|
||||||
protected $_where = array();
|
protected $_where = array();
|
||||||
|
|
||||||
|
// LIMIT ...
|
||||||
|
protected $_limit = NULL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @defunct Not implemented
|
* @defunct Not implemented
|
||||||
*/
|
*/
|
||||||
@ -207,9 +211,23 @@ abstract class Kohana_Database_LDAP_Search_Builder_Query extends Database_Query_
|
|||||||
case 'OR':
|
case 'OR':
|
||||||
return '(|'.$filter.')';
|
return '(|'.$filter.')';
|
||||||
|
|
||||||
|
case '':
|
||||||
|
return '(&(objectClass=*))';
|
||||||
default:
|
default:
|
||||||
throw new Kohana_Exception('Condition :condition not handled.',array(':condition'=>$condition));
|
throw new Kohana_Exception('Condition ":condition" not handled.',array(':condition'=>$current_condition));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return up to "LIMIT ..." results
|
||||||
|
*
|
||||||
|
* @param integer $number maximum results to return or NULL to reset
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function limit($number) {
|
||||||
|
$this->_limit = $number;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
90
classes/Kohana/Database/LDAP/Search/Result.php
Normal file
90
classes/Kohana/Database/LDAP/Search/Result.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php defined('SYSPATH') OR die('No direct script access.');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the query search result.
|
||||||
|
*
|
||||||
|
* @package Kohana/LDAP
|
||||||
|
* @category Query
|
||||||
|
* @author Deon George
|
||||||
|
* @copyright (c) 2013 phpLDAPadmin Development Team
|
||||||
|
* @license http://dev.phpldapadmin.org/license.html
|
||||||
|
*/
|
||||||
|
abstract class Kohana_Database_LDAP_Search_Result implements ArrayAccess,Iterator,Countable {
|
||||||
|
private $_count = 0;
|
||||||
|
protected $result = array();
|
||||||
|
private $_rewound = FALSE;
|
||||||
|
private $_current_key = NULL;
|
||||||
|
|
||||||
|
/** Countable **/
|
||||||
|
|
||||||
|
public function count() {
|
||||||
|
$c = 0;
|
||||||
|
|
||||||
|
foreach ($this->result as $k=>$v)
|
||||||
|
$c += count($v);
|
||||||
|
|
||||||
|
return $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ArrayAccess **/
|
||||||
|
|
||||||
|
public function offsetExists($offset) {
|
||||||
|
return isset($this->result[$offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetGet($offset) {
|
||||||
|
return $this->result[$offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetSet($offset,$value) {
|
||||||
|
if (isset($this->result[$offset]))
|
||||||
|
$this->_count -= $this->result[$offset]->count();
|
||||||
|
|
||||||
|
$this->result[$offset] = $value;
|
||||||
|
$this->_count += $value->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetUnset($offset) {
|
||||||
|
unset($this->result[$offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Iterator **/
|
||||||
|
|
||||||
|
public function current() {
|
||||||
|
if (! $this->_rewound)
|
||||||
|
$this->rewind();
|
||||||
|
|
||||||
|
return current($this->result)->current();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function key() {
|
||||||
|
return current($this->result)->key();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function next() {
|
||||||
|
if (current($this->result)->valid() AND current($this->result)->next()->valid())
|
||||||
|
return current($this->result);
|
||||||
|
|
||||||
|
next($this->result);
|
||||||
|
|
||||||
|
while (current($this->result) AND ! current($this->result)->valid())
|
||||||
|
if (next($this->result) === FALSE)
|
||||||
|
break;
|
||||||
|
|
||||||
|
return current($this->result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rewind() {
|
||||||
|
reset($this->result);
|
||||||
|
|
||||||
|
if (! current($this->result)->valid())
|
||||||
|
$this->next(FALSE);
|
||||||
|
|
||||||
|
$this->_rewound = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function valid() {
|
||||||
|
return is_object(current($this->result)) ? current($this->result)->valid() : FALSE;
|
||||||
|
}
|
||||||
|
} // End Database_LDAP_Search_Result
|
||||||
|
?>
|
93
classes/Kohana/ORM/LDAP.php
Normal file
93
classes/Kohana/ORM/LDAP.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kohana ORM LDAP Extension
|
||||||
|
*
|
||||||
|
* @package LDAP/ORM
|
||||||
|
* @category Helpers
|
||||||
|
* @author Deon George
|
||||||
|
* @copyright (c) 2013 phpLDAPadmin Development Team
|
||||||
|
* @license http://dev.phpldapadmin.org/license.html
|
||||||
|
*/
|
||||||
|
abstract class Kohana_ORM_LDAP extends ORM {
|
||||||
|
protected $_disable_wild_select = TRUE;
|
||||||
|
protected $_disable_join_table_name = TRUE;
|
||||||
|
protected $_primary_key = 'dn';
|
||||||
|
|
||||||
|
public function __construct($id = NULL) {
|
||||||
|
// We'll process our $id
|
||||||
|
parent::__construct(NULL);
|
||||||
|
|
||||||
|
if (empty($this->_cast_data) AND $id) {
|
||||||
|
if (is_array($id)) {
|
||||||
|
// Passing an array of column => values
|
||||||
|
foreach ($id as $column => $value)
|
||||||
|
$this->where($column, '=', $value);
|
||||||
|
|
||||||
|
$this->find();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Passing the primary key
|
||||||
|
$this->base($id)->find();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the Database Builder to given query type
|
||||||
|
*
|
||||||
|
* @param integer $type Type of Database query
|
||||||
|
* @return ORM
|
||||||
|
*/
|
||||||
|
protected function _build($type) {
|
||||||
|
// Construct new builder object based on query type
|
||||||
|
switch ($type) {
|
||||||
|
case Database::SELECT:
|
||||||
|
$this->_db_builder = new Database_LDAP_Search($this->_db,NULL);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw HTTP_Exception::factory(501,'Unknown type :type',array(':type'=>$type));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process pending database method calls
|
||||||
|
foreach ($this->_db_pending as $method) {
|
||||||
|
$name = $method['name'];
|
||||||
|
$args = $method['args'];
|
||||||
|
|
||||||
|
$this->_db_applied[$name] = $name;
|
||||||
|
|
||||||
|
call_user_func_array(array($this->_db_builder, $name), $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _initialize() {
|
||||||
|
parent::_initialize();
|
||||||
|
|
||||||
|
// Check if this model has already been initialized
|
||||||
|
if ($init = Arr::get(ORM::$_init_cache, $this->_object_name, FALSE)) {
|
||||||
|
|
||||||
|
// We need to make sure that our _db is an LDAP DB source.
|
||||||
|
if ( ! is_object($this->_db) OR ! $this->_db instanceof LDAP) {
|
||||||
|
// Get database instance
|
||||||
|
$init['_db'] = LDAP::factory('user',NULL,$this->_db_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
ORM::$_init_cache[$this->_object_name] = $init;
|
||||||
|
$this->_db = $init['_db'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function base($value) {
|
||||||
|
// Add pending database call which is executed after query type is determined
|
||||||
|
$this->_db_pending[] = array(
|
||||||
|
'name' => 'base',
|
||||||
|
'args' => array(array($value)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
14
classes/Model/LDAP.php
Normal file
14
classes/Model/LDAP.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides our ORM interaction with LDAP
|
||||||
|
*
|
||||||
|
* @package Kohana/LDAP
|
||||||
|
* @category Model/LDAP
|
||||||
|
* @author Deon George
|
||||||
|
* @copyright (c) 2013 phpLDAPadmin Development Team
|
||||||
|
* @license http://dev.phpldapadmin.org/license.html
|
||||||
|
*/
|
||||||
|
class Model_LDAP extends ORM_LDAP {
|
||||||
|
}
|
||||||
|
?>
|
4
classes/ORM/LDAP.php
Normal file
4
classes/ORM/LDAP.php
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||||
|
|
||||||
|
abstract class ORM_LDAP extends Kohana_ORM_LDAP {}
|
||||||
|
?>
|
@ -41,12 +41,12 @@ Class LDAPCaching extends Unittest_TestCase {
|
|||||||
$u = $x->search(array(''))
|
$u = $x->search(array(''))
|
||||||
->scope('base')
|
->scope('base')
|
||||||
->cached($cached,$force)
|
->cached($cached,$force)
|
||||||
->run();
|
->execute();
|
||||||
|
|
||||||
$u = $x->search(array(''))
|
$u = $x->search(array(''))
|
||||||
->scope('base')
|
->scope('base')
|
||||||
->cached($cached,$force)
|
->cached($cached,$force)
|
||||||
->run();
|
->execute();
|
||||||
|
|
||||||
if ($expect)
|
if ($expect)
|
||||||
$this->assertInstanceOf('Database_LDAP_Result',$u['']);
|
$this->assertInstanceOf('Database_LDAP_Result',$u['']);
|
||||||
|
141
tests/classes/LDAPResult.php
Normal file
141
tests/classes/LDAPResult.php
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will test our LDAPResult array is working as expected
|
||||||
|
*
|
||||||
|
* @package Kohana/LDAP
|
||||||
|
* @category Test
|
||||||
|
* @author Deon George
|
||||||
|
* @copyright (c) 2013 phpLDAPadmin Development Team
|
||||||
|
* @license http://dev.phpldapadmin.org/license.html
|
||||||
|
* @group ldap
|
||||||
|
* @group ldap.server
|
||||||
|
*/
|
||||||
|
Class LDAPResult extends Unittest_TestCase {
|
||||||
|
function testDatabase_LDAP_Result_One() {
|
||||||
|
$x = LDAP::factory('user');
|
||||||
|
$x->connect();
|
||||||
|
|
||||||
|
// We'll do a query first.
|
||||||
|
$u = $x->search(array(''))
|
||||||
|
->scope('base')
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->assertEquals(1,$u->count());
|
||||||
|
|
||||||
|
foreach ($u[''] as $k => $v) {
|
||||||
|
$this->assertEquals('',$k);
|
||||||
|
$this->assertTrue(is_array($v));
|
||||||
|
}
|
||||||
|
|
||||||
|
$x->disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testDatabase_LDAP_Result_Many() {
|
||||||
|
$x = LDAP::factory('user');
|
||||||
|
$x->connect();
|
||||||
|
$y = LDAP::factory('auth');
|
||||||
|
$y->connect();
|
||||||
|
|
||||||
|
// We'll do a query first.
|
||||||
|
$u = $x->search(NULL)
|
||||||
|
->scope('sub')
|
||||||
|
->where('uid','=','bart*')
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
foreach ($u['dc=example.com'] as $k => $v) {
|
||||||
|
$this->assertEquals('cn=Bart Simpson,dc=example.com',$k);
|
||||||
|
$this->assertTrue(is_array($v));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertEquals(2,count($u['o=Simpsons']));
|
||||||
|
$this->assertEquals(count($u['o=Simpsons']),$u['o=Simpsons']->count());
|
||||||
|
|
||||||
|
$c = 0;
|
||||||
|
foreach ($u['o=Simpsons'] as $k => $v) {
|
||||||
|
if (! $c++)
|
||||||
|
$this->assertEquals('cn=Bart Simpson,ou=People,o=Simpsons',$k);
|
||||||
|
else
|
||||||
|
$this->assertEquals('cn=Bart Simpson-ML,ou=People,o=Simpsons',$k);
|
||||||
|
|
||||||
|
$this->assertTrue(is_array($v));
|
||||||
|
}
|
||||||
|
|
||||||
|
$x->disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testDatabase_LDAP_Search_Result_One() {
|
||||||
|
$x = LDAP::factory('user');
|
||||||
|
$x->connect();
|
||||||
|
|
||||||
|
// We'll do a query first.
|
||||||
|
$u = $x->search(array(''))
|
||||||
|
->scope('base')
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->assertEquals(1,$u->count());
|
||||||
|
|
||||||
|
foreach ($u as $k => $v) {
|
||||||
|
$this->assertEquals('',$k);
|
||||||
|
$this->assertTrue(is_array($v));
|
||||||
|
}
|
||||||
|
|
||||||
|
$x->disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testDatabase_LDAP_Search_Result_Many() {
|
||||||
|
$x = LDAP::factory('user');
|
||||||
|
$x->connect();
|
||||||
|
$y = LDAP::factory('auth');
|
||||||
|
$y->connect();
|
||||||
|
|
||||||
|
// We'll do a query first.
|
||||||
|
$u = $x->search(NULL)
|
||||||
|
->scope('sub')
|
||||||
|
->where('uid','=','bart*')
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->assertEquals(3,count($u));
|
||||||
|
|
||||||
|
$c = 0;
|
||||||
|
foreach ($u as $k => $v) {
|
||||||
|
switch (++$c) {
|
||||||
|
case 1: $this->assertEquals('cn=Bart Simpson,dc=example.com',$k); break;
|
||||||
|
case 2: $this->assertEquals('cn=Bart Simpson,ou=People,o=Simpsons',$k); break;
|
||||||
|
case 3: $this->assertEquals('cn=Bart Simpson-ML,ou=People,o=Simpsons',$k); break;
|
||||||
|
default: throw new Kohana_Exception('Unknown value of c? (:c)',array(':c'=>serialize($c)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertTrue(is_array($v));
|
||||||
|
}
|
||||||
|
|
||||||
|
$x->disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testDatabase_LDAP_Search_Result_OneDeep() {
|
||||||
|
$x = LDAP::factory('user');
|
||||||
|
$x->connect();
|
||||||
|
$y = LDAP::factory('auth');
|
||||||
|
$y->connect();
|
||||||
|
|
||||||
|
// We'll do a query first.
|
||||||
|
$u = $x->search(NULL)
|
||||||
|
->scope('sub')
|
||||||
|
->where('uid','=','bart-ml')
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->assertEquals(1,count($u));
|
||||||
|
|
||||||
|
$c = 0;
|
||||||
|
foreach ($u as $k => $v) {
|
||||||
|
switch (++$c) {
|
||||||
|
case 1: $this->assertEquals('cn=Bart Simpson-ML,ou=People,o=Simpsons',$k); break;
|
||||||
|
default: throw new Kohana_Exception('Unknown value of c? (:c)',array(':c'=>serialize($c)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertTrue(is_array($v));
|
||||||
|
}
|
||||||
|
|
||||||
|
$x->disconnect();
|
||||||
|
}
|
||||||
|
}
|
55
tests/classes/LDAPorm.php
Normal file
55
tests/classes/LDAPorm.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should test all our ORM methods
|
||||||
|
*
|
||||||
|
* @package Kohana/LDAP
|
||||||
|
* @category Test
|
||||||
|
* @author Deon George
|
||||||
|
* @copyright (c) 2013 phpLDAPadmin Development Team
|
||||||
|
* @license http://dev.phpldapadmin.org/license.html
|
||||||
|
* @group ldap
|
||||||
|
* @group ldap.server
|
||||||
|
*/
|
||||||
|
Class LDAPorm extends Unittest_TestCase {
|
||||||
|
function hosts() {
|
||||||
|
return array(
|
||||||
|
array('localhost','389','a',TRUE),
|
||||||
|
array('localhost','389','b',TRUE),
|
||||||
|
array('localhost','390','a',FALSE),
|
||||||
|
array('localhost','390','b',FALSE),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that we can connect to an LDAP server
|
||||||
|
*/
|
||||||
|
function testorm() {
|
||||||
|
$x = ORM::factory('LDAP',NULL);
|
||||||
|
|
||||||
|
$this->assertInstanceOf('ORM',$x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testormloaddn() {
|
||||||
|
// @todo It would be nice if we auto connect as required.
|
||||||
|
$y = LDAP::factory('auth');
|
||||||
|
$y->connect();
|
||||||
|
$y = LDAP::factory('user');
|
||||||
|
$y->connect();
|
||||||
|
|
||||||
|
$x = ORM::factory('LDAP','cn=Bart Simpson,ou=People,o=Simpsons');
|
||||||
|
|
||||||
|
$this->assertInstanceOf('ORM',$x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testormdnval() {
|
||||||
|
// @todo It would be nice if we auto connect as required.
|
||||||
|
$y = LDAP::factory('auth');
|
||||||
|
$y->connect();
|
||||||
|
$y = LDAP::factory('user');
|
||||||
|
$y->connect();
|
||||||
|
|
||||||
|
$x = ORM::factory('LDAP','cn=Bart Simpson,ou=People,o=Simpsons');
|
||||||
|
$this->assertEquals($x->cn['0'],'Bart Simpson');
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user