Ejemplo n.º 1
0
 /**
  * Handles all recursive DAV:expand-property elements
  * 
  * @param   DAV_Resource          $path        The resource to perform the request on
  * @param   array                 $properties
  * @return  DAV_Element_response
  */
 private function handle_expand_property_recursively($path, $properties)
 {
     if (!($resource = DAV::$REGISTRY->resource($path))) {
         return null;
     }
     $retval = new DAV_Element_response($path);
     foreach ($properties as $parent => $children) {
         try {
             $oldprop = $newprop = $resource->prop($parent);
             if ($oldprop instanceof DAV_Element_href && $children) {
                 $newprop = '';
                 foreach ($oldprop->URIs as $URI) {
                     $tmp = $this->handle_expand_property_recursively($URI, $children);
                     $newprop .= $tmp ? $tmp->toXML() : "<D:href>" . DAV::encodeURIFullPath($URI) . "</D:href>";
                 }
             }
             $retval->setProperty($parent, $newprop);
         } catch (DAV_Status $e) {
             $retval->setStatus($parent, $e);
         }
     }
     return $retval;
 }
Ejemplo n.º 2
0
    /**
     * Serializes this lock to an XML string
     * @param array $tokens an array of tokens that may be displayed.
     * @return string an XML element
     */
    public function toXML()
    {
        $t_lockroot = "\n<D:lockroot><D:href>" . DAV::xmlescape(DAV::encodeURIFullPath($this->lockroot)) . "</D:href></D:lockroot>";
        if ($this->timeout === 0) {
            $t_timeout = 'Infinite';
        } else {
            $t_timeout = $this->timeout - time();
            $t_timeout = $t_timeout < 0 ? 'Second-0' : 'Second-' . $t_timeout;
        }
        $t_locktoken = isset(DAV::$SUBMITTEDTOKENS[$this->locktoken]) ? "\n<D:locktoken>\n<D:href>" . DAV::xmlescape($this->locktoken) . "</D:href>\n</D:locktoken>" : '';
        $t_owner = empty($this->owner) ? '' : "\n<D:owner>{$this->owner}</D:owner>";
        return <<<EOS
<D:activelock>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
<D:depth>{$this->depth}</D:depth>{$t_owner}
<D:timeout>{$t_timeout}</D:timeout>{$t_locktoken}{$t_lockroot}
</D:activelock>
EOS;
    }
Ejemplo n.º 3
0
    /**
     * Stores properties set earlier by set().
     * @return void
     * @throws DAV_Status in particular 507 (Insufficient Storage)
     */
    public function storeProperties()
    {
        if (!$this->touched) {
            return;
        }
        $collection = BeeHub::getNoSQL()->users;
        $document = $collection->findOne(array('name' => $this->name));
        if (isset($this->stored_props[DAV::PROP_DISPLAYNAME])) {
            $document['displayname'] = $this->stored_props[DAV::PROP_DISPLAYNAME];
        } else {
            unset($document['displayname']);
        }
        if (isset($this->stored_props[BeeHub::PROP_X509])) {
            $document['x509'] = $this->stored_props[BeeHub::PROP_X509];
        } else {
            unset($document['x509']);
        }
        // Check whether the SURFconext ID already exists
        if (isset($this->stored_props[BeeHub::PROP_SURFCONEXT])) {
            $conextDuplicate = $collection->findOne(array('surfconext_id' => $this->stored_props[BeeHub::PROP_SURFCONEXT]), array('name' => true));
            if (!is_null($conextDuplicate) && $conextDuplicate['name'] !== $this->name) {
                throw new DAV_Status(DAV::HTTP_CONFLICT, "This SURFconext id is already used by a different BeeHub user.");
            }
            $document['surfconext_id'] = @$this->stored_props[BeeHub::PROP_SURFCONEXT];
            $document['surfconext_description'] = @$this->stored_props[BeeHub::PROP_SURFCONEXT_DESCRIPTION];
        } else {
            unset($document['surfconext_id'], $document['surfconext_description']);
        }
        $p_sponsor = basename(@$this->stored_props[BeeHub::PROP_SPONSOR]);
        if (isset($document['sponsors']) && is_array($document['sponsors']) && in_array($p_sponsor, $document['sponsors'])) {
            $document['default_sponsor'] = $p_sponsor;
        }
        $change_email = false;
        if (@$this->stored_props[BeeHub::PROP_EMAIL] !== @$document['email']) {
            $change_email = true;
            $document['unverified_email'] = @$this->stored_props[BeeHub::PROP_EMAIL];
            $document['verification_code'] = md5(time() . '0-c934q2089#$#%@#$jcq2iojc43q9  i1d' . rand(0, 10000));
            $document['verification_expiration'] = time() + 60 * 60 * 24;
        }
        // Write all data to database
        $saveResult = $collection->save($document);
        if (!$saveResult['ok']) {
            throw new DAV_Status(DAV::HTTP_INTERNAL_SERVER_ERROR);
        }
        // Notify the user if needed
        if ($change_email) {
            $activation_link = BeeHub::urlbase(true) . DAV::encodeURIFullPath($this->path) . '?verification_code=' . $document['verification_code'];
            $message = 'Dear ' . $document['displayname'] . ',

This e-mail address (' . $document['unverified_email'] . ') is added to the BeeHub account \'' . $this->name . '\'. You need to confirm this action by following this link:

' . $activation_link . '

If this link doesn\'t work, on your profile page go to the tab \'Verify e-mail address\' and fill out the following verification code:

' . $document['verification_code'] . '

Note that your verification code is only valid for 24 hours. Also, for new users, if you don\'t have a validated e-mail address, your account will automatically be removed after 24 hours.

If this was a mistake, or you do not want to add this e-mail address to this BeeHub account, you don\'t have to do anything.

Best regards,

BeeHub';
            BeeHub::email(array($document['unverified_email'] => $document['displayname']), 'Verify e-mail address for BeeHub', $message);
        }
        // Update the json file containing all displaynames of all privileges
        self::update_principals_json();
        $this->touched = false;
    }
