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 = ["mt-0", "mt-0"]; const GENERIC_MARGIN_NO_BOTTOM = ["mb-0", "mb-0"]; const GENERIC_MARGIN_NO_LEFT = ["ml-0", "ml-0"]; const GENERIC_MARGIN_NO_RIGHT = ["mr-0", "mr-0"]; const GENERIC_MARGIN_NO_X = ["mx-0", "mx-0"]; const GENERIC_MARGIN_NO_Y = ["my-0", "my-0"]; const GENERIC_MARGIN_NONE = ["m-0", "m-0" ]; // Generic > Padding const GENERIC_PADDING_NO_TOP = ["pt-0", "pt-0"]; const GENERIC_PADDING_NO_BOTTOM = ["pb-0", "pb-0"]; const GENERIC_PADDING_NO_LEFT = ["pl-0", "pl-0"]; const GENERIC_PADDING_NO_RIGHT = ["pr-0", "pr-0"]; const GENERIC_PADDING_NO_X = ["px-0", "px-0"]; const GENERIC_PADDING_NO_Y = ["py-0", "py-0"]; const GENERIC_PADDING_NONE = ["p-0" , "p-0" ]; // Generic > Others const GENERIC_FULL_WIDTH = ["w-full", "w-full"]; const GENERIC_BOLD = ["bold", "f-w-500"]; // Containers const CONTAINER_SCROLL_HORIZONTAL = ["horizontal-scroll", "overflow-x-scroll hide-scrollbar"]; const CONTAINER_SCROLL_HORIZONTAL_AUTO = ["horizontal-scroll-auto", "overflow-x-auto"]; const CONTAINER_CARD = ["card", "card"]; // Buttons const BUTTON_THIN = ["thin", ""]; const BUTTON_THICK = ["thick", ""]; const BUTTON_ROUNDED = ["rounded", ""]; const BUTTON_CIRCLE = ["circle", ""]; const BUTTON_DOWNLOAD_PRIMARY = ["download-primary", "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"]; const TABLE_V2_STYLISH = ["stylish", "stylish r-s border o-hidden"]; const TABLE_V2_CELL_PADDING = ["auto-cell-padding", "table-p-xs table-h-p-s"]; const TABLE_V2_VERTICAL_ALIGN = ["v-center-cells", "table-v-center"]; // 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 - Isn't it done already ? 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_LEGACY && 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_LEGACY: // FIXME: Is this even used anymore ?!? $inner_html = '
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 = ""; // Setting up the link and its title if needed. if(!is_null($this->link)) { $htmlCode .= 'type == ComposerElementTypes::BUTTON ? 'class="bland-link 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: $htmlCode .= getMainHeader( $this->get_inner_html($content_root), null, null, null, !ComposerElementModifiers::is_modifier_in_modifiers( ComposerElementModifiers::GENERIC_MARGIN_NO_TOP, $this->modifiers), "bkgd-math", // heading-dyn-width-1 3, false, false, true ); break; 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) . '' . strtolower($this->type) . '>'; break; case ComposerElementTypes::PARAGRAPH: // Defining the text's indent level. // TODO: Join with others $_paragraph_ident_level = is_null($this->indent) ? 0 : $this->indent; $_paragraph_ident_level = $_paragraph_ident_level < 0 ? 0 : $_paragraph_ident_level; $_paragraph_ident_level = $_paragraph_ident_level > 5 ? 5 : $_paragraph_ident_level; $_paragraph_ident_level = (["", "ml-xs", "ml-s", "ml-m", "ml-l", "ml-xl"])[$_paragraph_ident_level]; // Calculating the vertical margin modifiers $_paragraph_no_margin_top = ComposerElementModifiers::is_modifier_in_modifiers( ComposerElementModifiers::GENERIC_MARGIN_NO_TOP, $this->modifiers); if($_paragraph_no_margin_top) { $_paragraph_margin_modifier = ''; } else { $_paragraph_margin_modifier = 'mt-xs '; } // Adding other tags manually // FIXME: Use the standard functions FFS... $_paragraph_margin_modifier .= ( ComposerElementModifiers::is_modifier_in_modifiers( ComposerElementModifiers::GENERIC_BOLD, $this->modifiers) ) ? "t-w-500 " : ""; // Fixes some "overflowing" issue when indent is bigger than 2. $_paragraph_margin_modifier .= "mr-s "; // 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; // Parent container with a relative position to handle the wedge when the code itself is // horizontally scrollable. $htmlCode .= '' . $head_element->get_html($content_root) . ' | '; } $htmlCode .= '
---|
colspan > 1 ? ' colspan="' . $body_cell->colspan . '"' : '') . ($body_cell->rowspan > 1 ? ' rowspan="' . $body_cell->rowspan . '"' : '') . '>' . $body_cell->get_html($content_root) . ' | '; } $htmlCode .= '
error.unknown !
"; break; } if(!is_null($this->link)) { $htmlCode .= '