getConstants(); } } // Defining the different types of elements available. abstract class ComposerElementTypes { const UNSET = "unset"; const RAW = "raw"; const H1 = "h1"; const H2 = "h2"; const H3 = "h3"; const PARAGRAPH = "paragraph"; const BUTTON = "button"; const CODE = "code"; const HR = "hr"; const CONTAINER = "container"; const COLLAPSE = "collapse"; const SPACER = "spacer"; const IMAGE = "image"; const TABLE = "table"; const GRID = "grid"; const GALLERY = "gallery"; const VIDEO = "video"; /** * Returns all the constants present in the class. * @return array All the constants in as "[[k, v], [k, v], ...]". * @see https://www.php.net/manual/en/reflectionclass.getconstants.php */ static function getConstants(): array { $oClass = new ReflectionClass(__CLASS__); return $oClass->getConstants(); } } // Defining modifiers. abstract class ComposerElementModifiers { // Generic > Margin const GENERIC_MARGIN_NO_TOP = ["no-top-margin", "mt-0"]; const GENERIC_MARGIN_NO_BOTTOM = ["no-bottom-margin", "mb-0"]; const GENERIC_MARGIN_NO_LEFT = ["no-left-margin", "ml-0"]; const GENERIC_MARGIN_NO_RIGHT = ["no-right-margin", "mr-0"]; const GENERIC_MARGIN_NO_X = ["no-y-margin", "mx-0"]; const GENERIC_MARGIN_NO_Y = ["no-x-margin", "my-0"]; const GENERIC_MARGIN_NONE = ["no-margin", "m-0" ]; // Generic > Padding const GENERIC_PADDING_NO_TOP = ["no-top-padding", "pt-0"]; const GENERIC_PADDING_NO_BOTTOM = ["no-bottom-padding", "pb-0"]; const GENERIC_PADDING_NO_LEFT = ["no-left-padding", "pl-0"]; const GENERIC_PADDING_NO_RIGHT = ["no-right-padding", "pr-0"]; const GENERIC_PADDING_NO_X = ["no-x-padding", "px-0"]; const GENERIC_PADDING_NO_Y = ["no-y-padding", "py-0"]; const GENERIC_PADDING_NONE = ["no-padding", "p-0" ]; // Containers const CONTAINER_SCROLL_HORIZONTAL = ["horizontal-scroll", "overflow-x-scroll hide-scrollbar"]; const CONTAINER_CARD = ["card", "card"]; // Buttons const BUTTON_THIN = ["thin", "btn-sm"]; const BUTTON_THICK = ["thick", "btn-lg"]; const BUTTON_ROUNDED = ["rounded", "btn-rounded"]; const BUTTON_CIRCLE = ["circle", "rounded-circle"]; const BUTTON_DOWNLOAD_PRIMARY = ["download-primary", "btn-primary"]; // Horizontal ruler const HR_SUBTLE = ["subtle", "subtle"]; // Collapse const DETAILS_NO_ROUNDING = ["no-rounding", ""]; const DETAILS_CLOSED = ["closed", ""]; // Tables const TABLE_NO_OUTER_PADDING = ["no-outer-padding", "table-no-outer-padding"]; const TABLE_STRIPED = ["striped", "table-striped"]; const TABLE_HOVER = ["hover", "table-hover"]; const TABLE_INNER_BORDER = ["inner-bordered", "table-inner-bordered"]; const TABLE_OUTER_BORDER = ["outer-bordered", "table-outer-bordered"]; // Code const CODE_BLOCK = ["code-block", "w-full d-inline-block"]; // Other internal constants const _INDEX_KEY = 0; const _INDEX_CLASSES = 1; /** * Returns all the constants present in the class. * @return array All the constants in as "[[k, v], [k, v], ...]". * @see https://www.php.net/manual/en/reflectionclass.getconstants.php */ static function getConstants(): array { $oClass = new ReflectionClass(__CLASS__); return $oClass->getConstants(); } /** * Returns the given modifier's constant's key * @param array $modifier_data A modifier constant defined in "ComposerElementModifiers". * @return string The modifier's key or an empty string if an error was encountered. */ static function get_modifier_key(array $modifier_data) : string { return sizeof($modifier_data) >= 1 ? $modifier_data[ComposerElementModifiers::_INDEX_KEY] : ''; } /** * Returns the given modifier's constant's classes * @param array $modifier_data A modifier constant defined in "ComposerElementModifiers". * @return string The modifier's classes or an empty string if an error was encountered. */ static function get_modifier_classes(array $modifier_data) : string { return sizeof($modifier_data) >= 2 ? $modifier_data[ComposerElementModifiers::_INDEX_CLASSES] : ''; } /** * @param string $modifier_key * @return string The resolved DOM classes, or an empty string if the given modifier is unknown. */ static function get_classes_from_key(string $modifier_key) : string { foreach(ComposerElementModifiers::getConstants() as $constant_values) { if(!is_array($constant_values)) { continue; } if($modifier_key == $constant_values[ComposerElementModifiers::_INDEX_KEY]) { return $constant_values[ComposerElementModifiers::_INDEX_CLASSES]; } } return ""; } static function is_modifier_in_modifiers(array $modifier_data, array $modifiers) : bool { foreach($modifiers as $modifier) { if($modifier_data[ComposerElementModifiers::_INDEX_KEY] == $modifier) { return true; } } return false; } } // Data classes class ComposerContent { public array $strings; public ComposerContentMetadata $metadata; public array $elements; function __construct(array $strings, ComposerContentMetadata $metadata, array $elements) { $this->strings = $strings; $this->metadata = $metadata; $this->elements = $elements; } static function from_json(array $json_data) : ComposerContent { global $default_language; return new ComposerContent( key_exists("strings", $json_data) ? $json_data["strings"] : array($default_language=>[]), ComposerContentMetadata::from_json( key_exists("metadata", $json_data) ? $json_data["metadata"] : array() ), key_exists("elements", $json_data) ? ComposerElement::from_json_array($json_data["elements"]) : array() ); } public function get_html() : string { $htmlCode = ""; // FIXME: Check for the template after the loop foreach($this->elements as $element) { /** @var ComposerElement $element */ $htmlCode .= $element->get_html($this); } return $this->metadata->apply_template($this, $htmlCode); } public function get_head_title() : string { if(!is_null($this->metadata->head->title)) { return localize_private($this->metadata->head->title, $this->strings, false); } return localize("content.default.head.title"); } public function get_head_description() : string { if(!is_null($this->metadata->head->title)) { return localize_private($this->metadata->head->description, $this->strings, false); } return localize("content.default.head.description"); } public function get_opengraph_tags(?string $title_prefix, ?string $type_override, ?string $url_override, ?string $image_url_override, ?string $image_url_fallback) : string { global $host_uri; $final_image_uri = (is_null($image_url_override) ? (is_null($this->metadata->opengraph->image) ? $image_url_fallback : $this->metadata->opengraph->image) : $image_url_override); return 'metadata->opengraph->title, $this->strings, false)) . '" />metadata->opengraph->description, $this->strings, false)) . '" />metadata->opengraph->type) : $type_override) . '" />'; // } } class ComposerContentMetadata { public string $title; public string $description; public string $template; public ComposerContentMetadataHead $head; public ComposerContentMetadataOpengraph $opengraph; public ?ComposerContentMetadataArticle $article; function __construct(string $title, string $description, string $template, ComposerContentMetadataHead $head, ComposerContentMetadataOpengraph $opengraph, ?ComposerContentMetadataArticle $article) { $this->title = $title; $this->description = $description; $this->template = $template; $this->head = $head; $this->opengraph = $opengraph; $this->article = $article; // Safety checks. if($this->template == ComposerTemplates::ARTICLE && is_null($this->article)) { $this->article = ComposerContentMetadataArticle::from_json([]); } } static function from_json(array $json_data) : ComposerContentMetadata { return new ComposerContentMetadata( key_exists("title", $json_data) ? $json_data["title"] : "", key_exists("description", $json_data) ? $json_data["description"] : "", key_exists("template", $json_data) ? $json_data["template"] : "", ComposerContentMetadataHead::from_json( key_exists("head", $json_data) ? $json_data["head"] : array() ), ComposerContentMetadataOpengraph::from_json( key_exists("opengraph", $json_data) ? $json_data["opengraph"] : array() ), key_exists("article", $json_data) ? ComposerContentMetadataArticle::from_json($json_data["article"]) : null, ); } function apply_template(ComposerContent $content_root, string $inner_html) : string { switch($this->template) { case ComposerTemplates::ARTICLE: $inner_html = '
' . '

