104fd306cSNickeau<?php 204fd306cSNickeau 304fd306cSNickeau 404fd306cSNickeaunamespace ComboStrap; 504fd306cSNickeau 604fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute; 704fd306cSNickeau 804fd306cSNickeau/** 904fd306cSNickeau * Class SectionEdit 1004fd306cSNickeau * @package ComboStrap 1104fd306cSNickeau * Manage the edit button 1204fd306cSNickeau * (ie add HTML comment that are parsed into forms 1304fd306cSNickeau * for editor user) 1404fd306cSNickeau */ 1504fd306cSNickeauclass EditButton 1604fd306cSNickeau{ 1704fd306cSNickeau 1804fd306cSNickeau 1904fd306cSNickeau const SEC_EDIT_PATTERN = "/" . self::ENTER_HTML_COMMENT . "\s*" . self::EDIT_BUTTON_PREFIX . "({.*?})\s*" . self::CLOSE_HTML_COMMENT . "/"; 2004fd306cSNickeau const EDIT_BUTTON_PREFIX = "EDIT"; 2104fd306cSNickeau const WIKI_ID = "wiki-id"; 2204fd306cSNickeau 2304fd306cSNickeau const FORM_ID = "hid"; // id to be dokuwiki conform 2404fd306cSNickeau const EDIT_MESSAGE = "name"; // name to be dokuwiki conform 2504fd306cSNickeau 2604fd306cSNickeau const CANONICAL = "edit-button"; 2704fd306cSNickeau const ENTER_HTML_COMMENT = "<!--"; 2804fd306cSNickeau const CLOSE_HTML_COMMENT = "-->"; 2904fd306cSNickeau const SNIPPET_ID = "edit-button"; 3004fd306cSNickeau 3104fd306cSNickeau 3204fd306cSNickeau /** 3304fd306cSNickeau * The target drive the type of editor 3404fd306cSNickeau * As of today, there is two type 3504fd306cSNickeau * section and table 3604fd306cSNickeau */ 3704fd306cSNickeau const TARGET_ATTRIBUTE_NAME = "target"; 3804fd306cSNickeau const TARGET_SECTION_VALUE = "section"; 3904fd306cSNickeau /** 4004fd306cSNickeau * The table does not have an edit form at all 4104fd306cSNickeau * It's created by {@link \Doku_Renderer_xhtml::table_close()} 4204fd306cSNickeau * They are not printed by default via CSS. Edittable show them by default via Javascript 4304fd306cSNickeau */ 4404fd306cSNickeau const TARGET_TABLE_VALUE = "table"; 4504fd306cSNickeau public const EDIT_SECTION_TARGET = 'section'; 4604fd306cSNickeau const RANGE = "range"; 4704fd306cSNickeau const DOKUWIKI_FORMAT = "dokuwiki"; 4804fd306cSNickeau const COMBO_FORMAT = "combo"; 4904fd306cSNickeau const TAG = "edit-button"; 5004fd306cSNickeau const CLASS_SUFFIX = "edit-button"; 5104fd306cSNickeau 5204fd306cSNickeau 5304fd306cSNickeau private $label; 5404fd306cSNickeau /** 5504fd306cSNickeau * @var string 5604fd306cSNickeau */ 5704fd306cSNickeau private $wikiId; 5804fd306cSNickeau 5904fd306cSNickeau /** 6004fd306cSNickeau * Edit type 6104fd306cSNickeau * @var string 6204fd306cSNickeau * This is the default 6304fd306cSNickeau */ 6404fd306cSNickeau private string $target = self::TARGET_SECTION_VALUE; 6504fd306cSNickeau /** 6604fd306cSNickeau * @var int 6704fd306cSNickeau */ 6804fd306cSNickeau private int $startPosition; 6904fd306cSNickeau 7004fd306cSNickeau private ?int $endPosition; 7104fd306cSNickeau /** 7204fd306cSNickeau * @var string $format - to conform or not to dokuwiki format 7304fd306cSNickeau */ 7404fd306cSNickeau private string $format = self::COMBO_FORMAT; 7504fd306cSNickeau 7604fd306cSNickeau /** 7704fd306cSNickeau * the id of the heading, ie the id of the section 7804fd306cSNickeau * Not really needed, just to be conform with Dokuwiki 7904fd306cSNickeau * When the edit button is not for an outline may be null 8004fd306cSNickeau */ 8104fd306cSNickeau private ?string $outlineHeadingId = null; 8204fd306cSNickeau /** 8304fd306cSNickeau * @var ?int $sectionid - sequence id of the section used only by dokuwiki 8404fd306cSNickeau * When the edit button is not for an outline may be null 8504fd306cSNickeau */ 8604fd306cSNickeau private ?int $outlineSectionId = null; 8704fd306cSNickeau 8804fd306cSNickeau 8904fd306cSNickeau /** 9004fd306cSNickeau * Section constructor. 9104fd306cSNickeau */ 9204fd306cSNickeau public function __construct($label) 9304fd306cSNickeau { 9404fd306cSNickeau $this->label = $label; 9504fd306cSNickeau } 9604fd306cSNickeau 9704fd306cSNickeau 9804fd306cSNickeau public static function create($label): EditButton 9904fd306cSNickeau { 10004fd306cSNickeau return new EditButton($label); 10104fd306cSNickeau } 10204fd306cSNickeau 10304fd306cSNickeau public static function createFromCallStackArray($attributes): EditButton 10404fd306cSNickeau { 10504fd306cSNickeau $label = $attributes[\syntax_plugin_combo_edit::LABEL]; 10604fd306cSNickeau $startPosition = $attributes[\syntax_plugin_combo_edit::START_POSITION]; 10704fd306cSNickeau $endPosition = $attributes[\syntax_plugin_combo_edit::END_POSITION]; 10804fd306cSNickeau $wikiId = $attributes[TagAttributes::WIKI_ID]; 10904fd306cSNickeau $editButton = EditButton::create($label) 11004fd306cSNickeau ->setStartPosition($startPosition) 11104fd306cSNickeau ->setEndPosition($endPosition) 11204fd306cSNickeau ->setWikiId($wikiId); 11304fd306cSNickeau $headingId = $attributes[\syntax_plugin_combo_edit::HEADING_ID]; 11404fd306cSNickeau if ($headingId !== null) { 11504fd306cSNickeau $editButton->setOutlineHeadingId($headingId); 11604fd306cSNickeau } 11704fd306cSNickeau $sectionId = $attributes[\syntax_plugin_combo_edit::SECTION_ID]; 11804fd306cSNickeau if ($sectionId !== null) { 11904fd306cSNickeau $editButton->setOutlineSectionId($sectionId); 12004fd306cSNickeau } 12104fd306cSNickeau $format = $attributes[\syntax_plugin_combo_edit::FORMAT]; 12204fd306cSNickeau if ($format !== null) { 12304fd306cSNickeau $editButton->setFormat($format); 12404fd306cSNickeau } 12504fd306cSNickeau return $editButton; 12604fd306cSNickeau 12704fd306cSNickeau 12804fd306cSNickeau } 12904fd306cSNickeau 13004fd306cSNickeau public static function deleteAll(string $html) 13104fd306cSNickeau { 13204fd306cSNickeau // Dokuwiki way is to delete 13304fd306cSNickeau // but because they are comment, they are not shown 13404fd306cSNickeau // We delete to serve clean page to search engine 13504fd306cSNickeau return preg_replace(SEC_EDIT_PATTERN, '', $html); 13604fd306cSNickeau } 13704fd306cSNickeau 13804fd306cSNickeau public static function replaceOrDeleteAll(string $html_output) 13904fd306cSNickeau { 14004fd306cSNickeau try { 14104fd306cSNickeau return EditButton::replaceAll($html_output); 14204fd306cSNickeau } catch (ExceptionNotAuthorized|ExceptionBadState $e) { 14304fd306cSNickeau return EditButton::deleteAll($html_output); 14404fd306cSNickeau } 14504fd306cSNickeau } 14604fd306cSNickeau 14704fd306cSNickeau /** 14804fd306cSNickeau * See {@link \Doku_Renderer_xhtml::finishSectionEdit()} 14904fd306cSNickeau */ 15004fd306cSNickeau public function toTag(): string 15104fd306cSNickeau { 15204fd306cSNickeau 15304fd306cSNickeau /** 15404fd306cSNickeau * The following data are mandatory from: 15504fd306cSNickeau * {@link html_secedit_get_button} 15604fd306cSNickeau */ 15704fd306cSNickeau $wikiId = $this->getWikiId(); 15804fd306cSNickeau 15904fd306cSNickeau 16004fd306cSNickeau /** 16104fd306cSNickeau * We follow the order of Dokuwiki for compatibility purpose 16204fd306cSNickeau */ 16304fd306cSNickeau $data[self::TARGET_ATTRIBUTE_NAME] = $this->target; 16404fd306cSNickeau 16504fd306cSNickeau if ($this->format === self::COMBO_FORMAT) { 16604fd306cSNickeau /** 16704fd306cSNickeau * In the combo edit format, we had the dokuwiki id 16804fd306cSNickeau * because the edit button may also be on the secondary slot 16904fd306cSNickeau */ 17004fd306cSNickeau $data[self::WIKI_ID] = $wikiId; 17104fd306cSNickeau } 17204fd306cSNickeau $data[self::EDIT_MESSAGE] = $this->label; 17304fd306cSNickeau if ($this->format === self::COMBO_FORMAT) { 17404fd306cSNickeau /** 17504fd306cSNickeau * In the combo edit format, we had the dokuwiki id as form id 17604fd306cSNickeau * to make it unique on the whole page 17704fd306cSNickeau * because the edit button may also be on the secondary slot 17804fd306cSNickeau */ 17904fd306cSNickeau $slotPath = WikiPath::createMarkupPathFromId($wikiId); 18004fd306cSNickeau $formId = ExecutionContext::getActualOrCreateFromEnv() 18104fd306cSNickeau ->getIdManager() 18204fd306cSNickeau ->generateNewHtmlIdForComponent(self::CANONICAL, $slotPath); 18304fd306cSNickeau $data[self::FORM_ID] = $formId; 18404fd306cSNickeau 18504fd306cSNickeau 18604fd306cSNickeau } else { 18704fd306cSNickeau $data[self::FORM_ID] = $this->getHeadingId(); 18804fd306cSNickeau $data["codeblockOffset"] = 0; // what is that ? 18904fd306cSNickeau $data["secid"] = $this->getSectionId(); 19004fd306cSNickeau } 19104fd306cSNickeau $data[self::RANGE] = $this->getRange(); 19204fd306cSNickeau 19304fd306cSNickeau 19404fd306cSNickeau return self::EDIT_BUTTON_PREFIX . Html::encode(json_encode($data)); 19504fd306cSNickeau } 19604fd306cSNickeau 19704fd306cSNickeau /** 19804fd306cSNickeau * 19904fd306cSNickeau * @throws ExceptionBadArgument - if the wiki id could not be found 20004fd306cSNickeau * @throws ExceptionNotEnabled 20104fd306cSNickeau */ 20204fd306cSNickeau public function toHtmlComment(): string 20304fd306cSNickeau { 20404fd306cSNickeau global $ACT; 20504fd306cSNickeau if ($ACT === FetcherMarkup::MARKUP_DYNAMIC_EXECUTION_NAME) { 20604fd306cSNickeau // ie weblog, they are generated via dynamic markup 20704fd306cSNickeau // meaning that there is no button to edit the file 20804fd306cSNickeau if (!PluginUtility::isTest()) { 20904fd306cSNickeau return ""; 21004fd306cSNickeau } 21104fd306cSNickeau } 21204fd306cSNickeau /** 21304fd306cSNickeau * We don't encode there is only internal information 21404fd306cSNickeau * and this is easier to see / debug the output 21504fd306cSNickeau */ 21604fd306cSNickeau return self::ENTER_HTML_COMMENT . " " . $this->toTag() . " " . self::CLOSE_HTML_COMMENT; 21704fd306cSNickeau } 21804fd306cSNickeau 21904fd306cSNickeau public function __toString() 22004fd306cSNickeau { 22104fd306cSNickeau return "Section Edit $this->label"; 22204fd306cSNickeau } 22304fd306cSNickeau 22404fd306cSNickeau 22504fd306cSNickeau /** 22604fd306cSNickeau * @throws ExceptionNotAuthorized - if the user cannot modify the page 22704fd306cSNickeau * @throws ExceptionBadState - if the page is a revision page or the HTML is not the output of a page 22804fd306cSNickeau */ 22904fd306cSNickeau public static function replaceAll($html) 23004fd306cSNickeau { 23104fd306cSNickeau 23204fd306cSNickeau if (!Identity::isWriter()) { 23304fd306cSNickeau throw new ExceptionNotAuthorized("Page is not writable by the user"); 23404fd306cSNickeau } 23504fd306cSNickeau /** 23604fd306cSNickeau * Delete the edit comment 23704fd306cSNickeau * * if not writable 23804fd306cSNickeau * * or an old revision 23904fd306cSNickeau * Original: {@link html_secedit()} {@link html_secedit_get_button()} 24004fd306cSNickeau */ 24104fd306cSNickeau global $INFO; 24204fd306cSNickeau if (isset($INFO)) { 24304fd306cSNickeau // the page is a revision page 244*39026d0aSgerardnico $rev = $INFO['rev'] ?? 0; 245*39026d0aSgerardnico if ($rev !== 0) { 24604fd306cSNickeau throw new ExceptionBadState("Internal Error: No edit button can be added to a revision page"); 24704fd306cSNickeau } 24804fd306cSNickeau } 24904fd306cSNickeau 25004fd306cSNickeau 25104fd306cSNickeau /** 25204fd306cSNickeau * Request based because the button are added only for a user that can write 25304fd306cSNickeau */ 25404fd306cSNickeau $snippetManager = PluginUtility::getSnippetManager(); 25504fd306cSNickeau $snippetManager->attachCssInternalStylesheet(self::SNIPPET_ID); 25604fd306cSNickeau $snippetManager->attachJavascriptFromComponentId(self::SNIPPET_ID); 25704fd306cSNickeau 25804fd306cSNickeau /** 25904fd306cSNickeau * The callback function on all edit comment 26004fd306cSNickeau * @param $matches 26104fd306cSNickeau * @return string 26204fd306cSNickeau */ 26304fd306cSNickeau $editFormCallBack = function ($matches) { 26404fd306cSNickeau $json = Html::decode($matches[1]); 26504fd306cSNickeau $data = json_decode($json, true); 26604fd306cSNickeau 26704fd306cSNickeau $target = $data[self::TARGET_ATTRIBUTE_NAME]; 26804fd306cSNickeau 26904fd306cSNickeau $message = $data[self::EDIT_MESSAGE]; 27004fd306cSNickeau unset($data[self::EDIT_MESSAGE]); 27104fd306cSNickeau if ($message === null || trim($message) === "") { 27204fd306cSNickeau $message = "Edit {$target}"; 27304fd306cSNickeau } 27404fd306cSNickeau 27504fd306cSNickeau if ($data === NULL) { 27604fd306cSNickeau LogUtility::internalError("No data found in the edit comment", self::CANONICAL); 27704fd306cSNickeau return ""; 27804fd306cSNickeau } 27904fd306cSNickeau $wikiId = $data[self::WIKI_ID]; 28004fd306cSNickeau unset($data[self::WIKI_ID]); 28104fd306cSNickeau if ($wikiId === null) { 28204fd306cSNickeau try { 28304fd306cSNickeau $page = MarkupPath::createPageFromExecutingId(); 28404fd306cSNickeau } catch (ExceptionNotFound $e) { 28504fd306cSNickeau LogUtility::internalError("A page id is mandatory for a edit button (no wiki id, no global ID were found). No edit buttons was created then.", self::CANONICAL); 28604fd306cSNickeau return ""; 28704fd306cSNickeau } 28804fd306cSNickeau } else { 28904fd306cSNickeau $page = MarkupPath::createMarkupFromId($wikiId); 29004fd306cSNickeau } 29104fd306cSNickeau $formId = $data[self::FORM_ID]; 29204fd306cSNickeau unset($data[self::FORM_ID]); 29304fd306cSNickeau $data["summary"] = $message; 29404fd306cSNickeau try { 29504fd306cSNickeau $data['rev'] = $page->getPathObject()->getRevisionOrDefault(); 29604fd306cSNickeau } catch (ExceptionNotFound $e) { 29704fd306cSNickeau //LogUtility::internalError("The file ({$page->getPathObject()}) does not exist, we cannot set the last modified time on the edit buttons.", self::CANONICAL); 29804fd306cSNickeau } 29904fd306cSNickeau $hiddenInputs = ""; 30004fd306cSNickeau foreach ($data as $key => $val) { 30104fd306cSNickeau $inputAttributes = TagAttributes::createEmpty() 30204fd306cSNickeau ->addOutputAttributeValue("name", $key) 30304fd306cSNickeau ->addOutputAttributeValue("value", $val) 30404fd306cSNickeau ->addOutputAttributeValue("type", "hidden"); 30504fd306cSNickeau $hiddenInputs .= $inputAttributes->toHtmlEmptyTag("input"); 30604fd306cSNickeau } 30704fd306cSNickeau $url = $page->getUrl() 30804fd306cSNickeau ->withoutRewrite() 30904fd306cSNickeau ->toHtmlString(); 31004fd306cSNickeau $classPageEdit = StyleAttribute::addComboStrapSuffix(self::CLASS_SUFFIX); 31104fd306cSNickeau 31204fd306cSNickeau /** 31304fd306cSNickeau * Important Note: the first div and the public class is mandatory for the edittable plugin 31404fd306cSNickeau * See {@link editbutton.js file} 31504fd306cSNickeau */ 31604fd306cSNickeau $editTableClass = "editbutton_{$target}"; 31704fd306cSNickeau return <<<EOF 31804fd306cSNickeau<div class="$classPageEdit $editTableClass"> 31904fd306cSNickeau <form id="$formId" method="post" action="{$url}"> 32004fd306cSNickeau $hiddenInputs 32104fd306cSNickeau <input name="do" type="hidden" value="edit"/> 32204fd306cSNickeau <button type="submit" title="$message"> 32304fd306cSNickeau </button> 32404fd306cSNickeau </form> 32504fd306cSNickeau</div> 32604fd306cSNickeauEOF; 32704fd306cSNickeau }; 32804fd306cSNickeau 32904fd306cSNickeau /** 33004fd306cSNickeau * The replacement 33104fd306cSNickeau */ 33204fd306cSNickeau return preg_replace_callback(self::SEC_EDIT_PATTERN, $editFormCallBack, $html); 33304fd306cSNickeau } 33404fd306cSNickeau 33504fd306cSNickeau 33604fd306cSNickeau public function setWikiId(string $id): EditButton 33704fd306cSNickeau { 33804fd306cSNickeau $this->wikiId = $id; 33904fd306cSNickeau return $this; 34004fd306cSNickeau } 34104fd306cSNickeau 34204fd306cSNickeau /** 34304fd306cSNickeau * Page / Section edit 34404fd306cSNickeau * (This is known as the target for dokuwiki) 34504fd306cSNickeau * @param string $target 34604fd306cSNickeau * @return $this 34704fd306cSNickeau * 34804fd306cSNickeau */ 34904fd306cSNickeau public function setTarget(string $target): EditButton 35004fd306cSNickeau { 35104fd306cSNickeau $this->target = $target; 35204fd306cSNickeau return $this; 35304fd306cSNickeau } 35404fd306cSNickeau 35504fd306cSNickeau public function setStartPosition(int $startPosition): EditButton 35604fd306cSNickeau { 35704fd306cSNickeau $this->startPosition = $startPosition; 35804fd306cSNickeau return $this; 35904fd306cSNickeau } 36004fd306cSNickeau 36104fd306cSNickeau public function setEndPosition(?int $endPosition): EditButton 36204fd306cSNickeau { 36304fd306cSNickeau $this->endPosition = $endPosition; 36404fd306cSNickeau return $this; 36504fd306cSNickeau } 36604fd306cSNickeau 36704fd306cSNickeau /** 36804fd306cSNickeau * @return string the file character position range of the section to edit 36904fd306cSNickeau */ 37004fd306cSNickeau private function getRange(): string 37104fd306cSNickeau { 37204fd306cSNickeau $range = ""; 37304fd306cSNickeau if (isset($this->startPosition)) { 37404fd306cSNickeau $range = $this->startPosition; 37504fd306cSNickeau } 37604fd306cSNickeau $range = "$range-"; 37704fd306cSNickeau if (isset($this->endPosition)) { 37804fd306cSNickeau $range = "$range{$this->endPosition}"; 37904fd306cSNickeau } 38004fd306cSNickeau return $range; 38104fd306cSNickeau 38204fd306cSNickeau } 38304fd306cSNickeau 38404fd306cSNickeau public function toComboCallComboFormat(): Call 38504fd306cSNickeau { 38604fd306cSNickeau return $this->toComboCall(self::COMBO_FORMAT); 38704fd306cSNickeau } 38804fd306cSNickeau 38904fd306cSNickeau public function toComboCall($format): Call 39004fd306cSNickeau { 39104fd306cSNickeau return Call::createComboCall( 39204fd306cSNickeau \syntax_plugin_combo_edit::TAG, 39304fd306cSNickeau DOKU_LEXER_SPECIAL, 39404fd306cSNickeau [ 39504fd306cSNickeau \syntax_plugin_combo_edit::START_POSITION => $this->startPosition, 39604fd306cSNickeau \syntax_plugin_combo_edit::END_POSITION => $this->endPosition, 39704fd306cSNickeau \syntax_plugin_combo_edit::LABEL => $this->label, 39804fd306cSNickeau \syntax_plugin_combo_edit::FORMAT => $format, 39904fd306cSNickeau \syntax_plugin_combo_edit::HEADING_ID => $this->getHeadingId(), 40004fd306cSNickeau \syntax_plugin_combo_edit::SECTION_ID => $this->getSectionId(), 40104fd306cSNickeau TagAttributes::WIKI_ID => $this->getWikiId() 40204fd306cSNickeau ] 40304fd306cSNickeau ); 40404fd306cSNickeau } 40504fd306cSNickeau 40604fd306cSNickeau 40704fd306cSNickeau /** 40804fd306cSNickeau * 40904fd306cSNickeau */ 41004fd306cSNickeau private function getWikiId(): string 41104fd306cSNickeau { 41204fd306cSNickeau 41304fd306cSNickeau $wikiId = $this->wikiId; 41404fd306cSNickeau if ($wikiId !== null) { 41504fd306cSNickeau return $wikiId; 41604fd306cSNickeau } 41704fd306cSNickeau 41804fd306cSNickeau return ExecutionContext::getActualOrCreateFromEnv()->getRequestedPath()->getWikiId(); 41904fd306cSNickeau 42004fd306cSNickeau 42104fd306cSNickeau } 42204fd306cSNickeau 42304fd306cSNickeau 42404fd306cSNickeau public function toComboCallDokuWikiForm(): Call 42504fd306cSNickeau { 42604fd306cSNickeau return $this->toComboCall(self::DOKUWIKI_FORMAT); 42704fd306cSNickeau } 42804fd306cSNickeau 42904fd306cSNickeau /** @noinspection PhpReturnValueOfMethodIsNeverUsedInspection */ 43004fd306cSNickeau private function setFormat($format): EditButton 43104fd306cSNickeau { 43204fd306cSNickeau 43304fd306cSNickeau if (!in_array($format, [self::DOKUWIKI_FORMAT, self::COMBO_FORMAT])) { 43404fd306cSNickeau LogUtility::internalError("The tag format ($format) is not valid", self::CANONICAL); 43504fd306cSNickeau return $this; 43604fd306cSNickeau } 43704fd306cSNickeau $this->format = $format; 43804fd306cSNickeau return $this; 43904fd306cSNickeau } 44004fd306cSNickeau 44104fd306cSNickeau public function setOutlineHeadingId($id): EditButton 44204fd306cSNickeau { 44304fd306cSNickeau $this->outlineHeadingId = $id; 44404fd306cSNickeau return $this; 44504fd306cSNickeau } 44604fd306cSNickeau 44704fd306cSNickeau /** 44804fd306cSNickeau * @return string|null 44904fd306cSNickeau */ 45004fd306cSNickeau private function getHeadingId(): ?string 45104fd306cSNickeau { 45204fd306cSNickeau return $this->outlineHeadingId; 45304fd306cSNickeau } 45404fd306cSNickeau 45504fd306cSNickeau private function getSectionId(): ?int 45604fd306cSNickeau { 45704fd306cSNickeau return $this->outlineSectionId; 45804fd306cSNickeau } 45904fd306cSNickeau 46004fd306cSNickeau public function setOutlineSectionId(int $sectionSequenceId): EditButton 46104fd306cSNickeau { 46204fd306cSNickeau $this->outlineSectionId = $sectionSequenceId; 46304fd306cSNickeau return $this; 46404fd306cSNickeau } 46504fd306cSNickeau 46604fd306cSNickeau} 467