diff --git a/endpoints/admin/guide.php b/endpoints/admin/guide.php new file mode 100644 index 00000000..8a6d93d8 --- /dev/null +++ b/endpoints/admin/guide.php @@ -0,0 +1,81 @@ + ['filter' => FILTER_VALIDATE_INT ], + 'status' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_range' => GuideMgr::STATUS_APPROVED, 'max_range' => GuideMgr::STATUS_REJECTED]], + 'msg' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob'] ] + ); + + protected function generate() : void + { + if (!$this->assertPOST('id', 'status')) + { + trigger_error('AdminGuideResponse - malformed request received', E_USER_ERROR); + $this->result = self::ERR_MISCELLANEOUS; + return; + } + + $guide = DB::Aowow()->selectRow('SELECT `userId`, `status` FROM ?_guides WHERE `id` = ?d', $this->_post['id']); + if (!$guide) + { + trigger_error('AdminGuideResponse - guide #'.$this->_post['id'].' not found', E_USER_ERROR); + $this->result = self::ERR_GUIDE; + return; + } + + if ($this->_post['status'] == $guide['status']) + { + trigger_error('AdminGuideResponse - guide #'.$this->_post['id'].' already has status #'.$this->_post['status'], E_USER_ERROR); + $this->result = self::ERR_STATUS; + return; + } + + // status can only be APPROVED or REJECTED due to input validation + if (!$this->update($this->_post['id'], $this->_post['status'], $this->_post['msg'])) + { + trigger_error('AdminGuideResponse - write to db failed for guide #'.$this->_post['id'], E_USER_ERROR); + $this->result = self::ERR_WRITE_DB; + return; + } + + if ($this->_post['status'] == GuideMgr::STATUS_APPROVED) + Util::gainSiteReputation($guide['userId'], SITEREP_ACTION_ARTICLE, ['id' => $this->_post['id']]); + + $this->result = self::ERR_NONE; + } + + private function update(int $id, int $status, ?string $msg = null) : bool + { + if ($status == GuideMgr::STATUS_APPROVED) // set display rev to latest + $ok = DB::Aowow()->query('UPDATE ?_guides SET `status` = ?d, `rev` = (SELECT `rev` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d ORDER BY `rev` DESC LIMIT 1), `approveUserId` = ?d, `approveDate` = ?d WHERE `id` = ?d', $status, Type::GUIDE, $id, User::$id, time(), $id); + else + $ok = DB::Aowow()->query('UPDATE ?_guides SET `status` = ?d WHERE `id` = ?d', $status, $id); + + if (!$ok) + return false; + + DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `status`) VALUES (?d, ?d, ?d, ?d)', $id, time(), User::$id, $status); + if ($msg) + DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `msg`) VALUES (?d, ?d, ?d, ?)', $id, time(), User::$id, $msg); + + return true; + } +} + +?> diff --git a/endpoints/admin/guides.php b/endpoints/admin/guides.php new file mode 100644 index 00000000..16aa460b --- /dev/null +++ b/endpoints/admin/guides.php @@ -0,0 +1,46 @@ + Content > Guides Awaiting Approval + + protected function generate() : void + { + $this->h1 = 'Pending Guides'; + array_unshift($this->title, $this->h1); + + $this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"]); + + parent::generate(); + + $pending = new GuideList([['status', GuideMgr::STATUS_REVIEW]]); + if ($pending->error) + $data = []; + else + { + $data = $pending->getListviewData(); + $latest = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, MAX(`rev`) FROM ?_articles WHERE `type` = ?d AND `typeId` IN (?a) GROUP BY `rev`', Type::GUIDE, $pending->getFoundIDs()); + foreach ($latest as $id => $rev) + $data[$id]['rev'] = $rev; + } + + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => array_values($data), + 'hiddenCols' => ['patch', 'comments', 'views', 'rating'], + 'extraCols' => '$_' + ), GuideList::$brickFile, 'guideAdminCol')); + } +} + +?> diff --git a/endpoints/edit/image.php b/endpoints/edit/image.php new file mode 100644 index 00000000..587d5a5e --- /dev/null +++ b/endpoints/edit/image.php @@ -0,0 +1,48 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine'] ], + 'guide' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_range' => 1, 'max_range' => 1]] + ); + + /* + success: bool + id: image enumerator + type: 3 ? png : jpg + name: old filename + error: errString + */ + protected function generate() : void + { + if (!$this->assertGET('qqfile', 'guide')) + { + $this->result = Util::toJSON(['success' => false, 'error' => Lang::main('genericError')]); + return; + } + + if (!User::canWriteGuide()) + { + $this->result = Util::toJSON(['success' => false, 'error' => Lang::main('genericError')]); + return; + } + + $this->result = GuideMgr::handleUpload(); + + if (isset($this->result['success'])) + $this->result += ['name' => $this->_get['qqfile']]; + + $this->result = Util::toJSON($this->result); + } +} + +?> diff --git a/endpoints/get-description/get-description.php b/endpoints/get-description/get-description.php new file mode 100644 index 00000000..139c6949 --- /dev/null +++ b/endpoints/get-description/get-description.php @@ -0,0 +1,35 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob']] + ); + + public function __construct(string $param) + { + if ($param) // should be empty + $this->generate404(); + + parent::__construct($param); + } + + protected function generate() : void + { + if (!User::canWriteGuide()) + return; + + $this->result = GuideMgr::createDescription($this->_post['description']); + } +} + +?> diff --git a/endpoints/guide/changelog.php b/endpoints/guide/changelog.php new file mode 100644 index 00000000..685dfad3 --- /dev/null +++ b/endpoints/guide/changelog.php @@ -0,0 +1,104 @@ + ['filter' => FILTER_VALIDATE_INT] + ); + + protected function generate() : void + { + // main container should be tagged:
+ + if (!$this->assertGET('id')) + $this->generateNotFound(Lang::game('guide'), Lang::guide('notFound')); + + $guide = new GuideList(array(['id', $this->_get['id']])); + if ($guide->error) + $this->generateNotFound(Lang::game('guide'), Lang::guide('notFound')); + + if (!$guide->canBeViewed() && !$guide->userCanView()) + $this->forward('?guides='.$guide->getField('category')); + + $this->h1 = lang::guide('clTitle', [$this->_get['id'], $guide->getField('title')]); + if (!$this->h1) + $this->h1 = $guide->getField('name'); + + $this->gPageInfo += ['name' => $guide->getField('name')]; + + + $this->breadcrumb[] = $guide->getField('category'); + + + parent::generate(); + + /* - NYI (see "&& false") + $this->addScript([SC_JS_STRING, + + <<= parseInt(e.value)); + }); + + }; + + radios.each(function (i, e) { + e.onchange = limit.bind(this, e.name, parseInt(e.value)); + + if (i < 2 && e.name == "b") // first pair + $(e).trigger("click"); + else if (e.value == 0 && e.name == "a") // last pair + $(e).trigger("click"); + }); + }); + JS + ]); + */ + + $buff = '\n"; + + if (User::isInGroup(U_GROUP_STAFF) && false) + $buff .= ''; + + $this->extraHTML = $buff; + } +} + +?> diff --git a/endpoints/guide/edit.php b/endpoints/guide/edit.php new file mode 100644 index 00000000..b2897167 --- /dev/null +++ b/endpoints/guide/edit.php @@ -0,0 +1,214 @@ + span { display: block; height: 22px; } + #upload-result { display: inline-block; text-align: right; } + #upload-progress { display: inline-block; margin-right: 8px; } + + CSS] + ); + protected array $expectedPOST = array( + 'save' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkEmptySet'] ], // saved for more editing + 'submit' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkEmptySet'] ], // submitted for review + 'title' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine'] ], + 'name' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine'] ], + 'description' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkDescription'] ], + 'changelog' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob'] ], + 'body' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob'] ], + 'locale' => ['filter' => FILTER_CALLBACK, 'options' => [Locale::class, 'tryFrom'] ], + 'category' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_value' => 1, 'max_value' => 9] ], + 'specId' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_value' => -1, 'max_value' => 2, 'default' => -1]], + 'classId' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_value' => 1, 'max_value' => 11, 'default' => 0]] + ); + protected array $expectedGET = array( + 'id' => ['filter' => FILTER_VALIDATE_INT], + 'rev' => ['filter' => FILTER_VALIDATE_INT] + ); + + public function __construct(string $param) + { + parent::__construct($param); + + if (!User::canWriteGuide()) + $this->generateError(); + + if (!is_int($this->_get['id'])) // edit existing guide + return; + + $this->typeId = $this->_get['id']; // just to display sensible not-found msg + $status = DB::Aowow()->selectCell('SELECT `status` FROM ?_guides WHERE `id` = ?d AND `status` <> ?d { AND `userId` = ?d }', $this->typeId, GuideMgr::STATUS_ARCHIVED, User::isInGroup(U_GROUP_STAFF) ? DBSIMPLE_SKIP : User::$id); + if (!$status && $this->typeId) + $this->generateNotFound(Lang::game('guide'), Lang::guide('notFound')); + else if (!$this->typeId) + return; + + // just so we don't have to access GuideMgr from template + $this->isDraft = $status == GuideMgr::STATUS_DRAFT; + $this->editStatus = $status; + $this->editRev = DB::Aowow()->selectCell('SELECT `rev` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d ORDER BY `rev` DESC', Type::GUIDE, $this->typeId); + } + + protected function generate() : void + { + if ($this->_post['save'] || $this->_post['submit']) + { + if (!$this->saveGuide()) + $this->error = Lang::main('intError'); + else if ($this->_get['id'] === 0) + $this->forward('?guide=edit&id='.$this->typeId); + } + + $guide = new GuideList(array(['id', $this->typeId])); + + $this->h1 = Lang::guide('editTitle'); + array_unshift($this->title, $this->h1.Lang::main('colon').$guide->getField('title'), Lang::game('guides')); + + Lang::sort('guide', 'category'); + + // init required template vars + $this->editCategory = $this->_post['category'] ?? $guide->getField('category'); + $this->editTitle = $this->_post['title'] ?? $guide->getField('title'); + $this->editName = $this->_post['name'] ?? $guide->getField('name'); + $this->editDescription = $this->_post['description'] ?? $guide->getField('description'); + $this->editText = $this->_post['body'] ?? $guide->getArticle(); + $this->editClassId = $this->_post['classId'] ?? $guide->getField('classId'); + $this->editSpecId = $this->_post['specId'] ?? $guide->getField('specId'); + $this->editLocale = $this->_post['locale'] ?? Locale::tryFrom($guide->getField('locale')); + $this->editStatus = $this->editStatus ?: $guide->getField('status'); + $this->editStatusColor = GuideMgr::STATUS_COLORS[$this->editStatus]; + + $this->extendGlobalData($guide->getJSGlobals()); + + parent::generate(); + } + + private function saveGuide() : bool + { + // test requiered fields set + if (!$this->assertPOST('title', 'name', 'body', 'locale', 'category')) + { + trigger_error('GuideEditResponse::saveGuide - received malformed request', E_USER_ERROR); + return false; + } + + // test required fields context + if (!$this->_post['locale']->validate()) + return false; + + // sanitize: spec / class + if ($this->_post['category'] == 1) // Classes + { + if ($this->_post['classId'] && !ChrClass::tryFrom($this->_post['classId'])) + $this->_post['classId'] = 0; + + if ($this->_post['specId'] > -1 && !$this->_post['classId']) + $this->_post['specId'] = -1; + } + else + { + $this->_post['classId'] = 0; + $this->_post['specId'] = -1; + } + + $guideData = array( + 'category' => $this->_post['category'], + 'classId' => $this->_post['classId'], + 'specId' => $this->_post['specId'], + 'title' => $this->_post['title'], + 'name' => $this->_post['name'], + 'description' => $this->_post['description'] ?: GuideMgr::createDescription($this->_post['body']), + 'locale' => $this->_post['locale']->value, + 'roles' => User::$groups, + 'status' => $this->_post['submit'] ? GuideMgr::STATUS_REVIEW : GuideMgr::STATUS_DRAFT, + 'date' => time() + ); + + // new guide > reload editor + if ($this->_get['id'] === 0) + { + $guideData += ['userId' => User::$id]; + if (!($this->typeId = (int)DB::Aowow()->query('INSERT INTO ?_guides (?#) VALUES (?a)', array_keys($guideData), array_values($guideData)))) + { + trigger_error('GuideEditResponse::saveGuide - failed to save guide to db', E_USER_ERROR); + return false; + } + } + // existing guide > :shrug: + else if (DB::Aowow()->query('UPDATE ?_guides SET ?a WHERE `id` = ?d', $guideData, $this->typeId)) + DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `rev`, `date`, `userId`, `msg`) VALUES (?d, ?d, ?d, ?d, ?)', $this->typeId, $this->editRev, time(), User::$id, $this->_post['changelog']); + else + { + trigger_error('GuideEditResponse::saveGuide - failed to update guide in db', E_USER_ERROR); + return false; + } + + // insert Article + $articleId = DB::Aowow()->query( + 'INSERT INTO ?_articles (`type`, `typeId`, `locale`, `rev`, `editAccess`, `article`) VALUES (?d, ?d, ?d, ?d, ?d, ?)', + Type::GUIDE, + $this->typeId, + $this->_post['locale']->value, + ++$this->editRev, + User::$groups & U_GROUP_STAFF ? User::$groups : User::$groups | U_GROUP_BLOGGER, + $this->_post['body'] + ); + + if (!is_int($articleId)) + { + if ($this->_get['id'] === 0) + DB::Aowow()->query('DELETE FROM ?_guides WHERE `id` = ?d', $this->typeId); + + trigger_error('GuideEditResponse::saveGuide - failed to save article to db', E_USER_ERROR); + return false; + } + + if ($this->_post['submit'] && $this->editStatus != GuideMgr::STATUS_REVIEW) + DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `status`) VALUES (?d, ?d, ?d, ?d)', $this->typeId, time(), User::$id, GuideMgr::STATUS_REVIEW); + + $this->editStatus = $guideData['status']; + + return true; + } + + protected static function checkDescription(string $str) : string + { + // run checkTextBlob and also replace \n => \s and \s+ => \s + $str = preg_replace(parent::PATTERN_TEXT_BLOB, '', $str); + + $str = strtr($str, ["\n" => ' ', "\r" => ' ']); + + return preg_replace('/\s+/', ' ', trim($str)); + } +} + +?> diff --git a/endpoints/guide/guide.php b/endpoints/guide/guide.php new file mode 100644 index 00000000..90de7eb8 --- /dev/null +++ b/endpoints/guide/guide.php @@ -0,0 +1,250 @@ + ['filter' => FILTER_VALIDATE_INT], + 'rev' => ['filter' => FILTER_VALIDATE_INT] + ); + + public int $type = Type::GUIDE; + public int $typeId = 0; + public int $guideStatus = 0; + public array $guideRating = []; + public ?int $guideRevision = null; + + private GuideList $subject; + + public function __construct(string $nameOrId) + { + parent::__construct($nameOrId); + + /**********************/ + /* get mode + guideId */ + /**********************/ + + if (Util::checkNumeric($nameOrId, NUM_CAST_INT)) + $this->typeId = $nameOrId; + else if (preg_match(GuideMgr::VALID_URL, $nameOrId)) + { + if ($id = DB::Aowow()->selectCell('SELECT `id` FROM ?_guides WHERE `url` = ?', Util::lower($nameOrId))) + { + $this->typeId = intVal($id); + $this->articleUrl = Util::lower($nameOrId); + } + } + + $this->contribute = Type::getClassAttrib($this->type, 'contribute') ?? CONTRIBUTE_NONE; + } + + protected function generate() : void + { + $this->subject = new GuideList(array(['id', $this->typeId])); + if ($this->subject->error) + $this->generateNotFound(Lang::game('guide'), Lang::guide('notFound')); + + if (!$this->subject->canBeViewed() && !$this->subject->userCanView()) + $this->forward('?guides='.$this->subject->getField('category')); + + $this->guideStatus = $this->subject->getField('status'); + if ($this->guideStatus != GuideMgr::STATUS_APPROVED && $this->guideStatus != GuideMgr::STATUS_ARCHIVED) + { + $this->cacheType = CACHE_TYPE_NONE; + $this->contribute = CONTRIBUTE_NONE; + } + + if ($this->articleUrl) + $this->guideRevision = $this->subject->getField('rev'); + else if ($this->subject->userCanView()) + $this->guideRevision = $this->_get['rev'] ?? $this->subject->getField('latest'); + else + $this->subject->getField('rev'); + + $this->h1 = $this->subject->getField('name'); + + $this->gPageInfo += array( + 'name' => $this->h1, + 'author' => $this->subject->getField('author') + ); + + + /*************/ + /* Menu Path */ + /*************/ + + if ($x = $this->subject?->getField('category')) + $this->breadcrumb[] = $x; + + + /**************/ + /* Page Title */ + /**************/ + + array_unshift($this->title, $this->subject->getField('title'), Lang::game('guides')); + + + /***********/ + /* Infobox */ + /***********/ + + if (!($this->subject->getField('cuFlags') & GUIDE_CU_NO_QUICKFACTS)) + $this->generateInfobox(); + + // needs post-cache updating + if (!($this->subject->getField('cuFlags') & GUIDE_CU_NO_RATING)) + $this->guideRating = array( + $this->subject->getField('rating'), // avg rating + User::canUpvote() && User::canDownvote() ? 'true' : 'false', + $this->subject->getField('_self'), // my rating amt; 0 = no vote + $this->typeId // guide Id + ); + + + /****************/ + /* Main Content */ + /****************/ + + if ($this->subject->userCanView()) + $this->redButtons[BUTTON_GUIDE_EDIT] = User::canWriteGuide() && $this->guideStatus != GuideMgr::STATUS_ARCHIVED; + + $this->redButtons[BUTTON_GUIDE_LOG] = true; + $this->redButtons[BUTTON_GUIDE_REPORT] = $this->subject->canBeReported(); + + $this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"], __forceTabs: true); + + // the article text itself is added by PageTemplate::addArticle() + parent::generate(); + + $this->result->registerDisplayHook('infobox', [self::class, 'infoboxHook']); + if ($this->guideRating) + $this->result->registerDisplayHook('guideRating', [self::class, 'starsHook']); + } + + private function generateInfobox() : void + { + $infobox = []; + + if ($this->subject->getField('cuFlags') & CC_FLAG_STICKY) + $infobox[] = '[span class=guide-sticky]'.Lang::guide('sticky').'[/span]'; + + $infobox[] = Lang::guide('author').'[url=?user='.$this->subject->getField('author').']'.$this->subject->getField('author').'[/url]'; + + if ($this->subject->getField('category') == 1) + { + $c = $this->subject->getField('classId'); + $s = $this->subject->getField('specId'); + if ($c > 0) + { + $this->extendGlobalIds(Type::CHR_CLASS, $c); + $infobox[] = Util::ucFirst(Lang::game('class')).Lang::main('colon').'[class='.$c.']'; + } + if ($s > -1) + $infobox[] = Lang::guide('spec').'[icon class="c'.$c.' icontiny" name='.Game::$specIconStrings[$c][$s].']'.Lang::game('classSpecs', $c, $s).'[/icon]'; + } + + // $infobox[] = Lang::guide('patch').Lang::main('colon').'3.3.5'; // replace with date + $infobox[] = Lang::guide('added').'[tooltip name=added]'.date('l, G:i:s', $this->subject->getField('date')).'[/tooltip][span class=tip tooltip=added]'.date(Lang::main('dateFmtShort'), $this->subject->getField('date')).'[/span]'; + + if ($this->guideStatus == GuideMgr::STATUS_ARCHIVED) + $infobox[] = Lang::guide('status', GuideMgr::STATUS_ARCHIVED); + + $this->infobox = new InfoboxMarkup($infobox, ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0'); + + if ($this->guideStatus == GuideMgr::STATUS_REVIEW && User::isInGroup(U_GROUP_STAFF) && $this->_get['rev']) + { + $this->addScript([SC_JS_STRING, <<infobox->append('[h3 style="text-align:center"]Admin[/h3]'); + $this->infobox->append('[div style="text-align:center"][url=# id="btn-accept" class=icon-tick]Approve[/url][url=# style="margin-left:20px" id="btn-reject" class=icon-delete]Reject[/url][/div]'); + } + } + + public static function infoboxHook(Template\PageTemplate &$pt, ?InfoboxMarkup &$infobox) : void + { + if ($pt->guideStatus != GuideMgr::STATUS_APPROVED) + return; + + // increment and display views + DB::Aowow()->query('UPDATE ?_guides SET `views` = `views` + 1 WHERE `id` = ?d', $pt->typeId); + + $nViews = DB::Aowow()->selectCell('SELECT `views` FROM ?_guides WHERE `id` = ?d', $pt->typeId); + + $infobox->addItem(Lang::guide('views').'[n5='.$nViews.']'); + + // should we have a rating item in the lv? + if (!$pt->guideRating) + return; + + $rating = GuideMgr::getRatings([$pt->typeId]); + if ($rating[$pt->typeId]['nvotes'] < 5) + $infobox->addItem(Lang::guide('rating').Lang::guide('noVotes')); + else + $infobox->addItem(Lang::guide('rating').Lang::guide('votes', [round($rating[$pt->typeId]['rating'], 1), $rating[$pt->typeId]['nvotes']])); + } + + public static function starsHook(Template\PageTemplate &$pt, ?array &$guideRating) : void + { + if ($pt->guideStatus != GuideMgr::STATUS_APPROVED) + return; + + $rating = GuideMgr::getRatings([$pt->typeId]); + $guideRating = array( + $rating[$pt->typeId]['rating'], + User::canUpvote() && User::canDownvote() ? 'true' : 'false', + $rating[$pt->typeId]['_self'] ?? 0, + $pt->typeId + ); + } +} + +?> diff --git a/endpoints/guide/guide_power.php b/endpoints/guide/guide_power.php new file mode 100644 index 00000000..99fd4dba --- /dev/null +++ b/endpoints/guide/guide_power.php @@ -0,0 +1,59 @@ + ['filter' => FILTER_CALLBACK, 'options' => [Locale::class, 'tryFromDomain']] + ); + + private string $url = ''; + + public function __construct(string $idOrName) + { + parent::__construct($idOrName); + + // temp locale + if ($this->_get['domain']) + Lang::load($this->_get['domain']); + + if (Util::checkNumeric($idOrName, NUM_CAST_INT)) + $this->typeId = $idOrName; + else if ($id = DB::Aowow()->selectCell('SELECT `id` FROM ?_guides WHERE `url` = ?', Util::lower($idOrName))) + { + $this->typeId = intVal($id); + $this->url = Util::lower($idOrName); + } + } + + protected function generate() : void + { + $opts = []; + if ($this->typeId) + if (!($guide = new GuideList(array(['id', $this->typeId])))->error) + $opts = array( + 'name' => $guide->getField('name', true), + 'tooltip' => $guide->renderTooltip() + ); + + if (!$opts) + $this->cacheType = CACHE_TYPE_NONE; + + $this->result = new Tooltip(self::POWER_TEMPLATE, $this->url ?: $this->typeId, $opts); + } +} + +?> diff --git a/endpoints/guide/new.php b/endpoints/guide/new.php new file mode 100644 index 00000000..95de5c4d --- /dev/null +++ b/endpoints/guide/new.php @@ -0,0 +1,66 @@ + span { display: block; height: 22px; } + #upload-result { display: inline-block; text-align: right; } + #upload-progress { display: inline-block; margin-right: 8px; } + + CSS] + ); + + public function __construct(string $param) + { + parent::__construct($param); + + if (!User::canWriteGuide()) + $this->generateError(); + } + + protected function generate() : void + { + $this->h1 = Lang::guide('newTitle'); + + array_unshift($this->title, $this->h1, Lang::game('guides')); + + Lang::sort('guide', 'category'); + + // update required template vars + $this->editLocale = Lang::getLocale(); + + parent::generate(); + } +} + +?> diff --git a/endpoints/guide/vote.php b/endpoints/guide/vote.php new file mode 100644 index 00000000..ca7ee945 --- /dev/null +++ b/endpoints/guide/vote.php @@ -0,0 +1,50 @@ + ['filter' => FILTER_VALIDATE_INT ], + 'rating' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_range' => 0, 'max_range' => 5]] + ); + + protected function generate() : void + { + if (!$this->assertPOST('id', 'rating')) + { + trigger_error('GuideVoteResponse - malformed request received', E_USER_ERROR); + $this->generate404(); + } + + if (!User::canUpvote() || !User::canDownvote()) // same logic as comments? + $this->generate403(); + + // by id, not own, published + $points = $votes = 0; + if ($g = DB::Aowow()->selectRow('SELECT `userId`, `cuFlags` FROM ?_guides WHERE `id` = ?d AND (`status` = ?d OR `rev` > 0)', $this->_post['id'], GuideMgr::STATUS_APPROVED)) + { + // apparently you are allowed to vote on your own guide + if ($g['cuFlags'] & GUIDE_CU_NO_RATING) + $this->generate403(); + + if (!$this->_post['rating']) + DB::Aowow()->query('DELETE FROM ?_user_ratings WHERE `type` = ?d AND `entry` = ?d AND `userId` = ?d', RATING_GUIDE, $this->_post['id'], User::$id); + else + DB::Aowow()->query('REPLACE INTO ?_user_ratings (`type`, `entry`, `userId`, `value`) VALUES (?d, ?d, ?d, ?d)', RATING_GUIDE, $this->_post['id'], User::$id, $this->_post['rating']); + + [$points, $votes] = DB::Aowow()->selectRow('SELECT IFNULL(SUM(`value`), 0) AS "0", IFNULL(COUNT(*), 0) AS "1" FROM ?_user_ratings WHERE `type` = ?d AND `entry` = ?d', RATING_GUIDE, $this->_post['id']); + } + + $this->result = Util::toJSON($votes ? ['rating' => $points / $votes, 'nvotes' => $votes] : ['rating' => 0, 'nvotes' => 0]); + } +} + +?> diff --git a/endpoints/guides/guides.php b/endpoints/guides/guides.php new file mode 100644 index 00000000..9c0db0ee --- /dev/null +++ b/endpoints/guides/guides.php @@ -0,0 +1,73 @@ +getCategoryFromUrl($pageParam); + + parent::__construct($pageParam); + } + + protected function generate() : void + { + $this->h1 = Util::ucFirst(Lang::game('guides')); + + + if ($this->category) + $this->breadcrumb[] = $this->category[0]; + + + array_unshift($this->title, $this->h1); + if ($this->category) + array_unshift($this->title, Lang::guide('category', $this->category[0])); + + + $conditions = array( + ['locale', Lang::getLocale()->value], + ['status', GuideMgr::STATUS_ARCHIVED, '!'], // never archived guides + [ + 'OR', + ['status', GuideMgr::STATUS_APPROVED], // currently approved + ['rev', 0, '>'] // has previously approved revision + ] + ); + if ($this->category) + $conditions[] = ['category', $this->category[0]]; + + $this->redButtons = [BUTTON_GUIDE_NEW => User::canWriteGuide()]; + + $guides = new GuideList($conditions); + + $this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"]); + + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $guides->getListviewData(), + 'name' => Util::ucFirst(Lang::game('guides')), + 'hiddenCols' => ['patch'], // pointless: display date instead + 'extraCols' => ['$Listview.extraCols.date'] // ok + ), GuideList::$brickFile)); + + parent::generate(); + } +} + +?> diff --git a/endpoints/my-guides/my-guides.php b/endpoints/my-guides/my-guides.php new file mode 100644 index 00000000..288d87aa --- /dev/null +++ b/endpoints/my-guides/my-guides.php @@ -0,0 +1,52 @@ +generateError(); + } + + protected function generate() : void + { + $this->h1 = Util::ucFirst(Lang::guide('myGuides')); + + array_unshift($this->title, $this->h1); + + $this->redButtons = [BUTTON_GUIDE_NEW => User::canWriteGuide()]; + + $guides = new GuideList(array(['userId', User::$id])); + + $this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"]); + + $this->lvTabs->addListviewTab(new Listview(array( + 'data' => $guides->getListviewData(), + 'name' => Util::ucFirst(Lang::game('guides')), + 'hiddenCols' => ['patch', 'author'], + 'visibleCols' => ['status'], + 'extraCols' => ['$Listview.extraCols.date'] + ), GuideList::$brickFile)); + + parent::generate(); + } +} + +?> diff --git a/endpoints/user/user.php b/endpoints/user/user.php index 7f4fee64..b3e8f7b8 100644 --- a/endpoints/user/user.php +++ b/endpoints/user/user.php @@ -253,7 +253,7 @@ class UserBaseResponse extends TemplateResponse } // My Guides - $guides = new GuideList(['status', [GUIDE_STATUS_APPROVED, GUIDE_STATUS_ARCHIVED]], ['userId', $this->user['id']]); + $guides = new GuideList(['status', [GuideMgr::STATUS_APPROVED, GuideMgr::STATUS_ARCHIVED]], ['userId', $this->user['id']]); if (!$guides->error) { $this->lvTabs->addListviewTab(new Listview(array( diff --git a/includes/ajaxHandler/admin.class.php b/includes/ajaxHandler/admin.class.php index 899c607e..1b2b6f95 100644 --- a/includes/ajaxHandler/admin.class.php +++ b/includes/ajaxHandler/admin.class.php @@ -7,7 +7,7 @@ if (!defined('AOWOW_REVISION')) class AjaxAdmin extends AjaxHandler { - protected $validParams = ['siteconfig', 'weight-presets', 'spawn-override', 'guide', 'comment']; + protected $validParams = ['siteconfig', 'weight-presets', 'spawn-override', 'comment']; protected $_get = array( 'action' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine' ], 'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkIdListUnsigned'], @@ -64,13 +64,6 @@ class AjaxAdmin extends AjaxHandler $this->handler = 'spawnPosFix'; } - else if ($this->params[0] == 'guide') - { - if (!User::isInGroup(U_GROUP_STAFF)) - return; - - $this->handler = 'guideManage'; - } else if ($this->params[0] == 'comment') { if (!User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_MOD)) @@ -207,57 +200,6 @@ class AjaxAdmin extends AjaxHandler return '-1'; } - protected function guideManage() : string - { - $update = function (int $id, int $status, ?string $msg = null) : bool - { - if (!DB::Aowow()->query('UPDATE ?_guides SET `status` = ?d WHERE `id` = ?d', $status, $id)) - return false; - - // set display rev to latest - if ($status == GUIDE_STATUS_APPROVED) - DB::Aowow()->query('UPDATE ?_guides SET `rev` = (SELECT `rev` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d ORDER BY `rev` DESC LIMIT 1), `approveUserId` = ?d, `approveDate` = ?d WHERE `id` = ?d', Type::GUIDE, $id, User::$id, time(), $id); - - DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `status`) VALUES (?d, ?d, ?d, ?d)', $id, time(), User::$id, $status); - if ($msg) - DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `msg`) VALUES (?d, ?d, ?d, ?)', $id, time(), User::$id, $msg); - return true; - }; - - if (!$this->_post['id']) - trigger_error('AjaxHander::guideManage - malformed request: id: '.$this->_post['id'].', status: '.$this->_post['status']); - else - { - $guide = DB::Aowow()->selectRow('SELECT `userId`, `status` FROM ?_guides WHERE `id` = ?d', $this->_post['id']); - if (!$guide) - trigger_error('AjaxHander::guideManage - guide #'.$this->_post['id'].' not found'); - else - { - if ($this->_post['status'] == $guide['status']) - trigger_error('AjaxHander::guideManage - guide #'.$this->_post['id'].' already has status #'.$this->_post['status']); - else - { - if ($this->_post['status'] == GUIDE_STATUS_APPROVED) - { - if ($update($this->_post['id'], GUIDE_STATUS_APPROVED, $this->_post['msg'])) - { - Util::gainSiteReputation($guide['userId'], SITEREP_ACTION_ARTICLE, ['id' => $this->_post['id']]); - return '1'; - } - else - return '-2'; - } - else if ($this->_post['status'] == GUIDE_STATUS_REJECTED) - return $update($this->_post['id'], GUIDE_STATUS_REJECTED, $this->_post['msg']) ? '1' : '-2'; - else - trigger_error('AjaxHander::guideManage - unhandled status change request'); - } - } - } - - return '-1'; - } - protected function commentOutOfDate() : string { $ok = false; diff --git a/includes/ajaxHandler/edit.class.php b/includes/ajaxHandler/edit.class.php deleted file mode 100644 index 1275b7c4..00000000 --- a/includes/ajaxHandler/edit.class.php +++ /dev/null @@ -1,82 +0,0 @@ - ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine'], - 'guide' => ['filter' => FILTER_SANITIZE_NUMBER_INT ] - ); - - public function __construct(array $params) - { - parent::__construct($params); - - if (!$params) - return; - - if ($params[0] == 'image') - $this->handler = 'handleUpload'; - else if ($params[0] == 'article') // has it's own editor page - $this->handler = null; - } - - /* - success: bool - id: image enumerator - type: 3 ? png : jpg - name: old filename - error: errString - */ - protected function handleUpload() : string - { - if (!User::canWriteGuide() || $this->_get['guide'] != 1) - return Util::toJSON(['success' => false, 'error' => '']); - - require_once('includes/libs/qqFileUploader.class.php'); - - $targetPath = 'static/uploads/guide/images/'; - $tmpPath = 'static/uploads/temp/'; - $tmpFile = User::$username.'-'.Type::GUIDE.'-0-'.Util::createHash(16); - - $uploader = new \qqFileUploader(['jpg', 'jpeg', 'png'], 10 * 1024 * 1024); - $result = $uploader->handleUpload($tmpPath, $tmpFile, true); - - if (isset($result['success'])) - { - $finfo = new \finfo(FILEINFO_MIME); - $mime = $finfo->file($tmpPath.$result['newFilename']); - if (preg_match('/^image\/(png|jpe?g)/i', $mime, $m)) - { - $i = 1; // image index - if ($files = scandir($targetPath, SCANDIR_SORT_DESCENDING)) - if (rsort($files, SORT_NATURAL) && $files[0] != '.' && $files[0] != '..') - $i = explode('.', $files[0])[0] + 1; - - $targetFile = $i . ($m[1] == 'png' ? '.png' : '.jpg'); - - // move to final location - if (!rename($tmpPath.$result['newFilename'], $targetPath.$targetFile)) - return Util::toJSON(['error' => Lang::main('intError')]); - - // send success - return Util::toJSON(array( - 'success' => true, - 'id' => $i, - 'type' => $m[1] == 'png' ? 3 : 2, - 'name' => $this->_get['qqfile'] - )); - } - - return Util::toJSON(['error' => Lang::screenshot('error', 'unkFormat')]); - } - - return Util::toJSON($result); - } -} - -?> diff --git a/includes/ajaxHandler/getdescription.class.php b/includes/ajaxHandler/getdescription.class.php deleted file mode 100644 index cabf84d9..00000000 --- a/includes/ajaxHandler/getdescription.class.php +++ /dev/null @@ -1,37 +0,0 @@ - [FILTER_CALLBACK, ['options' => 'Aowow\AjaxHandler::checkTextBlob']] - ); - - public function __construct(array $params) - { - parent::__construct($params); - - if (!$params || $params[0]) // should be empty - return; - - $this->handler = 'handleDescription'; - } - - protected function handleDescription() : string - { - $this->contentType = MIME_TYPE_TEXT; - - if (!User::canWriteGuide()) - return ''; - - $desc = Markup::stripTags($this->_post['description']); - - return Lang::trimTextClean($desc, 120); - } -} - -?> diff --git a/includes/ajaxHandler/guide.class.php b/includes/ajaxHandler/guide.class.php deleted file mode 100644 index eb425f15..00000000 --- a/includes/ajaxHandler/guide.class.php +++ /dev/null @@ -1,63 +0,0 @@ - [FILTER_SANITIZE_NUMBER_INT, null], - 'rating' => [FILTER_SANITIZE_NUMBER_INT, null] - ); - - public function __construct(array $params) - { - parent::__construct($params); - - if (!$this->params || count($this->params) != 1) - return; - - $this->contentType = MIME_TYPE_TEXT; - - // select handler - if ($this->params[0] == 'vote') - $this->handler = 'voteGuide'; - } - - protected function voteGuide() : string - { - if (!$this->_post['id'] || $this->_post['rating'] < 0 || $this->_post['rating'] > 5) - { - header('HTTP/1.0 404 Not Found', true, 404); - return ''; - } - else if (!User::canUpvote() || !User::canDownvote()) // same logic as comments? - { - header('HTTP/1.0 403 Forbidden', true, 403); - return ''; - } - // by id, not own, published - if ($g = DB::Aowow()->selectRow('SELECT `userId`, `cuFlags` FROM ?_guides WHERE `id` = ?d AND (`status` = ?d OR `rev` > 0)', $this->_post['id'], GUIDE_STATUS_APPROVED)) - { - if ($g['cuFlags'] & GUIDE_CU_NO_RATING || $g['userId'] == User::$id) - { - header('HTTP/1.0 403 Forbidden', true, 403); - return ''; - } - - if (!$this->_post['rating']) - DB::Aowow()->query('DELETE FROM ?_user_ratings WHERE `type` = ?d AND `entry` = ?d AND `userId` = ?d', RATING_GUIDE, $this->_post['id'], User::$id); - else - DB::Aowow()->query('REPLACE INTO ?_user_ratings VALUES (?d, ?d, ?d, ?d)', RATING_GUIDE, $this->_post['id'], User::$id, $this->_post['rating']); - - $res = DB::Aowow()->selectRow('SELECT IFNULL(SUM(`value`), 0) AS `t`, IFNULL(COUNT(*), 0) AS `n` FROM ?_user_ratings WHERE `type` = ?d AND `entry` = ?d', RATING_GUIDE, $this->_post['id']); - return Util::toJSON($res['n'] ? ['rating' => $res['t'] / $res['n'], 'nvotes' => $res['n']] : ['rating' => 0, 'nvotes' => 0]); - } - - return Util::toJSON(['rating' => 0, 'nvotes' => 0]); - } -} - -?> diff --git a/includes/components/guidemgr.class.php b/includes/components/guidemgr.class.php new file mode 100644 index 00000000..3b738404 --- /dev/null +++ b/includes/components/guidemgr.class.php @@ -0,0 +1,106 @@ + '#71D5FF', + self::STATUS_REVIEW => '#FFFF00', + self::STATUS_APPROVED => '#1EFF00', + self::STATUS_REJECTED => '#FF4040', + self::STATUS_ARCHIVED => '#FFD100' + ); + + private static array $ratingsStore = []; + private static ?int $imgUploadIdx = null; + + public static function createDescription(string $text) : string + { + return Lang::trimTextClean(Markup::stripTags($text), 120); + } + + public static function getRatings(array $guideIds) : array + { + if (!$guideIds) + return []; + + if (array_keys(self::$ratingsStore) == $guideIds) + return self::$ratingsStore; + + self::$ratingsStore = array_fill_keys($guideIds, ['nvotes' => 0, 'rating' => -1]); + + $ratings = DB::Aowow()->select('SELECT `entry` AS ARRAY_KEY, IFNULL(SUM(`value`), 0) AS "0", IFNULL(COUNT(*), 0) AS "1", IFNULL(MAX(IF(`userId` = ?d, `value`, 0)), 0) AS "2" FROM ?_user_ratings WHERE `type` = ?d AND `entry` IN (?a) GROUP BY `entry`', User::$id, RATING_GUIDE, $guideIds); + foreach ($ratings as $id => [$total, $count, $self]) + { + self::$ratingsStore[$id]['nvotes'] = (int)$count; + self::$ratingsStore[$id]['_self'] = (int)$self; + if ($count >= 5 ) + self::$ratingsStore[$id]['rating'] = $total / $count; + } + + return self::$ratingsStore; + } + + public static function handleUpload() : array + { + require_once('includes/libs/qqFileUploader.class.php'); + + $tmpFile = User::$username.'-'.Type::GUIDE.'-0-'.Util::createHash(16); + + $uploader = new \qqFileUploader(['jpg', 'jpeg', 'png'], 10 * 1024 * 1024); + $result = $uploader->handleUpload(self::IMG_TMP_DIR, $tmpFile, true); + + if (isset($result['error'])) + return $result; + + $mime = (new \finfo(FILEINFO_MIME))?->file(self::IMG_TMP_DIR . $result['newFilename']); + + if (!preg_match('/^image\/(png|jpe?g)/i', $mime, $m)) + return ['error' => Lang::screenshot('error', 'unkFormat')]; + + // find next empty image name (an int) + if (is_null(self::$imgUploadIdx)) + { + if ($files = scandir(self::IMG_DEST_DIR, SCANDIR_SORT_DESCENDING)) + if (rsort($files, SORT_NATURAL) && $files[0] != '.' && $files[0] != '..') + $i = explode('.', $files[0])[0] + 1; + + self::$imgUploadIdx = $i ?? 1; + } + + $targetFile = self::$imgUploadIdx . ($m[1] == 'png' ? '.png' : '.jpg'); + + // move to final location + if (!rename(self::IMG_TMP_DIR.$result['newFilename'], self::IMG_DEST_DIR.$targetFile)) + { + trigger_error('GuideMgr::handleUpload - failed to move file', E_USER_ERROR); + return ['error' => Lang::main('intError')]; + } + + return array( + 'success' => true, + 'id' => self::$imgUploadIdx, + 'type' => $m[1] == 'png' ? 3 : 2 + ); + } +} + +?> diff --git a/includes/components/pagetemplate.class.php b/includes/components/pagetemplate.class.php index c53c58b5..1923a192 100644 --- a/includes/components/pagetemplate.class.php +++ b/includes/components/pagetemplate.class.php @@ -27,7 +27,6 @@ class PageTemplate private array $pageData = []; // processed by display hooks // template data that needs further processing .. ! WARNING ! they will not get aut fetched from $context as they are already defined here - protected array $guideRating = []; private string $gStaticUrl; private string $gHost; private string $gServerTime; diff --git a/includes/components/response/templateresponse.class.php b/includes/components/response/templateresponse.class.php index 76fb8672..bef62121 100644 --- a/includes/components/response/templateresponse.class.php +++ b/includes/components/response/templateresponse.class.php @@ -71,13 +71,15 @@ trait TrGuideEditor public int $editClassId = 0; public int $editSpecId = 0; public int $editRev = 0; - public int $editStatus = GUIDE_STATUS_DRAFT; - public string $editStatusColor = GuideMgr::STATUS_COLORS[GUIDE_STATUS_DRAFT]; + public int $editStatus = GuideMgr::STATUS_DRAFT; + public string $editStatusColor = GuideMgr::STATUS_COLORS[GuideMgr::STATUS_DRAFT]; public string $editTitle = ''; public string $editName = ''; public string $editDescription = ''; public string $editText = ''; + public string $error = ''; public Locale $editLocale = Locale::EN; + public bool $isDraft = false; } class TemplateResponse extends BaseResponse diff --git a/includes/dbtypes/guide.class.php b/includes/dbtypes/guide.class.php index 5f262f55..492b47a0 100644 --- a/includes/dbtypes/guide.class.php +++ b/includes/dbtypes/guide.class.php @@ -10,14 +10,6 @@ class GuideList extends DBTypeList { use ListviewHelper; - public const /* array */ STATUS_COLORS = array( - GUIDE_STATUS_DRAFT => '#71D5FF', - GUIDE_STATUS_REVIEW => '#FFFF00', - GUIDE_STATUS_APPROVED => '#1EFF00', - GUIDE_STATUS_REJECTED => '#FF4040', - GUIDE_STATUS_ARCHIVED => '#FFD100' - ); - public static int $type = Type::GUIDE; public static string $brickFile = 'guide'; public static string $dataTable = '?_guides'; @@ -28,9 +20,10 @@ class GuideList extends DBTypeList protected string $queryBase = 'SELECT g.*, g.`id` AS ARRAY_KEY FROM ?_guides g'; protected array $queryOpts = array( - 'g' => [['a', 'c'], 'g' => 'g.`id`'], - 'a' => ['j' => ['?_account a ON a.`id` = g.`userId`', true], 's' => ', IFNULL(a.`username`, "") AS "author"'], - 'c' => ['j' => ['?_comments c ON c.`type` = '.Type::GUIDE.' AND c.`typeId` = g.`id` AND (c.`flags` & '.CC_FLAG_DELETED.') = 0', true], 's' => ', COUNT(c.`id`) AS "comments"'] + 'g' => [['a', 'c', 'ar'], 'g' => 'g.`id`'], + 'a' => ['j' => ['?_account a ON a.`id` = g.`userId`', true], 's' => ', IFNULL(a.`username`, "") AS "author"'], + 'c' => ['j' => ['?_comments c ON c.`type` = '.Type::GUIDE.' AND c.`typeId` = g.`id` AND (c.`flags` & '.CC_FLAG_DELETED.') = 0', true], 's' => ', COUNT(c.`id`) AS "comments"'], + 'ar' => ['j' => ['?_articles ar ON ar.`type` = 300 AND ar.`typeId` = g.`id`'], 's' => ', MAX(ar.`rev`) AS "latest"'] ); public function __construct(array $conditions = [], array $miscData = []) @@ -40,23 +33,11 @@ class GuideList extends DBTypeList if ($this->error) return; - $ratings = DB::Aowow()->select('SELECT `entry` AS ARRAY_KEY, IFNULL(SUM(`value`), 0) AS `t`, IFNULL(COUNT(*), 0) AS `n`, IFNULL(MAX(IF(`userId` = ?d, `value`, 0)), 0) AS `s` FROM ?_user_ratings WHERE `type` = ?d AND `entry` IN (?a)', User::$id, RATING_GUIDE, $this->getFoundIDs()); + $ratings = GuideMgr::getRatings($this->getFoundIDs()); // post processing foreach ($this->iterate() as $id => &$_curTpl) - { - if (isset($ratings[$id])) - { - $_curTpl['nvotes'] = $ratings[$id]['n']; - $_curTpl['rating'] = $ratings[$id]['n'] < 5 ? -1 : $ratings[$id]['t'] / $ratings[$id]['n']; - $_curTpl['_self'] = $ratings[$id]['s']; - } - else - { - $_curTpl['nvotes'] = 0; - $_curTpl['rating'] = -1; - } - } + $_curTpl = array_merge($_curTpl, $ratings[$id]); } public static function getName(int $id) : ?LocString @@ -133,13 +114,13 @@ class GuideList extends DBTypeList public function canBeViewed() : bool { // currently approved || has prev. approved version - return $this->getField('status') == GUIDE_STATUS_APPROVED || $this->getField('rev') > 0; + return $this->getField('status') == GuideMgr::STATUS_APPROVED || $this->getField('rev') > 0; } public function canBeReported() : bool { // not own guide && is not archived - return $this->getField('userId') != User::$id && $this->getField('status') != GUIDE_STATUS_ARCHIVED; + return $this->getField('userId') != User::$id && $this->getField('status') != GuideMgr::STATUS_ARCHIVED; } public function getJSGlobals(int $addMask = GLOBALINFO_ANY) : array diff --git a/includes/defines.php b/includes/defines.php index a9f440ef..1c423f5e 100644 --- a/includes/defines.php +++ b/includes/defines.php @@ -227,13 +227,6 @@ define('STR_ALLOW_SHORT', 0x4); define('RATING_COMMENT', 1); define('RATING_GUIDE', 2); -define('GUIDE_STATUS_NONE', 0); -define('GUIDE_STATUS_DRAFT', 1); -define('GUIDE_STATUS_REVIEW', 2); -define('GUIDE_STATUS_APPROVED', 3); -define('GUIDE_STATUS_REJECTED', 4); -define('GUIDE_STATUS_ARCHIVED', 5); - define('DEFAULT_ICON', 'inv_misc_questionmark'); define('MENU_IDX_ID', 0); // ID: A number or string; null makes the menu item a separator diff --git a/includes/libs/qqFileUploader.class.php b/includes/libs/qqFileUploader.class.php index 27b27ab8..dae92a58 100644 --- a/includes/libs/qqFileUploader.class.php +++ b/includes/libs/qqFileUploader.class.php @@ -132,7 +132,7 @@ class qqFileUploader private function toBytes(string $str) : int { - $val = trim($str); + $val = substr(trim($str), 0, -1); $last = strtolower(substr($str, -1, 1)); switch ($last) { diff --git a/includes/type.class.php b/includes/type.class.php index 189e7cea..822b31f1 100644 --- a/includes/type.class.php +++ b/includes/type.class.php @@ -111,7 +111,7 @@ abstract class Type self::CURRENCY => [CurrencyList::class, 'currency', 'g_gatheredcurrencies', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_DB_TYPE | self::FLAG_HAS_ICON], self::SOUND => [SoundList::class, 'sound', 'g_sounds', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE], self::ICON => [IconList::class, 'icon', 'g_icons', self::FLAG_RANDOM_SEARCHABLE | self::FLAG_FILTRABLE | self::FLAG_DB_TYPE | self::FLAG_HAS_ICON], - self::GUIDE => [GuideList::class, 'guide', '', self::FLAG_NONE], + self::GUIDE => [GuideList::class, 'guide', '', self::FLAG_DB_TYPE], self::PROFILE => [ProfileList::class, 'profile', '', self::FLAG_FILTRABLE], // x - not known in javascript self::GUILD => [GuildList::class, 'guild', '', self::FLAG_FILTRABLE], // x self::ARENA_TEAM => [ArenaTeamList::class, 'arena-team', '', self::FLAG_FILTRABLE], // x diff --git a/includes/user.class.php b/includes/user.class.php index dc9fe853..46d11f6d 100644 --- a/includes/user.class.php +++ b/includes/user.class.php @@ -681,7 +681,7 @@ class User if (!self::isLoggedIn() || self::isBanned(ACC_BAN_GUIDE)) return $result; - if ($guides = DB::Aowow()->select('SELECT `id`, `title`, `url` FROM ?_guides WHERE `userId` = ?d AND `status` <> ?d', self::$id, GUIDE_STATUS_ARCHIVED)) + if ($guides = DB::Aowow()->select('SELECT `id`, `title`, `url` FROM ?_guides WHERE `userId` = ?d AND `status` <> ?d', self::$id, GuideMgr::STATUS_ARCHIVED)) { // fix url array_walk($guides, fn(&$x) => $x['url'] = '?guide='.($x['url'] ?: $x['id'])); diff --git a/includes/utilities.php b/includes/utilities.php index 10d84995..b435c06b 100644 --- a/includes/utilities.php +++ b/includes/utilities.php @@ -75,8 +75,6 @@ abstract class Util public static $mapSelectorString = '%s (%d)'; - public static $guideratingString = " $(document).ready(function() {\n $('#guiderating').append(GetStars(%.10F, %s, %u, %u));\n });"; - public static $expansionString = [null, 'bc', 'wotlk']; public static $tcEncoding = '0zMcmVokRsaqbdrfwihuGINALpTjnyxtgevElBCDFHJKOPQSUWXYZ123456789'; diff --git a/localization/locale_dede.php b/localization/locale_dede.php index c3e0007b..d934a107 100644 --- a/localization/locale_dede.php +++ b/localization/locale_dede.php @@ -167,20 +167,20 @@ $lang = array( 'myGuides' => "Meine Leitfäden", 'editTitle' => "Eigenen Leitfaden bearbeiten", 'newTitle' => "Leitfaden erstellen", - 'author' => "Autor", - 'spec' => "Spezialisierung", + 'author' => "Autor: ", + 'spec' => "Spezialisierung: ", 'sticky' => "Angeheftet", - 'views' => "Ansichten", + 'views' => "Ansichten: ", 'patch' => "Patch", - 'added' => "Hinzugefügt", - 'rating' => "Wertung", - 'votes' => "[span id=guiderating-value]%d[/span]/5 ([span id=guiderating-votes][n5=%d][/span] Bewertungen) [span id=guiderating][/span]", + 'added' => "Hinzugefügt: ", + 'rating' => "Wertung: ", + 'votes' => "[span id=guiderating-value]%.2g[/span]/5 ([span id=guiderating-votes][n5=%d][/span] Bewertungen) [span id=guiderating][/span]", 'noVotes' => "nicht genug Bewertungen [span id=guiderating][/span]", 'byAuthor' => "Von %s", 'notFound' => "Dieser Leitfaden existiert nicht.", 'clTitle' => 'Änderungsprotokoll für "%2$s"', - 'clStatusSet' => 'Status gesetzt auf %s', - 'clCreated' => 'Erstellt', + 'clStatusSet' => 'Status gesetzt auf %s: ', + 'clCreated' => 'Erstellt: ', 'clMinorEdit' => 'Kleinere Bearbeitung', 'editor' => array( 'fullTitle' => 'Ganze Überschrift', @@ -188,7 +188,7 @@ $lang = array( 'name' => 'Name', 'nameTip' => 'Dies sollte ein einfacher und klarer Name für den Leitfaden sein, der an Orten wie Menüs und Leitfadenlisten verwendet werden kann.', 'description' => 'Beschreibung', - 'descriptionTip' => 'Beschreibung, die für Suchmaschinen verwendet wird.<br /><br />Wenn leer, wird es automatisch generiert.', + 'descriptionTip' => 'Beschreibung, die für Suchmaschinen verwendet wird.