' . '  ' . localize_private($this->article->title, $content_root->strings, false) . '' . localize_private($this->article->subtitle, $content_root->strings, false) . '

' . '
' . $inner_html . '
' . '
' . '
'; break; case ComposerTemplates::RAW: default: break; } return $inner_html; } } class ComposerContentMetadataHead { public ?string $title; public ?string $description; function __construct(?string $title, ?string $description) { $this->title = $title; $this->description = $description; } static function from_json(array $json_data) : ComposerContentMetadataHead { return new ComposerContentMetadataHead( key_exists("title", $json_data) ? $json_data["title"] : null, key_exists("description", $json_data) ? $json_data["description"] : null ); } } class ComposerContentMetadataOpengraph { public ?string $title; public ?string $description; public ?string $type; public ?string $url; public ?string $image; public ?string $image_type; function __construct(?string $title, ?string $description, ?string $type, ?string $url, ?string $image, ?string $image_type) { $this->title = $title; $this->description = $description; $this->type = $type; $this->url = $url; $this->image = $image; $this->image_type = $image_type; } static function from_json(array $json_data) : ComposerContentMetadataOpengraph { return new ComposerContentMetadataOpengraph( key_exists("title", $json_data) ? $json_data["title"] : null, key_exists("description", $json_data) ? $json_data["description"] : null, key_exists("type", $json_data) ? $json_data["type"] : null, key_exists("url", $json_data) ? $json_data["url"] : null, key_exists("image", $json_data) ? $json_data["image"] : null, key_exists("image_type", $json_data) ? $json_data["image_type"] : null, ); } } class ComposerContentMetadataArticle { public string $icon; public string $title; public string $subtitle; public array $tags; function __construct(string $icon, string $title, string $subtitle, array $tags) { $this->icon = $icon; $this->title = $title; $this->subtitle = $subtitle; $this->tags = $tags; } static function from_json(array $json_data) : ComposerContentMetadataArticle { return new ComposerContentMetadataArticle( key_exists("icon", $json_data) ? $json_data["icon"] : "fad fa-question", key_exists("title", $json_data) ? $json_data["title"] : ''.localize("error.content.data.no.title").'', key_exists("subtitle", $json_data) ? $json_data["subtitle"] : '', key_exists("tags", $json_data) ? $json_data["tags"] : [], ); } public function __get($property) { return is_null($this->$property) ? "" : $this->$property; } } class ComposerElement { // Global parameters private string $type; private ?array $modifiers; private ?string $link; // Any direct element-container private ?array $parts; // Any direct text-container private ?string $content; private bool $localize; // Generic modifier values private ?int $padding; private ?int $margin; // Spacer's size private ?int $size; // Table's parameters private ?array $head; private ?array $body; private int $colspan; private int $rowspan; // Paragraph and code's parameters private ?int $indent; // Code's parameters private ?array $code; private ?string $codeLanguage; private bool $codeCopyable; // Button's parameters private ?string $color; // Image and video's parameters private ?string $source; private ?string $thumbnail; // Galleries parameters private array $images; function __construct(string $type, ?array $modifiers, ?string $link, ?array $parts, ?string $content, bool $localize, ?int $padding, ?int $margin, ?int $size, ?array $head, ?array $body, int $colspan, int $rowspan, ?int $indent, ?array $code, ?string $codeLanguage, bool $codeCopyable, ?string $color, ?string $source, ?string $thumbnail, ?array $images) { $this->type = $type; $this->modifiers = $modifiers; $this->link = $link; $this->parts = $parts; $this->content = $content; $this->localize = $localize; $this->padding = $padding; $this->margin = $margin; $this->size = $size; $this->head = ComposerElement::from_json_array($head); if(is_null($body)) { $this->body = array(); } else { $this->body = array_fill(0, sizeof($body), []); for($body_row_index = 0; $body_row_index < sizeof($body); $body_row_index++) { $this->body[$body_row_index] = ComposerElement::from_json_array($body[$body_row_index]); } } $this->colspan = $colspan; $this->rowspan = $rowspan; $this->indent = $indent; $this->code = $code; $this->codeLanguage = $codeLanguage; $this->codeCopyable = $codeCopyable; $this->color = $color; $this->source = $source; $this->thumbnail = $thumbnail; if(is_null($images)) { $this->images = array(); } else { $this->images = $images; } } static function from_json_array(?array $json_dataArray) : array { $parts = array(); if(!is_null($json_dataArray)) { foreach($json_dataArray as $part) { $parts[] = ComposerElement::from_json($part); } } return $parts; } static function from_json(array $json_data) : ComposerElement { return new ComposerElement( key_exists("type", $json_data) ? $json_data["type"] : ComposerElementTypes::UNSET, key_exists("modifiers", $json_data) ? $json_data["modifiers"] : [], key_exists("link", $json_data) ? $json_data["link"] : null, key_exists("parts", $json_data) ? ComposerElement::from_json_array($json_data["parts"]) : null, key_exists("content", $json_data) ? $json_data["content"] : null, key_exists("localize", $json_data) ? $json_data["localize"] : true, key_exists("padding", $json_data) ? $json_data["padding"] : null, key_exists("margin", $json_data) ? $json_data["margin"] : null, key_exists("size", $json_data) ? $json_data["size"] : null, key_exists("head", $json_data) ? $json_data["head"] : null, key_exists("body", $json_data) ? $json_data["body"] : null, key_exists("colspan", $json_data) ? $json_data["colspan"] : 1, key_exists("rowspan", $json_data) ? $json_data["rowspan"] : 1, key_exists("indent", $json_data) ? $json_data["indent"] : null, key_exists("code", $json_data) ? $json_data["code"] : null, key_exists("language", $json_data) ? $json_data["language"] : null, key_exists("copyable", $json_data) ? $json_data["copyable"] : false, key_exists("color", $json_data) ? $json_data["color"] : null, key_exists("source", $json_data) ? $json_data["source"] : null, key_exists("thumbnail", $json_data) ? $json_data["thumbnail"] : null, key_exists("images", $json_data) ? $json_data["images"] : null, ); } /** * Processes the "content" and "parts" class' variables and returns their interpreted content as HTML. * @param ComposerContent $content_root The content in which this element is contained. * @param bool $doLocalization Whether the "content" variable should be processed to return localized text. * @param bool $doSubElements Whether the "parts" variable should be processed to return localized text. * @param bool $stopIfLocalized Whether the process should return if some text was in the "content" variable. * @return string The interpreted content as HTML. */ private function get_inner_html(ComposerContent $content_root, bool $doLocalization = true, bool $doSubElements = true, bool $stopIfLocalized = true) : string { global $LANG_FALLBACK_KEY_PREFIX; $htmlCode = ""; $wasTextLocalized = false; if($doLocalization) { // Checking if "content" was declared. if(is_null($this->content) && !$doSubElements) { return "

