sprintf(_("The object with the uid \"%s\" does already exist!"),
$uid));
}
+ unset($info['type']);
$object->save($info);
return $object;
}
*/
class Horde_Kolab_Server_Ldap extends Horde_Kolab_Server
{
+ /** Maximum accepted level for the object class hierarchy */
+ const MAX_HIERARCHY = 100;
/**
* LDAP connection handle.
private $_schema;
/**
+ * A cache for object attribute definitions.
+ *
+ * @var array
+ */
+ static protected $attributes;
+
+ /**
* Construct a new Horde_Kolab_Server_ldap object.
*
* @param array $params Parameter array.
}
/**
+ * Save an object.
+ *
+ * @param string $dn The DN of the object to be added.
+ * @param array $data The attributes of the object to be added.
+ * @param boolean $exists Does the object already exist on the server?
+ *
+ * @return boolean True if saving succeeded.
+ */
+ public function save($dn, $data, $exists = false)
+ {
+ if ($exists === false) {
+ $entry = Net_LDAP2_Entry::createFresh($dn, $data);
+ $result = $this->_ldap->add($entry);
+ if ($result instanceOf PEAR_Error) {
+ throw new Horde_Kolab_Server_Exception($result->getMessage());
+ }
+ } else {
+ $deletes = array();
+ foreach ($data as $key => $values) {
+ $empty = true;
+ foreach ($values as $value) {
+ if (!empty($value)) {
+ $empty = false;
+ break;
+ }
+ }
+ if ($empty === true) {
+ $deletes[] = $key;
+ unset($data[$key]);
+ }
+ }
+ $result = $this->_ldap->modify($dn, array('delete' => $deletes,
+ 'replace' => $data));
+ if ($result instanceOf PEAR_Error) {
+ throw new Horde_Kolab_Server_Exception($result->getMessage());
+ }
+ }
+ return true;
+ }
+
+ /**
* List all objects of a specific type.
*
* @param string $type The type of the objects to be listed
}
/**
- * Return the attributes supported by the given object classes.
+ * Return the attributes supported by the given object class.
*
- * @param array $object_classes The object classes to search for attributes.
+ * @param string $class Determine the attributes for this class.
*
* @return array The supported attributes.
*
* @throws Horde_Kolab_Server_Exception If the schema analysis fails.
*/
- public function getSupportedAttributes($object_classes)
+ public function &getAttributes($class)
{
- if (!isset($this->_schema)) {
- $result = $this->_ldap->schema();
- if ($result instanceOf PEAR_Error) {
- throw new Horde_Kolab_Server_Exception($result->getMessage());
+ static $cache = null;
+ static $lifetime;
+
+ if (!isset($this->attributes)) {
+ if (!empty($GLOBALS['conf']['kolab']['server']['cache']['driver'])) {
+ $params = isset($GLOBALS['conf']['kolab']['server']['cache']['params'])
+ ? $GLOBALS['conf']['kolab']['server']['cache']['params'] : null;
+ $params['sub'] = 'attributes';
+ $cache = Horde_Cache::singleton($GLOBALS['conf']['kolab']['server']['cache']['driver'],
+ $params);
+ register_shutdown_function(array($this, 'shutdown'));
+ $lifetime = isset($GLOBALS['conf']['kolab']['server']['cache']['lifetime'])
+ ? $GLOBALS['conf']['kolab']['server']['cache']['lifetime'] : 300;
}
- $this->_schema = &$result;
}
- $supported = array();
- $required = array();
- foreach ($object_classes as $object_class) {
- $info = $this->_schema->get('objectclass', $object_class);
- if ($info instanceOf PEAR_Error) {
- throw new Horde_Kolab_Server_Exception($info->getMessage());
- }
- if (isset($info['may'])) {
- $supported = array_merge($supported, $info['may']);
+
+ if (empty($this->attributes[$class])) {
+
+ if (!empty($cache)) {
+ $this->attributes[$class] = @unserialize($cache->get($class, $lifetime));
}
- if (isset($info['must'])) {
- $supported = array_merge($supported, $info['must']);
- $required = array_merge($required, $info['must']);
+
+ if (empty($this->attributes[$class])) {
+
+ $childclass = $class;
+ $classes = array();
+ $level = 0;
+ while ($childclass != 'Horde_Kolab_Server_Object'
+ && $level < self::MAX_HIERARCHY) {
+ $classes[] = $childclass;
+ $childclass = get_parent_class($childclass);
+ $level++;
+ }
+
+ /** Finally add the basic object class */
+ $classes[] = $childclass;
+
+ if ($level == self::MAX_HIERARCHY) {
+ Horde::logMessage(sprintf('The maximal level of the object hierarchy has been exceeded for class \"%s\"!',
+ $class),
+ __FILE__, __LINE__, PEAR_LOG_ERROR);
+ }
+
+ /**
+ * Collect attributes from top to bottom.
+ */
+ $classes = array_reverse($classes);
+
+ $derived = array();
+ $defaults = array();
+ $locked = array();
+ $object_classes = array();
+
+ foreach ($classes as $childclass) {
+ $vars = get_class_vars($childclass);
+ if (isset($vars['init_attributes'])) {
+ $derived = array_merge($derived,
+ $vars['init_attributes']['derived']);
+ $defaults = array_merge($defaults,
+ $vars['init_attributes']['defaults']);
+ $locked = array_merge($locked,
+ $vars['init_attributes']['locked']);
+ $object_classes = array_merge($object_classes,
+ $vars['init_attributes']['object_classes']);
+ }
+ }
+
+ if ($this->schema_support === true) {
+ if (!isset($this->_schema)) {
+ $result = $this->_ldap->schema();
+ if ($result instanceOf PEAR_Error) {
+ throw new Horde_Kolab_Server_Exception($result->getMessage());
+ }
+ $this->_schema = &$result;
+ }
+ $supported = array();
+ $required = array();
+ foreach ($object_classes as $object_class) {
+ $info = $this->_schema->get('objectclass', $object_class);
+ if ($info instanceOf PEAR_Error) {
+ throw new Horde_Kolab_Server_Exception($info->getMessage());
+ }
+ if (isset($info['may'])) {
+ $supported = array_merge($supported, $info['may']);
+ }
+ if (isset($info['must'])) {
+ $supported = array_merge($supported, $info['must']);
+ $required = array_merge($required, $info['must']);
+ }
+ }
+ if (empty($supported) && empty($required)) {
+ return false;
+ }
+ foreach ($supported as $attribute) {
+ $info = $this->_schema->get('attribute', $attribute);
+ if ($info instanceOf PEAR_Error) {
+ throw new Horde_Kolab_Server_Exception($info->getMessage());
+ }
+ $attrs[$attribute] = $info;
+ }
+ foreach ($required as $attribute) {
+ $attrs[$attribute]['required'] = true;
+ }
+ foreach ($locked as $attribute) {
+ $attrs[$attribute]['locked'] = true;
+ }
+ foreach ($defaults as $attribute) {
+ $attrs[$attribute]['default'] = true;
+ }
+ $attrs[Horde_Kolab_Server_Object::ATTRIBUTE_OC]['default'] = $object_classes;
+
+ $attrs = array_merge($attrs, $derived);
+
+ $this->attributes[$class] = array($attrs,
+ array(
+ 'derived' => $derived,
+ 'locked' => $locked,
+ 'required' => $required,
+ ));
+ } else {
+ $this->attributes[$class] = array(array(),
+ array(
+ 'derived' => array(),
+ 'locked' => array(),
+ 'required' => array(),
+ ));
+ }
}
}
- if (empty($supported) && empty($required)) {
- return false;
+ return $this->attributes[$class];
+ }
+
+ /**
+ * Stores the attribute definitions in the cache.
+ */
+ function shutdown()
+ {
+ if (isset($this->attributes)) {
+ if (!empty($GLOBALS['conf']['kolab']['server']['cache']['driver'])) {
+ $params = isset($GLOBALS['conf']['kolab']['server']['cache']['params'])
+ ? $GLOBALS['conf']['kolab']['server']['cache']['params'] : null;
+ $params['sub'] = 'attributes';
+ $cache = Horde_Cache::singleton($GLOBALS['conf']['kolab']['server']['cache']['driver'],
+ $params);
+ foreach ($this->attributes as $key => $value) {
+ $cache->set($key, @serialize($value));
+ }
+ }
}
- return array('required' => $required, 'supported' => $supported);
}
+
/**
* Search for object data.
*
*/
private $_cache = false;
- /** FIXME: Add an attribute cache for the get() function */
-
/**
- * The LDAP attributes supported by this class.
+ * Does the object exist in the LDAP database?
*
- * @var array
+ * @var boolean
*/
- public $supported_attributes = false;
+ private $_exists;
+
+ /** FIXME: Add an attribute cache for the get() function */
/**
- * Attributes derived from other object attributes.
+ * The LDAP attributes supported by this class.
*
* @var array
*/
- public $derived_attributes = array(
- self::ATTRIBUTE_ID,
- );
+ public $attributes;
/**
- * The attributes required when creating an object of this class.
+ * An attribute map for faster access.
*
* @var array
*/
- public $required_attributes = false;
+ public $attribute_map;
/**
- * The ldap classes for this type of object.
+ * A structure to initialize the attribute structure for this class.
*
* @var array
*/
- protected $object_classes = array(
- self::OBJECTCLASS_TOP
+ static public $init_attributes = array(
+ /**
+ * Derived attributes are calculated based on other attribute values.
+ */
+ 'derived' => array(
+ self::ATTRIBUTE_ID => array(
+ ),
+ ),
+ /**
+ * Default values for attributes without a value.
+ */
+ 'defaults' => array(
+ ),
+ /**
+ * Locked attributes. These are fixed after the object has been stored
+ * once. They may not be modified again.
+ */
+ 'locked' => array(
+ self::ATTRIBUTE_ID,
+ self::ATTRIBUTE_OC,
+ ),
+ /**
+ * The object classes representing this object.
+ */
+ 'object_classes' => array(
+ self::OBJECTCLASS_TOP
+ ),
);
/**
$this->uid = $uid;
}
- if ($server->schema_support === true) {
- $result = $server->getSupportedAttributes($this->object_classes);
-
- $this->supported_attributes = $result['supported'];
- $this->required_attributes = $result['required'];
- }
+ list($this->attributes, $this->attribute_map) = $server->getAttributes(get_class($this));
}
/**
*/
public function exists()
{
- try {
- $this->read();
- } catch (Horde_Kolab_Server_Exception $e) {
- return false;
+ if (!isset($this->_exists)) {
+ try {
+ $this->read();
+ $this->_exists = true;
+ } catch (Horde_Kolab_Server_Exception $e) {
+ $this->_exists = false;
+ }
}
- return true;
+ return $this->_exists;
}
/**
*/
protected function read()
{
+ if (!empty($this->attributes)) {
+ $attributes = array_keys($this->attributes);
+ } else {
+ $attributes = null;
+ }
$this->_cache = $this->server->read($this->uid,
- $this->supported_attributes);
+ $attributes);
}
/**
{
if ($attr != self::ATTRIBUTE_UID) {
// FIXME: This wont work this way.
- if ($this->supported_attributes !== false
- && !in_array($attr, $this->supported_attributes)
- && !in_array($attr, $this->derived_attributes)) {
- var_dump($this->supported_attributes);
+ if (!empty($this->attributes)
+ && !in_array($attr, array_keys($this->attributes))
+ && !empty($this->attribute_map['derived'])
+ && !in_array($attr, $this->attribute_map['derived'])) {
throw new Horde_Kolab_Server_Exception(sprintf(_("Attribute \"%s\" not supported!"),
$attr));
}
}
}
- if (in_array($attr, $this->derived_attributes)) {
+ if (!empty($this->attribute_map['derived'])
+ && in_array($attr, $this->attribute_map['derived'])) {
return $this->derive($attr);
}
}
/**
+ * Collapse derived values back into the main attributes.
+ *
+ * @param string $attr The attribute to collapse.
+ * @param array $info The information currently working on.
+ *
+ * @return mixed The value of the attribute.
+ */
+ protected function collapse($attr, &$info)
+ {
+ switch ($attr) {
+ default:
+ }
+ }
+
+ /**
* Convert the object attributes to a hash.
*
* @param string $attrs The attributes to return.
}
/**
- * Saves object information.
+ * Saves object information. This may either create a new entry or modify an
+ * existing entry.
+ *
+ * Please note that fields with multiple allowed values require the callee
+ * to provide the full set of values for the field. Any old values that are
+ * not resubmitted will be considered to be deleted.
*
* @param array $info The information about the object.
*
*/
public function save($info)
{
- if ($this->required_attributes !== false) {
- foreach ($this->required_attributes as $attribute) {
- if (!isset($info[$attribute])) {
- throw new Horde_Kolab_Server_Exception(sprintf(_("The value for \"%s\" is missing!"),
- $attribute));
+ if (!empty($this->attributes)) {
+ foreach ($info as $key => $value) {
+ if (!in_array($key, array_keys($this->attributes))) {
+ throw new Horde_Kolab_Server_Exception(sprintf(_("Attribute \"%s\" not supported!"),
+ $key));
}
}
}
- $info[self::ATTRIBUTE_OC] = $this->object_classes;
+ if (!$this->exists()) {
+ foreach ($this->attribute_map['required'] as $attribute) {
+ if (!in_array($key, array_keys($info)) || empty($info[$key])) {
+ if (empty($this->attributes[$key]['default'])) {
+ throw new Horde_Kolab_Server_Exception(sprintf(_("The value for \"%s\" is empty but required!"),
+ $key));
+ } else {
+ $info[$key] = $this->attributes[$key]['default'];
+ }
+ }
+ }
- $result = $this->server->save($this->uid, $info);
- if ($result === false || $result instanceOf PEAR_Error) {
- return $result;
+ $submitted = $info;
+ foreach ($submitted as $key => $value) {
+ if (empty($value)) {
+ unset($info[$key]);
+ }
+ }
+ } else {
+ foreach ($info as $key => $value) {
+ if (in_array($key, $this->attribute_map['locked'])) {
+ throw new Horde_Kolab_Server_Exception(sprintf(_("The value for \"%s\" may not be modified on an existing object!"),
+ $key));
+ }
+ }
+
+ $old_keys = array_keys($this->_cache);
+ $submitted = $info;
+ foreach ($submitted as $key => $value) {
+ if (empty($value) && !isset($this->_cache[$key])) {
+ unset($info[$key]);
+ continue;
+ }
+ if (in_array($key, $old_keys)) {
+ if (!is_array($value) && !is_array($this->_cache[$key])) {
+ if ($value == $this->_cache[$key]) {
+ // Unchanged value
+ unset($info[$key]);
+ }
+ } else {
+ if (!is_array($value)) {
+ $value = array($value);
+ $info[$key] = $value;
+ }
+ if (!is_array($this->_cache[$key])) {
+ $changes = array_diff(array($this->_cache[$key]), $value);
+ } else {
+ $changes = array_diff($this->_cache[$key], $value);
+ }
+ if (empty($changes)) {
+ // Unchanged value
+ unset($info[$key]);
+ }
+ }
+ }
+ }
}
- $this->_cache = $info;
+ foreach ($this->attribute_map['derived'] as $attribute) {
+ if (isset($info[$attribute])) {
+ $this->collapse($attribute, $info);
+ }
+ }
+
+ $result = $this->server->save($this->uid, $info, $this->exists());
+
+ $this->_exists = true;
+ $this->_cache = array_merge($this->_cache, $info);
return $result;
}
const OBJECTCLASS_INETORGPERSON = 'inetOrgPerson';
- /**
- * The ldap classes for this type of object.
- *
- * @var array
- */
- protected $object_classes = array(
- self::OBJECTCLASS_TOP,
- self::OBJECTCLASS_PERSON,
- self::OBJECTCLASS_ORGANIZATIONALPERSON,
- self::OBJECTCLASS_INETORGPERSON,
- );
+ /** Middle names */
+ const ATTRIBUTE_MIDDLENAMES = 'middleNames';
/**
- * The attributes supported by this class
+ * A structure to initialize the attribute structure for this class.
*
* @var array
*/
-/* public $supported_attributes = array( */
-/* self::ATTRIBUTE_FBPAST, */
-/* ); */
-
+ static public $init_attributes = array(
+ /**
+ * Derived attributes are calculated based on other attribute values.
+ */
+ 'derived' => array(
+ self::ATTRIBUTE_GIVENNAME => array(
+ 'desc' => 'Given name.',
+ ),
+ self::ATTRIBUTE_MIDDLENAMES => array(
+ 'desc' => 'Additional names separated from the given name by whitespace.',
+ ),
+ ),
+ /**
+ * Default values for attributes without a value.
+ */
+ 'defaults' => array(
+ ),
+ /**
+ * Locked attributes. These are fixed after the object has been stored
+ * once. They may not be modified again.
+ */
+ 'locked' => array(
+ ),
+ /**
+ * The object classes representing this object.
+ */
+ 'object_classes' => array(
+ self::OBJECTCLASS_INETORGPERSON,
+ ),
+ );
/**
* Derive an attribute value.
} else {
return $result[0];
}
+ case self::ATTRIBUTE_GIVENNAME:
+ case self::ATTRIBUTE_MIDDLENAMES:
+ $gn = $this->_get(self::ATTRIBUTE_GIVENNAME);
+ if (empty($gn)) {
+ return;
+ }
+ list($a[self::ATTRIBUTE_GIVENNAME],
+ $a[self::ATTRIBUTE_MIDDLENAMES]) = explode(' ', $gn, 2);
+ if (empty($a[$attr])) {
+ return;
+ }
+ return $a[$attr];
default:
return parent::derive($attr);
}
}
/**
+ * Collapse derived values back into the main attributes.
+ *
+ * @param string $attr The attribute to collapse.
+ * @param array $info The information currently working on.
+ *
+ * @return mixed The value of the attribute.
+ */
+ protected function collapse($attr, &$info)
+ {
+ switch ($attr) {
+ case self::ATTRIBUTE_GIVENNAME:
+ case self::ATTRIBUTE_MIDDLENAMES:
+ if (!isset($info[self::ATTRIBUTE_MIDDLENAMES])
+ && !isset($info[self::ATTRIBUTE_GIVENNAME])) {
+ return;
+ }
+
+ if (isset($info[self::ATTRIBUTE_MIDDLENAMES])) {
+ $givenname = isset($info[self::ATTRIBUTE_GIVENNAME]) ? $info[self::ATTRIBUTE_GIVENNAME] : '';
+ $info[self::ATTRIBUTE_GIVENNAME] = $givenname . isset($info[self::ATTRIBUTE_MIDDLENAMES]) ? ' ' . $info[self::ATTRIBUTE_MIDDLENAMES] : '';
+ unset($info[self::ATTRIBUTE_MIDDLENAMES]);
+ }
+ default:
+ return parent::derive($attr);
+ }
+ }
+
+
+ /**
* Generates an ID for the given information.
*
* @param array $info The data of the object.
{
/** Define attributes specific to this object type */
+
+ /** The user type */
const ATTRIBUTE_USERTYPE = 'usertype';
/** Define the possible Kolab user types */
const USERTYPE_RESOURCE = 3;
/**
- * The attributes supported by this class
- *
- * @var array
- */
- public $supported_attributes = false;
-
- /**
- * Attributes derived from the LDAP values.
+ * A structure to initialize the attribute structure for this class.
*
* @var array
*/
- public $derived_attributes = array(
- self::ATTRIBUTE_FN,
- 'id',
- 'usertype',
- //FIXME: Do we really want to have this type of functionality within this library?
- 'lnfn',
- 'fnln',
+ static public $init_attributes = array(
+ /**
+ * Derived attributes are calculated based on other attribute values.
+ */
+ 'derived' => array(
+ self::ATTRIBUTE_USERTYPE => array(),
+ ),
+ /**
+ * Default values for attributes without a value.
+ */
+ 'defaults' => array(
+ ),
+ /**
+ * Locked attributes. These are fixed after the object has been stored
+ * once. They may not be modified again.
+ */
+ 'locked' => array(
+ ),
+ /**
+ * The object classes representing this object.
+ */
+ 'object_classes' => array(
+ ),
);
/**
- * The attributes required when creating an object of this class.
- *
- * @var array
- */
- public $required_attributes = false;
-
- /**
* Initialize the Kolab Object. Provide either the UID or a
* LDAP search result.
*
*
* @var array
*/
- protected $object_classes = array(
+ public $object_classes = array(
self::OBJECTCLASS_TOP,
self::OBJECTCLASS_INETORGPERSON,
self::OBJECTCLASS_KOLABGROUPOFNAMES,
const OBJECTCLASS_KOLABINETORGPERSON = 'kolabInetOrgPerson';
/**
- * The ldap classes for this type of object.
+ * A structure to initialize the attribute structure for this class.
*
* @var array
*/
- protected $object_classes = array(
- self::OBJECTCLASS_TOP,
- self::OBJECTCLASS_PERSON,
- self::OBJECTCLASS_ORGANIZATIONALPERSON,
- self::OBJECTCLASS_INETORGPERSON,
- self::OBJECTCLASS_KOLABINETORGPERSON,
+ static public $init_attributes = array(
+ /**
+ * Derived attributes are calculated based on other attribute values.
+ */
+ 'derived' => array(
+ ),
+ /**
+ * Default values for attributes without a value.
+ */
+ 'defaults' => array(
+ ),
+ /**
+ * Locked attributes. These are fixed after the object has been stored
+ * once. They may not be modified again.
+ */
+ 'locked' => array(
+ self::ATTRIBUTE_MAIL,
+ ),
+ /**
+ * The object classes representing this object.
+ */
+ 'object_classes' => array(
+ self::OBJECTCLASS_KOLABINETORGPERSON,
+ ),
);
/**
- * The attributes supported by this class
- *
- * @var array
- */
-/* public $supported_attributes = array( */
-/* self::ATTRIBUTE_FBPAST, */
-/* ); */
-
-
- /**
* Derive an attribute value.
*
* @param string $attr The attribute to derive.
const OBJECTCLASS_ORGANIZATIONALPERSON = 'organizationalPerson';
/**
- * The ldap classes for this type of object.
+ * A structure to initialize the attribute structure for this class.
*
* @var array
*/
- protected $object_classes = array(
- self::OBJECTCLASS_TOP,
- self::OBJECTCLASS_PERSON,
- self::OBJECTCLASS_ORGANIZATIONALPERSON,
+ static public $init_attributes = array(
+ /**
+ * Derived attributes are calculated based on other attribute values.
+ */
+ 'derived' => array(
+ ),
+ /**
+ * Default values for attributes without a value.
+ */
+ 'defaults' => array(
+ ),
+ /**
+ * Locked attributes. These are fixed after the object has been stored
+ * once. They may not be modified again.
+ */
+ 'locked' => array(
+ ),
+ /**
+ * The object classes representing this object.
+ */
+ 'object_classes' => array(
+ self::OBJECTCLASS_ORGANIZATIONALPERSON,
+ ),
);
/**
const OBJECTCLASS_PERSON = 'person';
/**
- * The ldap classes for this type of object.
+ * A structure to initialize the attribute structure for this class.
*
* @var array
*/
- protected $object_classes = array(
- self::OBJECTCLASS_TOP,
- self::OBJECTCLASS_PERSON
+ static public $init_attributes = array(
+ /**
+ * Derived attributes are calculated based on other attribute values.
+ */
+ 'derived' => array(
+ ),
+ /**
+ * Default values for attributes without a value.
+ */
+ 'defaults' => array(
+ ),
+ /**
+ * Locked attributes. These are fixed after the object has been stored
+ * once. They may not be modified again.
+ */
+ 'locked' => array(
+ ),
+ /**
+ * The object classes representing this object.
+ */
+ 'object_classes' => array(
+ self::OBJECTCLASS_PERSON
+ ),
);
/**
- * Alias names for attributes.
- *
- * @var array
- */
- protected $attribute_aliases = array(
- self::ATTRIBUTE_CN => 'commonName',
- self::ATTRIBUTE_SN => 'surname',
- );
-
- /**
- * The attributes supported by this class
- *
- * @var array
- */
-/* public $supported_attributes = array( */
-/* self::ATTRIBUTE_FBPAST, */
-/* ); */
-
-
- /**
* Derive an attribute value.
*
* @param string $attr The attribute to derive.