Exemplo n.º 1
0
 /**
  * Create an instance of the tree - this is used when we call this class directly
  * Tree::getInstance($index)
  *
  * @return object Tree
  */
 public static function getInstance($server_id)
 {
     if (DEBUG_ENABLED && (($fargs = func_get_args()) || ($fargs = 'NOARGS'))) {
         debug_log('Entered (%%)', 33, 0, __FILE__, __LINE__, __METHOD__, $fargs);
     }
     $tree = get_cached_item($server_id, 'tree');
     if (!$tree) {
         $server = $_SESSION[APPCONFIG]->getServer($server_id);
         if (!$server) {
             return null;
         }
         $treeclass = $_SESSION[APPCONFIG]->getValue('appearance', 'tree');
         $tree = new $treeclass($server_id);
         # If we are not logged in, just return the empty tree.
         if (is_null($server->getLogin(null))) {
             return $tree;
         }
         foreach ($server->getBaseDN(null) as $base) {
             if ($base) {
                 $tree->addEntry($base);
                 if ($server->getValue('appearance', 'open_tree')) {
                     $baseEntry = $tree->getEntry($base);
                     $baseEntry->open();
                 }
             }
         }
         set_cached_item($server_id, 'tree', 'null', $tree);
     }
     return $tree;
 }