error.no.inner.content

"; } // Checking if there is something to process. if(!empty($this->content)) { $wasTextLocalized = true; if(!$this->localize) { $htmlCode .= $this->content; } else { // We can now localize the content key. $htmlCode .= localize_private($this->content, $content_root->strings, true, $LANG_FALLBACK_KEY_PREFIX); } } // Checking for early stop. if($wasTextLocalized && $stopIfLocalized) { return $htmlCode; } } if($doSubElements) { // Checking if "parts" was declared. if(is_null($this->parts)) { if(!$wasTextLocalized) { $htmlCode = "

error.no.inner.parts

"; } return $htmlCode; } // Appending each sub-element. foreach($this->parts as $subElement) { /** @var ComposerElement $subElement */ $htmlCode .= $subElement->get_html($content_root); } } return $htmlCode; } private function get_inner_html_elements(ComposerContent $content_root) : string { return $this->get_inner_html($content_root, false, true, false); } private function get_inner_html_text(ComposerContent $content_root) : string { return $this->get_inner_html($content_root, true, false, false); } private function get_modifiers_classes() : string { if(!is_null($this->modifiers)) { $classes = ""; // Combining classes. foreach($this->modifiers as $modifier) { /** @var string $modifier */ $classes .= ComposerElementModifiers::get_classes_from_key($modifier) . ' '; } // Removing redundant and useless spaces. return preg_replace('/\s+/', ' ', trim($classes)); } return ""; } /** * Processes the element and returns its interpreted form as HTML. * @param ComposerContent $content_root The content in which this element is contained. * @return string The interpreted element as HTML. */ public function get_html(ComposerContent $content_root) : string { $htmlCode = ""; if(!is_null($this->link)) { $htmlCode .= 'type == ComposerElementTypes::BUTTON ? 'class="button-link"' : '') . '>'; } switch($this->type) { case ComposerElementTypes::UNSET: $htmlCode .= "