Wenn leer, wird es automatisch generiert.', // 'commentEmail' => 'Emailbenachrichtigung', // 'commentEmailTip' => 'Soll der Autor darüber benachrichtigt werden, dass Nutzer diesen Guide kommentieren?', 'changelog' => 'Änderungsprotokoll für diese Änderung', @@ -203,11 +203,11 @@ $lang = array( 'testGuide' => 'Sehen Sie, wie Ihr Leitfaden aussehen wird', 'images' => 'Bilder', 'statusTip' => array( - GUIDE_STATUS_DRAFT => 'Ihr Leitfaden ist im "Entwurfs"-Status und Sie sind der einzige der ihn sehen kann. Bearbeiten Sie ihn so lange Sie wollen und wenn Sie fertig sind reichen Sie ihn zur Überprüfung ein.', - GUIDE_STATUS_REVIEW => 'Ihr Leitfaden wird überprüft.', - GUIDE_STATUS_APPROVED => 'Ihr Leitfaden wurde veröffentlicht.', - GUIDE_STATUS_REJECTED => 'Ihr Leitfaden wurde abgewiesen. Nachdem die Mängel behoben wurde kann er erneut zur Überprüfung eingereicht werden.', - GUIDE_STATUS_ARCHIVED => 'Ihr Leitfaden ist veraltet und wurde archiviert. Er wird nicht mehr in der Übersicht gelistet und ist kann nicht mehr bearbeitet werden.]', + GuideMgr::STATUS_DRAFT => 'Ihr Leitfaden ist im "Entwurfs"-Status und Sie sind der einzige der ihn sehen kann. Bearbeiten Sie ihn so lange Sie wollen und wenn Sie fertig sind reichen Sie ihn zur Überprüfung ein.', + GuideMgr::STATUS_REVIEW => 'Ihr Leitfaden wird überprüft.', + GuideMgr::STATUS_APPROVED => 'Ihr Leitfaden wurde veröffentlicht.', + GuideMgr::STATUS_REJECTED => 'Ihr Leitfaden wurde abgewiesen. Nachdem die Mängel behoben wurde kann er erneut zur Überprüfung eingereicht werden.', + GuideMgr::STATUS_ARCHIVED => 'Ihr Leitfaden ist veraltet und wurde archiviert. Er wird nicht mehr in der Übersicht gelistet und ist kann nicht mehr bearbeitet werden.]', ) ), 'category' => array( diff --git a/localization/locale_enus.php b/localization/locale_enus.php index e4a7fe76..bc8d7ff0 100644 --- a/localization/locale_enus.php +++ b/localization/locale_enus.php @@ -167,20 +167,20 @@ $lang = array( 'myGuides' => "My Guides", 'editTitle' => "Edit your Guide", 'newTitle' => "Create New Guide", - 'author' => "Author", - 'spec' => "Specialization", + 'author' => "Author: ", + 'spec' => "Specialization: ", 'sticky' => "Sticky Status", - 'views' => "Views", + 'views' => "Views: ", 'patch' => "Patch", - 'added' => "Added", - 'rating' => "Rating", - 'votes' => "[span id=guiderating-value]%d[/span]/5 ([span id=guiderating-votes][n5=%d][/span] votes) [span id=guiderating][/span]", + 'added' => "Added: ", + 'rating' => "Rating: ", + 'votes' => "[span id=guiderating-value]%.2g[/span]/5 ([span id=guiderating-votes][n5=%d][/span] votes) [span id=guiderating][/span]", 'noVotes' => "not enough votes [span id=guiderating][/span]", 'byAuthor' => "By %s", 'notFound' => "This guide doesn't exist.", 'clTitle' => 'Changelog For "%2$s"', - 'clStatusSet' => 'Status set to %s', - 'clCreated' => 'Created', + 'clStatusSet' => 'Status set to %s: ', + 'clCreated' => 'Created: ', 'clMinorEdit' => 'Minor Edit', 'editor' => array( 'fullTitle' => 'Full Title', @@ -188,7 +188,7 @@ $lang = array( 'name' => 'Name', 'nameTip' => 'This should be a simple and clear name of what the guide is, for use in places like menus and guide lists.', 'description' => 'Description', - 'descriptionTip' => 'Description that will be used for search engines.<br><br>If left empty, it will be generated automatically.', + 'descriptionTip' => "Description that will be used for search engines.