Ejemplo n.º 4
0
 /**
  * An XML representation of the object.
  * @return string
  */
 public function toXML()
 {
     $retval = "<D:ace>\n";
     // First the principal (or inversion thereof):
     if ('/' === $this->principal[0]) {
         $principal = new DAV_Element_href($this->principal);
     } elseif (!($principal = @DAVACL::$PRINCIPALS[$this->principal])) {
         $principal = explode(' ', $this->principal);
         $principal = "<D:property><{$principal[1]} xmlns=\"{$principal[0]}\"/></D:property>";
     }
     $principal = "<D:principal>{$principal}</D:principal>";
     if ($this->invert) {
         $principal = "<D:invert>{$principal}</D:invert>";
     }
     $retval .= $principal;
     // Second, the privileges (denied or granted):
     $privileges = '';
     foreach ($this->privileges as $privilege) {
         $privilege = explode(' ', $privilege);
         if ('DAV:' === $privilege[0]) {
             $privileges .= "<D:privilege><D:{$privilege[1]}/></D:privilege>";
         } else {
             $privileges .= "<D:privilege><{$privilege[1]} xmlns=\"{$privilege[0]}\"/></D:privilege>";
         }
     }
     $retval .= $this->deny ? "\n<D:deny>{$privileges}</D:deny>" : "\n<D:grant>{$privileges}</D:grant>";
     // Finally, the DAV:protected and DAV:inherited props:
     if ($this->protected) {
         $retval .= "\n<D:protected/>";
     }
     if ($this->inherited) {
         $retval .= "\n<D:inherited><D:href>" . DAV::encodeURIFullPath($this->inherited) . "</D:href></D:inherited>";
     }
     $retval .= "\n</D:ace>";
     return $retval;
 }