Exemplo n.º 2
0
# If cancel was selected, we'll redirect
if (get_request('cancel', 'REQUEST')) {
    header('Location: index.php');
    die;
}
$request = array();
$request['redirect'] = get_request('redirect', 'POST', false, false);
$request['page'] = new PageRender($app['server']->getIndex(), get_request('template', 'REQUEST', false, 'none'));
$request['page']->setContainer(get_request('container', 'REQUEST', true));
$request['page']->accept();
$request['template'] = $request['page']->getTemplate();
if ((!$request['template']->getContainer() || !$app['server']->dnExists($request['template']->getContainer())) && !get_request('create_base')) {
    error(sprintf(_('The container you specified (%s) does not exist. Please try again.'), $request['template']->getContainer()), 'error', 'index.php');
}
# Check if the container is a leaf - we shouldnt really return a hit here, the template engine shouldnt have allowed a user to attempt to create an entry...
$tree = get_cached_item($app['server']->getIndex(), 'tree');
$request['container'] = $tree->getEntry($request['template']->getContainer());
if (!$request['container'] && !get_request('create_base')) {
    $tree->addEntry($request['template']->getContainer());
    $request['container'] = $tree->getEntry($request['template']->getContainer());
}
# Check our RDN
if (!count($request['template']->getRDNAttrs())) {
    error(_('The were no attributes marked as an RDN attribute.'), 'error', 'index.php');
}
if (!$request['template']->getRDN()) {
    error(_('The RDN field is empty?'), 'error', 'index.php');
}
# Some other attribute checking...
foreach ($request['template']->getAttributes() as $attribute) {
    # Check that our Required Attributes have a value - we shouldnt really return a hit here, the template engine shouldnt have allowed this to slip through.
Exemplo n.º 3
0
 /**
  * Rename objects
  */
 public function rename($dn, $new_rdn, $container, $deleteoldrdn, $method = null)
 {
     if (DEBUG_ENABLED && (($fargs = func_get_args()) || ($fargs = 'NOARGS'))) {
         debug_log('Entered (%%)', 17, 0, __FILE__, __LINE__, __METHOD__, $fargs);
     }
     $result = false;
     if (run_hook('pre_entry_rename', array('server_id' => $this->index, 'method' => $method, 'dn' => $dn, 'rdn' => $new_rdn, 'container' => $container))) {
         $result = @ldap_rename($this->connect($method), $dn, $new_rdn, $container, $deleteoldrdn);
         if ($result) {
             # Update the tree
             $tree = get_cached_item($this->index, 'tree');
             $newdn = sprintf('%s,%s', $new_rdn, $container);
             $tree->renameEntry($dn, $newdn);
             set_cached_item($this->index, 'tree', 'null', $tree);
             run_hook('post_entry_rename', array('server_id' => $this->index, 'method' => $method, 'dn' => $dn, 'rdn' => $new_rdn, 'container' => $container));
         }
     }
     return $result;
 }
Exemplo n.º 4
0
 /**
  * Returns an array of Syntax objects that this LDAP server uses mapped to
  * their descriptions. The key of each entry is the OID of the Syntax.
  */
 public function SchemaSyntaxes($method = null, $dn = '')
 {
     if (DEBUG_ENABLED && (($fargs = func_get_args()) || ($fargs = 'NOARGS'))) {
         debug_log('Entered (%%)', 25, 0, __FILE__, __LINE__, __METHOD__, $fargs);
     }
     # Set default return
     $return = null;
     if ($return = get_cached_item($this->index, 'schema', 'syntaxes')) {
         if (DEBUG_ENABLED) {
             debug_log('Returning CACHED [%s] (%s).', 25, 0, __FILE__, __LINE__, __METHOD__, $this->index, 'syntaxes');
         }
         return $return;
     }
     $raw = $this->getRawSchema($method, 'ldapSyntaxes', $dn);
     if ($raw) {
         # build the array of attributes
         $return = array();
         foreach ($raw as $line) {
             if (is_null($line) || !strlen($line)) {
                 continue;
             }
             $syntax = new Syntax($line);
             $key = strtolower(trim($syntax->getOID()));
             if (!$key) {
                 continue;
             }
             $return[$key] = $syntax;
         }
         ksort($return);
         # cache the schema to prevent multiple schema fetches from LDAP server
         set_cached_item($this->index, 'schema', 'syntaxes', $return);
     }
     if (DEBUG_ENABLED) {
         debug_log('Returning (%s)', 25, 0, __FILE__, __LINE__, __METHOD__, $return);
     }
     return $return;
 }
Exemplo n.º 5
0
 /**
  * Gets a list of child entries for an entry. Given a DN, this function fetches the list of DNs of
  * child entries one level beneath the parent. For example, for the following tree:
  *
  * <code>
  * dc=example,dc=com
  *   ou=People
  *      cn=Dave
  *      cn=Fred
  *      cn=Joe
  *      ou=More People
  *         cn=Mark
  *         cn=Bob
  * </code>
  *
  * Calling <code>getContainerContents("ou=people,dc=example,dc=com")</code>
  * would return the following list:
  *
  * <code>
  *  cn=Dave
  *  cn=Fred
  *  cn=Joe
  *  ou=More People
  * </code>
  *
  * @param string $dn The DN of the entry whose children to return.
  * @param int $size_limit (optional) The maximum number of entries to return.
  *            If unspecified, no limit is applied to the number of entries in the returned.
  * @param string $filter (optional) An LDAP filter to apply when fetching children, example: "(objectClass=inetOrgPerson)"
  * @return array An array of DN strings listing the immediate children of the specified entry.
  */
 function getContainerContents($dn, $size_limit = 0, $filter = '(objectClass=*)', $deref = LDAP_DEREF_ALWAYS)
 {
     $tree = get_cached_item($this->server_id, 'tree');
     if (isset($tree['browser'][$dn]['children']) && $filter == '(objectClass=*)') {
         if (!isset($tree['browser'][$dn]['size_limited']) || !$tree['browser'][$dn]['size_limited']) {
             return $tree['browser'][$dn]['children'];
         }
     }
     $return = array();
     $search = $this->search(null, dn_escape($dn), $filter, array('dn'), 'one', true, $deref, $size_limit > 0 ? $size_limit + 1 : $size_limit);
     if (!$search) {
         $tree['browser'][$dn]['children'] = array();
     } else {
         foreach ($search as $searchdn => $entry) {
             $child_dn = dn_unescape($entry['dn']);
             $tree['browser'][$child_dn]['icon'] = get_icon($this, $child_dn);
             $return[] = $child_dn;
         }
         usort($return, 'pla_compare_dns');
         $tree['browser'][$dn]['children'] = $return;
         if ($size_limit > 0 && count($tree['browser'][$dn]['children']) > $size_limit) {
             $tree['browser'][$dn]['size_limited'] = true;
         } else {
             if (isset($tree['browser'][$dn]['size_limited'])) {
                 unset($tree['browser'][$dn]['size_limited']);
             }
         }
     }
     set_cached_item($this->server_id, 'tree', 'null', $tree);
     if (DEBUG_ENABLED) {
         debug_log('%s::getContainerContents(): Entered with (%s,%s,%s,%s), Returning (%s)', 17, get_class($this), $dn, $size_limit, $filter, $deref, $return);
     }
     return $tree['browser'][$dn]['children'];
 }
Exemplo n.º 6
0
 * Note: this script is equal and opposite to collapse.php
 * @package phpLDAPadmin
 * @see collapse.php
 */
/**
 */
require './common.php';
no_expire_header();
if (!$ldapserver->haveAuthInfo()) {
    pla_error(_('Not enough information to login to server. Please check your configuration.'));
}
# This allows us to display large sub-trees without running out of time.
@set_time_limit(0);
$dn = $_GET['dn'];
# We dont need this result, as we'll use the SESSION value when we call tree.php
$ldapserver->getContainerContents($dn, 0, '(objectClass=*)', $config->GetValue('deref', 'tree'));
$tree = get_cached_item($ldapserver->server_id, 'tree');
$tree['browser'][$dn]['open'] = true;
set_cached_item($ldapserver->server_id, 'tree', 'null', $tree);
/* This is for Opera. By putting "random junk" in the query string, it thinks
   that it does not have a cached version of the page, and will thus
   fetch the page rather than display the cached version */
$time = gettimeofday();
$random_junk = md5(strtotime('now') . $time['usec']);
/* If cookies were disabled, build the url parameter for the session id.
   It will be append to the url to be redirect */
$id_session_param = '';
if (SID != '') {
    $id_session_param = sprintf('&%s=%s', session_name(), session_id());
}
header(sprintf('Location:tree.php?foo=%s#%s_%s%s', $random_junk, $ldapserver->server_id, rawurlencode($dn), $id_session_param));
 function Templates($server_id)
 {
     if (DEBUG_ENABLED) {
         debug_log('%s::__construct(): Entered with ()', 5, get_class($this));
     }
     if ($this->_template = get_cached_item($server_id, 'template', 'all')) {
         if (DEBUG_ENABLED) {
             debug_log('%s::init(): Using CACHED [%s]', 5, get_class($this), 'templates');
         }
     } else {
         $dir = opendir(TMPLDIR);
         $this->template_num = 0;
         while (($file = readdir($dir)) !== false) {
             if (!preg_match('/.xml$/', $file)) {
                 continue;
             }
             $objXML = new xml2array();
             $xmldata = $objXML->parse(TMPLDIR . $file);
             $template_name = preg_replace('/.xml$/', '', $file);
             $this->storeTemplate($template_name, $xmldata);
         }
         masort($this->_template, 'title');
         set_cached_item($server_id, 'template', 'all', $this->_template);
     }
 }
Exemplo n.º 8
0
/**
 * Recursively descend on the given dn and draw the tree in plm
 *
 * @param dn $dn Current dn.
 * @param object $LDAPServer LDAPServer object
 * @param int $level Level to start drawing (defaults to 2)
 * @todo: Currently draw PLM only shows the first 50 entries of the base children - possibly the childrens children too. Have disabed the size_limit on the base - need to check that it doesnt affect non PLM tree viewer and children where size > size_limit.
 */
function draw_tree_plm($dn, $ldapserver, $level = 2)
{
    if (DEBUG_ENABLED) {
        debug_log('draw_tree_plm(): Entered with (%s,%s,%s)', 33, $dn, $ldapserver, $level);
    }
    global $config;
    $tree = get_cached_item($ldapserver->server_id, 'tree');
    $encoded_dn = rawurlencode($dn);
    #$expand_href = sprintf('expand.php?server_id=%s&amp;dn=%s',$ldapserver->server_id,$encoded_dn);
    $edit_href = sprintf('template_engine.php?server_id=%s&amp;dn=%s', $ldapserver->server_id, $encoded_dn);
    #$img_src = sprintf('images/%s',$tree['browser'][$dn]['icon']);
    $rdn = get_rdn($dn);
    $dots = '';
    for ($i = 1; $i <= $level; $i++) {
        $dots .= '.';
    }
    # Have we tranversed this part of the tree yet?
    if (isset($tree['browser'][$dn]['open'])) {
        $tree_plm = sprintf("%s|%s|%s|%s|%s|%s|%s\n", $dots, $rdn . ' (' . number_format(count($tree['browser'][$dn]['children'])) . ')', $edit_href, $dn, $tree['browser'][$dn]['icon'], 'right_frame', isset($tree['browser'][$dn]['open']) ? $tree['browser'][$dn]['open'] : 0);
        foreach ($tree['browser'][$dn]['children'] as $dn) {
            $tree_plm .= draw_tree_plm($dn, $ldapserver, $level + 1);
        }
    } else {
        $size_limit = $config->GetValue('search', 'size_limit');
        $child_count = count($ldapserver->getContainerContents($dn, $size_limit + 1, '(objectClass=*)', $config->GetValue('deref', 'tree')));
        if ($child_count > $size_limit) {
            $child_count = $size_limit . '+';
        }
        if ($child_count) {
            $tree_plm = sprintf("%s|%s|%s|%s|%s|%s|%s|%s\n", $dots, $rdn . ' (' . $child_count . ')', $edit_href, $dn, $tree['browser'][$dn]['icon'], 'right_frame', isset($tree['browser'][$dn]['open']) ? $tree['browser'][$dn]['open'] : 0, $child_count);
        } else {
            $tree_plm = sprintf("%s|%s|%s|%s|%s|%s|%s|%s\n", $dots, $rdn . ' (0)', $edit_href, $dn, $tree['browser'][$dn]['icon'], 'right_frame', isset($tree['browser'][$dn]['open']) ? $tree['browser'][$dn]['open'] : 0, $child_count);
        }
    }
    if (DEBUG_ENABLED) {
        debug_log('draw_tree_plm(): Returning (%s)', 33, $tree_plm);
    }
    return $tree_plm;
}
Exemplo n.º 9
0
 function __construct($server_id)
 {
     if (DEBUG_ENABLED && (($fargs = func_get_args()) || ($fargs = 'NOARGS'))) {
         debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs);
     }
     $this->server_id = $server_id;
     $server = $_SESSION[APPCONFIG]->getServer($this->server_id);
     $custom_prefix = $server->getValue('custom', 'pages_prefix');
     $class = $this->getClassVars();
     $changed = false;
     # Try to get the templates from our CACHE.
     if ($this->templates = get_cached_item($server_id, $class['item'])) {
         if (DEBUG_ENABLED) {
             debug_log('Using CACHED templates', 4, 0, __FILE__, __LINE__, __METHOD__);
         }
         # See if the template_time has expired to see if we should reload the templates.
         foreach ($this->templates as $index => $template) {
             # If the file no longer exists, we'll delete the template.
             if (!file_exists($template->getFileName())) {
                 unset($this->templates[$index]);
                 $changed = true;
                 system_message(array('title' => _('Template XML file removed.'), 'body' => sprintf('%s %s (%s)', _('Template XML file has removed'), $template->getName(false), $template->getType()), 'type' => 'info', 'special' => true));
                 continue;
             }
             if ($template->getReadTime() < time() - $class['cachetime'] && filectime($template->getFileName()) > $template->getReadTime()) {
                 system_message(array('title' => _('Template XML file changed.'), 'body' => sprintf('%s %s (%s)', _('Template XML file has changed and been reread'), $template->getName(false), $template->getType()), 'type' => 'info', 'special' => true));
                 $changed = true;
                 eval(sprintf('$this->templates[$index] = new %s($this->server_id,$template->getName(false),$template->getFileName(),$template->getType(),$index);', $class['name']));
             }
         }
         if (DEBUG_ENABLED) {
             debug_log('Templates refreshed', 4, 0, __FILE__, __LINE__, __METHOD__);
         }
         # See if there are any new template files
         $index = max(array_keys($this->templates)) + 1;
         foreach ($class['types'] as $type) {
             $dir = $class['dir'] . $type;
             $dh = opendir($dir);
             if (!$type) {
                 $type = 'template';
             }
             while ($file = readdir($dh)) {
                 # Ignore any files that are not XML files.
                 if (!preg_match('/.xml$/', $file)) {
                     continue;
                 }
                 # Ignore any files that are not the predefined custom files.
                 if ($_SESSION[APPCONFIG]->getValue('appearance', 'custom_templates_only') && !preg_match("/^{$custom_prefix}/", $file)) {
                     continue;
                 }
                 $filename = sprintf('%s/%s', $dir, $file);
                 if (!in_array($filename, $this->getTemplateFiles())) {
                     $templatename = preg_replace('/.xml$/', '', $file);
                     eval(sprintf('$this->templates[$index] = new %s($this->server_id,$templatename,$filename,$type,$index);', $class['name']));
                     $index++;
                     $changed = true;
                     system_message(array('title' => _('New Template XML found.'), 'body' => sprintf('%s %s (%s)', _('A new template XML file has been loaded'), $file, $type), 'type' => 'info', 'special' => true));
                 }
             }
         }
     } else {
         if (DEBUG_ENABLED) {
             debug_log('Parsing templates', 4, 0, __FILE__, __LINE__, __METHOD__);
         }
         # Need to reset this, as get_cached_item() returns null if nothing cached.
         $this->templates = array();
         $changed = true;
         $counter = 0;
         foreach ($class['types'] as $type) {
             $dir = $class['dir'] . $type;
             $dh = opendir($class['dir'] . $type);
             if (!$type) {
                 $type = 'template';
             }
             while ($file = readdir($dh)) {
                 # Ignore any files that are not XML files.
                 if (!preg_match('/.xml$/', $file)) {
                     continue;
                 }
                 # Ignore any files that are not the predefined custom files.
                 if ($_SESSION[APPCONFIG]->getValue('appearance', 'custom_templates_only') && !preg_match("/^{$custom_prefix}/", $file)) {
                     continue;
                 }
                 $filename = sprintf('%s/%s', $dir, $file);
                 # Store the template
                 $templatename = preg_replace('/.xml$/', '', $file);
                 eval(sprintf('$this->templates[$counter] = new %s($this->server_id,$templatename,$filename,$type,$counter);', $class['name']));
                 $counter++;
             }
         }
     }
     if (DEBUG_ENABLED) {
         debug_log('Templates loaded', 4, 0, __FILE__, __LINE__, __METHOD__);
     }
     if ($changed) {
         masort($this->templates, 'title');
         set_cached_item($server_id, $class['item'], 'null', $this->templates);
     }
 }
