/**
  * Returns true if the passed {@link WizardComponent} validates against this rule.
  * @param ref object $component
  * @access public
  * @return boolean
  */
 function checkValue($component)
 {
     $doc = new Harmoni_DOMDocument();
     try {
         $doc->loadXML($component->getAllValues());
     } catch (DOMException $e) {
         $component->setErrorText($e->getMessage());
         return false;
     }
     try {
         $doc->schemaValidateWithException($this->xmlSchemaPath);
     } catch (ValidationFailedException $e) {
         $component->setErrorText($e->getMessage());
         return false;
     }
     return true;
 }
 /**
  * Answer a element that represents the content for this Block
  * 
  * @return object DOMElement
  * @access protected
  * @since 2/12/08
  */
 protected function getContentElement(DOMElement $mediaElement)
 {
     // Content/Description
     $descElement = $this->sourceXPath->query('./description', $this->sourceElement)->item(0);
     $descHtml = $this->getStringValue($descElement);
     $descHtml = $this->rewriteLocalLinks($descHtml);
     // Content
     $filename = $this->getStringValue($this->getSingleSourceElement('./filename', $this->sourceElement));
     $currentContent = $this->doc->createElement('currentContent');
     try {
         $fileUrlString = $this->attachFile($filename, $mediaElement);
         $fileUrlString = str_replace('asset_id', 'assetId', $fileUrlString);
         $fileUrlString = str_replace('record_id', 'recordId', $fileUrlString);
         // 			$fileUrlString = str_replace('&', '&', $fileUrlString);
         $doc = new Harmoni_DOMDocument();
         $root = $doc->appendChild($doc->createElement('AudioPlayerPlugin'));
         $fileNode = $root->appendChild($doc->createElement('File'));
         $fileNode->setAttribute('show_download_link', 'true');
         $idNode = $fileNode->appendChild($doc->createElement('Id', $fileUrlString));
         $content = $currentContent->appendChild($this->createCDATAElement('content', $doc->saveXMLWithWhitespace()));
     } catch (MissingNodeException $e) {
         $content = $currentContent->appendChild($this->createCDATAElement('content', ''));
     } catch (Segue1To2_MissingFileException $e) {
         $content = $currentContent->appendChild($this->createCDATAElement('content', ''));
     }
     $rawDesc = $currentContent->appendChild($this->createCDATAElement('rawDescription', $this->cleanHtml($descHtml)));
     return $currentContent;
 }
 /**
  * Answer a element that represents the content for this Block
  * 
  * @return object DOMElement
  * @access protected
  * @since 2/12/08
  */
 protected function getContentElement(DOMElement $mediaElement)
 {
     // Content/Description
     $descElement = $this->sourceXPath->query('./description', $this->sourceElement)->item(0);
     if ($descElement) {
         $descHtml = $this->cleanHtml($this->getStringValue($descElement));
     } else {
         $descHtml = '';
     }
     $descHtml = $this->rewriteLocalLinks($descHtml);
     $title = $this->getDisplayName();
     $url = $this->rewriteLocalLinks($this->getStringValue($this->getSingleSourceElement('./url', $this->sourceElement)));
     // Content
     $dataDoc = new Harmoni_DOMDocument();
     $root = $dataDoc->appendChild($dataDoc->createElement('RssFeedPlugin'));
     $feed = $root->appendChild($dataDoc->createElement('RssFeed'));
     $urlElement = $feed->appendChild($dataDoc->createElement('Url', str_replace('&', '&', $url)));
     $sourceUrlElement = $this->getSingleSourceElement('./url', $this->sourceElement);
     $this->setAttributes($sourceUrlElement, $feed);
     $currentContent = $this->doc->createElement('currentContent');
     $currentContent->appendChild($this->createCDATAElement('content', $dataDoc->saveXMLWithWhitespace()));
     $currentContent->appendChild($this->doc->createElement('rawDescription', ''));
     return $currentContent;
 }