Ejemplo n.º 5
0
 /**
  * Serializes this object to XML.
  * Must only be called by DAV_Multistatus.
  * @return string XML
  */
 public function toXML()
 {
     // Set the default status to 200:
     foreach (array_keys($this->properties) as $p) {
         if (!isset($this->status[$p])) {
             $this->status[$p] = DAV_Status::$OK;
         }
     }
     // Rearrange by status:
     $hashed_statusses = array();
     $hashed_properties = array();
     foreach ($this->status as $p => $s) {
         $hash = md5($s->getCode() . "\t" . $s->getMessage() . "\t" . implode("\t", $s->conditions) . $s->location);
         $hashed_statusses[$hash] = $s;
         $hashed_properties[$hash][] = $p;
     }
     // Start generating some XML:
     $xml = "\n<D:response><D:href>" . DAV::xmlescape(DAV::encodeURIFullPath($this->path)) . "</D:href>";
     // Each defined status gets its own <D:propstat> element:
     foreach ($hashed_statusses as $hash => $status) {
         $xml .= "\n<D:propstat><D:prop>";
         foreach ($hashed_properties[$hash] as $prop) {
             list($namespaceURI, $localName) = explode(' ', $prop);
             $xml .= "\n<";
             switch ($namespaceURI) {
                 case 'DAV:':
                     $xml .= "D:{$localName}";
                     break;
                 case '':
                     $xml .= "{$localName}";
                     break;
                 default:
                     $xml .= "ns:{$localName} xmlns:ns=\"{$namespaceURI}\"";
             }
             if (isset($this->properties[$prop])) {
                 $xml .= '>' . $this->properties[$prop] . '</';
                 switch ($namespaceURI) {
                     case 'DAV:':
                         $xml .= "D:";
                         break;
                     case '':
                         break;
                     default:
                         $xml .= "ns:";
                 }
                 $xml .= "{$localName}>";
             } else {
                 $xml .= '/>';
             }
         }
         // And give the status itself!
         $xml .= "\n</D:prop>\n<D:status>HTTP/1.1 " . DAV::status_code($status->getCode()) . '</D:status>';
         if (!empty($status->conditions)) {
             $xml .= "\n<D:error>";
             foreach ($status->conditions as $condition) {
                 $xml .= @DAV::$CONDITIONS[$condition];
             }
             $xml .= "</D:error>";
         }
         $message = $status->getMessage();
         if (!empty($message)) {
             $xml .= "\n<D:responsedescription>" . DAV::xmlescape($message) . '</D:responsedescription>';
         }
         $xml .= "\n</D:propstat>";
     }
     $xml .= "\n</D:response>";
     return $xml;
 }
Ejemplo n.º 6
0
 /**
  * Returns the ACL restrictions in XML format
  *
  * @return string XML
  */
 public final function prop_acl_restrictions()
 {
     $retval = '';
     foreach ($this->user_prop_acl_restrictions() as $restriction) {
         if (is_array($restriction)) {
             // An array of required principals
             $retval .= "\n<D:required-principal>";
             foreach ($restriction as $principal) {
                 if ($p = @DAVACL::$PRINCIPALS[$principal]) {
                     $retval .= "\n{$p}";
                 } elseif ('/' === $principal[0]) {
                     $retval .= "\n<D:href>" . DAV::xmlescape(DAV::encodeURIFullPath($principal)) . '</D:href>';
                 } else {
                     $retval .= "\n<D:property><" . DAV::expand($principal) . '/></D:property>';
                 }
             }
             $retval .= "\n</D:required-principal>";
         } else {
             // Normal predefined restrictions:
             $retval .= '<' . DAV::expand($restriction) . '/>';
         }
     }
     return $retval;
 }
Ejemplo n.º 7
0
 /**
  * Serve WebDAV HTTP request.
  * @param DAV_Registry $registry
  */
 public function handleRequest()
 {
     // We want to catch every exception thrown in this code, and report about it
     // to the user appropriately.
     $shallow_lock = false;
     try {
         $shallow_lock = $this->check_if_headers();
         $resource = DAV::$REGISTRY->resource(DAV::getPath());
         if (!$resource || !$resource->isVisible() and in_array($_SERVER['REQUEST_METHOD'], array('ACL', 'COPY', 'DELETE', 'GET', 'HEAD', 'MOVE', 'OPTIONS', 'POST', 'PROPFIND', 'PROPPATCH', 'REPORT', 'UNLOCK'))) {
             throw new DAV_Status(DAV::HTTP_NOT_FOUND);
         }
         if ('/' !== substr(DAV::getPath(), -1) && ($resource && $resource instanceof DAV_Collection || 'MKCOL' === $_SERVER['REQUEST_METHOD'])) {
             $newPath = DAV::getPath() . '/';
             DAV::setPath(DAV::encodeURIFullPath($newPath));
             DAV::header(array('Content-Location' => DAV::path2uri($newPath)));
         }
         $this->handle($resource);
     } catch (Exception $e) {
         if (!$e instanceof DAV_Status) {
             $e = new DAV_Status(DAV::HTTP_INTERNAL_SERVER_ERROR, "{$e}");
         }
         $e->output();
     }
     if ($shallow_lock) {
         DAV::$REGISTRY->shallowUnlock();
     }
     if (DAV_Multistatus::active()) {
         DAV_Multistatus::inst()->close();
     }
 }