If left empty, it will be generated automatically.", // 'commentEmail' => 'Comment Emails', // 'commentEmailTip' => 'Should the author get emailed whenever a user comments on this guide?', 'changelog' => 'Changelog For This Edit', @@ -203,11 +203,11 @@ $lang = array( 'testGuide' => 'See how your guide will look', 'images' => 'Images', 'statusTip' => array( - GUIDE_STATUS_DRAFT => 'Your guide is in "Draft" status and you are the only one able to see it. Keep editing it as long as you like, and when you feel it's ready submit it for review.', - GUIDE_STATUS_REVIEW => 'Your guide is being reviewed.', - GUIDE_STATUS_APPROVED => 'Your guide has been published.', - GUIDE_STATUS_REJECTED => 'Your guide has been rejected. After it\'s shortcomings have been remedied you may resubmit it for review.', - GUIDE_STATUS_ARCHIVED => 'Your guide is outdated and has been archived. Is will no longer be listed and can\'t be edited.', + GuideMgr::STATUS_DRAFT => 'Your guide is in "Draft" status and you are the only one able to see it. Keep editing it as long as you like, and when you feel it's ready submit it for review.', + GuideMgr::STATUS_REVIEW => 'Your guide is being reviewed.', + GuideMgr::STATUS_APPROVED => 'Your guide has been published.', + GuideMgr::STATUS_REJECTED => 'Your guide has been rejected. After it\'s shortcomings have been remedied you may resubmit it for review.', + GuideMgr::STATUS_ARCHIVED => 'Your guide is outdated and has been archived. Is will no longer be listed and can\'t be edited.', ) ), 'category' => array( diff --git a/localization/locale_eses.php b/localization/locale_eses.php index 95a75707..1b8ac56b 100644 --- a/localization/locale_eses.php +++ b/localization/locale_eses.php @@ -167,20 +167,20 @@ $lang = array( 'myGuides' => "Mis Guías", 'editTitle' => "Editar tu Guía", 'newTitle' => "Crear Nueva Guía", - 'author' => "Autor", - 'spec' => "Especialización", + 'author' => "Autor: ", + 'spec' => "Especialización: ", 'sticky' => "Estado Fijo", - 'views' => "Visto", + 'views' => "Visto: ", 'patch' => "Parche", - 'added' => "Añadido", - 'rating' => "Valoración", - 'votes' => "[span id=guiderating-value]%d[/span]/5 ([span id=guiderating-votes][n5=%d][/span] Votos) [span id=guiderating][/span]", + 'added' => "Añadido: ", + 'rating' => "Valoración: ", + 'votes' => "[span id=guiderating-value]%.2g[/span]/5 ([span id=guiderating-votes][n5=%d][/span] Votos) [span id=guiderating][/span]", 'noVotes' => "necesita más votaciones [span id=guiderating][/span]", 'byAuthor' => "Por %s", 'notFound' => "Este/a guía no existe.", 'clTitle' => 'Historial de cambios para "%2$s"', - 'clStatusSet' => 'Estado cambiado a %s', - 'clCreated' => 'Creado', + 'clStatusSet' => 'Estado cambiado a %s: ', + 'clCreated' => 'Creado: ', 'clMinorEdit' => 'Modificación menor', 'editor' => array( 'fullTitle' => 'Título completo', @@ -188,7 +188,7 @@ $lang = array( 'name' => 'Nombre', 'nameTip' => 'Este debe ser un nombre simple y claro de lo que es la guía, para usar en menús y listas de guías.', 'description' => 'Descripción', - 'descriptionTip' => 'Descripción utilizada para los motores de búsqueda.<br /><br />Si se deja vacío, se generará automáticamente.', + 'descriptionTip' => 'Descripción utilizada para los motores de búsqueda.