Example #4
0
 /**
  * Update the options XML with a new document
  * 
  * @param object Harmoni_DOMDocument $optionsDocument
  * @return null
  * @access public
  * @since 5/15/08
  */
 public function updateOptionsDocument(Harmoni_DOMDocument $optionsDocument)
 {
     if (!$this->canModify()) {
         throw new PermissionDeniedException();
     }
     // Unset the options if we have an empty document.
     if (!isset($optionsDocument->documentElement)) {
         $this->updateThemeDataByType('options.xml', '');
         return;
     }
     // Validate any options document given.
     $optionsDocument->schemaValidateWithException(HARMONI . '/Gui2/theme_options.xsd');
     $this->updateThemeDataByType('options.xml', $optionsDocument->saveXML());
 }
 /**
  * Answer an XML document for the options for this theme
  * 
  * @return object Harmoni_DOMDocument
  * @access public
  * @since 5/15/08
  */
 public function getOptionsDocument()
 {
     $options = new Harmoni_DOMDocument();
     if (file_exists($this->optionsPath)) {
         $options->preserveWhiteSpace = false;
         $options->load($this->optionsPath);
         $options->schemaValidateWithException(dirname(__FILE__) . '/theme_options.xsd');
     }
     return $options;
 }
Example #6
0
 /**
  * Save the advanced step
  * 
  * @param array $values
  * @return boolean
  * @access protected
  * @since 5/15/08
  */
 protected function saveAdvancedStep(array $values)
 {
     $component = $this->getSiteComponent();
     $theme = $component->getTheme();
     if (isset($values['create_copy']) && $values['create_copy']) {
         // Get the first source that supports admin.
         $guiMgr = Services::getService('GUIManager');
         foreach ($guiMgr->getThemeSources() as $source) {
             if ($source->supportsThemeAdmin()) {
                 $adminSession = $source->getThemeAdminSession();
                 $newTheme = $adminSession->createCopy($theme);
                 $component->updateTheme($newTheme);
                 $this->createdCopy = true;
                 return true;
             }
         }
         // Nowhere to copy to.
         print "<p>" . _("Error: No available source to copy this theme to.") . "</p>";
         return false;
     }
     /*********************************************************
      * Return if we are not editing advanced options
      *********************************************************/
     if (!$theme->supportsModification()) {
         return true;
     }
     $modSess = $theme->getModificationSession();
     if (!$modSess->canModify()) {
         return true;
     }
     /*********************************************************
      * Info
      *********************************************************/
     $modSess->updateDisplayName($values['display_name']);
     $modSess->updateDescription($values['description']);
     if (!is_null($values['thumbnail']['tmp_name'])) {
         $file = new Harmoni_Filing_FileSystemFile($values['thumbnail']['tmp_name']);
         $file->setMimeType($values['thumbnail']['type']);
         $modSess->updateThumbnail($file);
     }
     /*********************************************************
      * CSS and HTML
      *********************************************************/
     $modSess->updateGlobalCss($values['global_css']);
     foreach ($modSess->getComponentTypes() as $type) {
         $modSess->updateCssForType($type, $values[$type . '-css']);
         $modSess->updateTemplateForType($type, $values[$type . '-html']);
     }
     /*********************************************************
      * Images
      *********************************************************/
     $missingImages = $theme->getImages();
     foreach ($values['images'] as $imageVal) {
         // Add new images
         if ($imageVal['image']['tmp_name']) {
             $file = new Harmoni_Filing_FileSystemFile($imageVal['image']['tmp_name']);
             $file->setMimeType($imageVal['image']['type']);
             $theme->addImage($file, $imageVal['image']['name'], $imageVal['path_prefix']);
         } else {
             if ($imageVal['path_prefix'] != $imageVal['orig_path_prefix']) {
                 $image = $modSess->getImage($imageVal['orig_path_prefix'] . '/' . $imageVal['image']['name']);
                 $image->setPath($imageVal['path_prefix'] . '/' . $imageVal['image']['name']);
             }
         }
         // Mark images as existing
         if ($imageVal['path_prefix']) {
             $path = $imageVal['path_prefix'] . '/' . $imageVal['image']['name'];
         } else {
             $path = $imageVal['image']['name'];
         }
         foreach ($missingImages as $key => $image) {
             if ($image->getPath() == $path) {
                 unset($missingImages[$key]);
             }
         }
     }
     // Remove any images that were removed.
     foreach ($missingImages as $image) {
         $image->delete();
     }
     /*********************************************************
      * Options
      *********************************************************/
     $optionsString = trim($values['options']);
     $optionsDoc = new Harmoni_DOMDocument();
     $optionsDoc->preserveWhiteSpace = false;
     if (strlen($optionsString) && $optionsString != '<?xml version="1.0"?>') {
         try {
             $optionsDoc->loadXML($values['options']);
         } catch (DOMException $e) {
             print "<strong>" . _("Error in Options Definition:") . " </strong>";
             print $e->getMessage();
             return false;
         }
     }
     try {
         $modSess->updateOptionsDocument($optionsDoc);
     } catch (ValidationFailedException $e) {
         print "<strong>" . _("Error in Options Definition:") . " </strong>";
         print $e->getMessage();
         return false;
     }
     return true;
 }