error.element.type.unset !

"; break; case ComposerElementTypes::RAW: $htmlCode .= $this->get_inner_html($content_root); break; case ComposerElementTypes::H1: case ComposerElementTypes::H2: case ComposerElementTypes::H3: // Defining the text's indent level. $_paragraph_ident_level = is_null($this->indent) ? 0 : $this->indent; // Defining the text's size. $_headingFontSize = ($this->type == ComposerElementTypes::H3 ? '18' : ( $this->type == ComposerElementTypes::H2 ? '20' : '22' )); // Composing heading. $htmlCode .= '<' . strtolower($this->type) . ' class="font-weight-semi-bold font-size-' . $_headingFontSize . ' m-0 ml-md-' . ($_paragraph_ident_level * 5) . '">' . $this->get_inner_html($content_root) . 'type) . '>'; break; case ComposerElementTypes::PARAGRAPH: // Defining the text's indent level. $_paragraph_ident_level = is_null($this->indent) ? 0 : $this->indent; // Calculating the vertical margin modifiers $_paragraph_no_margin_top = ComposerElementModifiers::is_modifier_in_modifiers( ComposerElementModifiers::GENERIC_MARGIN_NO_TOP, $this->modifiers); $_paragraph_no_margin_bottom = ComposerElementModifiers::is_modifier_in_modifiers( ComposerElementModifiers::GENERIC_MARGIN_NO_BOTTOM, $this->modifiers); if($_paragraph_no_margin_top && $_paragraph_no_margin_bottom) { $_paragraph_margin_modifier = 'my-0'; } else if($_paragraph_no_margin_top) { $_paragraph_margin_modifier = 'mt-0 mb-10'; } else if($_paragraph_no_margin_bottom) { $_paragraph_margin_modifier = 'mt-10 mb-0'; } else { $_paragraph_margin_modifier = 'my-10'; } // Composing the paragraph $htmlCode .= '

