lib/api lib/Auth lib/client lib/ext lib/kolab_api_service.php lib/kolab_client_task.php lib/kolab_form.php lib/kolab_html.php lib/locale public_html/js public_html/skins
Aleksander Machniak
machniak at kolabsys.com
Tue Mar 4 14:45:41 CET 2014
lib/Auth/LDAP.php | 8 +
lib/api/kolab_api_service_ou.php | 8 +
lib/client/kolab_client_task_ou.php | 159 +++++++++++++++++++++++++++
lib/ext/Net/LDAP3.php | 41 ++++--
lib/kolab_api_service.php | 9 +
lib/kolab_client_task.php | 49 ++++----
lib/kolab_form.php | 2
lib/kolab_html.php | 2
lib/locale/en_US.php | 1
public_html/js/kolab_admin.js | 41 ++++++
public_html/skins/default/images/buttons.png |binary
public_html/skins/default/style.css | 22 +++
12 files changed, 301 insertions(+), 41 deletions(-)
New commits:
commit 2a8ac19a13e682bd1e52b2364702414031f36767
Author: Aleksander Machniak <machniak at kolabsys.com>
Date: Tue Mar 4 14:43:24 2014 +0100
Add support for organizational unit hierarchy
diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index 25bef97..83eb859 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -723,7 +723,13 @@ class LDAP extends Net_LDAP3 {
public function organizationalunit_add($attrs, $typeid = null)
{
- $base_dn = $this->entry_base_dn('ou', $typeid);
+ if (!empty($attrs['base_dn'])) {
+ $base_dn = $attrs['base_dn'];
+ unset($attrs['base_dn']);
+ }
+ else {
+ $base_dn = $this->entry_base_dn('ou', $typeid);
+ }
// TODO: The rdn is configurable as well.
// Use [$type_str . "_"]ou_rdn_attr
diff --git a/lib/api/kolab_api_service_ou.php b/lib/api/kolab_api_service_ou.php
index 015d736..7e9eddc 100644
--- a/lib/api/kolab_api_service_ou.php
+++ b/lib/api/kolab_api_service_ou.php
@@ -167,9 +167,15 @@ class kolab_api_service_ou extends kolab_api_service
$result = $auth->organizationalunit_info($getdata['id']);
// normalize result
- $result = $this->parse_result_attributes('ou', $result);
+ $result = $this->parse_result_attributes('ou', $result, $dn);
if ($result) {
+ // get base_dn "attribute" for the API client
+ $dn = substr($dn, strlen($result['ou']) + 4);
+ if (strpos($dn, 'ou=') === 0) {
+ $result['base_dn'] = $dn;
+ }
+
return $result;
}
diff --git a/lib/client/kolab_client_task_ou.php b/lib/client/kolab_client_task_ou.php
index 67c1df3..e1d80a8 100644
--- a/lib/client/kolab_client_task_ou.php
+++ b/lib/client/kolab_client_task_ou.php
@@ -78,6 +78,55 @@ class kolab_client_task_ou extends kolab_client_task
$this->output->set_object('taskcontent', $output);
}
+ /**
+ * OU list result handler, tree list builder
+ */
+ protected function list_result_handler($result, $head, $foot, $table_class)
+ {
+ $result = $this->ou_list_sort($result);
+ $result = $this->ou_list_build_tree($result, $max_tree_level);
+
+ foreach ($result as $idx => $item) {
+ $class = array('selectable');
+ $tree = '';
+ $style = '';
+
+ if ($item['level']) {
+ $tree .= '<span class="level" style="width:' . ($item['level'] * 16) . 'px"></span>';
+ $style = 'display:none';
+ }
+
+ if ($item['has_children']) {
+ $tree .= '<span class="expando"></span>';
+ }
+ else if ($max_tree_level) {
+ $tree .= '<span class="spacer"></span>';
+ }
+
+ $i++;
+ $cells = array();
+ $cells[] = array(
+ 'class' => 'name',
+ 'body' => $tree . kolab_html::escape($item['name']),
+ 'onclick' => "kadm.command('ou.info', '$idx')",
+ );
+
+ $rows[] = array(
+ 'id' => $i,
+ 'class' => implode(' ', $class),
+ 'cells' => $cells,
+ 'style' => $style,
+ 'data-level' => !empty($item['level']) ? $item['level'] : '',
+ );
+ }
+
+ if ($max_tree_level) {
+ $this->output->command('tree_list_init');
+ }
+
+ return array($rows, $head, '', $table_class . ' tree');
+ }
+
private function ou_form($attribs, $data = array())
{
if (empty($attribs['id'])) {
@@ -95,6 +144,7 @@ class kolab_client_task_ou extends kolab_client_task
'type_id' => 'system',
'type_id_name' => 'system',
'ou' => 'system',
+ 'base_dn' => 'system',
'description' => 'system',
);
@@ -122,6 +172,17 @@ class kolab_client_task_ou extends kolab_client_task
$fields['type_id']['type'] = kolab_form::INPUT_HIDDEN;
}
+ $ou_roots = array_merge(array(''), $this->ou_list());
+
+ // Add OU root selector
+ $fields['base_dn'] = array(
+ 'section' => 'system',
+ 'type' => kolab_form::INPUT_SELECT,
+ 'options' => $ou_roots,
+ 'escaped' => true,
+ 'default' => $data['base_dn'],
+ );
+
// Create mode
if ($add_mode) {
// Page title
@@ -146,4 +207,102 @@ class kolab_client_task_ou extends kolab_client_task
return $form->output();
}
+
+ private function ou_list()
+ {
+ $result = $this->api_get('ous.list', null, array('page_size' => 999));
+ $list = (array) $result->get('list');
+
+ $sorted = $this->ou_list_sort($list);
+
+ return $this->ou_list_build($sorted);
+ }
+
+ private function ou_list_sort($list)
+ {
+ // build the list for sorting
+ foreach (array_keys($list) as $dn) {
+ $name = kolab_utils::dn2ufn($dn);
+ $name = explode(',', $name);
+
+ $sort_name = array_pop($name) . ',' . implode(',', array_reverse($name));
+
+ $list[$dn] = mb_strtolower($sort_name);
+ }
+
+ // sort
+ asort($list, SORT_LOCALE_STRING);
+
+ return $list;
+ }
+
+ private function ou_list_build($list)
+ {
+ // @TODO: this code assumes parent always exist
+ foreach (array_keys($list) as $dn) {
+ $item = $this->parse_dn($dn);
+ $list[$dn] = str_repeat(' ', $item['level']) . kolab_html::escape($item['name']);
+ }
+
+ return $list;
+ }
+
+ /**
+ * Builds a tree from OUs list
+ */
+ private function ou_list_build_tree($list, &$max_level)
+ {
+ $last = '';
+ $result = array();
+
+ foreach (array_keys($list) as $dn) {
+ $item = $this->parse_dn($dn);
+
+ if ($item['level']) {
+ // create a parent unit entry if it does not exist (search result)
+ $parent = $this->domain_dn($item['domain']);
+
+ foreach ($item['path'] as $sub) {
+ $parent = "ou=$sub,$parent";
+ if (!isset($result[$parent])) {
+ $result[$parent] = $this->parse_dn($parent);
+ $last = $parent;
+ }
+
+ $result[$parent]['has_children'] = true;
+ }
+ }
+
+ $result[$dn] = $item;
+ $last = $dn;
+ $max_level = max($max_level, $item['level']);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Parse OU DN string into an array
+ */
+ private function parse_dn($dn)
+ {
+ $path = kolab_utils::dn2ufn($dn);
+ $path = explode(', ', $path);
+ $domain = array_pop($path); // remove domain
+
+ return array(
+ 'name' => array_shift($path),
+ 'path' => array_reverse($path),
+ 'level' => count($path),
+ 'domain' => $domain,
+ );
+ }
+
+ /**
+ * Converts domain name into DN string
+ */
+ private function domain_dn($domain)
+ {
+ return "dc=" . implode(',dc=', explode('.', $domain));
+ }
}
diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 52d8626..674f261 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -1228,8 +1228,7 @@ class Net_LDAP3
// TODO: Get $rdn_attr - we have type_id in $new_attrs
$dn_components = ldap_explode_dn($subject_dn, 0);
$rdn_components = explode('=', $dn_components[0]);
-
- $rdn_attr = $rdn_components[0];
+ $rdn_attr = $rdn_components[0];
$this->_debug("Net_LDAP3::modify_entry() using rdn attribute: " . $rdn_attr);
@@ -1363,6 +1362,11 @@ class Net_LDAP3
}
foreach ($new_attrs as $attr => $value) {
+ // OU's parent base dn
+ if ($attr == 'base_dn') {
+ continue;
+ }
+
if (is_array($value)) {
if (count($value) == 1) {
$new_attrs[$attr] = $value[0];
@@ -1420,21 +1424,28 @@ class Net_LDAP3
$old_ou = implode(',', $subject_dn_components);
}
- if ((!empty($old_ou) || !empty($new_ou)) && strtolower($old_ou) !== strtolower($new_ou)) {
- // object is an organizational unit
- if (strpos($subject_dn, 'ou=' . $old_ou) === 0) {
- $mod_array['rename']['new_parent'] = substr($subject_dn, strlen('ou=' . $old_ou) + 1);
- if (empty($mod_array['rename']['dn']) || empty($mod_array['rename']['new_rdn'])) {
- $mod_array['rename']['dn'] = $subject_dn;
- $mod_array['rename']['new_rdn'] = 'ou=' . $new_ou;
+ // object is an organizational unit
+ if (strpos($subject_dn, 'ou=' . $old_ou) === 0) {
+ $root = substr($subject_dn, strlen($old_ou) + 4); // remove ou=*,
+
+ if ((!empty($new_attrs['base_dn']) && strtolower($new_attrs['base_dn']) !== strtolower($root))
+ || (strtolower($old_ou) !== strtolower($new_ou))
+ ) {
+ if (!empty($new_attrs['base_dn'])) {
+ $root = $new_attrs['base_dn'];
}
+
+ $mod_array['rename']['new_parent'] = $root;
+ $mod_array['rename']['dn'] = $subject_dn;
+ $mod_array['rename']['new_rdn'] = 'ou=' . $new_ou;
}
- else {
- $mod_array['rename']['new_parent'] = $new_ou;
- if (empty($mod_array['rename']['dn']) || empty($mod_array['rename']['new_rdn'])) {
- $mod_array['rename']['dn'] = $subject_dn;
- $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . $new_attrs[$rdn_attr];
- }
+ }
+ // not OU object, but changed ou attribute
+ else if ((!empty($old_ou) || !empty($new_ou)) && strtolower($old_ou) !== strtolower($new_ou)) {
+ $mod_array['rename']['new_parent'] = $new_ou;
+ if (empty($mod_array['rename']['dn']) || empty($mod_array['rename']['new_rdn'])) {
+ $mod_array['rename']['dn'] = $subject_dn;
+ $mod_array['rename']['new_rdn'] = $rdn_attr . '=' . $new_attrs[$rdn_attr];
}
}
diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php
index 0e01a9e..4733347 100644
--- a/lib/kolab_api_service.php
+++ b/lib/kolab_api_service.php
@@ -347,6 +347,12 @@ abstract class kolab_api_service
}
}
+ // OU's parent attribute
+ if ($object_name == 'ou' && !empty($attribs['base_dn'])) {
+ // @TODO: validate?
+ $result['base_dn'] = $attribs['base_dn'];
+ }
+
$result = array_merge($result, $special_attr_validate);
Log::trace("parse_input_attributes result (merge of \$result and \$special_attr_validate)", $result);
@@ -452,10 +458,11 @@ abstract class kolab_api_service
*
* @param string $object_name Name of the object (user, group, etc.)
* @param array $attrs Entry attributes
+ * @param string $dn Will be filled with object base DN
*
* @return array Entry attributes
*/
- protected function parse_result_attributes($object_name, $attrs = array())
+ protected function parse_result_attributes($object_name, $attrs = array(), &$dn = null)
{
//console("parse_result_attributes($object_name, \$attrs = ", $attrs);
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index c9bcae9..35eec94 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -1298,7 +1298,7 @@ class kolab_client_task
$field['suffix'] = kolab_html::escape($this->translate($field['suffix']));
}
*/
- if (!empty($field['options'])) {
+ if (!empty($field['options']) && empty($field['escaped'])) {
foreach ($field['options'] as $opt_idx => $option) {
if (is_array($option)) {
$field['options'][$opt_idx]['content'] = kolab_html::escape($this->translate($option['content']));
@@ -1527,6 +1527,8 @@ class kolab_client_task
$cols = array('name');
$i = 0;
+ $table_class = 'list';
+
// table header
$head[0]['cells'][] = array('class' => 'name', 'body' => $this->translate($task . '.list'));
@@ -1555,29 +1557,34 @@ class kolab_client_task
// table body
if (!empty($result)) {
- foreach ($result as $idx => $item) {
- if (!is_array($item)) {
- continue;
- }
+ if (method_exists($this, 'list_result_handler')) {
+ list($rows, $head, $foot, $table_class) = $this->list_result_handler($result, $head, $foot, $table_class);
+ }
+ else {
+ foreach ($result as $idx => $item) {
+ if (!is_array($item)) {
+ continue;
+ }
- $class = array('selectable');
+ $class = array('selectable');
- if (method_exists($this, 'list_item_handler')) {
- $item = $this->list_item_handler($item, $class);
- }
- else {
- $item = array_shift($item);
- }
+ if (method_exists($this, 'list_item_handler')) {
+ $item = $this->list_item_handler($item, $class);
+ }
+ else {
+ $item = array_shift($item);
+ }
- if (empty($item)) {
- continue;
- }
+ if (empty($item)) {
+ continue;
+ }
- $i++;
- $cells = array();
- $cells[] = array('class' => 'name', 'body' => kolab_html::escape($item),
- 'onclick' => "kadm.command('$task.info', '$idx')");
- $rows[] = array('id' => $i, 'class' => implode(' ', $class), 'cells' => $cells);
+ $i++;
+ $cells = array();
+ $cells[] = array('class' => 'name', 'body' => kolab_html::escape($item),
+ 'onclick' => "kadm.command('$task.info', '$idx')");
+ $rows[] = array('id' => $i, 'class' => implode(' ', $class), 'cells' => $cells);
+ }
}
}
else {
@@ -1588,7 +1595,7 @@ class kolab_client_task
$table = kolab_html::table(array(
'id' => $task . 'list',
- 'class' => 'list',
+ 'class' => $table_class,
'head' => $head,
'body' => $rows,
'foot' => $foot,
diff --git a/lib/kolab_form.php b/lib/kolab_form.php
index 2fe3e9c..6ce7332 100644
--- a/lib/kolab_form.php
+++ b/lib/kolab_form.php
@@ -319,7 +319,7 @@ class kolab_form
$attribs['size'] = 5;
}
- $content = kolab_html::select($attribs, true);
+ $content = kolab_html::select($attribs, empty($attribs['escaped']));
break;
case self::INPUT_CUSTOM:
diff --git a/lib/kolab_html.php b/lib/kolab_html.php
index 1107d4b..2317bd0 100644
--- a/lib/kolab_html.php
+++ b/lib/kolab_html.php
@@ -460,7 +460,7 @@ class kolab_html
}
// ignore empty values
- if ($value === null) {
+ if ($value === null || $value === '') {
continue;
}
diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php
index c89fd2c..d4e28cd 100644
--- a/lib/locale/en_US.php
+++ b/lib/locale/en_US.php
@@ -147,6 +147,7 @@ $LANG['ou.list'] = 'Organizational Unit List';
$LANG['ou.norecords'] = 'No organizational unit records found!';
$LANG['ou.system'] = 'Details';
$LANG['ou.type_id'] = 'Unit Type';
+$LANG['ou.base_dn'] = 'Parent Unit';
$LANG['resource.add'] = 'Add Resource';
$LANG['resource.add.success'] = 'Resource created successfully.';
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index e4741f2..171ae01 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1266,6 +1266,47 @@ function kolab_admin()
return url;
};
+
+ /*********************************************************/
+ /********* Forms *********/
+ /*********************************************************/
+
+ this.tree_list_init = function()
+ {
+ $('table.list.tree span.expando').click(function() {
+ var tr = $(this).parents('table.list.tree tr'),
+ expanded = tr.hasClass('expanded'),
+ level = tr.data('level') || 0,
+ row = tr[0],
+ found = false;
+
+ tr[expanded ? 'removeClass' : 'addClass']('expanded');
+
+ $('tr', tr.parent()).each(function() {
+ if (this === row) {
+ found = true;
+ return;
+ }
+
+ if (!found)
+ return;
+
+ var r = $(this), l = r.data('level') || 0;
+
+ if (l <= level)
+ return false;
+
+ if (!expanded && l == level+1)
+ r.show();
+ else if (expanded && l > level)
+ r.hide().removeClass('expanded');
+ });
+
+ return false;
+ });
+ };
+
+
/*********************************************************/
/********* Forms *********/
/*********************************************************/
diff --git a/public_html/skins/default/images/buttons.png b/public_html/skins/default/images/buttons.png
index 75e966f..fd814a5 100644
Binary files a/public_html/skins/default/images/buttons.png and b/public_html/skins/default/images/buttons.png differ
diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css
index c6af986..7da1cc9 100644
--- a/public_html/skins/default/style.css
+++ b/public_html/skins/default/style.css
@@ -140,6 +140,28 @@ td.label {
white-space: nowrap;
}
+table.list.tree td span.expando {
+ background: url(images/buttons.png) -4px -161px no-repeat;
+ width: 16px;
+ height: 14px;
+ cursor: pointer;
+}
+
+table.list.tree tr.expanded td span.expando {
+ background-position: -4px -180px;
+}
+
+table.list.tree td span.expando,
+table.list.tree td span.spacer,
+table.list.tree td span.level {
+ display: inline-block;
+}
+
+table.list.tree td span.spacer {
+ width: 16px;
+}
+
+
/**** Common UI elements ****/
#topmenu {
More information about the commits
mailing list