Example #7
0
 /**
  * Load feed data, convert and clean it, and return its string value.
  * 
  * @param string $url
  * @return string RSS xml
  * @access protected
  * @since 7/8/08
  */
 protected function loadFeedXml($url)
 {
     $feedData = @file_get_contents($url);
     if (!strlen($feedData)) {
         throw new OperationFailedException("Could not access feed, '" . $url . "'.");
     }
     $feed = new DOMDocument();
     // If the encoding is not UTF-8, convert the document
     if (preg_match('/^<\\?xml .*encoding=[\'"]([a-zA-Z0-9-]+)[\'"].*\\?>/m', $feedData, $matches)) {
         $encoding = $matches[1];
         if (strtoupper($encoding) != 'UTF8' && strtoupper($encoding) != 'UTF-8') {
             $feedData = mb_convert_encoding($feedData, 'UTF-8', strtoupper($encoding));
             $feedData = preg_replace('/^(<\\?xml .*encoding=[\'"])([a-zA-Z0-9-]+)([\'"].*\\?>)/m', '\\1UTF-8\\3', $feedData);
         }
     }
     // Convert any non-UTF-8 characters
     $string = String::withValue($feedData);
     $string->makeUtf8();
     $feedData = $string->asString();
     if (!@$feed->loadXML($feedData)) {
         throw new OperationFailedException("Invalid feed data: \"" . $feedData . "\" for URL: " . $url);
     }
     // Handle any format conversions
     $feed = $this->convertToRss($feed);
     // Validate Feed.
     // 		$tmpFeed = $feed;
     // 		$feed = new Harmoni_DOMDocument;
     // 		$feed->loadXML($tmpFeed->saveXML());
     // 		unset($tmpFeed);
     // 		$feed->schemaValidateWithException(dirname(__FILE__).'/rss-2_0-lax.xsd');
     // Run through the titles, authors, and descriptions and clean out any unsafe HTML
     foreach ($feed->getElementsByTagName('title') as $element) {
         $element->nodeValue = strip_tags(htmlspecialchars_decode($element->nodeValue));
     }
     foreach ($feed->getElementsByTagName('author') as $element) {
         $element->nodeValue = strip_tags(htmlspecialchars_decode($element->nodeValue));
     }
     foreach ($feed->getElementsByTagName('comments') as $element) {
         $element->nodeValue = htmlentities(strip_tags(html_entity_decode($element->nodeValue)));
     }
     foreach ($feed->getElementsByTagName('link') as $element) {
         $element->nodeValue = htmlentities(strip_tags(html_entity_decode($element->nodeValue)));
     }
     foreach ($feed->getElementsByTagName('description') as $description) {
         $html = HtmlString::fromString(htmlspecialchars_decode($description->nodeValue));
         $html->cleanXSS();
         $description->nodeValue = htmlspecialchars($html->asString());
     }
     // Move the feed into a dom document.
     $tmpFeed = $feed;
     $feed = new Harmoni_DOMDocument();
     $feed->loadXML($tmpFeed->saveXML());
     unset($tmpFeed);
     // Validate the feed again
     // 		$feed->schemaValidateWithException(dirname(__FILE__).'/rss-2_0-lax.xsd');
     // Just ensure a few basic things:
     if (!$feed->documentElement->nodeName == 'rss') {
         throw new DOMDocumentException("Feed root must be an rss element");
     }
     // Check for channels
     foreach ($feed->documentElement->childNodes as $element) {
         if ($element->nodeType == 1 && $element->nodeName != 'channel') {
             throw new DOMDocumentException("'" . $node->nodeName . "' is not expected, expecting 'channel'.");
         }
     }
     // Check dates
     foreach ($feed->getElementsByTagName('pubdate') as $element) {
         if (!preg_match('/(((Mon)|(Tue)|(Wed)|(Thu)|(Fri)|(Sat)|(Sun)), *)?\\d\\d? +((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec)) +\\d\\d(\\d\\d)? +\\d\\d:\\d\\d(:\\d\\d)? +(([+\\-]?\\d\\d\\d\\d)|(UT)|(GMT)|(EST)|(EDT)|(CST)|(CDT)|(MST)|(MDT)|(PST)|(PDT)|\\w)/', $element->nodeValue)) {
             throw new DOMDocumentException("'" . $element->nodeValue . "' is not a valid date.");
         }
     }
     return $feed->saveXMLWithWhitespace();
 }