' . $this->get_inner_html($content_root) . '

'; break; case ComposerElementTypes::BUTTON: // Composing the button. $htmlCode .= ''; break; case ComposerElementTypes::CODE: // Defining the code's indent level. $_paragraph_ident_level = is_null($this->indent) ? 0 : $this->indent; // Defining the highlight language. $_language_class = is_null($this->codeLanguage) ? "nohighlight" : "language-" . $this->codeLanguage; // Opening the code element. // Note: The "mt-10" may have to be removed if it clashes with 'no-margin-top' ! $htmlCode .= '
'; // Adding code lines. if(!is_null($this->code)) { foreach($this->code as $code_line) { //$htmlCode .= htmlspecialchars($code_line) . '
'; // Old method (Not compatible with hljs) $htmlCode .= '' . str_replace(" ", " ", htmlspecialchars($code_line)) . '
'; } } if($this->codeCopyable) { $htmlCode .= '
'; } // Closing code element. $htmlCode .= '
'; break; case ComposerElementTypes::HR: // Getting the modifiers' classes $_hr_classes = $this->get_modifiers_classes(); // Composing the element. if(empty($_hr_classes)) { $htmlCode .= ''; } else { $htmlCode .= '
'; } break; case ComposerElementTypes::CONTAINER: // Defining the padding's size. $_container_padding = is_null($this->padding) ? 10 : $this->padding; // Composing the container. // FIXME: Can be re-standardized if a check for the default mt-10 is added at the end after adding the mods. $htmlCode .= '
modifiers ) ? "" : " mt-10") . (ComposerElementModifiers::is_modifier_in_modifiers( ComposerElementModifiers::GENERIC_PADDING_NO_X, $this->modifiers ) ? " " . ComposerElementModifiers::get_modifier_classes( ComposerElementModifiers::GENERIC_PADDING_NO_X ) : "") . (ComposerElementModifiers::is_modifier_in_modifiers( ComposerElementModifiers::GENERIC_PADDING_NO_BOTTOM, $this->modifiers ) ? " " . ComposerElementModifiers::get_modifier_classes( ComposerElementModifiers::GENERIC_PADDING_NO_BOTTOM ) : "") . (ComposerElementModifiers::is_modifier_in_modifiers( ComposerElementModifiers::GENERIC_PADDING_NO_TOP, $this->modifiers ) ? " " . ComposerElementModifiers::get_modifier_classes( ComposerElementModifiers::GENERIC_PADDING_NO_TOP ) : "") . (ComposerElementModifiers::is_modifier_in_modifiers( ComposerElementModifiers::CONTAINER_SCROLL_HORIZONTAL, $this->modifiers ) ? " " . ComposerElementModifiers::get_modifier_classes( ComposerElementModifiers::CONTAINER_SCROLL_HORIZONTAL ) : "") . '">' . $this->get_inner_html($content_root) . '
'; break; case ComposerElementTypes::COLLAPSE: // Composing collapse. $htmlCode .= '
modifiers ) ? "closed" : "open") . '>'; $htmlCode .= ''; $_title = "title"; $_subtitle = "subtitle"; $htmlCode .= '

