diff --git a/includes/ajaxHandler/getdescription.class.php b/includes/ajaxHandler/getdescription.class.php index 41802c13..cabf84d9 100644 --- a/includes/ajaxHandler/getdescription.class.php +++ b/includes/ajaxHandler/getdescription.class.php @@ -28,7 +28,7 @@ class AjaxGetdescription extends AjaxHandler if (!User::canWriteGuide()) return ''; - $desc = (new Markup($this->_post['description']))->stripTags(); + $desc = Markup::stripTags($this->_post['description']); return Lang::trimTextClean($desc, 120); } diff --git a/includes/components/communitycontent.class.php b/includes/components/communitycontent.class.php index 2fc8843f..3cd88627 100644 --- a/includes/components/communitycontent.class.php +++ b/includes/components/communitycontent.class.php @@ -198,7 +198,7 @@ class CommunityContent foreach ($results as $r) { - (new Markup($r['body']))->parseGlobalsFromText(self::$jsGlobals); + Markup::parseTags($r['body'], self::$jsGlobals); $reply = array( 'commentid' => $commentId, @@ -359,7 +359,7 @@ class CommunityContent $i = 0; foreach ($results as $r) { - (new Markup($r['body']))->parseGlobalsFromText(self::$jsGlobals); + Markup::parseTags($r['body'], self::$jsGlobals); self::$jsGlobals[Type::USER][$r['userId']] = $r['userId']; @@ -384,7 +384,7 @@ class CommunityContent $c['responseroles'] = $r['responseRoles']; $c['responseuser'] = $r['responseUser']; - (new Markup($r['responseBody']))->parseGlobalsFromText(self::$jsGlobals); + Markup::parseTags($r['responseBody'], self::$jsGlobals); } if ($r['editCount']) // lastEdit diff --git a/includes/components/frontend/announcement.class.php b/includes/components/frontend/announcement.class.php new file mode 100644 index 00000000..8823428f --- /dev/null +++ b/includes/components/frontend/announcement.class.php @@ -0,0 +1,69 @@ +editable = true; + + if ($this->mode != self::MODE_PAGE_TOP && $this->mode != self::MODE_CONTENT_TOP) + $this->mode = self::MODE_PAGE_TOP; + + if ($status != self::STATUS_DISABLED && $status != self::STATUS_ENABLED && $status != self::STATUS_DELETED) + $this->status = self::STATUS_DELETED; + else + $this->status = $status; + } + + public function jsonSerialize() : array + { + $json = array( + 'parent' => 'announcement-' . abs($this->id), + 'id' => $this->editable ? -$this->id : $this->id, + 'mode' => $this->mode, + 'status' => $this->status, + 'name' => $this->name, + 'text' => (string)$this->text // force LocString to naive string for display + ); + + if ($this->style) + $json['style'] = $this->style; + + return $json; + } + + public function __toString() : string + { + if ($this->status == self::STATUS_DELETED) + return ''; + + return "new Announcement(".Util::toJSON($this).");\n"; + } +} + +?> diff --git a/includes/components/frontend/book.class.php b/includes/components/frontend/book.class.php new file mode 100644 index 00000000..b1ef62e7 --- /dev/null +++ b/includes/components/frontend/book.class.php @@ -0,0 +1,50 @@ +parent) + trigger_error(self::class.'::__construct - initialized without parent element', E_USER_WARNING); + + if (!$this->pages) + trigger_error(self::class.'::__construct - initialized without content', E_USER_WARNING); + else + $this->pages = Util::parseHtmlText($this->pages); + } + + public function &iterate() : \Generator + { + reset($this->pages); + + foreach ($this->pages as $idx => &$page) + yield $idx => $page; + } + + public function jsonSerialize() : array + { + $result = []; + + foreach ($this as $prop => $val) + if ($val !== null && $prop[0] != '_') + $result[$prop] = $val; + + return $result; + } + + public function __toString() : string + { + return "new Book(".Util::toJSON($this).");\n"; + } +} + +?> diff --git a/includes/components/frontend/iconelement.class.php b/includes/components/frontend/iconelement.class.php new file mode 100644 index 00000000..a9fdac67 --- /dev/null +++ b/includes/components/frontend/iconelement.class.php @@ -0,0 +1,159 @@ +quality = 'q'.$quality; + else if ($quality !== null) + $this->quality = 'q'; + else + $this->quality = ''; + + if ($size < self::SIZE_SMALL || $size > self::SIZE_LARGE) + { + trigger_error('IconElement::__construct - invalid icon size '.$size.'. Normalied to 1 [small]', E_USER_WARNING); + $this->size = self::SIZE_SMALL; + } + else + $this->size = $size; + + if ($align && !in_array($align, ['left', 'right', 'center', 'justify'])) + { + trigger_error('IconElement::__construct - unset invalid align value "'.$align.'".', E_USER_WARNING); + $this->align = null; + } + else + $this->align = $align; + + if ($type && $typeId && !Type::validateIds($type, $typeId)) + { + $link = false; + trigger_error('IconElement::__construct - invalid typeId '.$typeId.' for '.Type::getFileString($type).'.', E_USER_WARNING); + } + else if (!$type || !$typeId) + $link = false; + + if ($link || $url) + $this->href = $url ?: '?'.Type::getFileString($this->type).'='.$this->typeId; + + // see Spell/Tools having icon container but no actual icon and having to be inline with other IconElements + $this->noIcon = !$typeId || !Type::hasIcon($type); + } + + public function renderContainer(int $lpad = 0, int &$iconIdxOffset = 0, bool $rowWrap = false) : string + { + if (!$this->noIcon) + $this->idx = ++$iconIdxOffset; + + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $td = $dom->createElement('td'); + $th = $dom->createElement('th'); + + if ($this->noIcon) // see Spell/Tools or AchievementCriteria having no actual icon, but placeholder + { + $ul = $dom->createElement('ul'); + $li = $dom->createElement('li'); + $var = $dom->createElement('var', ' '); + $li->appendChild($var); + $ul->appendChild($li); + $th->appendChild($ul); + } + else + { + $th->setAttribute('id', $this->element . $this->idx); + if ($this->align) + $th->setAttribute('align', $this->align); + } + + if ($this->href) + ($a = $dom->createElement('a', $this->text))->setAttribute('href', $this->href); + else + $a = $dom->createTextNode($this->text); + + if ($this->quality) + { + ($sp = $dom->createElement('span'))->setAttribute('class', $this->quality); + $sp->appendChild($a); + $td->appendChild($sp); + } + else + $td->appendChild($a); + + // extraText can be HTML, so import it as a fragment + if ($this->extraText) + { + $fragment = $dom->createDocumentFragment(); + $fragment->appendXML(' '.$this->extraText); + $td->appendChild($fragment); + } + // only for objectives list..? + if ($this->num && $this->size == self::SIZE_SMALL) + $td->appendChild($dom->createTextNode(' ('.$this->num.')')); + + if ($rowWrap) + { + $tr = $dom->createElement('tr'); + $tr->appendChild($th); + $tr->appendChild($td); + $dom->append($tr); + } + else + $dom->append($th, $td); + + return str_repeat(' ', $lpad) . $dom->saveHTML(); + } + + // $WH.ge('icontab-icon1').appendChild(g_spells.createIcon(40120, 1, '1-4', 0)); + + public function renderJS(int $lpad = 0) : string + { + if ($this->noIcon) + return ''; + + $params = [$this->typeId, $this->size]; + if ($this->num || $this->qty) + $params[] = is_numeric($this->num) ? $this->num : "'".$this->num."'"; + if ($this->qty) + $params[] = is_numeric($this->qty) ? $this->qty : "'".$this->qty."'"; + + return str_repeat(' ', $lpad) . sprintf(self::CREATE_ICON_TPL, $this->element, $this->idx, Type::getJSGlobalString($this->type), implode(', ', $params)); + } +} + +?> diff --git a/includes/components/frontend/infoboxmarkup.class.php b/includes/components/frontend/infoboxmarkup.class.php new file mode 100644 index 00000000..c88972b9 --- /dev/null +++ b/includes/components/frontend/infoboxmarkup.class.php @@ -0,0 +1,49 @@ += count($this->items)) + $this->items[] = $item; + else + array_splice($this->items, $pos, 0, $item); + } + + public function append(string $text) : self + { + if ($this->items && !$this->__text) + $this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]'); + + return parent::append($text); + } + + public function __toString() : string + { + if ($this->items && !$this->__text) + $this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]'); + + return parent::__toString(); + } + + public function getJsGlobals() : array + { + if ($this->items && !$this->__text) + $this->replace('[ul][li]' . implode('[/li][li]', $this->items) . '[/li][/ul]'); + + return parent::getJsGlobals(); + } +} + +?> diff --git a/includes/components/frontend/listview.class.php b/includes/components/frontend/listview.class.php new file mode 100644 index 00000000..7180d6ba --- /dev/null +++ b/includes/components/frontend/listview.class.php @@ -0,0 +1,174 @@ + ['template' => 'achievement', 'id' => 'achievements', 'name' => '$LANG.tab_achievements' ], + 'areatrigger' => ['template' => 'areatrigger', 'id' => 'areatrigger', ], + 'calendar' => ['template' => 'holidaycal', 'id' => 'calendar', 'name' => '$LANG.tab_calendar' ], + 'class' => ['template' => 'classs', 'id' => 'classes', 'name' => '$LANG.tab_classes' ], + 'commentpreview' => ['template' => 'commentpreview', 'id' => 'comments', 'name' => '$LANG.tab_comments' ], + 'npc' => ['template' => 'npc', 'id' => 'npcs', 'name' => '$LANG.tab_npcs' ], + 'currency' => ['template' => 'currency', 'id' => 'currencies', 'name' => '$LANG.tab_currencies' ], + 'emote' => ['template' => 'emote', 'id' => 'emotes', ], + 'enchantment' => ['template' => 'enchantment', 'id' => 'enchantments', ], + 'event' => ['template' => 'holiday', 'id' => 'holidays', 'name' => '$LANG.tab_holidays' ], + 'faction' => ['template' => 'faction', 'id' => 'factions', 'name' => '$LANG.tab_factions' ], + 'genericmodel' => ['template' => 'genericmodel', 'id' => 'same-model-as', 'name' => '$LANG.tab_samemodelas' ], + 'icongallery' => ['template' => 'icongallery', 'id' => 'icons', ], + 'item' => ['template' => 'item', 'id' => 'items', 'name' => '$LANG.tab_items' ], + 'itemset' => ['template' => 'itemset', 'id' => 'itemsets', 'name' => '$LANG.tab_itemsets' ], + 'mail' => ['template' => 'mail', 'id' => 'mails', ], + 'model' => ['template' => 'model', 'id' => 'gallery', 'name' => '$LANG.tab_gallery' ], + 'object' => ['template' => 'object', 'id' => 'objects', 'name' => '$LANG.tab_objects' ], + 'pet' => ['template' => 'pet', 'id' => 'hunter-pets', 'name' => '$LANG.tab_pets' ], + 'profile' => ['template' => 'profile', 'id' => 'profiles', 'name' => '$LANG.tab_profiles' ], + 'quest' => ['template' => 'quest', 'id' => 'quests', 'name' => '$LANG.tab_quests' ], + 'race' => ['template' => 'race', 'id' => 'races', 'name' => '$LANG.tab_races' ], + 'replypreview' => ['template' => 'replypreview', 'id' => 'comment-replies', 'name' => '$LANG.tab_commentreplies'], + 'reputationhistory' => ['template' => 'reputationhistory', 'id' => 'reputation', 'name' => '$LANG.tab_reputation' ], + 'screenshot' => ['template' => 'screenshot', 'id' => 'screenshots', 'name' => '$LANG.tab_screenshots' ], + 'skill' => ['template' => 'skill', 'id' => 'skills', 'name' => '$LANG.tab_skills' ], + 'sound' => ['template' => 'sound', 'id' => 'sounds', 'name' => '$LANG.types[19][2]' ], + 'spell' => ['template' => 'spell', 'id' => 'spells', 'name' => '$LANG.tab_spells' ], + 'title' => ['template' => 'title', 'id' => 'titles', 'name' => '$LANG.tab_titles' ], + 'topusers' => ['template' => 'topusers', 'id' => 'topusers', 'name' => '$LANG.topusers' ], + 'video' => ['template' => 'video', 'id' => 'videos', 'name' => '$LANG.tab_videos' ], + 'zone' => ['template' => 'zone', 'id' => 'zones', 'name' => '$LANG.tab_zones' ], + 'guide' => ['template' => 'guide', 'id' => 'guides', ] + ); + + private string $id = ''; + private ?string $name = null; + private ?array $data = null; // js:array of object + private ?string $tabs = null; // js:Object; instance of "Tabs" + private ?string $parent = 'lv-generic'; // HTMLNode.id; can be null but is pretty much always 'lv-generic' + private ?string $template = null; + private ?int $mode = null; // js:int; defaults to MODE_DEFAULT + private ?string $note = null; // text in top band + + private ?int $poundable = null; // 0 (no); 1 (always); 2 (yes, w/o sorting); defaults to 1 + private ?int $searchable = null; // js:bool; defaults to FALSE + private ?int $filtrable = null; // js:bool; defaults to FALSE + private ?int $sortable = null; // js:bool; defaults to FALSE + private ?int $searchDelay = null; // in ms; defalts to 333 + private ?int $clickable = null; // js:bool; defaults to TRUE + private ?int $hideBands = null; // js:int; 1:top, 2:bottom, 3:both; + private ?int $hideNav = null; // js:int; 1:top, 2:bottom, 3:both; + private ?int $hideHeader = null; // js:bool + private ?int $hideCount = null; // js:bool + private ?int $debug = null; // js:bool + private ?int $_truncated = null; // js:bool; adds predefined note to top band, because there was too much data to display + private ?int $_errors = null; // js:bool; adds predefined note to top band, because there was an error + private ?int $_petTalents = null; // js:bool; applies modifier for talent levels + + private ?int $nItemsPerPage = null; // js:int; defaults to 50 + private ?int $_totalCount = null; // js:int; used by loot and comments + private ?array $clip = null; // js:array of int {w:, h:} + private ?string $customPound = null; + private ?string $genericlinktype = null; // sometimes set when expecting to display model + private ?array $_upgradeIds = null; // js:array of int (itemIds) + + private null|array|string $extraCols = null; // js:callable or js:array of object + private null|array|string $visibleCols = null; // js:callable or js:array of string + private null|array|string $hiddenCols = null; // js:callable or js:array of string + private null|array|string $sort = null; // js:callable or js:array of colIndizes + + private ?string $onBeforeCreate = null; // js:callable + private ?string $onAfterCreate = null; // js:callable + private ?string $onNoData = null; // js:callable + private ?string $computeDataFunc = null; // js:callable + private ?string $onSearchSubmit = null; // js:callable + private ?string $createNote = null; // js:callable + private ?string $createCbControls = null; // js:callable + private ?string $customFilter = null; // js:callable + private ?string $getItemLink = null; // js:callable + private ?array $sortOptions = null; // js:array of object {id:, name:, hidden:, type:"text", sortFunc:} + + private string $__addIn = ''; + + public function __construct(array $opts, string $template = '', string $addIn = '') + { + if ($template && isset(self::TEMPLATES[$template])) + foreach (self::TEMPLATES[$template] as $k => $v) + $this->$k = $v; + + foreach ($opts as $k => $v) + { + if (property_exists($this, $k)) + { + // reindex arrays to force json_encode to treat them as arrays + if (is_array($v)) // in_array($k, ['data', 'extraCols', 'visibleCols', 'hiddenCols', 'sort', 'sortOptions'])) + $v = array_values($v); + $this->$k = $v; + } + else + trigger_error(self::class.'::__construct - unrecognized option: ' . $k); + } + + if ($addIn && !Template\PageTemplate::test('listviews/', $addIn.'.tpl')) + trigger_error('Nonexistent Listview addin requested: template/listviews/'.$addIn.'.tpl', E_USER_ERROR); + else if ($addIn) + $this->__addIn = 'template/listviews/'.$addIn.'.tpl'; + } + + public function &iterate() : \Generator + { + reset($this->data); + + foreach ($this->data as $idx => &$row) + yield $idx => $row; + } + + public function getTemplate() : string + { + return $this->template; + } + + public function setTabs(string $tabVar) : void + { + if ($tabVar[0] !== '$') // expects a jsVar, which we denote with a prefixed $ + $tabVar = '$' . $tabVar; + + $this->tabs = $tabVar; + } + + public function setError() : void + { + $this->_errors = 1; + } + + public function jsonSerialize() : array + { + $result = []; + + foreach ($this as $prop => $val) + if ($val !== null && substr($prop, 0, 2) != '__') + $result[$prop] = $val; + + return $result; + } + + public function __toString() : string + { + if ($this->__addIn) + include($this->__addIn); + + return "new Listview(".Util::toJSON($this).");\n"; + } +} + +?> diff --git a/includes/components/frontend/markup.class.php b/includes/components/frontend/markup.class.php new file mode 100644 index 00000000..4c07861a --- /dev/null +++ b/includes/components/frontend/markup.class.php @@ -0,0 +1,291 @@ + $v) + { + if (property_exists($this, $k)) + $this->$k = $v; + else + trigger_error(self::class.'::__construct - unrecognized option: ' . $k); + } + + $this->__text = $text; + + if ($parent) + $this->__parent = $parent; + } + + public function getJsGlobals() : array + { + return $this->_parseTags(); + } + + public function getParent() : string + { + return $this->__parent; + } + + + /***********************/ + /* Markup tag handling */ + /***********************/ + + private function _parseTags(array &$jsg = []) : array + { + return self::parseTags($this->__text, $jsg); + } + + public static function parseTags(string $text, array &$jsg = []) : array + { + $jsGlobals = []; + + if (preg_match_all(self::DB_TAG_PATTERN, $text, $matches, PREG_SET_ORDER)) + { + foreach ($matches as $match) + { + if ($match[1] == 'statistic') + $match[1] = 'achievement'; + else if ($match[1] == 'icondb') + $match[1] = 'icon'; + + if ($match[1] == 'money') + { + if (stripos($match[0], 'items')) + { + if (preg_match('/items=([0-9,]+)/i', $match[0], $submatch)) + { + $sm = explode(',', $submatch[1]); + for ($i = 0; $i < count($sm); $i+=2) + $jsGlobals[Type::ITEM][$sm[$i]] = $sm[$i]; + } + } + + if (stripos($match[0], 'currency')) + { + if (preg_match('/currency=([0-9,]+)/i', $match[0], $submatch)) + { + $sm = explode(',', $submatch[1]); + for ($i = 0; $i < count($sm); $i+=2) + $jsGlobals[Type::CURRENCY][$sm[$i]] = $sm[$i]; + } + } + } + else if ($type = Type::getIndexFrom(Type::IDX_FILE_STR, $match[1])) + $jsGlobals[$type][$match[2]] = $match[2]; + } + } + + Util::mergeJsGlobals($jsg, $jsGlobals); + + return $jsGlobals; + } + + private function _stripTags(array $jsgData = []) : string + { + return self::stripTags($this->__text, $jsgData); + } + + public static function stripTags(string $text, array $jsgData = []) : string + { + // replace DB Tags + $text = preg_replace_callback(self::DB_TAG_PATTERN, function ($match) use ($jsgData) { + if ($match[1] == 'statistic') + $match[1] = 'achievement'; + else if ($match[1] == 'icondb') + $match[1] = 'icon'; + else if ($match[1] == 'money') + { + $moneys = []; + if (stripos($match[0], 'items')) + { + if (preg_match('/items=([0-9,]+)/i', $match[0], $submatch)) + { + $sm = explode(',', $submatch[1]); + for ($i = 0; $i < count($sm); $i += 2) + { + if (!empty($jsgData[Type::ITEM][1][$sm[$i]])) + $moneys[] = $jsgData[Type::ITEM][1][$sm[$i]]['name']; + else + $moneys[] = Util::ucFirst(Lang::game('item')).' #'.$sm[$i]; + } + } + } + + if (stripos($match[0], 'currency')) + { + if (preg_match('/currency=([0-9,]+)/i', $match[0], $submatch)) + { + $sm = explode(',', $submatch[1]); + for ($i = 0; $i < count($sm); $i += 2) + { + if (!empty($jsgData[Type::CURRENCY][1][$sm[$i]])) + $moneys[] = $jsgData[Type::CURRENCY][1][$sm[$i]]['name']; + else + $moneys[] = Util::ucFirst(Lang::game('curency')).' #'.$sm[$i]; + } + } + } + + return Lang::concat($moneys); + } + if ($type = Type::getIndexFrom(Type::IDX_FILE_STR, $match[1])) + { + if (!empty($jsgData[$type][1][$match[2]])) + return $jsgData[$type][1][$match[2]]['name']; + else + return Util::ucFirst(Lang::game($match[1])).' #'.$match[2]; + } + + trigger_error('Markup::stripTags() - encountered unhandled db-tag: '.var_export($match)); + return ''; + }, $text); + + // replace line endings + $text = str_replace('[br]', "\n", $text); + + // strip other Tags + $stripped = ''; + $inTag = false; + for ($i = 0; $i < strlen($text); $i++) + { + if ($text[$i] == '[' && (!$i || $text[$i - 1] != '\\')) + $inTag = true; + if (!$inTag) + $stripped .= $text[$i]; + if ($inTag && $text[$i] == ']' && (!$i || $text[$i - 1] != '\\')) + $inTag = false; + } + + return $stripped; + } + + + /*********************/ + /* String Operations */ + /*********************/ + + public function append(string $text) : self + { + $this->__text .= $text; + return $this; + } + + public function prepend(string $text) : self + { + $this->__text = $text . $this->__text; + return $this; + } + + public function apply(\Closure $fn) : void + { + $this->__text = $fn($this->__text); + } + + public function replace(string $middle, int $offset = 0, ?int $len = null) : self + { + // y no mb_substr_replace >:( + $start = $end = ''; + + if ($offset < 0) + $offset = mb_strlen($this->__text) + $offset; + + $start = mb_substr($this->__text, 0, $offset); + + if (!is_null($len) && $len >= 0) + $end = mb_substr($this->__text, $offset + $len); + else if (!is_null($len) && $len < 0) + $end = mb_substr($this->__text, $offset + mb_strlen($this->__text) + $len); + + $this->__text = $start . $middle . $end; + return $this; + } + + private function cleanText() : string + { + // break script-tags, unify newlines + $val = preg_replace(['/script\s*\>/i', "/\r\n/", "/\r/"], ['script>', "\n", "\n"], $this->__text); + + return strtr(Util::jsEscape($val), ['script>' => 'scr"+"ipt>']); + } + + public function jsonSerialize() : array + { + $result = []; + + foreach ($this as $prop => $val) + if ($val !== null && $prop[0] != '_') + $result[$prop] = $val; + + return $result; + } + + public function __toString() : string + { + if ($this->jsonSerialize()) + return 'Markup.printHtml("'.$this->cleanText().'", "'.$this->__parent.'", '.Util::toJSON($this).");\n"; + + return 'Markup.printHtml("'.$this->cleanText().'", "'.$this->__parent."\");\n"; + } +} + +?> diff --git a/includes/components/frontend/summary.class.php b/includes/components/frontend/summary.class.php new file mode 100644 index 00000000..62ad87e5 --- /dev/null +++ b/includes/components/frontend/summary.class.php @@ -0,0 +1,70 @@ + $v) + { + if (property_exists($this, $k)) + $this->$k = $v; + else + trigger_error(self::class.'::__construct - unrecognized option: ' . $k); + } + + if (!$this->template) + trigger_error(self::class.'::__construct - initialized without template', E_USER_WARNING); + if (!$this->id) + trigger_error(self::class.'::__construct - initialized without HTMLNode#id to reference', E_USER_WARNING); + } + + public function &iterate() : \Generator + { + reset($this->groups); + + foreach ($this->groups as $idx => &$group) + yield $idx => $group; + } + + public function addGroup(array $group) : void + { + $this->groups[] = $group; + } + + public function jsonSerialize() : array + { + $result = []; + + foreach ($this as $prop => $val) + if ($val !== null && $prop[0] != '_') + $result[$prop] = $val; + + return $result; + } + + public function __toString() : string + { + return "new Summary(".Util::toJSON($this).");\n"; + } +} + +?> diff --git a/includes/components/frontend/tabs.class.php b/includes/components/frontend/tabs.class.php new file mode 100644 index 00000000..2563d7a9 --- /dev/null +++ b/includes/components/frontend/tabs.class.php @@ -0,0 +1,142 @@ + $v) + { + if (property_exists($this, $k)) + $this->$k = $v; + else + trigger_error(self::class.'::__construct - unrecognized option: ' . $k); + } + } + + public function &iterate() : \Generator + { + reset($this->__tabs); + + foreach ($this->__tabs as $idx => &$tab) + yield $idx => $tab; + } + + public function addListviewTab(Listview $lv) : void + { + $this->__tabs[] = $lv; + } + + public function addDataTab(string $id, string $name, string $data) : void + { + $this->__tabs[] = ['id' => $id, 'name' => $name, 'data' => $data]; + $this->__forceTabs = true; // otherwise a single DataTab could not be accessed + } + + public function getDataContainer() : \Generator + { + foreach ($this->__tabs as $tab) + if (is_array($tab)) + yield ''; + } + + public function getFlush() : string + { + if ($this->isTabbed()) + return $this->__tabVar.".flush();"; + + return ''; + } + + public function isTabbed() : bool + { + return count($this->__tabs) > 1 || $this->__forceTabs; + } + + + /***********************/ + /* enable deep cloning */ + /***********************/ + + public function __clone() + { + foreach ($this->__tabs as $idx => $tab) + { + if (is_array($tab)) + continue; + + $this->__tabs[$idx] = clone $tab; + } + } + + + /******************/ + /* make countable */ + /******************/ + + public function count() : int + { + return count($this->__tabs); + } + + + /************************/ + /* make Tabs stringable */ + /************************/ + + public function jsonSerialize() : array + { + $result = []; + + foreach ($this as $prop => $val) + if ($val !== null && $prop[0] != '_') + $result[$prop] = $val; + + return $result; + } + + public function __toString() : string + { + $result = ''; + + if ($this->isTabbed()) + $result .= "var ".$this->__tabVar." = new Tabs(".Util::toJSON($this).");\n"; + + foreach ($this->__tabs as $tab) + { + if (is_array($tab)) + { + $n = $tab['name'][0] == '$' ? substr($tab['name'], 1) : "'".$tab['name']."'"; + $result .= $this->__tabVar.".add(".$n.", { id: '".$tab['id']."' });\n"; + } + else + { + if ($this->isTabbed()) + $tab->setTabs($this->__tabVar); + + $result .= $tab; // Listview::__toString here + } + } + + return $result . "\n"; + } +} + +?> diff --git a/includes/components/frontend/tooltip.class.php b/includes/components/frontend/tooltip.class.php new file mode 100644 index 00000000..b9c51634 --- /dev/null +++ b/includes/components/frontend/tooltip.class.php @@ -0,0 +1,60 @@ + $v) + { + if (property_exists($this, $k)) + $this->$k = $v; + else + trigger_error(self::class.'::__construct - unrecognized option: ' . $k); + } + } + + public function jsonSerialize() : array + { + $out = []; + + $locString = Lang::getLocale()->json(); + + foreach ($this as $k => $v) + { + if ($v === null || $k[0] == '_') + continue; + + if ($k == 'icon') + $out[$k] = rawurldecode($v); + else if ($k == 'quality' || $k == 'map' || $k == 'daily') + $out[$k] = $v; + else + $out[$k . '_' . $locString] = $v; + } + + return $out; + } + + public function __toString() : string + { + return sprintf($this->__powerTpl, Util::toJSON($this->__subject, JSON_AOWOW_POWER), Lang::getLocale()->value, Util::toJSON($this, JSON_AOWOW_POWER))."\n"; + } +} + +?> diff --git a/includes/components/markup.class.php b/includes/components/markup.class.php deleted file mode 100644 index ff8b652a..00000000 --- a/includes/components/markup.class.php +++ /dev/null @@ -1,150 +0,0 @@ -text = $text; - } - - public function parseGlobalsFromText(&$jsg = []) - { - if (preg_match_all(self::$dbTagPattern, $this->text, $matches, PREG_SET_ORDER)) - { - foreach ($matches as $match) - { - if ($match[1] == 'statistic') - $match[1] = 'achievement'; - else if ($match[1] == 'icondb') - $match[1] = 'icon'; - - if ($match[1] == 'money') - { - if (stripos($match[0], 'items')) - { - if (preg_match('/items=([0-9,]+)/i', $match[0], $submatch)) - { - $sm = explode(',', $submatch[1]); - for ($i = 0; $i < count($sm); $i+=2) - $this->jsGlobals[Type::ITEM][$sm[$i]] = $sm[$i]; - } - } - - if (stripos($match[0], 'currency')) - { - if (preg_match('/currency=([0-9,]+)/i', $match[0], $submatch)) - { - $sm = explode(',', $submatch[1]); - for ($i = 0; $i < count($sm); $i+=2) - $this->jsGlobals[Type::CURRENCY][$sm[$i]] = $sm[$i]; - } - } - } - else if ($type = Type::getIndexFrom(Type::IDX_FILE_STR, $match[1])) - $this->jsGlobals[$type][$match[2]] = $match[2]; - } - } - - Util::mergeJsGlobals($jsg, $this->jsGlobals); - - return $this->jsGlobals; - } - - public function stripTags($globals = []) - { - // since this is an article the db-tags should already be parsed - $text = preg_replace_callback(self::$dbTagPattern, function ($match) use ($globals) { - if ($match[1] == 'statistic') - $match[1] = 'achievement'; - else if ($match[1] == 'icondb') - $match[1] = 'icon'; - else if ($match[1] == 'money') - { - $moneys = []; - if (stripos($match[0], 'items')) - { - if (preg_match('/items=([0-9,]+)/i', $match[0], $submatch)) - { - $sm = explode(',', $submatch[1]); - for ($i = 0; $i < count($sm); $i += 2) - { - if (!empty($globals[Type::ITEM][1][$sm[$i]])) - $moneys[] = $globals[Type::ITEM][1][$sm[$i]]['name']; - else - $moneys[] = Util::ucFirst(Lang::game('item')).' #'.$sm[$i]; - } - } - } - - if (stripos($match[0], 'currency')) - { - if (preg_match('/currency=([0-9,]+)/i', $match[0], $submatch)) - { - $sm = explode(',', $submatch[1]); - for ($i = 0; $i < count($sm); $i += 2) - { - if (!empty($globals[Type::CURRENCY][1][$sm[$i]])) - $moneys[] = $globals[Type::CURRENCY][1][$sm[$i]]['name']; - else - $moneys[] = Util::ucFirst(Lang::game('curency')).' #'.$sm[$i]; - } - } - } - - return Lang::concat($moneys); - } - if ($type = Type::getIndexFrom(Type::IDX_FILE_STR, $match[1])) - { - if (!empty($globals[$type][1][$match[2]])) - return $globals[$type][1][$match[2]]['name']; - else - return Util::ucFirst(Lang::game($match[1])).' #'.$match[2]; - } - - trigger_error('Markup::stripTags() - encountered unhandled db-tag: '.var_export($match)); - return ''; - }, $this->text); - - $text = str_replace('[br]', "\n", $text); - $stripped = ''; - - $inTag = false; - for ($i = 0; $i < strlen($text); $i++) - { - if ($text[$i] == '[') - $inTag = true; - if (!$inTag) - $stripped .= $text[$i]; - if ($text[$i] == ']') - $inTag = false; - } - - return $stripped; - } - - public function fromHtml() - { - } - - public function toHtml() - { - } -} - -?> diff --git a/includes/kernel.php b/includes/kernel.php index 8da5f9b6..b9e050a1 100644 --- a/includes/kernel.php +++ b/includes/kernel.php @@ -65,6 +65,8 @@ spl_autoload_register(function ($class) if (file_exists('includes/components/'.strtolower($class).'.class.php')) require_once 'includes/components/'.strtolower($class).'.class.php'; + else if (file_exists('includes/components/frontend/'.strtolower($class).'.class.php')) + require_once 'includes/components/frontend/'.strtolower($class).'.class.php'; }); // TC systems in components diff --git a/includes/types/guide.class.php b/includes/types/guide.class.php index 61f314df..dc980582 100644 --- a/includes/types/guide.class.php +++ b/includes/types/guide.class.php @@ -72,7 +72,7 @@ class GuideList extends BaseType $this->article[$a['rev']] = $a['article']; if ($this->article[$a['rev']]) { - (new Markup($this->article[$a['rev']]))->parseGlobalsFromText($this->jsGlobals); + Markup::parseTags($this->article[$a['rev']], $this->jsGlobals); return $this->article[$a['rev']]; } else diff --git a/pages/genericPage.class.php b/pages/genericPage.class.php index 66e92447..1919a90f 100644 --- a/pages/genericPage.class.php +++ b/pages/genericPage.class.php @@ -559,9 +559,9 @@ class GenericPage if ($article) { if ($article['article']) - (new Markup($article['article']))->parseGlobalsFromText($this->jsgBuffer); + Markup::parseTags($article['article'], $this->jsgBuffer); if ($article['quickInfo']) - (new Markup($article['quickInfo']))->parseGlobalsFromText($this->jsgBuffer); + Markup::parseTags($article['quickInfo'], $this->jsgBuffer); $this->article = array( 'text' => Util::jsEscape(Util::defStatic($article['article'])), diff --git a/pages/guide.php b/pages/guide.php index e002809c..f48d0374 100644 --- a/pages/guide.php +++ b/pages/guide.php @@ -195,7 +195,7 @@ class GuidePage extends GenericPage 'specId' => $this->_post['specId'], 'title' => $this->_post['title'], 'name' => $this->_post['name'], - 'description' => $this->_post['description'] ?: Lang::trimTextClean((new Markup($this->_post['body']))->stripTags(), 120), + 'description' => $this->_post['description'] ?: Lang::trimTextClean(Markup::stripTags($this->_post['body']), 120), 'locale' => $this->_post['locale'], 'roles' => User::$groups, 'status' => GUIDE_STATUS_DRAFT diff --git a/pages/home.php b/pages/home.php index a1714d66..366a94ea 100644 --- a/pages/home.php +++ b/pages/home.php @@ -39,7 +39,7 @@ class HomePage extends GenericPage $this->featuredBox['text'] = Util::localizedString($this->featuredBox, 'text', true); - if ($_ = (new Markup($this->featuredBox['text']))->parseGlobalsFromText()) + if ($_ = Markup::parseTags($this->featuredBox['text'])) $this->extendGlobalData($_); if (empty($this->featuredBox['boxBG']))