Example #8
0
 /**
  * Save our results. Tearing down and unsetting the Wizard is handled by
  * in {@link runWizard()} and does not need to be implemented here.
  * 
  * @param string $cacheName
  * @return boolean TRUE if save was successful and tear-down/cleanup of the
  *		Wizard should ensue.
  * @access public
  * @since 1/28/08
  */
 public function saveWizard($cacheName)
 {
     $wizard = $this->getWizard($cacheName);
     if (!$wizard->validate()) {
         return false;
     }
     $values = $wizard->getAllValues();
     // 		printpre($values);
     // 		return false;
     try {
         if (!defined('DATAPORT_TMP_DIR')) {
             throw new Exception("DATAPORT_TMP_DIR must be defined in the Segue configuration.");
         }
         $archivePath = $values['mode']['backup_file']['tmp_name'];
         $archiveName = basename($archivePath);
         $decompressDir = DATAPORT_TMP_DIR . '/' . $archiveName . '_source';
         if (!$values['mode']['backup_file']['size']) {
             throw new Exception("File upload error - archive was not successfully uploaded and has no size.");
         }
         $this->decompressArchive($archivePath, $decompressDir);
         // Do the import
         $director = SiteDispatcher::getSiteDirector();
         $doc = new Harmoni_DOMDocument();
         $doc->load($decompressDir . "/site.xml");
         // Validate the document contents
         $doc->schemaValidateWithException(MYDIR . "/doc/raw/dtds/segue2-site.xsd");
         $mediaDir = $decompressDir;
         switch ($values['mode']['trust']) {
             case 'all':
                 $class = 'DomImportSiteVisitor';
                 break;
             case 'time_only':
                 $class = 'UntrustedAgentDomImportSiteVisitor';
                 break;
             default:
                 $class = 'UntrustedAgentAndTimeDomImportSiteVisitor';
         }
         $importer = new $class($doc, $mediaDir, $director);
         if ($values['mode']['roles'] == '1') {
             $importer->enableRoleImport();
         }
         if ($values['mode']['comments'] == '0') {
             $importer->disableCommentImport();
         }
         if (isset($values['owners'])) {
             $idMgr = Services::getService('Id');
             foreach ($values['owners']['admins'] as $adminIdString) {
                 $importer->addSiteAdministrator($idMgr->getId($adminIdString));
             }
         }
         $importer->enableStatusOutput();
         $site = $importer->importAtSlot($values['mode']['slotname']);
         // Delete the uploaded file
         unlink($archivePath);
         // Delete the decompressed Archive
         $this->deleteRecursive($decompressDir);
     } catch (Exception $importException) {
         // Delete the uploaded file
         try {
             if (file_exists($archivePath)) {
                 unlink($archivePath);
             }
         } catch (Exception $deleteException) {
             print "\n<div>\n\t";
             print $deleteException->getMessage();
             print "\n</div>";
         }
         // Delete the decompressed Archive
         try {
             if (file_exists($decompressDir)) {
                 $this->deleteRecursive($decompressDir);
             }
         } catch (Exception $deleteException) {
             print "\n<div>\n\t";
             print $deleteException->getMessage();
             print "\n</div>";
         }
         print "\n<div>\n\t";
         print $importException->getMessage();
         // 			print HarmoniErrorHandler::printDebugBacktrace($importException->getTrace());
         print "\n</div>";
         $wizard->backupFile->setValue(array('name' => null, 'size' => null, 'type' => null));
         /*********************************************************
          * Log the failure
          *********************************************************/
         if (Services::serviceRunning("Logging")) {
             $loggingManager = Services::getService("Logging");
             $log = $loggingManager->getLogForWriting("Segue");
             $formatType = new Type("logging", "edu.middlebury", "AgentsAndNodes", "A format in which the acting Agent[s] and the target nodes affected are specified.");
             $priorityType = new Type("logging", "edu.middlebury", "Error", "Recoverable errors.");
             $item = new AgentNodeEntryItem("Create Site", "Failure in importing site for placeholder, '" . $values['mode']['slotname'] . "'. " . $importException->getMessage());
             $item->setBacktrace($importException->getTrace());
             $item->addTextToBactrace("Archive Upload: " . printpre($values['mode']['backup_file'], true));
             $log->appendLogWithTypes($item, $formatType, $priorityType);
         }
         return false;
     }
     /*********************************************************
      * Log the success
      *********************************************************/
     if (Services::serviceRunning("Logging")) {
         $loggingManager = Services::getService("Logging");
         $log = $loggingManager->getLogForWriting("Segue");
         $formatType = new Type("logging", "edu.middlebury", "AgentsAndNodes", "A format in which the acting Agent[s] and the target nodes affected are specified.");
         $priorityType = new Type("logging", "edu.middlebury", "Event_Notice", "Normal events.");
         $item = new AgentNodeEntryItem("Create Site", "Site imported for placeholder, '" . $values['mode']['slotname'] . "'.");
         $item->addNodeId($site->getQualifierId());
         $log->appendLogWithTypes($item, $formatType, $priorityType);
     }
     return true;
 }
 /**
  * Dumps the internal XML tree back into a string with added whitespace 
  * (new-lines and tabbing).
  * 
  * @param optional object DOMNode $node
  * @param optional int $options
  * @return string
  * @access public
  * @since 1/23/08
  */
 public function saveXMLWithWhitespace(DOMNode $node = null, $options = null)
 {
     $doc = new Harmoni_DOMDocument();
     if (is_null($node)) {
         if ($this->documentElement) {
             $doc->appendChild($doc->importNode($this->documentElement, true));
         }
     } else {
         $doc->appendChild($doc->importNode($node, true));
     }
     $doc->addWhitespaceToDocument();
     return $doc->saveXML($doc->documentElement, $options);
 }