' . '' . '' . '  '.$_title. ''.$_subtitle.'

'; $htmlCode .= '
modifiers ) ? " p-0 py-01" : "") . (ComposerElementModifiers::is_modifier_in_modifiers( ComposerElementModifiers::DETAILS_NO_ROUNDING, $this->modifiers ) ? " rounded-0" : "") . ' border-0 border-bottom">' . $this->get_inner_html($content_root) . '
'; break; case ComposerElementTypes::SPACER: // Defining the spacer's size. $_spacer_size = is_null($this->size) ? 1 : $this->size; // Composing spacer. $htmlCode .= '
'; break; case ComposerElementTypes::IMAGE: break; case ComposerElementTypes::TABLE: // Composing table. $htmlCode .= ''; if(!is_null($this->head)) { $htmlCode .= ''; foreach($this->head as $head_element) { /** @var ComposerElement $head_element */ $htmlCode .= ''; } $htmlCode .= ''; } if(!is_null($this->body)) { $htmlCode .= ''; for($body_row_index = 0; $body_row_index < sizeof($this->body); $body_row_index++) { $htmlCode .= ''; foreach($this->body[$body_row_index] as $body_cell) { /** @var ComposerElement $body_cell */ $htmlCode .= 'colspan > 1 ? ' colspan="' . $body_cell->colspan . '"' : '') . ($body_cell->rowspan > 1 ? ' rowspan="' . $body_cell->rowspan . '"' : '') . '>' . $body_cell->get_html($content_root) . ''; } $htmlCode .= ''; } $htmlCode .= ''; } $htmlCode .= '
' . $head_element->get_html($content_root) . '
'; break; case ComposerElementTypes::GRID: break; case ComposerElementTypes::GALLERY: $htmlCode .= '
'; foreach($this->images as $galleryImageUrl) { $htmlCode .= ''; } $htmlCode .= '
'; break; case ComposerElementTypes::VIDEO: // Composing the video element $htmlCode .= ''; break; default: $htmlCode .= "

error.unknown !

"; break; } if(!is_null($this->link)) { $htmlCode .= '
'; } return $htmlCode; } } // Generic functions function get_content_error(string $error_title_key, string $error_description_key) : ?ComposerContent { // FIXME: Make this non-nullable !!! return null; } function get_content_file_path(string $content_id) : ?string { global $dir_content; if(ctype_alnum(str_replace("-", "", $content_id))) { return realpath($dir_content . "/items/" . $content_id . ".json"); } return null; } function load_content_by_file_path(string $file_path) : ?ComposerContent { $content_json_data = json_decode(file_get_contents($file_path), true); if(is_null($content_json_data)) { return null; } return ComposerContent::from_json($content_json_data); } function load_content_by_id(string $content_id) : ?ComposerContent { $content_file_path = get_content_file_path($content_id); if(is_null($content_file_path)) { return null; } else { return load_content_by_file_path($content_file_path); } } // Test if(basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) { $content = load_content_by_id("test2"); if(!is_null($content)) { echo "
";
		print_r(htmlspecialchars($content->get_html()));
		echo "
"; echo "
";
		print_r($content);
		echo "
"; } echo("
"); } ?>