Ejemplo n.º 8
0
 /**
  * Authenticates the user through one of the authentication mechanisms.
  * @param  boolean $requireAuth  If set to false and authentication fails,
  *   the user will continue as an unauthenticated user. If set to true
  *   (default), status 401 UNAUTHORIZED will be returned upon authentication
  *   failure.
  * @param  boolean  $allowDoubleLogin  TODO documentation
  */
 public function handle_authentication($requireAuth = true, $allowDoubleLogin = false)
 {
     // We start with assuming nobody is logged in
     $this->set_user(null);
     $this->SURFconext = false;
     if (isset($_GET['logout'])) {
         if ($this->simpleSAML_authentication->isAuthenticated()) {
             $this->simpleSAML_authentication->logout();
         }
         if (!empty($_SERVER['HTTPS'])) {
             DAV::redirect(DAV::HTTP_SEE_OTHER, BeeHub::urlbase(false) . '/system/');
             return;
         }
     }
     if (isset($_SERVER['PHP_AUTH_PW'])) {
         if (!$allowDoubleLogin) {
             if ($this->simpleSAML_authentication->isAuthenticated()) {
                 // You can't be logged in through SURFconext and HTTP Basic at the same time!
                 $this->simpleSAML_authentication->logout();
             }
             if ('conext' === @$_GET['login']) {
                 throw new DAV_Status(DAV::HTTP_BAD_REQUEST, "You are already logged in using your username/password. Therefore you are not allowed to login using SURFconext. Unfortunately the only way to logout with your username and password is to close all browser windows. Hit the 'back' button in your browser and login using username/password.");
             }
         }
         // The user already sent username and password: check them!
         try {
             $user = BeeHub::user($_SERVER['PHP_AUTH_USER']);
             $password_verified = $user->check_password($_SERVER['PHP_AUTH_PW']);
         } catch (DAV_Status $status) {
             if ($status->getCode() === DAV::HTTP_FORBIDDEN) {
                 $password_verified = false;
             }
         }
         if (!$password_verified) {
             // If authentication fails, respond accordingly
             if ('passwd' === @$_GET['login'] || $requireAuth) {
                 // User could not be authenticated with supplied credentials, but we
                 // require authentication, so we ask again!
                 $this->unauthorized();
             }
         } else {
             // Authentication succeeded: store credentials!
             $this->set_user($_SERVER['PHP_AUTH_USER']);
         }
         // end of: if (user sent username/passwd)
     } elseif ('passwd' !== @$_GET['login'] && $this->simpleSAML_authentication->isAuthenticated()) {
         $surfId = $this->simpleSAML_authentication->getAuthData("saml:sp:NameID");
         $surfId = $surfId['Value'];
         $collection = BeeHub::getNoSQL()->users;
         $result = $collection->findOne(array('surfconext_id' => $surfId), array('name' => true));
         if (!is_null($result)) {
             // We found a user, this is the one that's logged in!
             $this->SURFconext = true;
             $this->set_user($result['name']);
         } elseif (rawurldecode($_SERVER['REQUEST_URI']) !== BeeHub::USERS_PATH) {
             throw new DAV_Status(DAV::HTTP_TEMPORARY_REDIRECT, BeeHub::urlbase(true) . BeeHub::USERS_PATH);
         }
     } elseif ('conext' === @$_GET['login']) {
         // We don't know this SURFconext ID, this is a new user
         $this->simpleSAML_authentication->login();
     } elseif ('passwd' === @$_GET['login'] || $requireAuth) {
         // If the user didn't send any credentials, but we require authentication, ask for it!
         $this->unauthorized();
     }
     // If the current user is logged in, but has no verified e-mail address.
     // He/she is not authorized to do anything, but will get a message that we
     // want a verified e-mail address. Although he has to be able to verify
     // his e-mail address of course (so GET and POST on /system/users/<name>
     // is allowed)
     $user = $this->current_user();
     if (!is_null($user)) {
         // Update the http://beehub.nl/ last-activity property
         $user->user_set(BeeHub::PROP_LAST_ACTIVITY, date('Y-m-d\\TH:i:sP'));
         $user->storeProperties();
         $email = $user->prop(BeeHub::PROP_EMAIL);
         if (empty($email) && DAV::unslashify(DAV::getPath()) != DAV::unslashify($user->path)) {
             $message = file_get_contents(dirname(dirname(__FILE__)) . '/views/error_no_verified_email.html');
             $message = str_replace('%USER_PATH%', BeeHub::urlbase(true) . DAV::encodeURIFullPath($user->path), $message);
             BeeHub::htmlError($message, DAV::HTTP_FORBIDDEN);
         }
     }
 }