Example #10
0
 /**
  * Convert a Segue1 export into a Segue2 export
  * 
  * @param string $destFilePath The path that Segue2 export file will be placed in.
  * @param string $relativeOutputFilePath The output file path relative to
  * 				encode into the xml output.	 * @return object DOMDocument The Segue2 export document
  * @access protected
  * @since 3/14/08
  */
 protected function convertFrom1To2($destFilePath, $relativeOutputFilePath)
 {
     try {
         $sourcePath = $this->downloadSegue1Export();
         $sourceFilePath = $sourcePath . "/media";
         $sourceDocPath = $sourcePath . "/site.xml";
         $sourceDoc = new Harmoni_DOMDocument();
         $sourceDoc->load($sourceDocPath);
         $converter = new Segue1To2Director($destFilePath, $relativeOutputFilePath);
         $outputDoc = $converter->convert($sourceDoc, $sourceFilePath);
         // Delete the source directory
         $this->cleanUpSourcePath($sourcePath);
     } catch (DOMException $e) {
         $size = ByteSize::withValue(filesize($sourceDocPath));
         $this->cleanUpSourcePath($sourcePath);
         if ($e->getCode() === DOMSTRING_SIZE_ERR) {
             throw new DOMException("The export of '" . $this->getSourceSlotName() . "' is too large to load (" . $size->asString() . ") or contains an element that is too large to load.", DOMSTRING_SIZE_ERR);
         }
         throw $e;
     } catch (Exception $e) {
         if (isset($sourcePath)) {
             $this->cleanUpSourcePath($sourcePath);
         }
         throw $e;
     }
     return $outputDoc;
 }