Si se deja vacío, se generará automáticamente.', // 'commentEmail' => 'Enviar comentarios por email', // 'commentEmailTip' => '¿El autor debería ser notificado por correo cuando un usuario escriba comentarios en esta guía?', 'changelog' => 'Historial de cambios para esta modificación', @@ -203,11 +203,11 @@ $lang = array( 'testGuide' => 'Mira el aspecto de tu guía.', 'images' => 'Imágenes', 'statusTip' => array( - GUIDE_STATUS_DRAFT => 'Tu guía está en estado "borrador" y solo tú puedes verla. Tienes todo el tiempo del mundo para editarla y, cuando creas que ya está lista, envíala para su revisión.', - GUIDE_STATUS_REVIEW => 'Tu guía está siendo revisada.', - GUIDE_STATUS_APPROVED => 'Tu guía ha sido publicada.', - GUIDE_STATUS_REJECTED => 'Tu guía ha sido rechazada. Una vez que se hayan corregido las deficiencias, puedes volver a enviarla para revisión.', - GUIDE_STATUS_ARCHIVED => 'Tu guía está desactualizada y ha sido archivada. Ya no aparecerá en la lista y no se puede editar.', + GuideMgr::STATUS_DRAFT => 'Tu guía está en estado "borrador" y solo tú puedes verla. Tienes todo el tiempo del mundo para editarla y, cuando creas que ya está lista, envíala para su revisión.', + GuideMgr::STATUS_REVIEW => 'Tu guía está siendo revisada.', + GuideMgr::STATUS_APPROVED => 'Tu guía ha sido publicada.', + GuideMgr::STATUS_REJECTED => 'Tu guía ha sido rechazada. Una vez que se hayan corregido las deficiencias, puedes volver a enviarla para revisión.', + GuideMgr::STATUS_ARCHIVED => 'Tu guía está desactualizada y ha sido archivada. Ya no aparecerá en la lista y no se puede editar.', ) ), 'category' => array( diff --git a/localization/locale_frfr.php b/localization/locale_frfr.php index 7123dde1..8d94d2c5 100644 --- a/localization/locale_frfr.php +++ b/localization/locale_frfr.php @@ -167,20 +167,20 @@ $lang = array( 'myGuides' => "Mes guides", 'editTitle' => "Editez votre Guide", 'newTitle' => "Créer un nouveau Guide", - 'author' => "Auteur", - 'spec' => "Spécialisation", + 'author' => "Auteur : ", + 'spec' => "Spécialisation : ", 'sticky' => "Statut coller", - 'views' => "Vues", + 'views' => "Vues : ", 'patch' => "Patch", - 'added' => "Ajouté", - 'rating' => "Note", - 'votes' => "[span id=guiderating-value]%d[/span]/5 ([span id=guiderating-votes][n5=%d][/span] Votes) [span id=guiderating][/span]", + 'added' => "Ajouté : ", + 'rating' => "Note : ", + 'votes' => "[span id=guiderating-value]%.2g[/span]/5 ([span id=guiderating-votes][n5=%d][/span] Votes) [span id=guiderating][/span]", 'noVotes' => "pas assez de votes [span id=guiderating][/span]", 'byAuthor' => "Par %s", 'notFound' => "Ce guide n'existe pas.", 'clTitle' => 'Journal des changements pour "%2$s"', - 'clStatusSet' => 'Statut défini comme %s', - 'clCreated' => 'Créé', + 'clStatusSet' => 'Statut défini comme %s : ', + 'clCreated' => 'Créé : ', 'clMinorEdit' => 'Modification mineure', 'editor' => array( 'fullTitle' => 'Titre complet', @@ -188,7 +188,7 @@ $lang = array( 'name' => 'Nom', 'nameTip' => 'Ceci devrait être un nom clair et concis de ce en quoi consiste le guide, qui sera affiché dans les menus et listes de guides.', 'description' => 'Description', - 'descriptionTip' => 'Description qui sera utilisée par les moteurs de recherche.<br /><br />S'il est laissé vide, le résumé sera généré automatiquement.', + 'descriptionTip' => 'Description qui sera utilisée par les moteurs de recherche.