Ejemplo n.º 9
0
 /**
  * Handles the creation of a lock
  * 
  * @param DAV_Resource $resource
  * @return void
  * @throws DAV_Status
  */
 private function handleCreateLock($resource)
 {
     // Check conflicting (parent) locks:
     if ($lock = DAV::$LOCKPROVIDER->getlock(DAV::getPath())) {
         throw new DAV_Status(DAV::HTTP_LOCKED, array(DAV::COND_NO_CONFLICTING_LOCK => new DAV_Element_href($lock->lockroot)));
     }
     // Find out the depth:
     $depth = $this->depth();
     if (DAV::DEPTH_1 === $depth) {
         throw new DAV_Status(DAV::HTTP_BAD_REQUEST, 'Depth: 1 is not supported for method LOCK.');
     }
     $headers = array('Content-Type' => 'application/xml; charset="utf-8"');
     if (!$resource) {
         // Check unmapped collection resource:
         if (substr(DAV::getPath(), -1) === '/') {
             throw new DAV_Status(DAV::HTTP_NOT_FOUND, 'Unmapped collection resource');
         }
         $parent = DAV::$REGISTRY->resource(dirname(DAV::getPath()));
         if (!$parent || !$parent->isVisible()) {
             throw new DAV_Status(DAV::HTTP_CONFLICT, 'Unable to LOCK unexisting parent collection');
         }
         $parent->assertLock();
         $resource = $parent->create_member(basename(DAV::getPath()));
         if (false !== strpos($_SERVER['HTTP_USER_AGENT'], 'Microsoft')) {
             // For M$, we need to mimic RFC2518:
             $headers['status'] = DAV::HTTP_OK;
         } else {
             $headers['status'] = DAV::HTTP_CREATED;
             $headers['Location'] = DAV::encodeURIFullPath(DAV::getPath());
         }
     }
     if ($resource instanceof DAV_Collection && $depth === DAV::DEPTH_INF && ($memberLocks = DAV::$LOCKPROVIDER->memberLocks(DAV::getPath()))) {
         $memberLockPaths = array();
         foreach ($memberLocks as $memberLock) {
             $memberLockPaths[] = $memberLock->lockroot;
         }
         throw new DAV_Status(DAV::HTTP_LOCKED, array(DAV::COND_NO_CONFLICTING_LOCK => new DAV_Element_href($memberLockPaths)));
     }
     $token = DAV::$LOCKPROVIDER->setlock(DAV::getPath(), $depth, $this->owner, $this->timeout);
     DAV::$SUBMITTEDTOKENS[$token] = $token;
     $headers['Lock-Token'] = "<{$token}>";
     if (!($lockdiscovery = $resource->prop_lockdiscovery())) {
         throw new DAV_Status(DAV::HTTP_INTERNAL_SERVER_ERROR);
     }
     // Generate output:
     DAV::header($headers);
     echo DAV::xml_header() . '<D:prop xmlns:D="DAV:"><D:lockdiscovery>' . $lockdiscovery . '</D:lockdiscovery></D:prop>';
 }
Ejemplo n.º 10
0
 /**
  * Sends this status to client.
  * @return void
  */
 public function output()
 {
     $status = $this->getCode();
     if ($status < 300) {
         throw new DAV_Status(DAV::HTTP_INTERNAL_SERVER_ERROR, "DAV_Status object with status {$status} " . var_export($this->getMessage(), true));
     }
     if (DAV::HTTP_UNAUTHORIZED === $status && DAV::$UNAUTHORIZED) {
         call_user_func(DAV::$UNAUTHORIZED);
         return;
     } elseif (!empty($this->conditions)) {
         $headers = array('status' => $status, 'Content-Type' => 'application/xml; charset="UTF-8"');
         if ($this->location) {
             $headers['Location'] = DAV::encodeURIFullPath($this->location);
         }
         DAV::header($headers);
         echo DAV::xml_header() . '<D:error xmlns:D="DAV:">';
         foreach ($this->conditions as $condition => $xml) {
             echo "\n<D:" . $condition;
             echo $xml ? ">{$xml}</D:{$condition}>" : "/>";
         }
         echo "\n</D:error>";
     } elseif ($this->location) {
         DAV::redirect($status, $this->location);
     } else {
         if (self::$RESPONSE_GENERATOR && in_array($_SERVER['REQUEST_METHOD'], array('GET', 'POST'))) {
             DAV::header(array('status' => $status));
             call_user_func(self::$RESPONSE_GENERATOR, $status, $this->getMessage());
         } else {
             DAV::header(array('status' => $status, 'Content-Type' => 'text/plain; charset="UTF-8"'));
             echo "HTTP/1.1 " . DAV::status_code($status) . "\n" . $this->getMessage();
         }
     }
 }