Example #11
0
 /**
  * Answer a DOM XML Document for the given topic
  * 
  * @param string $topic
  * @return object DOMDocument
  * @access public
  * @since 12/9/05
  */
 function getTopicXmlDocument($topic)
 {
     $document = new Harmoni_DOMDocument();
     $tocPart = $this->_tableOfContents->getTableOfContentsPart($topic);
     if (!$tocPart || !$tocPart->file || !file_exists($tocPart->file)) {
         ob_start();
         print "<html>\n";
         print "\t<head>\n";
         print "\t\t<title>";
         print dgettext("polyphony", "Topic Not Found");
         print "</title>\n";
         print "\t</head>\n";
         print "\t<body>\n";
         print "\t\t<h1>";
         print dgettext("polyphony", "Topic Not Found");
         print "</h1>\n";
         print "\t\t<p>";
         print dgettext("polyphony", "The topic that you requested was not found.");
         print "</p>\n";
         print "\t</body>\n";
         print "</html>\n";
         $document->loadXML(ob_get_clean());
     } else {
         $document->load($tocPart->file);
     }
     return $document;
 }
 /**
  * Apply a single history entry to the plugin's history. 
  * 
  * @param SeguePluginsAPI $plugin
  * @param object DOMElement $element
  * @return void
  * @access protected
  * @since 1/23/08
  */
 protected function addPluginHistoryEntry(SeguePluginsAPI $plugin, DOMElement $element)
 {
     foreach ($element->childNodes as $child) {
         if ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName != 'comment') {
             $doc = new Harmoni_DOMDocument();
             $doc->appendChild($doc->importNode($child, true));
             break;
         }
     }
     if (!isset($doc)) {
         throw new Exception("No version found.");
     }
     $comment = $this->getPluginHistoryComment($element);
     $timestamp = $this->getPluginHistoryTimestamp($element);
     $agentId = $this->getPluginHistoryAgentId($element);
     $plugin->importVersion($doc, $agentId, $timestamp, $comment);
 }
 /**
  * Write an option
  * 
  * @param string $groupId
  * @return void
  * @access protected
  * @since 2/4/09
  */
 protected function getOptions()
 {
     $doc = new Harmoni_DOMDocument();
     $doc->preserveWhiteSpace = false;
     try {
         $doc->loadXML($this->getContent());
     } catch (DOMException $e) {
         $doc->appendChild($doc->createElement('options'));
     }
     if (!$doc->documentElement->nodeName == 'options') {
         throw new OperationFailedException('Expection root-node "options", found "' . $doc->documentElement->nodeName . '".');
     }
     return $doc;
 }
Example #14
0
 /**
  * Create the new site with this template at the slot specified.
  * 
  * @param object Slot $slot
  * @param optional string $displayName
  * @param optional string $description
  * @return object SiteNavBlockSiteComponent
  * @access public
  * @since 6/11/08
  */
 public function createSite(Slot $slot, $displayName = 'Untitled', $description = '')
 {
     $director = SiteDispatcher::getSiteDirector();
     $doc = new Harmoni_DOMDocument();
     $doc->load($this->_path . "/site.xml");
     // Validate the document contents
     $doc->schemaValidateWithException(MYDIR . "/doc/raw/dtds/segue2-site.xsd");
     $mediaDir = $this->_path . "/media";
     if (!file_exists($mediaDir)) {
         $mediaDir = null;
     }
     // @todo Strip out any history.
     $importer = new StripHistoryImportSiteVisitor($doc, $mediaDir, $director);
     $importer->disableCommentImport();
     $site = $importer->importAtSlot($slot->getShortname());
     try {
         // Replace #SITE_NAME# and #SITE_DESCRIPTION# placeholders
         $site->acceptVisitor(new Segue_Templates_ReplacePlaceholderVisitor($displayName, $description));
     } catch (Exception $e) {
         $director->deleteSiteComponent($site);
         $slot->deleteSiteId();
         throw $e;
     }
     return $site;
 }
 /**
  * Write an option
  * 
  * @param string $key
  * @param string $val
  * @return void
  * @access protected
  * @since 2/4/09
  */
 protected function writeOption($key, $val)
 {
     // The options will look like:
     /*
     <options>
     	<targetNodeId>12345</targetNodeId>
     	<defaultSortMethod>alpha</defaultSortMethod>
     	<defaultDisplayType>cloud</defaultDisplayType>
     </options>
     */
     if (!in_array($key, $this->_allowedOptions)) {
         throw new InvalidArgumentException("Unknown option, {$key}");
     }
     $doc = new Harmoni_DOMDocument();
     $doc->preserveWhiteSpace = false;
     try {
         $doc->loadXML($this->getContent());
     } catch (DOMException $e) {
         $doc->appendChild($doc->createElement('options'));
     }
     if (!$doc->documentElement->nodeName == 'options') {
         throw new OperationFailedException('Expection root-node "options", found "' . $doc->documentElement->nodeName . '".');
     }
     // Fetch the existing element or create a new one for this key
     $xpath = new DOMXPath($doc);
     $elements = $xpath->query('/options/' . $key);
     if ($elements->length) {
         $element = $elements->item(0);
     } else {
         $element = $doc->documentElement->appendChild($doc->createElement($key));
     }
     // Set the value and save
     $element->nodeValue = $val;
     $this->setContent($doc->saveXMLWithWhitespace());
 }