/** * Answer the organizer above the reference node that can accept the component type given. * * @param object SiteComponent $refNode * @param object Type $componentType * @return object OrganizerSiteComponent * @access public * @since 1/15/09 * @static */ public static function getOrganizerForComponentType(SiteComponent $refNode, Type $componentType) { // For pages and sections, find the menu above our component. if ($componentType->getDomain() == 'segue-multipart') { $parentComponent = $refNode->getParentComponent(); while ($parentComponent) { if ($parentComponent->getComponentClass() == 'MenuOrganizer') { return $parentComponent; } $parentComponent = $parentComponent->getParentComponent(); } // If we didn't find a menu above our ref node, maybe we started in a heading. // Search down for a menu. $director = SiteDispatcher::getSiteDirector(); $rootNode = $director->getRootSiteComponent($refNode->getId()); $result = $rootNode->acceptVisitor(new GetMenuBelowSiteVisitor()); if ($result) { return $result; } // If we still haven't found a menu, then there isn't one in this site. // Nothing more we can do. throw new OperationFailedException("Cannot create a " . $componentType->getKeyword() . ". Site " . $rootNode->getSlot()->getShortname() . " - '" . $rootNode->getDisplayName() . "' does not have any menus to add this component to."); } else { $parentComponent = $refNode->getParentComponent(); while ($parentComponent) { if ($parentComponent->getComponentClass() == 'FlowOrganizer' || $parentComponent->getComponentClass() == 'MenuOrganizer') { return $parentComponent; } } // If we haven't found a flow organizer above the refNode, something is wrong. throw new OperationFailedException("Cannot create a " . $componentType->getKeyword() . ". A ContentOrganizer was not found above reference node " . $refNode->getId()); } }
/** * Visit any kind of SiteComponent and record its visibility * * @param object SiteComponent $siteComponent * @return array * @access public * @since 8/31/07 */ private function visitSiteComponent(SiteComponent $siteComponent) { $this->_visibleComponents[$siteComponent->getId()] = $siteComponent; $results = array(); $results['VisibleComponents'] = $this->_visibleComponents; $results['FilledTargetIds'] = $this->_filledTargetIds; return $results; }
/** * Answer true if the component passed is the direct child of the site nav organizer. * * @param object SiteComponent $siteComponent * @return boolean * @access private * @since 4/10/08 */ private function isChildOfSiteNavOrg(SiteComponent $siteComponent) { if (!isset($this->siteNavOrgId)) { $siteNav = $siteComponent->getDirector()->getRootSiteComponent($siteComponent->getId()); $this->siteNavOrgId = $siteNav->getOrganizer()->getId(); } if ($this->siteNavOrgId == $siteComponent->getParentComponent()->getId()) { return true; } else { return false; } }
/** * Search for SiteComponents with the title passed going up the hierarchy, then down * into siblings. * * @param object SiteComponent $startingComponent * @return mixed string or null * @access private * @since 12/3/07 */ private function searchUp(SiteComponent $startingComponent) { $parent = $startingComponent->getParentComponent(); if ($parent) { $result = $this->searchDown($parent); if (!is_null($result)) { return $result; } else { return $this->searchUp($parent); } } return null; }
public function __get($var) { $path = "{$this->_files_path}/{$var}"; if (is_dir($path)) { return new SiteDataFiles($this, $path); } return parent::__get($var); }
/** * Answer an array of RSS link info. * * @param object SiteComponent $siteComponent * @return array * @access private * @since 3/11/08 * @static */ private static function getLinks(SiteComponent $siteComponent) { $harmoni = Harmoni::instance(); $harmoni->request->startNamespace(null); $links = array(); // Content RSS $links[] = array('url' => SiteDispatcher::quickUrl("rss", "content", array('node' => $siteComponent->getId())), 'label' => _('Content RSS'), 'title' => _("Content RSS for") . " " . htmlentities(strip_tags($siteComponent->getDisplayName()))); // Comments RSS $links[] = array('url' => SiteDispatcher::quickUrl("rss", "comments", array('node' => $siteComponent->getId())), 'label' => _('Discussion RSS'), 'title' => _("Discussion RSS for") . " " . htmlentities(strip_tags($siteComponent->getDisplayName()))); $harmoni->request->endNamespace(); return $links; }
/** * Print the history link * * @param SiteComponent $siteComponent * @return void * @access public * @since 6/04/08 */ function printHistoryLink(SiteComponent $siteComponent) { print "\n\t\t\t\t<tr><td class='ui2_settingborder'>"; print "\n\t\t\t\t<div class='ui2_settingtitle'>"; print _('Edit History: ') . "\n\t\t\t\t</div>"; print "\n\t\t\t\t</td><td class='ui2_settingborder'>"; print "\n\t\t\t\t\t"; print "<a href='"; $harmoni = Harmoni::instance(); $harmoni->history->markReturnURL('view_history_' . $siteComponent->getId()); print SiteDispatcher::quickURL('versioning', 'view_history', array("node" => $siteComponent->getId(), 'returnModule' => $harmoni->request->getRequestedModule(), 'returnAction' => $harmoni->request->getRequestedAction())); print "'>"; print _("view history"); print " »</a>"; print "\n\t\t\t\t</td></tr>"; }
/** * Answer the root of the site * * @param SiteComponent $siteComponent * @return SiteNavBlockSiteComponent * @access public * @since 9/24/07 */ public function getSiteNavBlock(SiteComponent $siteComponent) { $parent = $siteComponent->getParentComponent(); if (is_null($parent)) { return $siteComponent; } else { return $this->getSiteNavBlock($parent); } }
/** * Apply common properties a siteComponent * * @param object SiteComponent $siteComponent * @param object DOMElement $element * @return void * @access protected * @since 1/22/08 */ protected function applyCommonProperties(SiteComponent $siteComponent, DOMElement $element) { if ($element->hasAttribute('showDisplayNames')) { $siteComponent->updateShowDisplayNames($element->getAttribute('showDisplayNames')); } if ($element->hasAttribute('showHistory')) { $siteComponent->updateShowHistorySetting($element->getAttribute('showHistory')); } if ($element->hasAttribute('showDates')) { $siteComponent->updateShowDatesSetting($element->getAttribute('showDates')); } if ($element->hasAttribute('showAttribution')) { $siteComponent->updateShowAttributionSetting($element->getAttribute('showAttribution')); } if ($element->hasAttribute('sortMethod')) { $siteComponent->updateSortMethodSetting($element->getAttribute('sortMethod')); } if ($element->hasAttribute('commentsEnabled')) { $siteComponent->updateCommentsEnabled($element->getAttribute('commentsEnabled')); } if ($element->hasAttribute('width')) { $siteComponent->updateWidth($element->getAttribute('width')); } if ($element->hasAttribute('showDates')) { $siteComponent->updateShowDatesSetting($element->getAttribute('showDates')); } if ($element->hasAttribute('showAttribution')) { $siteComponent->updateShowAttributionSetting($element->getAttribute('showAttribution')); } }
/** * Answer a wrapping div that triggers showing and hiding the border and * controls-bar for the item * * @param string $borderColor * @param SiteComponent $siteComponent * @return string * @access public * @since 1/16/07 */ function getBarPreHTML($borderColor, SiteComponent $siteComponent, $borderWidth = '2px') { ob_start(); print "\n<div class='site_component_wrapper'"; if (!$this->controlsAlwaysVisible()) { print " onmouseover='this.borderColor = \"{$borderColor}\"; showControls(this)'"; print " onmouseout='if (isValidMouseOut(this, event)) {hideControls(this);} '"; print " style='position: relative; border: {$borderWidth} solid transparent;'"; } else { print " style='position: relative; margin: 1px; border: {$borderWidth} solid {$borderColor};'"; } print " onclick='"; print "if (shiftAndMetaOrControl(event)) { "; print "toggleControls(document.get_element_by_id(\"controls_bar__" . $siteComponent->getId() . "\")); "; print "if (event.stopPropagation) { "; print "event.stopPropagation(); "; print "} else if (window.event) { "; print "window.event.cancelBubble = true; "; print "}"; print "}"; print "'"; print ">"; return ob_get_clean(); }
/** * Answer the history control * * @param object SiteComponent $siteComponent * @return string * @access public * @since 1/10/08 */ public function getHistory(SiteComponent $siteComponent) { ob_start(); $authZ = Services::getService("AuthZ"); $idManager = Services::getService("Id"); $harmoni = Harmoni::instance(); if ($authZ->isUserAuthorized($idManager->getId("edu.middlebury.authorization.modify"), $siteComponent->getQualifierId())) { $harmoni = Harmoni::instance(); $harmoni->history->markReturnURL('view_history_' . $siteComponent->getId()); $url = SiteDispatcher::quickURL('versioning', 'view_history', array("node" => $siteComponent->getId(), 'returnModule' => $harmoni->request->getRequestedModule(), 'returnAction' => $harmoni->request->getRequestedAction())); print "\n\t\t\t\t\t<a href='" . $url . "'>"; print _("history"); print "</a>"; } return ob_get_clean(); }
/** * Save a role for a hierarchy node * * @param object SiteComponent $component * @param object SegueRole $role * @return <##> * @access public * @since 11/16/07 */ public function saveRole(SiteComponent $component, SegueRole $role) { $roleMgr = SegueRoleManager::instance(); $idMgr = Services::getService("Id"); $agentId = $this->getAgentId(); $componentId = $idMgr->getId($component->getId()); // Ensure that Everyone or Institute are not set to admin $everyoneId = $idMgr->getId('edu.middlebury.agents.everyone'); $instituteId = $idMgr->getId('edu.middlebury.institute'); if ($agentId->isEqual($everyoneId) || $agentId->isEqual($instituteId)) { if ($role->getIdString() == 'admin') { $role = $roleMgr->getRole('editor'); } } // printpre("Saving role '".$role->getIdString()."' for ".$agentId." at ".$component->getDisplayName()); // Find the parent node. $parent = $component->getParentComponent(); if ($parent) { $parentQualifierId = $parent->getQualifierId(); $parentRole = $roleMgr->getAgentsRole($agentId, $parentQualifierId, true); } // Apply the role or clear it if it is less than the implicitly given role. try { if (isset($parentRole) && $role->isLessThanOrEqualTo($parentRole)) { $roleMgr->clearRoleAZs($agentId, $componentId); // printpre("Clearing duplicate role '".$role->getIdString()."' for ".$agentId." at ".$component->getDisplayName()); } else { $role->apply($agentId, $componentId); } } catch (PermissionDeniedException $e) { } return true; }
/** * Answer the NavBlock above the node passed * * @param SiteComponent $siteComponent * @return NavBlockSiteComponent * @access protected * @since 8/27/08 */ protected function getParentNavBlock(SiteComponent $siteComponent) { $parent = $siteComponent->getParentComponent(); if (!$parent) { throw new OperationFailedException("No Parent Component."); } switch ($parent->getComponentClass()) { case 'NavBlock': case 'SiteNavBlock': return $parent; default: return $this->getParentNavBlock($parent); } }
/** * Save the displayType options * * @param object SiteComponent $siteComponent * @param array $values * @return boolean * @access protected * @since 5/12/08 */ protected function saveDisplayType(SiteComponent $siteComponent, array $values) { $siteComponent->setDisplayType($values['displayType']); $siteComponent->setHeadingDisplayType($values['headingDisplayType']); return true; }
/** * Destructor. */ public function __destruct() { if ($this->dbh_ro) { $this->dbh_ro->disconnect(); } if ($this->dbh_rw) { $this->dbh_rw->disconnect(); } parent::__destruct(); }
/** * Add a qualifierId * * @param object SiteComponent $siteComponent * @return void * @access private * @since 11/14/07 */ private function addQualifierForSiteComponent(SiteComponent $siteComponent, $isRoot = false) { $qualifierId = $siteComponent->getQualifierId(); $authZ = Services::getService('AuthZ'); $idMgr = Services::getService('Id'); // Skip if we've added it already if (in_array($qualifierId->getIdString(), $this->qualifierIdsAdded)) { return; } $this->qualifierIdsAdded[] = $qualifierId->getIdString(); // Skip any printing of the node if the current user has no authorization // to view the node or any descendents. if (!$authZ->isUserAuthorized($idMgr->getId("edu.middlebury.authorization.view"), $qualifierId) && !$authZ->isUserAuthorizedBelow($idMgr->getId("edu.middlebury.authorization.view_authorizations"), $qualifierId)) { return; } $roleMgr = SegueRoleManager::instance(); $valuesHidden = false; try { $role = $roleMgr->getAgentsRole($this->agentId, $qualifierId); } catch (PermissionDeniedException $e) { $role = $roleMgr->getAgentsRole($this->agentId, $qualifierId, true); $valuesHidden = true; } // Create the property with the current role $title = strip_tags($siteComponent->getDisplayName()); if (!strlen($title)) { $title = _("Untitled"); } if ($isRoot) { $this->property->addField($qualifierId->getIdString(), $title, $role->getIdString(), ">="); } else { $parentQualifierId = $siteComponent->getParentComponent()->getQualifierId(); $this->property->addChildField($parentQualifierId->getIdString(), $qualifierId->getIdString(), $title, $role->getIdString(), ">="); } // Make the values hidden if the current user has no authorization // to view the authorizations of the node. if ($valuesHidden) { $this->property->makeValuesHidden($qualifierId->getIdString()); } // Disable options that are precluded by implicit authorizations // coming from group membership. $groupRole = $roleMgr->getGroupImplictRole($this->agentId, $qualifierId, true); try { $groupIds = $groupRole->getAgentsCausing(); $names = array(); $agentMgr = Services::getService("Agent"); foreach ($groupIds as $id) { $group = $agentMgr->getAgentOrGroup($id); if ($group->getDisplayName()) { $names[] = "'" . $group->getDisplayName() . "'"; } else { $names[] = "'" . $id->getIdString() . "'"; } } $groupNames = ' (' . implode(", ", $names) . ")"; } catch (Exception $e) { $groupNames = ''; } foreach ($roleMgr->getRoles() as $role) { if ($role->isLessThan($groupRole)) { $message = _("You cannot remove the '%1' role because '%2' is a member a group%3 that has been given the '%1' role."); $message = str_replace("%1", $groupRole->getDisplayName(), $message); $message = str_replace("%2", $this->agent->getDisplayName(), $message); $message = str_replace("%3", $groupNames, $message); $this->property->makeDisabled($qualifierId->getIdString(), $role->getIdString(), $message); } } // Disable options that are precluded by implicit authorizations // coming from above the site in the AuthZ hierarchy. foreach ($roleMgr->getRoles() as $role) { if ($role->isLessThan($this->siteImplicitRole)) { $this->property->makeDisabled($qualifierId->getIdString(), $role->getIdString(), $this->siteImplicitRoleMessage); } } // Disable options where modify_authorization is not allowed. $authN = Services::getService('AuthN'); $adminRole = $roleMgr->getRole('admin'); if (!$authZ->isUserAuthorized($idMgr->getId("edu.middlebury.authorization.modify_authorizations"), $qualifierId)) { foreach ($roleMgr->getRoles() as $role) { $this->property->makeDisabled($qualifierId->getIdString(), $role->getIdString(), _("You are not authorized to change authorization here.")); } } else { if ($authN->getFirstUserId()->isEqual($this->agentId)) { foreach ($roleMgr->getRoles() as $role) { if ($role->isLessThan($adminRole)) { $this->property->makeDisabled($qualifierId->getIdString(), $role->getIdString(), _("You cannot remove your own Administrator access.")); } } } } // Disable the Administrator role for everyone and institute. $nonAdminAgents = array(); $nonAdminAgents[] = $idMgr->getId('edu.middlebury.agents.everyone'); $nonAdminAgents[] = $idMgr->getId('edu.middlebury.agents.anonymous'); $nonAdminAgents[] = $idMgr->getId('edu.middlebury.agents.users'); $nonAdminAgents[] = $idMgr->getId('edu.middlebury.institute'); foreach ($nonAdminAgents as $agentId) { if ($agentId->isEqual($this->agentId)) { $message = _("You cannot give the '%1' role to '%2' for security reasons."); $message = str_replace("%1", $adminRole->getDisplayName(), $message); $message = str_replace("%2", $this->agent->getDisplayName(), $message); $this->property->makeDisabled($qualifierId->getIdString(), 'admin', $message); break; } } }
/** * get all actions in the node * * @return array of Participation_Action * @access public * @since 1/23/09 */ public function getActions() { $visitor = new ParticipationSiteVisitor(); $this->_node->acceptVisitor($visitor); return $visitor->getActions(); }
/** * Print the reorder form * * @param object SiteComponent $siteComponent * @return void * @access public * @since 9/20/07 */ public function printReorderForm(SiteComponent $siteComponent) { $authZ = Services::getService("AuthZ"); $idManager = Services::getService("Id"); $harmoni = Harmoni::instance(); $parent = $siteComponent->getParentComponent(); if ($authZ->isUserAuthorized($idManager->getId("edu.middlebury.authorization.modify"), $parent->getQualifierId())) { $url = $harmoni->request->quickURL($this->module, 'reorder', array('returnNode' => SiteDispatcher::getCurrentNodeId(), 'returnAction' => $this->action)); $harmoni->request->startNamespace('reorder'); $organizer = $siteComponent->getParentComponent(); $myCell = $organizer->getCellForSubcomponent($siteComponent); print "\n\t\t\t\t\t<form class='ui1_controls reorder_form_" . $parent->getId() . "' action='" . $url . "' method='post' style='display: none'>"; print "\n\t<input type='hidden' name='" . RequestContext::name('node') . "' value='" . $siteComponent->getId() . "' />"; print "\n\t<select name='" . RequestContext::name('position') . "' onchange='this.form.submit();'>"; for ($i = 0; $i < $organizer->getTotalNumberOfCells(); $i++) { print "\n\t\t<option value='{$i}'"; if ($myCell == $i) { print " selected='selected'"; } print ">" . ($i + 1) . "</option>"; } print "\n\t</select>"; print "\n\t<input type='button' onclick=\""; print "hideReorder('" . $parent->getId() . "'); "; print "\" value='" . _("Cancel") . "'/>"; print "\n\t\t\t\t\t</form>"; $harmoni->request->endNamespace(); } }
/** * Move a component. * * @param object SiteComponent * @return void * @access protected * @since 8/4/08 */ protected function moveComponent(SiteComponent $siteComponent) { // Check that we are allow to remove the source component $authZ = Services::getService("AuthZ"); $idManager = Services::getService("Id"); if (!$authZ->isUserAuthorized($idManager->getId("edu.middlebury.authorization.remove_children"), $siteComponent->getParentComponent()->getQualifierId())) { throw new PermissionDeniedException("You are not authorized to remove this node from its original location."); } $oldParent = $siteComponent->getParentComponent(); $oldParent->detatchSubcomponent($siteComponent); $this->getDestinationComponent()->addSubcomponent($siteComponent); return $siteComponent; }
/** * Answer the title for a nodeId * * @param string $nodeIdString * @param object SiteComponent $startingSiteComponent * @return string * @access public * @since 12/3/07 */ public function getNodeTitle($nodeIdString, SiteComponent $startingSiteComponent) { $director = $startingSiteComponent->getDirector(); $node = $director->getSiteComponentById($nodeIdString); if (strlen($node->getDisplayName())) { return $node->getDisplayName(); } else { return _("untitled"); } }
/** * Print direction controls * * @param SiteComponent $siteComponent * @return void * @access public * @since 5/13/07 */ function printDirection($siteComponent, $step) { $property = $step->addComponent('direction', new WSelectList()); $property->setValue($siteComponent->getDirection()); $directions = array("Left-Right/Top-Bottom" => _("Left-Right/Top-Bottom"), "Top-Bottom/Left-Right" => _("Top-Bottom/Left-Right"), "Right-Left/Top-Bottom" => _("Right-Left/Top-Bottom"), "Top-Bottom/Right-Left" => _("Top-Bottom/Right-Left")); foreach ($directions as $direction => $label) { $property->addOption($direction, $label); } print "\n\t\t\t\t<p style='white-space: nowrap; font-weight: bold;'>"; print "\n\t\t\t\t\t" . _('Index Direction: ') . "[[direction]]"; print "\n\t\t\t\t</p>"; }
/** * get url of node that action is applied to * * @return string * @access public * @since 1/23/09 */ public function getTargetUrl() { return SiteDispatcher::quickURL('view', 'html', array('node' => $this->_node->getId())); }
/** * Print width controls * * @param SiteComponent $siteComponent * @return void * @access public * @since 4/17/06 */ function printWidth($siteComponent, $step) { $property = $step->addComponent('width', new WTextField()); $property->setValue($siteComponent->getWidth()); $property->setSize(6); $property->setErrorRule(new WECRegex("^([0-9]+(px|%))?\$")); $property->setErrorText(_("Must be blank or in either pixel or percent form; e.g. '150px', 200px', '100%', '50%', etc.")); print "<div style='font-weight: bold;'>" . _('Maximum Width Guideline: '); print "[[width]]"; print "</div>"; print "<div style='font-size: smaller;'>" . _("If desired, enter a width in either pixel or percent form; e.g. '150px', 200px', '100%', '50%', etc.<br/><strong>Note:</strong> This width is a guideline and is not guarenteed to be enforced. Content will fill the page, using this guideline where possible. Content inside of this container may stretch it beyond the specified width.") . "</div>"; }
/** * get url of node that action is applied to * * @return string * @access public * @since 1/23/09 */ public function getTargetUrl() { $url = SiteDispatcher::quickURL('versioning', 'compare_versions', array('node' => $this->_node->getId(), 'late_rev' => $this->_version->getVersionId())); return $url; }
/** * Apply the block heading style. * * @param SiteComponent $siteComponent * @return void * @access public * @since 6/45/08 */ public function applyBlockHeadingDisplayType(SiteComponent $siteComponent) { if (RequestContext::value('headingDisplayType') && RequestContext::value('headingDisplayType') !== $siteComponent->getHeadingDisplayType()) { $siteComponent->setHeadingDisplayType(RequestContext::value('headingDisplayType')); } }
/** * Answer true if the current user is authorized to export this node. * * @param SiteComponent $siteComponent * @return boolean * @access protected */ protected function isAuthorizedToExportComments(SiteComponent $siteComponent) { $authZ = Services::getService("AuthZ"); $idMgr = Services::getService("Id"); return $authZ->isUserAuthorized($idMgr->getId('edu.middlebury.authorization.view_comments'), $siteComponent->getQualifierId()); }
/** * Print Node info html * * @param object SiteComponent $siteComponent * @return void * @access protected * @since 3/17/08 */ protected function printNodeInfo(SiteComponent $siteComponent, $inMenu = false) { $harmoni = Harmoni::instance(); print $this->getTabs() . "\t"; if ($siteComponent->getId() == SiteDispatcher::getCurrentNodeId()) { print "<div class='info current'>"; } else { print "<div class='info'>"; } print $this->getTabs() . "\t\t"; print "<div class='title'>"; $nodeUrl = SiteDispatcher::quickURL('view', 'html', array('node' => $siteComponent->getId())); if (!$inMenu) { print "<a href='" . $nodeUrl . "' "; print ' onclick="'; print "if (window.opener) { "; print "window.opener.location = this.href; "; print "return false; "; print '}" '; print " title='" . _("View this node") . "'>"; } print $siteComponent->getDisplayName(); if (!$inMenu) { print "</a>"; } print "</div>"; $nodeDescription = HtmlString::withValue($siteComponent->getDescription()); $nodeDescription->stripTagsAndTrim(5); print $this->getTabs() . "\t\t"; print "<div class='description'>" . $nodeDescription->stripTagsAndTrim(20) . "</div>"; print $this->getTabs() . "\t"; print "</div>"; }
/** * Answer the link to add a particular SiteComponent to the selection * * @param object SiteComponent $siteComponent * @return string XHTML * @access public * @since 7/31/08 */ public function getAddLink(SiteComponent $siteComponent) { $this->addHeadJavascript(); $harmoni = Harmoni::instance(); $harmoni->request->startNamespace("selection"); ob_start(); print "\n\t\t<a "; print " id='selection_add_link-" . $siteComponent->getId() . "'"; print " class='"; if ($this->isSiteComponentInSet($siteComponent)) { print "Selection_add_link_selected"; } else { print "Selection_add_link_deselected"; } print "' "; print " style='cursor: pointer;'"; print " href='#' "; print " onclick=\"Segue_Selection.instance().toggleComponent({"; print "id: '" . $siteComponent->getId() . "', "; print "type: '" . $siteComponent->getComponentClass() . "', "; print "displayName: '" . addslashes(str_replace('"', '"', preg_replace('/\\s+/', ' ', strip_tags($siteComponent->getDisplayName())))) . "' "; print "}); return false;\""; print 'title="' . _("Copy to your Selection") . '" '; print ">" . _('Copy'); print "</a>"; $harmoni->request->endNamespace(); return ob_get_clean(); }
/** * Remove a subcomponent, but don't delete it from the director completely. * This is used to remove nested menus. * * @param object SiteComponent $subcomponent * @return void * @access public * @since 9/22/06 */ function detatchSubcomponent(SiteComponent $subcomponent) { $this->_element->removeChild($subcomponent->getElement()); $this->_saveXml(); }