Ejemplo n.º 11
0
 /**
  * Converts a full path to an URL encoded version, while keeping the path delimiters in place
  * 
  * @param   String  $path  The path to URL encode
  * @return  String         The path with all elements URL encoded
  */
 public static function encodeURIFullPath($path)
 {
     if (!empty($path) && (substr($path, 0, 7) === 'http://' || substr($path, 0, 8) === 'https://')) {
         $urlParts = parse_url($path);
         $location = $urlParts['scheme'] . '://';
         if (isset($urlParts['user'])) {
             $location .= $urlParts['user'];
             if (isset($urlParts['pass'])) {
                 $location .= ':' . $urlParts['pass'];
             }
             $location .= '@';
         }
         $location .= $urlParts['host'];
         if (isset($urlParts['port'])) {
             $location .= ':' . $urlParts['port'];
         }
         if (isset($urlParts['path'])) {
             $location .= DAV::encodeURIFullPath($urlParts['path']);
         }
         if (isset($urlParts['query'])) {
             $location .= '?' . $urlParts['query'];
         }
         if (isset($urlParts['fragment'])) {
             $location .= '#' . $urlParts['fragment'];
         }
         return $location;
     } else {
         $pathElements = explode('/', $path);
         $encodedPathElements = array_map('rawurlencode', $pathElements);
         return implode('/', $encodedPathElements);
     }
 }
Ejemplo n.º 12
0
function printTree($path, $treeState, $selectedPath)
{
    $registry = BeeHub_Registry::inst();
    $resource = $registry->resource($path);
    $members = array();
    foreach ($resource as $member) {
        // Skip the /system/ directory, as there is no need to see this
        if ($path === '/' && $member === 'system/' || substr($member, -1) !== '/') {
            continue;
        }
        $members[] = $member;
    }
    usort($members, 'strnatcasecmp');
    $last = count($members) - 1;
    for ($counter = 0; $counter <= $last; $counter++) {
        $member = $members[$counter];
        $memberResource = $registry->resource($path . $member);
        // Previous versions actually checked whether the resource has children
        // or not. But with the new set-up that means a query for each resource
        // and this is simply to expensive. Setting this to true for all cases
        // means the end-user will find out there are no child resources on the
        // first attempt to unfold the directory in the tree
        $hasChildren = true;
        $expanded = isset($treeState[$memberResource->path]) && $hasChildren ? $treeState[$memberResource->path] : false;
        ?>
<li <?php 
        echo $counter === $last ? 'class="dynatree-lastsib"' : '';
        ?>
          ><span class="dynatree-node dynatree-folder
                       <?php 
        echo $hasChildren ? 'dynatree-has-children' : '';
        ?>
                       <?php 
        echo $expanded ? 'dynatree-expanded' : ($hasChildren ? 'dynatree-lazy' : '');
        ?>
                       dynatree-exp-<?php 
        echo $expanded ? 'e' : 'cd';
        echo $counter === $last ? 'l dynatree-lastsib' : '';
        ?>
                       dynatree-ico-<?php 
        echo $expanded ? 'e' : 'c';
        ?>
f
                       <?php 
        echo $memberResource->path === $selectedPath ? 'dynatree-focused' : '';
        ?>
                "
            ><span class="dynatree-<?php 
        echo $hasChildren ? 'expander' : 'connector';
        ?>
"></span
            ><span class="dynatree-icon"></span
            ><a class="dynatree-title" href="<?php 
        echo DAV::xmlescape(DAV::encodeURIFullPath($memberResource->path));
        ?>
"><?php 
        echo DAV::xmlescape($memberResource->user_prop_displayname());
        ?>
</a
          ></span
          <?php 
        if ($expanded && $hasChildren) {
            ?>
            ><ul><?php 
            echo printTree($memberResource->path, $treeState, $selectedPath);
            ?>
</ul
          <?php 
        }
        ?>
        ></li>
        <?php 
        $registry->forget($path . $member);
    }
}