S'il est laissé vide, le résumé sera généré automatiquement.', // 'commentEmail' => 'Recevoir les commentaires par courriel', // 'commentEmailTip' => 'L'auteur doit-il recevoir un courriel chaque fois qu'un utilisateur commente ce guide ?', 'changelog' => 'Journal des changements pour cette modification', @@ -203,11 +203,11 @@ $lang = array( 'testGuide' => 'Ayez un aperçu de votre guide', 'images' => 'Images', 'statusTip' => array( - GUIDE_STATUS_DRAFT => 'Votre guide est en statut "Brouillon" et vous êtes le seul à pouvoir le lire. Continuez de l'écrire comme vous le voulez, et quand vous sentez qu'il est prêt, soumettez-le pour approbation.', - GUIDE_STATUS_REVIEW => 'Your guide is being reviewed.', - GUIDE_STATUS_APPROVED => 'Your guide has been published.', - GUIDE_STATUS_REJECTED => 'Your guide has been rejected. After it\'s shortcomings have been remedied you may resubmit it for review.', - GUIDE_STATUS_ARCHIVED => 'Your guide is outdated and has been archived. Is will no longer be listed and can\'t be edited.', + GuideMgr::STATUS_DRAFT => 'Votre guide est en statut "Brouillon" et vous êtes le seul à pouvoir le lire. Continuez de l'écrire comme vous le voulez, et quand vous sentez qu'il est prêt, soumettez-le pour approbation.', + GuideMgr::STATUS_REVIEW => 'Your guide is being reviewed.', + GuideMgr::STATUS_APPROVED => 'Your guide has been published.', + GuideMgr::STATUS_REJECTED => 'Your guide has been rejected. After it\'s shortcomings have been remedied you may resubmit it for review.', + GuideMgr::STATUS_ARCHIVED => 'Your guide is outdated and has been archived. Is will no longer be listed and can\'t be edited.', ) ), 'category' => array( diff --git a/localization/locale_ruru.php b/localization/locale_ruru.php index 6e0de9a1..7304a86d 100644 --- a/localization/locale_ruru.php +++ b/localization/locale_ruru.php @@ -167,20 +167,20 @@ $lang = array( 'myGuides' => "Мои руководства", 'editTitle' => "Редактировать руководство", 'newTitle' => "Написать новое руководство", - 'author' => "Автор", - 'spec' => "Спек", + 'author' => "Автор: ", + 'spec' => "Спек: ", 'sticky' => "Закрепленный", - 'views' => "Просмотры", + 'views' => "Просмотры: ", 'patch' => "Обновление", - 'added' => "Добавлено", - 'rating' => "Рейтинг", - 'votes' => "[span id=guiderating-value]%d[/span]/5 ([span id=guiderating-votes][n5=%d][/span] проголосовало) [span id=guiderating][/span]", + 'added' => "Добавлено: ", + 'rating' => "Рейтинг: ", + 'votes' => "[span id=guiderating-value]%.2g[/span]/5 ([span id=guiderating-votes][n5=%d][/span] проголосовало) [span id=guiderating][/span]", 'noVotes' => "недостаточно голосов [span id=guiderating][/span]", 'byAuthor' => "От %s", 'notFound' => "Такого руководство не существует.", 'clTitle' => 'История изменений «%2$s»', - 'clStatusSet' => 'Присвоен статус «%s»', - 'clCreated' => 'Создано', + 'clStatusSet' => 'Присвоен статус «%s»: ', + 'clCreated' => 'Создано: ', 'clMinorEdit' => 'Небольшое изменение', 'editor' => array( 'fullTitle' => 'Полный заголовок', @@ -188,7 +188,7 @@ $lang = array( 'name' => 'Имя', 'nameTip' => 'Укажите краткое и понятное название руководства. Оно будет использоватья в меню и перечнях руководств.', 'description' => 'Описание', - 'descriptionTip' => 'Описание для поисковых систем.<br><br>Если поле будет оставлено пустым, то сайт сгенерирует описание автоматически.', + 'descriptionTip' => 'Описание для поисковых систем.