Exemplo n.º 10
0
 /** PAGE ENTRY MENU ITEMS **/
 private function getMenuItem($i)
 {
     if (DEBUG_ENABLED && (($fargs = func_get_args()) || ($fargs = 'NOARGS'))) {
         debug_log('Entered (%%)', 129, 0, __FILE__, __LINE__, __METHOD__, $fargs);
     }
     if (DEBUGTMP) {
         printf('<font size=-2>%s (%s)</font><br />', __METHOD__, $i);
     }
     switch ($i) {
         case 'entryrefresh':
             if ($_SESSION[APPCONFIG]->isCommandAvailable('cmd', 'entry_refresh')) {
                 return $this->getMenuItemRefresh();
             } else {
                 return '';
             }
         case 'switchtemplate':
             if ($_SESSION[APPCONFIG]->isCommandAvailable('cmd', 'switch_template')) {
                 return $this->getMenuItemSwitchTemplate();
             } else {
                 return '';
             }
         case 'entryexport':
             if ($_SESSION[APPCONFIG]->isCommandAvailable('script', 'export_form') && $_SESSION[APPCONFIG]->isCommandAvailable('script', 'export')) {
                 return $this->getMenuItemExportBase();
             } else {
                 return '';
             }
         case 'entrycopy':
             if ($_SESSION[APPCONFIG]->isCommandAvailable('script', 'copy_form') && $_SESSION[APPCONFIG]->isCommandAvailable('script', 'copy') && !$this->template->isReadOnly()) {
                 return $this->getMenuItemMove();
             } else {
                 return '';
             }
         case 'showinternal':
             if ($_SESSION[APPCONFIG]->isCommandAvailable('cmd', 'entry_internal_attributes_show')) {
                 return $this->getMenuItemInternalAttributes();
             } else {
                 return '';
             }
         case 'entrydelete':
             if ($_SESSION[APPCONFIG]->isCommandAvailable('script', 'delete_form') && $_SESSION[APPCONFIG]->isCommandAvailable('script', 'delete') && !$this->template->isReadOnly()) {
                 return $this->getMenuItemDelete();
             } else {
                 return '';
             }
         case 'entryrename':
             if ($_SESSION[APPCONFIG]->isCommandAvailable('script', 'rename_form') && $_SESSION[APPCONFIG]->isCommandAvailable('script', 'rename') && !$this->template->isReadOnly()) {
                 # Check if any of the RDN's are read only.
                 $rdnro = false;
                 foreach ($this->template->getRDNAttributeName() as $attr) {
                     $attribute = $this->template->getAttribute($attr);
                     if ($attribute && $attribute->isVisible() && !$attribute->isReadOnly()) {
                         $rdnro = true;
                         break;
                     }
                 }
                 if (!$rdnro) {
                     return $this->getMenuItemRename();
                 }
             }
             return '';
         case 'msgdel':
             if ($_SESSION[APPCONFIG]->getValue('appearance', 'show_hints') && $_SESSION[APPCONFIG]->isCommandAvailable('script', 'delete_form') && $_SESSION[APPCONFIG]->isCommandAvailable('script', 'delete') && !$this->template->isReadOnly()) {
                 return array('', $this->getDeleteAttributeMessage());
             } else {
                 return '';
             }
         case 'entrycompare':
             if ($_SESSION[APPCONFIG]->isCommandAvailable('script', 'compare_form') && $_SESSION[APPCONFIG]->isCommandAvailable('script', 'compare') && !$this->template->isReadOnly()) {
                 return $this->getMenuItemCompare();
             } else {
                 return '';
             }
         case 'childcreate':
             if ($_SESSION[APPCONFIG]->isCommandAvailable('script', 'create') && !$this->template->isReadOnly() && !$this->template->isNoLeaf()) {
                 return $this->getMenuItemCreate();
             } else {
                 return '';
             }
         case 'addattr':
             if ($_SESSION[APPCONFIG]->isCommandAvailable('script', 'add_attr_form') && !$this->template->isReadOnly()) {
                 return $this->getMenuItemAddAttribute();
             } else {
                 return '';
             }
         case 'childview':
         case 'childexport':
             static $children_count = false;
             static $more_children = false;
             $tree = get_cached_item($this->getServerID(), 'tree');
             $tree_item = $tree->getEntry($this->template->getDN());
             if (!$tree_item) {
                 $tree->addEntry($this->template->getDN());
                 $tree_item = $tree->getEntry($this->template->getDN());
             }
             if ($children_count === false) {
                 # Visible children in the tree
                 $children_count = count($tree_item->getChildren());
                 # Is there filtered children ?
                 $more_children = $tree_item->isSizeLimited();
                 if (!$children_count || !$more_children) {
                     # All children in ldap
                     $all_children = $this->getServer()->getContainerContents($this->template->getDN(), null, $children_count + 1, '(objectClass=*)', $_SESSION[APPCONFIG]->getValue('deref', 'view'), null);
                     $more_children = count($all_children) > $children_count;
                 }
             }
             if ($children_count > 0 || $more_children) {
                 if ($children_count <= 0) {
                     $children_count = '';
                 }
                 if ($more_children) {
                     $children_count .= '+';
                 }
                 if ($i == 'childview') {
                     return $this->getMenuItemShowChildren($children_count);
                 } elseif ($i == 'childexport' && $_SESSION[APPCONFIG]->isCommandAvailable('script', 'export_form') && $_SESSION[APPCONFIG]->isCommandAvailable('script', 'export')) {
                     return $this->getMenuItemExportSub();
                 } else {
                     return '';
                 }
             } else {
                 return '';
             }
         case 'msgschema':
             if ($_SESSION[APPCONFIG]->getValue('appearance', 'show_hints') && $_SESSION[APPCONFIG]->isCommandAvailable('script', 'schema')) {
                 return array('', $this->getViewSchemaMessage());
             } else {
                 return array();
             }
         case 'msgro':
             if ($this->template->isReadOnly()) {
                 return array('', $this->getReadOnlyMessage());
             } else {
                 return array();
             }
         case 'msgmodattr':
             $modified_attrs = array();
             $modified = get_request('modified_attrs', 'REQUEST', false, array());
             foreach ($this->template->getAttributes(true) as $attribute) {
                 if (in_array($attribute->getName(), $modified)) {
                     array_push($modified_attrs, $attribute->getFriendlyName());
                 }
             }
             if (count($modified_attrs)) {
                 return array('', $this->getModifiedAttributesMessage($modified_attrs));
             } else {
                 return array();
             }
         default:
             return false;
     }
 }