Если поле будет оставлено пустым, то сайт сгенерирует описание автоматически.', // 'commentEmail' => 'E-mail уведомления', // 'commentEmailTip' => 'Должен ли автор руководства получать e-mail оповещения, когда к руководству оставляют комментарий?', 'changelog' => 'История изменений, внесенных этой правкой', @@ -203,11 +203,11 @@ $lang = array( 'testGuide' => 'Посмотрите, как будет выглядеть руководство', 'images' => 'Images', 'statusTip' => array( - GUIDE_STATUS_DRAFT => 'Руководство сохранено как "Черновик" — видеть его можете только вы. Правьте руководство так долго, как сочтете нужным, а когда решите, что оно готово — отправьте на одобрение.', - GUIDE_STATUS_REVIEW => 'Your guide is being reviewed.', - GUIDE_STATUS_APPROVED => 'Your guide has been published.', - GUIDE_STATUS_REJECTED => 'Your guide has been rejected. After it\'s shortcomings have been remedied you may resubmit it for review.', - GUIDE_STATUS_ARCHIVED => 'Your guide is outdated and has been archived. Is will no longer be listed and can\'t be edited.', + GuideMgr::STATUS_DRAFT => 'Руководство сохранено как "Черновик" — видеть его можете только вы. Правьте руководство так долго, как сочтете нужным, а когда решите, что оно готово — отправьте на одобрение.', + GuideMgr::STATUS_REVIEW => 'Your guide is being reviewed.', + GuideMgr::STATUS_APPROVED => 'Your guide has been published.', + GuideMgr::STATUS_REJECTED => 'Your guide has been rejected. After it\'s shortcomings have been remedied you may resubmit it for review.', + GuideMgr::STATUS_ARCHIVED => 'Your guide is outdated and has been archived. Is will no longer be listed and can\'t be edited.', ) ), 'category' => array( diff --git a/localization/locale_zhcn.php b/localization/locale_zhcn.php index 42e090cb..2ebf767e 100644 --- a/localization/locale_zhcn.php +++ b/localization/locale_zhcn.php @@ -167,20 +167,20 @@ $lang = array( 'myGuides' => "我的指南", 'editTitle' => "编辑你的指南", 'newTitle' => "创建新指南", - 'author' => "作者", - 'spec' => "专精", + 'author' => "作者:", + 'spec' => "专精:", 'sticky' => "置顶状态", - 'views' => "浏览量", + 'views' => "浏览量:", 'patch' => "补丁", - 'added' => "已添加", - 'rating' => "评分", - 'votes' => "[span id=guiderating-value]%d[/span]/5 ([span id=guiderating-votes][n5=%d][/span] 投票)[span id=guiderating][/span]", + 'added' => "已添加:", + 'rating' => "评分:", + 'votes' => "[span id=guiderating-value]%.2g[/span]/5 ([span id=guiderating-votes][n5=%d][/span] 投票)[span id=guiderating][/span]", 'noVotes' => "投票数量不足 [span id=guiderating][/span]", 'byAuthor' => "来自 %s", 'notFound' => "该指南不存在。", 'clTitle' => '修改日志 "%2$s"', - 'clStatusSet' => '状态已设置为 %s', - 'clCreated' => '已创建', + 'clStatusSet' => '状态已设置为 %s:', + 'clCreated' => '已创建:', 'clMinorEdit' => '小修改', 'editor' => array( 'fullTitle' => '完整标题', @@ -188,7 +188,7 @@ $lang = array( 'name' => '名称', 'nameTip' => '这应该是一个简单明了的指南名称,用于菜单和指南列表', 'description' => '描述', - 'descriptionTip' => '描述将用于说明片段<br /><br />如果不填,则自动生成。', + 'descriptionTip' => '描述将用于说明片段

如果不填,则自动生成。', // 'commentEmail' => '评论电子邮件', // 'commentEmailTip' => '当用户对此指南发表评论时,作者是否收到电子邮件通知?', 'changelog' => '当前编辑的修改日志', @@ -203,11 +203,11 @@ $lang = array( 'testGuide' => '自我浏览你的指南', 'images' => '图片', 'statusTip' => array( - GUIDE_STATUS_DRAFT => '你的指南目前是草稿状态,只有你自己可见。试着输入更多的文字,当你觉得可以了的时候就提交送审吧。', - GUIDE_STATUS_REVIEW => '你的指南正在审核中', - GUIDE_STATUS_APPROVED => '你的指南已发布', - GUIDE_STATUS_REJECTED => '你的指南已被拒绝。在修正问题后,你可以重新提交审核。', - GUIDE_STATUS_ARCHIVED => '你的指南已过时,并已归档。它将不再列出,也无法编辑。', + GuideMgr::STATUS_DRAFT => '你的指南目前是草稿状态,只有你自己可见。试着输入更多的文字,当你觉得可以了的时候就提交送审吧。', + GuideMgr::STATUS_REVIEW => '你的指南正在审核中', + GuideMgr::STATUS_APPROVED => '你的指南已发布', + GuideMgr::STATUS_REJECTED => '你的指南已被拒绝。在修正问题后,你可以重新提交审核。', + GuideMgr::STATUS_ARCHIVED => '你的指南已过时,并已归档。它将不再列出,也无法编辑。', ) ), 'category' => array( diff --git a/pages/admin.php b/pages/admin.php index a69209a0..6709c008 100644 --- a/pages/admin.php +++ b/pages/admin.php @@ -225,7 +225,7 @@ class AdminPage extends GenericPage private function handleGuideApprove() : void { - $pending = new GuideList([['status', GUIDE_STATUS_REVIEW]]); + $pending = new GuideList([['status', GuideMgr::STATUS_REVIEW]]); if ($pending->error) $data = []; else diff --git a/pages/guide.php b/pages/guide.php deleted file mode 100644 index a371694f..00000000 --- a/pages/guide.php +++ /dev/null @@ -1,549 +0,0 @@ - ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkInt'], - 'rev' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkInt'] - ); - - protected /* array */ $_post = array( - 'save' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkEmptySet'], - 'submit' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkEmptySet'], - 'title' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkTextLine'], - 'name' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkTextLine'], - 'description' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GuidePage::checkDescription'], - 'changelog' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkTextBlob'], - 'body' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkTextBlob'], - 'locale' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkInt'], - 'category' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkInt'], - 'specId' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkInt'], - 'classId' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkInt'] - ); - - public function __construct($pageCall, $pageParam) - { - $guide = explode( "&", $pageParam, 2); - - parent::__construct($pageCall, $pageParam); - - if (isset($guide[1]) && preg_match(self::VALID_URL, $guide[1])) - $this->extra = $guide[1]; - - - /**********************/ - /* get mode + guideId */ - /**********************/ - - if (Util::checkNumeric($guide[0], NUM_CAST_INT)) - $this->typeId = $guide[0]; - else if (preg_match(self::VALID_URL, $guide[0])) - { - switch ($guide[0]) - { - case 'changelog': - if (!$this->_get['id']) - break; - - $this->show = self::SHOW_CHANGELOG; - $this->tpl = 'text-page-generic'; - $this->article = false; // do not include article from db - - // main container should be tagged:
- // why is this here: is there a mediawiki like diff function for staff? - $this->addScript([SC_CSS_STRING, 'li input[type="radio"] {margin:0}']); - - $this->typeId = $this->_get['id']; // just to display sensible not-found msg - if ($id = DB::Aowow()->selectCell('SELECT `id` FROM ?_guides WHERE `id` = ?d', $this->typeId)) - $this->typeId = intVal($id); - - break; - case 'new': - if (User::canWriteGuide()) - { - $this->show = self::SHOW_NEW; - $this->guideRevision = null; - - $this->initNew(); - return; // do not create new GuideList - } - break; - case 'edit': - if (User::canWriteGuide()) - { - if (!$this->initEdit()) - $this->notFound(Lang::game('guide'), Lang::guide('notFound')); - - $this->show = self::SHOW_EDITOR; - } - break; - default: - if ($id = DB::Aowow()->selectCell('SELECT `id` FROM ?_guides WHERE `url` = ?', Util::lower($guide[0]))) - { - $this->typeId = intVal($id); - $this->guideRevision = null; - $this->articleUrl = Util::lower($guide[0]); - } - } - } - - - /*********************/ - /* load actual guide */ - /*********************/ - - $this->subject = new GuideList(array(['id', $this->typeId])); - if ($this->subject->error) - $this->notFound(Lang::game('guide'), Lang::guide('notFound')); - - if (!$this->subject->canBeViewed() && !$this->subject->userCanView()) - header('Location: ?guides='.$this->subject->getField('category'), true, 302); - - if ($this->show == self::SHOW_GUIDE && $this->_get['rev'] !== null && !$this->articleUrl && $this->subject->userCanView()) - $this->guideRevision = $this->_get['rev']; - else if ($this->show == self::SHOW_GUIDE && !$this->articleUrl) - $this->guideRevision = $this->subject->getField('rev'); - else - $this->guideRevision = null; - - if (!$this->name) - $this->name = $this->subject->getField('name'); - } - - protected function generateContent() : void - { - match ($this->show) - { - self::SHOW_NEW => $this->displayNew(), - self::SHOW_EDITOR => $this->displayEditor(), - self::SHOW_GUIDE => $this->displayGuide(), - self::SHOW_CHANGELOG => $this->displayChangelog(), - default => trigger_error('GuidePage::generateContent - what content!?') - }; - } - - private function displayNew() : void - { - // init required template vars - $this->editorFields = array( - 'locale' => Lang::getLocale()->value, - 'status' => GUIDE_STATUS_DRAFT - ); - } - - private function displayEditor() : void - { - // can't check in init as subject is unknown - if ($this->subject->getField('status') == GUIDE_STATUS_ARCHIVED) - $this->notFound(Lang::game('guide'), Lang::guide('notFound')); - - $status = GUIDE_STATUS_NONE; - $rev = DB::Aowow()->selectCell('SELECT `rev` FROM ?_articles WHERE `type` = ?d AND `typeId` = ?d ORDER BY `rev` DESC LIMIT 1', Type::GUIDE, $this->typeId); - $curStatus = DB::Aowow()->selectCell('SELECT `status` FROM ?_guides WHERE `id` = ?d ', $this->typeId); - if ($rev === null) - $rev = 0; - - if ($this->save) - { - $rev++; - - // insert Article - DB::Aowow()->query('INSERT INTO ?_articles (`type`, `typeId`, `locale`, `rev`, `editAccess`, `article`) VALUES (?d, ?d, ?d, ?d, ?d, ?)', - Type::GUIDE, $this->typeId, $this->_post['locale'], $rev, User::$groups & U_GROUP_STAFF ? User::$groups : User::$groups | U_GROUP_BLOGGER, $this->_post['body']); - - // link to Guide - $guideData = array( - 'category' => $this->_post['category'], - 'classId' => $this->_post['classId'], - 'specId' => $this->_post['specId'], - 'title' => $this->_post['title'], - 'name' => $this->_post['name'], - 'description' => $this->_post['description'] ?: Lang::trimTextClean(Markup::stripTags($this->_post['body']), 120), - 'locale' => $this->_post['locale'], - 'roles' => User::$groups, - 'status' => GUIDE_STATUS_DRAFT - ); - - DB::Aowow()->query('UPDATE ?_guides SET ?a WHERE `id` = ?d', $guideData, $this->typeId); - - // new guide -> reload editor - if ($this->_get['id'] === 0) - header('Location: ?guide=edit&id='.$this->typeId, true, 302); - else - DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `rev`, `date`, `userId`, `msg`) VALUES (?d, ?d, ?d, ?d, ?)', $this->typeId, $rev, time(), User::$id, $this->_post['changelog']); - - if ($this->_post['submit']) - { - $status = GUIDE_STATUS_REVIEW; - if ($curStatus != GUIDE_STATUS_REVIEW) - { - DB::Aowow()->query('UPDATE ?_guides SET `status` = ?d WHERE `id` = ?d', GUIDE_STATUS_REVIEW, $this->typeId); - DB::Aowow()->query('INSERT INTO ?_guides_changelog (`id`, `date`, `userId`, `status`) VALUES (?d, ?d, ?d, ?d)', $this->typeId, time(), User::$id, GUIDE_STATUS_REVIEW); - } - } - } - - // init required template vars - $this->editorFields = array( - 'category' => $this->_post['category'] ?? $this->subject->getField('category'), - 'title' => $this->_post['title'] ?? $this->subject->getField('title'), - 'name' => $this->_post['name'] ?? $this->subject->getField('name'), - 'description' => $this->_post['description'] ?? $this->subject->getField('description'), - 'text' => $this->_post['body'] ?? $this->subject->getArticle(), - 'status' => $status ?: $this->subject->getField('status'), - 'classId' => $this->_post['classId'] ?? $this->subject->getField('classId'), - 'specId' => $this->_post['specId'] ?? $this->subject->getField('specId'), - 'locale' => $this->_post['locale'] ?? $this->subject->getField('locale'), - 'rev' => $rev - ); - - $this->extendGlobalData($this->subject->getJSGlobals()); - } - - private function displayGuide() : void - { - if (!($this->subject->getField('cuFlags') & GUIDE_CU_NO_QUICKFACTS)) - { - $qf = []; - if ($this->subject->getField('cuFlags') & CC_FLAG_STICKY) - $qf[] = '[span class=guide-sticky]'.Lang::guide('sticky').'[/span]'; - - $qf[] = Lang::guide('author').Lang::main('colon').'[url=?user='.$this->subject->getField('author').']'.$this->subject->getField('author').'[/url]'; - - if ($this->subject->getField('category') == 1) - { - $c = $this->subject->getField('classId'); - $s = $this->subject->getField('specId'); - if ($c > 0) - { - $this->extendGlobalIds(Type::CHR_CLASS, $c); - $qf[] = Util::ucFirst(Lang::game('class')).Lang::main('colon').'[class='.$c.']'; - } - if ($s > -1) - $qf[] = Lang::guide('spec').Lang::main('colon').'[icon class="c'.$c.' icontiny" name='.Game::$specIconStrings[$c][$s].']'.Lang::game('classSpecs', $c, $s).'[/icon]'; - } - - // $qf[] = Lang::guide('patch').Lang::main('colon').'3.3.5'; // replace with date - $qf[] = Lang::guide('added').Lang::main('colon').'[tooltip name=added]'.date('l, G:i:s', $this->subject->getField('date')).'[/tooltip][span class=tip tooltip=added]'.date(Lang::main('dateFmtShort'), $this->subject->getField('date')).'[/span]'; - - switch ($this->subject->getField('status')) - { - case GUIDE_STATUS_APPROVED: - $qf[] = Lang::guide('views').Lang::main('colon').'[n5='.$this->subject->getField('views').']'; - - if (!($this->subject->getField('cuFlags') & GUIDE_CU_NO_RATING)) - { - $this->guideRating = array( - $this->subject->getField('rating'), // avg rating - User::canUpvote() && User::canDownvote() ? 'true' : 'false', - $this->subject->getField('_self'), // my rating amt; 0 = no vote - $this->typeId // guide Id - ); - - if ($this->subject->getField('nvotes') < 5) - $qf[] = Lang::guide('rating').Lang::main('colon').Lang::guide('noVotes'); - else - $qf[] = Lang::guide('rating').Lang::main('colon').Lang::guide('votes', [round($this->subject->getField('rating'), 1), $this->subject->getField('nvotes')]); - } - break; - case GUIDE_STATUS_ARCHIVED: - $qf[] = Lang::guide('status', GUIDE_STATUS_ARCHIVED); - break; - } - - $qf = '[ul][li]'.implode('[/li][li]', $qf).'[/li][/ul]'; - - if ($this->subject->getField('status') == GUIDE_STATUS_REVIEW && User::isInGroup(U_GROUP_STAFF) && $this->_get['rev']) - { - $this->addScript([SC_JS_STRING, ' - DomContentLoaded.addEvent(function() { - let send = function (status) - { - let message = ""; - let id = $WH.g_getGets().guide; - if (status == 4) // rejected - { - while (message === "") - message = prompt("Please provide your reasoning."); - - if (message === null) - return false; - } - - $.ajax({cache: false, url: "?admin=guide", type: "POST", - error: function() { - alert("Operation failed."); - }, - success: function(json) { - if (json != 1) - alert("Operation failed."); - else - window.location.href = "?admin=guides"; - }, - data: { id: id, status: status, msg: message } - }) - - return true; - }; - - $WH.ge("btn-accept").onclick = send.bind(null, 3); - $WH.ge("btn-reject").onclick = send.bind(null, 4); - }); - ']); - - $qf .= '[h3 style="text-align:center"]Admin[/h3]'; - - $qf .= '[div style="text-align:center"][url=# id="btn-accept" class=icon-tick]Approve[/url][url=# style="margin-left:20px" id="btn-reject" class=icon-delete]Reject[/url][/div]'; - } - } - - $this->redButtons[BUTTON_GUIDE_LOG] = true; - $this->redButtons[BUTTON_GUIDE_REPORT] = $this->subject->canBeReported(); - - $this->infobox = $qf ?? ''; - $this->author = $this->subject->getField('author'); // add to g_pageInfo in GenericPage:prepareContent() - - if ($this->subject->userCanView()) - $this->redButtons[BUTTON_GUIDE_EDIT] = User::canWriteGuide() && $this->subject->getField('status') != GUIDE_STATUS_ARCHIVED; - - // the article text itself is added by GenericPage::addArticle() - } - - private function displayChangelog() : void - { - $this->addScript([SC_JS_STRING, ' - $(document).ready(function() { - var radios = $("input[type=radio]"); - function limit(col, val) { - radios.each(function(i, e) { - if (col == e.name) - return; - - if (col == "b") - e.disabled = (val <= parseInt(e.value)); - else if (col == "a") - e.disabled = (val >= parseInt(e.value)); - }); - - }; - - radios.each(function (i, e) { - e.onchange = limit.bind(this, e.name, parseInt(e.value)); - - if (i < 2 && e.name == "b") // first pair - $(e).trigger("click"); - else if (e.value == 0 && e.name == "a") // last pair - $(e).trigger("click"); - }); - }); - ']); - - $buff = '
    '; - $inp = fn($rev) => User::isInGroup(U_GROUP_STAFF) ? ($rev !== null ? '' : '') : ''; - - $logEntries = DB::Aowow()->select('SELECT a.`username` AS `name`, gcl.`date`, gcl.`status`, gcl.`msg`, gcl.`rev` FROM ?_guides_changelog gcl JOIN ?_account a ON a.`id` = gcl.`userId` WHERE gcl.`id` = ?d ORDER BY gcl.`date` DESC', $this->typeId); - foreach ($logEntries as $log) - { - if ($log['status'] != GUIDE_STATUS_NONE) - $buff .= '
  • '.$inp($log['rev']).Lang::guide('clStatusSet', [Lang::guide('status', $log['status'])]).Lang::main('colon').''.Util::formatTimeDiff($log['date'])."
  • \n"; - else if ($log['msg']) - $buff .= '
  • '.$inp($log['rev']).Util::formatTimeDiff($log['date']).Lang::main('colon').''.$log['msg'].' '.Lang::main('byUser', [$log['name'], 'style="text-decoration:underline"'])."
  • \n"; - else - $buff .= '
  • '.$inp($log['rev']).Util::formatTimeDiff($log['date']).Lang::main('colon').''.Lang::guide('clMinorEdit').' '.Lang::main('byUser', [$log['name'], 'style="text-decoration:underline"'])."
  • \n"; - } - - // append creation - $buff .= '
  • '.$inp(0).''.Lang::guide('clCreated').Lang::main('colon').''.Util::formatTimeDiff($this->subject->getField('date'))."
  • \n
\n"; - - - if (User::isInGroup(U_GROUP_STAFF)) - $buff .= ''; - - $this->name = lang::guide('clTitle', [$this->typeId, $this->subject->getField('title')]); - $this->extraHTML = $buff; - } - - private function initNew() : void - { - $this->addScript( - [SC_JS_FILE, 'js/article-description.js'], - [SC_JS_FILE, 'js/article-editing.js'], - [SC_JS_FILE, 'js/guide-editing.js'], - [SC_JS_FILE, 'js/fileuploader.js'], - [SC_JS_FILE, 'js/toolbar.js'], - [SC_JS_FILE, 'js/AdjacentPreview.js'], - [SC_CSS_FILE, 'css/article-editing.css'], - [SC_CSS_FILE, 'css/fileuploader.css'], - [SC_CSS_FILE, 'css/guide-edit.css'], - [SC_CSS_FILE, 'css/AdjacentPreview.css'], - - [SC_CSS_STRING, '#upload-result input[type=text] { padding: 0px 2px; font-size: 12px; }'], - [SC_CSS_STRING, '#upload-result > span { display:block; height: 22px; }'], - [SC_CSS_STRING, '#upload-result { display: inline-block; text-align:right; }'], - [SC_CSS_STRING, '#upload-progress { display: inline-block; margin-right:8px; }'] - ); - - $this->articleUrl = 'new'; - $this->tpl = 'guide-edit'; - $this->name = Lang::guide('newTitle'); - - Lang::sort('guide', 'category'); - - $this->typeId = 0; // signals 'edit' to create new guide - } - - private function initEdit() : bool - { - $this->addScript( - [SC_JS_FILE, 'js/article-description.js'], - [SC_JS_FILE, 'js/article-editing.js'], - [SC_JS_FILE, 'js/guide-editing.js'], - [SC_JS_FILE, 'js/fileuploader.js'], - [SC_JS_FILE, 'js/toolbar.js'], - [SC_JS_FILE, 'js/AdjacentPreview.js'], - [SC_CSS_FILE, 'css/article-editing.css'], - [SC_CSS_FILE, 'css/fileuploader.css'], - [SC_CSS_FILE, 'css/guide-edit.css'], - [SC_CSS_FILE, 'css/AdjacentPreview.css'], - - [SC_CSS_STRING, '#upload-result input[type=text] { padding: 0px 2px; font-size: 12px; }'], - [SC_CSS_STRING, '#upload-result > span { display:block; height: 22px; }'], - [SC_CSS_STRING, '#upload-result { display: inline-block; text-align:right; }'], - [SC_CSS_STRING, '#upload-progress { display: inline-block; margin-right:8px; }'] - ); - - $this->articleUrl = 'edit'; - $this->tpl = 'guide-edit'; - $this->name = Lang::guide('editTitle'); - $this->save = $this->_post['save'] || $this->_post['submit']; - - // reject inconsistent guide data - if ($this->save) - { - // req: set data - if (!$this->_post['title'] || !$this->_post['name'] || !$this->_post['body'] || $this->_post['locale'] === null) - return false; - - // req: valid data - if (!in_array($this->_post['category'], $this->validCats) || !(Cfg::get('LOCALES') & (1 << $this->_post['locale']))) - return false; - - // sanitize: spec / class - if ($this->_post['category'] == 1) // Classes - { - if ($this->_post['classId'] && !ChrClass::tryFrom($this->_post['classId'])) - $this->_post['classId'] = 0; - - if (!in_array($this->_post['specId'], [-1, 0, 1, 2])) - $this->_post['specId'] = -1; - if ($this->_post['specId'] > -1 && !$this->_post['classId']) - $this->_post['specId'] = -1; - } - else - { - $this->_post['classId'] = 0; - $this->_post['specId'] = -1; - } - } - - if ($this->_get['id']) // edit existing guide - { - $this->typeId = $this->_get['id']; // just to display sensible not-found msg - if ($id = DB::Aowow()->selectCell('SELECT `id` FROM ?_guides WHERE `id` = ?d AND `status` <> ?d {AND `userId` = ?d}', $this->typeId, GUIDE_STATUS_ARCHIVED, User::isInGroup(U_GROUP_STAFF) ? DBSIMPLE_SKIP : User::$id)) - $this->typeId = intVal($id); - } - else if ($this->_get['id'] === 0) // create new guide and load in editor - $this->typeId = DB::Aowow()->query('INSERT INTO ?_guides (`userId`, `date`, `status`) VALUES (?d, ?d, ?d)', User::$id, time(), GUIDE_STATUS_DRAFT); - - return $this->typeId > 0; - } - - protected function editorFields(string $field, bool $asInt = false) : string|int - { - return $this->editorFields[$field] ?? ($asInt ? 0 : ''); - } - - protected function generateTooltip() - { - $power = new \StdClass(); - if (!$this->subject->error) - { - $power->{'name_'.Lang::getLocale()->json()} = strip_tags($this->name); - $power->{'tooltip_'.Lang::getLocale()->json()} = $this->subject->renderTooltip(); - } - - return sprintf($this->powerTpl, Util::toJSON($this->articleUrl ?: $this->typeId), Lang::getLocale()->value, Util::toJSON($power, JSON_AOWOW_POWER)); - } - - protected function generatePath() : void - { - if ($x = $this->subject?->getField('category')) - $this->path[] = $x; - } - - protected function generateTitle() : void - { - if ($this->show == self::SHOW_EDITOR) - array_unshift($this->title, Lang::guide('editTitle').Lang::main('colon').$this->subject->getField('title'), Lang::game('guides')); - if ($this->show == self::SHOW_NEW) - array_unshift($this->title, Lang::guide('newTitle'), Lang::game('guides')); - else - array_unshift($this->title, $this->subject->getField('title'), Lang::game('guides')); - } - - protected function postCache() : void - { - // increment views of published guide; ignore caching - if ($this->subject?->getField('status') == GUIDE_STATUS_APPROVED) - DB::Aowow()->query('UPDATE ?_guides SET `views` = `views` + 1 WHERE `id` = ?d', $this->typeId); - } - - protected static function checkDescription(string $str) : string - { - // run checkTextBlob and also replace \n => \s and \s+ => \s - $str = preg_replace(parent::PATTERN_TEXT_BLOB, '', $str); - - $str = strtr($str, ["\n" => ' ', "\r" => ' ']); - - return preg_replace('/\s+/', ' ', trim($str)); - } -} - -?> diff --git a/pages/guides.php b/pages/guides.php deleted file mode 100644 index a2194a6c..00000000 --- a/pages/guides.php +++ /dev/null @@ -1,102 +0,0 @@ -getCategoryFromUrl($pageParam); - - parent::__construct($pageCall, $pageParam); - - if ($pageCall == 'my-guides') - { - if (!User::isLoggedIn()) - $this->error(); - - $this->name = Util::ucFirst(Lang::guide('myGuides')); - $this->myGuides = true; - } - else - $this->name = Util::ucFirst(Lang::game('guides')); - } - - protected function generateContent() - { - $hCols = ['patch']; // pointless: display date instead - $vCols = []; - $xCols = ['$Listview.extraCols.date']; // ok - - if ($this->myGuides) - { - $conditions = [['userId', User::$id]]; - $hCols[] = 'author'; - $vCols[] = 'status'; - } - else - { - $conditions = array( - ['locale', Lang::getLocale()->value], - ['status', GUIDE_STATUS_ARCHIVED, '!'], // never archived guides - [ - 'OR', - ['status', GUIDE_STATUS_APPROVED], // currently approved - ['rev', 0, '>'] // has previously approved revision - ] - ); - if (isset($this->category[0])) - $conditions[] = ['category', $this->category]; - } - - $data = []; - $guides = new GuideList($conditions); - if (!$guides->error) - $data = array_values($guides->getListviewData()); - - $tabData = array( - 'data' => $data, - 'name' => Util::ucFirst(Lang::game('guides')), - 'hiddenCols' => $hCols, - 'visibleCols' => $vCols, - 'extraCols' => $xCols - ); - - $this->lvTabs[] = [GuideList::$brickFile, $tabData]; - - $this->redButtons = [BUTTON_GUIDE_NEW => User::canWriteGuide()]; - } - - protected function generateTitle() - { - array_unshift($this->title, $this->name); - if (isset($this->category[0])) - array_unshift($this->title, Lang::guide('category', $this->category[0])); - - } - - protected function generatePath() - { - if (isset($this->category[0])) - $this->path[] = $this->category[0]; - } -} - -?> diff --git a/static/js/global.js b/static/js/global.js index 453f644e..3c8741aa 100644 --- a/static/js/global.js +++ b/static/js/global.js @@ -22987,17 +22987,14 @@ function g_modifyUrl(url, params, opt) { } function g_enhanceTextarea (ta, opt) { - if (!(ta instanceof jQuery)) { + if (!(ta instanceof jQuery)) ta = $(ta); - } - if (ta.data("wh-enhanced") || ta.prop("tagName") != "TEXTAREA") { + if (ta.data("wh-enhanced") || ta.prop("tagName") != "TEXTAREA") return; - } - if (typeof opt != "object") { + if (typeof opt != "object") opt = {}; - } var canResize = (function(el) { if (!el.dynamicResizeOption) @@ -23016,63 +23013,66 @@ function g_enhanceTextarea (ta, opt) { var wrapper = $("
", { "class": "enhanced-textarea-wrapper" }).insertBefore(ta).append(ta); if (!opt.hasOwnProperty("color")) - wrapper.addClass("enhanced-textarea-dark") + wrapper.addClass("enhanced-textarea-dark"); else if (opt.color) - wrapper.addClass("enhanced-textarea-" + opt.color) + wrapper.addClass("enhanced-textarea-" + opt.color); if (!opt.hasOwnProperty("dynamicSizing") || opt.dynamicSizing || opt.dynamicResizeOption) { var expander = $("
", { "class": "enhanced-textarea-expander" }).prependTo(wrapper); - var n = function(E, D, F) { - if (!F()) + var dynamicResize = function(textarea, exactHeight, canResizeFn) { + if (!canResizeFn()) return; - // E.css("height", E.siblings(".enhanced-textarea-expander").html($WH.htmlentities(E.val()).replace(/\n/g, "
") + "
").height() + (D ? 14 : 34) + "px") - E.css("height", E.siblings(".enhanced-textarea-expander").html($WH.htmlentities(E.val()) + "
").height() + (D ? 14 : 34) + "px") + // E.css("height", E.siblings(".enhanced-textarea-expander").html($WH.htmlentities(E.val()).replace(/\n/g, "
") + "
").height() + (D ? 14 : 34) + "px"); + textarea.css("height", textarea.siblings(".enhanced-textarea-expander").html($WH.htmlentities(textarea.val()) + "
").height() + (exactHeight ? 14 : 34) + "px"); }; - ta.bind("keydown keyup change", n.bind(this, ta, opt.exactLineHeights, canResize)); - n(ta, opt.exactLineHeights, canResize); - var setWidth = function(D) { - D.css("width", D.parent().width() + "px") - }; + ta.bind("keydown keyup change", dynamicResize.bind(this, ta, opt.exactLineHeights, canResize)); + dynamicResize(ta, opt.exactLineHeights, canResize); + + var setWidth = function(el) { el.css("width", el.parent().width() + "px"); }; + setWidth(expander); setTimeout(setWidth.bind(null, expander), 1); - if (!opt.dynamicResizeOption || (opt.dynamicResizeOption && canResize())) { - wrapper.addClass("enhanced-textarea-dynamic-sizing") - } - } - if (!opt.hasOwnProperty("focusChanges") || opt.focusChanges) { - wrapper.addClass("enhanced-textarea-focus-changes") + + if (!opt.dynamicResizeOption || (opt.dynamicResizeOption && canResize())) + wrapper.addClass("enhanced-textarea-dynamic-sizing"); } + + if (!opt.hasOwnProperty("focusChanges") || opt.focusChanges) + wrapper.addClass("enhanced-textarea-focus-changes"); + if (opt.markup) { - var w = $("
", { "class": "enhanced-textarea-markup-wrapper" }).prependTo(wrapper); - var y = $("
", { "class": "enhanced-textarea-markup" }).appendTo(w); - var z = $("
", { "class": "enhanced-textarea-markup-segment" }).appendTo(y); - var k = $("
", { "class": "enhanced-textarea-markup-segment" }).appendTo(y); + var _markupMenu = $("
", { "class": "enhanced-textarea-markup-wrapper" }).prependTo(wrapper); + var _segments = $("
", { "class": "enhanced-textarea-markup" }).appendTo(_markupMenu); + var _toolbar = $("
", { "class": "enhanced-textarea-markup-segment" }).appendTo(_segments); + var _menu = $("
", { "class": "enhanced-textarea-markup-segment" }).appendTo(_segments); if (opt.markup == "inline") - ar_AddInlineToolbar(ta.get(0), z.get(0), k.get(0)); + ar_AddInlineToolbar(ta.get(0), _toolbar.get(0), _menu.get(0)); else - ar_AddToolbar(ta.get(0), z.get(0), k.get(0)); + ar_AddToolbar(ta.get(0), _toolbar.get(0), _menu.get(0)); if (opt.dynamicResizeOption) { - var t = $("
", { "class": "enhanced-textarea-markup-segment" }).appendTo(y); - var C = $("