diff --git a/endpoints/admin/screenshots.php b/endpoints/admin/screenshots.php new file mode 100644 index 00000000..33fe5a21 --- /dev/null +++ b/endpoints/admin/screenshots.php @@ -0,0 +1,68 @@ + Content > Screenshots + + protected array $scripts = array( + [SC_JS_FILE, 'js/screenshot.js'], + [SC_CSS_STRING, '.layout {margin: 0px 25px; max-width: inherit; min-width: 1200px; }'], + [SC_CSS_STRING, '#highlightedRow { background-color: #322C1C; }'] + ); + protected array $expectedGET = array( + 'action' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']], + 'all' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkEmptySet']], + 'type' => ['filter' => FILTER_VALIDATE_INT ], + 'typeid' => ['filter' => FILTER_VALIDATE_INT ], + 'user' => ['filter' => FILTER_CALLBACK, 'options' => 'urldecode' ] + ); + + public ?bool $getAll = null; + public array $ssPages = []; + public array $ssData = []; + public int $ssNFound = 0; + public array $pageTypes = []; + + protected function generate() : void + { + $this->h1 = 'Screenshot Manager'; + + // types that can have screenshots + foreach (Type::getClassesFor(0, 'contribute', CONTRIBUTE_SS) as $type => $obj) + $this->pageTypes[$type] = Util::ucWords(Lang::game(Type::getFileString($type))); + + $ssGetAll = $this->_get['all']; + $ssPages = []; + $ssData = []; + $nMatches = 0; + + if ($this->_get['type'] && $this->_get['typeid']) + $ssData = ScreenshotMgr::getScreenshots($this->_get['type'], $this->_get['typeid'], nFound: $nMatches); + else if ($this->_get['user']) + { + if (mb_strlen($this->_get['user']) >= 3) + if ($uId = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE LOWER(`username`) = LOWER(?)', $this->_get['user'])) + $ssData = ScreenshotMgr::getScreenshots(userId: $uId, nFound: $nMatches); + } + else + $ssPages = ScreenshotMgr::getPages($ssGetAll, $nMatches); + + $this->getAll = $ssGetAll; + $this->ssPages = $ssPages; + $this->ssData = $ssData; + $this->ssNFound = $nMatches; // ssm_numPagesFound + + parent::generate(); + } +} diff --git a/endpoints/admin/screenshots_approve.php b/endpoints/admin/screenshots_approve.php new file mode 100644 index 00000000..da4c9763 --- /dev/null +++ b/endpoints/admin/screenshots_approve.php @@ -0,0 +1,62 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdListUnsigned']] + ); + + protected function generate() : void + { + if (!$this->assertGET('id')) + { + trigger_error('AdminScreenshotsActionApproveResponse - screenshotId empty', E_USER_ERROR); + return; + } + + ScreenshotMgr::init(); + + // create resized and thumb version of screenshot + $ssEntries = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `userIdOwner`, `date`, `type`, `typeId` FROM ?_screenshots WHERE (`status` & ?d) = 0 AND `id` IN (?a)', CC_FLAG_APPROVED, $this->_get['id']); + foreach ($ssEntries as $id => $ssData) + { + if (!ScreenshotMgr::loadFile(ScreenshotMgr::PATH_PENDING, $id)) + continue; + + if (!ScreenshotMgr::createResized($id)) + continue; + + if (!ScreenshotMgr::createThumbnail($id)) + continue; + + // move pending > normal + if (!rename(sprintf(ScreenshotMgr::PATH_PENDING, $id), sprintf(ScreenshotMgr::PATH_NORMAL, $id))) + continue; + + // set as approved in DB + DB::Aowow()->query('UPDATE ?_screenshots SET `status` = ?d, `userIdApprove` = ?d WHERE `id` = ?d', CC_FLAG_APPROVED, User::$id, $id); + + // gain siterep + Util::gainSiteReputation($ssData['userIdOwner'], SITEREP_ACTION_SUBMIT_SCREENSHOT, ['id' => $id, 'what' => 1, 'date' => $ssData['date']]); + + // flag DB entry as having screenshots + if ($tbl = Type::getClassAttrib($ssData['type'], 'dataTable')) + DB::Aowow()->query('UPDATE ?# SET `cuFlags` = `cuFlags` | ?d WHERE `id` = ?d', $tbl, CUSTOM_HAS_SCREENSHOT, $ssData['typeId']); + + unset($ssEntries[$id]); + } + + if (!$ssEntries) + trigger_error('AdminScreenshotsActionApproveResponse - screenshot(s) # '.implode(', ', array_keys($ssEntries)).' not in db or already approved', E_USER_WARNING); + } +} diff --git a/endpoints/admin/screenshots_delete.php b/endpoints/admin/screenshots_delete.php new file mode 100644 index 00000000..402bc0ec --- /dev/null +++ b/endpoints/admin/screenshots_delete.php @@ -0,0 +1,62 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdListUnsigned']] + ); + + // 2 steps: 1) remove from sight, 2) remove from disk + protected function generate() : void + { + if (!$this->assertGET('id')) + { + trigger_error('AdminScreenshotsActionDeleteResponse - screenshotId empty', E_USER_ERROR); + return; + } + + foreach ($this->_get['id'] as $id) + { + // irrevocably purge files already flagged as deleted (should only exist as pending) + if (User::isInGroup(U_GROUP_ADMIN) && DB::Aowow()->selectCell('SELECT 1 FROM ?_screenshots WHERE `status` & ?d AND `id` = ?d', CC_FLAG_DELETED, $id)) + { + DB::Aowow()->query('DELETE FROM ?_screenshots WHERE `id` = ?d', $id); + if (file_exists(sprintf(ScreenshotMgr::PATH_PENDING, $id))) + unlink(sprintf(ScreenshotMgr::PATH_PENDING, $id)); + + continue; + } + + // move normal to pending and remove resized and thumb + if (file_exists(sprintf(ScreenshotMgr::PATH_NORMAL, $id))) + rename(sprintf(ScreenshotMgr::PATH_NORMAL, $id), sprintf(ScreenshotMgr::PATH_PENDING, $id)); + + if (file_exists(sprintf(ScreenshotMgr::PATH_THUMB, $id))) + unlink(sprintf(ScreenshotMgr::PATH_THUMB, $id)); + + if (file_exists(sprintf(ScreenshotMgr::PATH_RESIZED, $id))) + unlink(sprintf(ScreenshotMgr::PATH_RESIZED, $id)); + } + + // flag as deleted if not aready + $oldEntries = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, GROUP_CONCAT(`typeId`) FROM ?_screenshots WHERE `id` IN (?a) GROUP BY `type`', $this->_get['id']); + DB::Aowow()->query('UPDATE ?_screenshots SET `status` = ?d, `userIdDelete` = ?d WHERE `id` IN (?a)', CC_FLAG_DELETED, User::$id, $this->_get['id']); + + // deflag db entry as having screenshots + foreach ($oldEntries as $type => $typeIds) + { + $typeIds = explode(',', $typeIds); + $toUnflag = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, IF(BIT_OR(`status`) & ?d, 1, 0) AS "hasMore" FROM ?_screenshots WHERE `type` = ?d AND `typeId` IN (?a) GROUP BY `typeId` HAVING `hasMore` = 0', CC_FLAG_APPROVED, $type, $typeIds); + if ($toUnflag && ($tbl = Type::getClassAttrib($type, 'dataTable'))) + DB::Aowow()->query('UPDATE ?# SET cuFlags = cuFlags & ~?d WHERE id IN (?a)', $tbl, CUSTOM_HAS_SCREENSHOT, array_keys($toUnflag)); + } + } +} diff --git a/endpoints/admin/screenshots_editalt.php b/endpoints/admin/screenshots_editalt.php new file mode 100644 index 00000000..0c5feee4 --- /dev/null +++ b/endpoints/admin/screenshots_editalt.php @@ -0,0 +1,32 @@ + ['filter' => FILTER_VALIDATE_INT] + ); + protected array $expectedPOST = array( + 'alt' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']] + ); + + protected function generate() : void + { + if (!$this->assertGET('id')) + return; + + DB::Aowow()->query('UPDATE ?_screenshots SET `caption` = ? WHERE `id` = ?d', + $this->handleCaption($this->_post['alt']), + $this->_get['id'] + ); + } +} diff --git a/endpoints/admin/screenshots_list.php b/endpoints/admin/screenshots_list.php new file mode 100644 index 00000000..bd884d42 --- /dev/null +++ b/endpoints/admin/screenshots_list.php @@ -0,0 +1,23 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkEmptySet']] + ); + + protected function generate() : void + { + $pages = ScreenshotMgr::getPages($this->_get['all'], $nPages); + $this->result = 'ssm_screenshotPages = '.Util::toJSON($pages).";\n"; + $this->result .= 'ssm_numPagesFound = '.$nPages.';'; + } +} diff --git a/endpoints/admin/screenshots_manage.php b/endpoints/admin/screenshots_manage.php new file mode 100644 index 00000000..eddd0d92 --- /dev/null +++ b/endpoints/admin/screenshots_manage.php @@ -0,0 +1,31 @@ + ['filter' => FILTER_VALIDATE_INT ], + 'typeid' => ['filter' => FILTER_VALIDATE_INT ], + 'user' => ['filter' => FILTER_CALLBACK, 'options' => 'urldecode'] + ); + + protected function generate() : void + { + $res = []; + + if ($this->_get['type'] && $this->_get['typeid']) + $res = ScreenshotMgr::getScreenshots($this->_get['type'], $this->_get['typeid']); + else if ($this->_get['user']) + if ($uId = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE LOWER(`username`) = LOWER(?)', $this->_get['user'])) + $res = ScreenshotMgr::getScreenshots(userId: $uId); + + $this->result = 'ssm_screenshotData = '.Util::toJSON($res); + } +} diff --git a/endpoints/admin/screenshots_relocate.php b/endpoints/admin/screenshots_relocate.php new file mode 100644 index 00000000..3f387fb7 --- /dev/null +++ b/endpoints/admin/screenshots_relocate.php @@ -0,0 +1,48 @@ + ['filter' => FILTER_VALIDATE_INT], + 'typeid' => ['filter' => FILTER_VALIDATE_INT] + // (but not type..?) + ); + + protected function generate() : void + { + if (!$this->assertGET('id', 'typeid')) + { + trigger_error('AdminScreenshotsActionRelocateResponse - screenshotId or typeId empty', E_USER_ERROR); + return; + } + + [$type, $oldTypeId] = array_values(DB::Aowow()->selectRow('SELECT `type`, `typeId` FROM ?_screenshots WHERE `id` = ?d', $this->_get['id'])); + $typeId = $this->_get['typeid']; + + if (Type::validateIds($type, $typeId)) + { + $tbl = Type::getClassAttrib($type, 'dataTable'); + + // move screenshot + DB::Aowow()->query('UPDATE ?_screenshots SET `typeId` = ?d WHERE `id` = ?d', $typeId, $this->_get['id']); + + // flag target as having screenshot + DB::Aowow()->query('UPDATE ?# SET `cuFlags` = `cuFlags` | ?d WHERE `id` = ?d', $tbl, CUSTOM_HAS_SCREENSHOT, $typeId); + + // deflag source for having had screenshots (maybe) + $ssInfo = DB::Aowow()->selectRow('SELECT IF(BIT_OR(~`status`) & ?d, 1, 0) AS "hasMore" FROM ?_screenshots WHERE `status`& ?d AND `type` = ?d AND `typeId` = ?d', CC_FLAG_DELETED, CC_FLAG_APPROVED, $type, $oldTypeId); + if ($ssInfo || !$ssInfo['hasMore']) + DB::Aowow()->query('UPDATE ?# SET `cuFlags` = `cuFlags` & ~?d WHERE `id` = ?d', $tbl, CUSTOM_HAS_SCREENSHOT, $oldTypeId); + } + else + trigger_error('AdminScreenshotsActionRelocateResponse - invalid typeId #'.$typeId.' for type #'.$type, E_USER_ERROR); + } +} diff --git a/endpoints/admin/screenshots_sticky.php b/endpoints/admin/screenshots_sticky.php new file mode 100644 index 00000000..26e3235f --- /dev/null +++ b/endpoints/admin/screenshots_sticky.php @@ -0,0 +1,72 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdListUnsigned']] + ); + + protected function generate() : void + { + if (!$this->assertGET('id')) + { + trigger_error('AdminScreenshotsActionStickyResponse - screenshotId empty', E_USER_ERROR); + return; + } + + // this one is a bit strange: as far as i've seen, the only thing a 'sticky' screenshot does is show up in the infobox + // this also means, that only one screenshot per page should be sticky + // so, handle it one by one and the last one affecting one particular type/typId-key gets the cake + $ssEntries = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `userIdOwner`, `date`, `type`, `typeId`, `status` FROM ?_screenshots WHERE (`status` & ?d) = 0 AND `id` IN (?a)', CC_FLAG_DELETED, $this->_get['id']); + foreach ($ssEntries as $id => $ssData) + { + // approve yet unapproved screenshots + if (!($ssData['status'] & CC_FLAG_APPROVED)) + { + ScreenshotMgr::init(); + + if (!ScreenshotMgr::loadFile(ScreenshotMgr::PATH_PENDING, $id)) + continue; + + if (!ScreenshotMgr::createResized($id)) + continue; + + if (!ScreenshotMgr::createThumbnail($id)) + continue; + + // move pending > normal + if (!rename(sprintf(ScreenshotMgr::PATH_PENDING, $id), sprintf(ScreenshotMgr::PATH_NORMAL, $id))) + continue; + + // set as approved in DB + DB::Aowow()->query('UPDATE ?_screenshots SET `status` = ?d, `userIdApprove` = ?d WHERE `id` = ?d', CC_FLAG_APPROVED, User::$id, $id); + + // gain siterep + Util::gainSiteReputation($ssData['userIdOwner'], SITEREP_ACTION_SUBMIT_SCREENSHOT, ['id' => $id, 'what' => 1, 'date' => $ssData['date']]); + + // flag DB entry as having screenshots + if ($tbl = Type::getClassAttrib($ssData['type'], 'dataTable')) + DB::Aowow()->query('UPDATE ?# SET `cuFlags` = `cuFlags` | ?d WHERE `id` = ?d', $tbl, CUSTOM_HAS_SCREENSHOT, $ssData['typeId']); + } + + // reset all others + DB::Aowow()->query('UPDATE ?_screenshots a, ?_screenshots b SET a.`status` = a.`status` & ~?d WHERE a.`type` = b.`type` AND a.`typeId` = b.`typeId` AND a.`id` <> b.`id` AND b.`id` = ?d', CC_FLAG_STICKY, $id); + + // toggle sticky status + DB::Aowow()->query('UPDATE ?_screenshots SET `status` = IF(`status` & ?d, `status` & ~?d, `status` | ?d) WHERE `id` = ?d AND `status` & ?d', CC_FLAG_STICKY, CC_FLAG_STICKY, CC_FLAG_STICKY, $id, CC_FLAG_APPROVED); + + unset($ssEntries[$id]); + } + + if ($ssEntries) + trigger_error('AdminScreenshotsActionStickyResponse - screenshot(s) # '.implode(', ', array_keys($ssEntries)).' not in db or flagged as deleted', E_USER_WARNING); + } +} diff --git a/endpoints/screenshot/add.php b/endpoints/screenshot/add.php new file mode 100644 index 00000000..f6452771 --- /dev/null +++ b/endpoints/screenshot/add.php @@ -0,0 +1,98 @@ + 1. =add: receives user upload + 1.1. checks and processing on the upload + 1.2. forward to =crop or blank response + 2. =crop: user edites upload + 3. =complete: store edited screenshot file and data + 4. =thankyou +*/ + +// filename: Username-type-typeId-[_original].jpg + +class ScreenshotAddResponse extends TextResponse +{ + protected bool $requiresLogin = true; + + private string $imgHash = ''; + private int $destType = 0; + private int $destTypeId = 0; + + public function __construct(string $pageParam) + { + parent::__construct($pageParam); + + // get screenshot destination + // target delivered as screenshot=&.. (hash is optional) + if (!preg_match('/^screenshot=\w+&(-?\d+)\.(-?\d+)(\.(\w{16}))?$/i', $_SERVER['QUERY_STRING'] ?? '', $m, PREG_UNMATCHED_AS_NULL)) + $this->generate404(); + + [, $this->destType, $this->destTypeId, , $imgHash] = $m; + + // no such type or this type cannot receive screenshots + if (!Type::checkClassAttrib($this->destType, 'contribute', CONTRIBUTE_SS)) + $this->generate404(); + + // no such typeId + if (!Type::validateIds($this->destType, $this->destTypeId)) + $this->generate404(); + + // only accept/expect hash for crop & complete + if ($imgHash) + $this->generate404(); + } + + protected function generate() : void + { + if ($this->handleAdd()) + $this->redirectTo = '?screenshot=crop&'.$this->destType.'.'.$this->destTypeId.'.'.$this->imgHash; + else if ($this->destType && $this->destTypeId) + $this->redirectTo = '?'.Type::getFileString($this->destType).'='.$this->destTypeId.'#submit-a-screenshot'; + else + $this->generate404(); + } + + private function handleAdd() : bool + { + if (!User::canUploadScreenshot()) + { + $_SESSION['error']['ss'] = Lang::screenshot('error', 'notAllowed'); + return false; + } + + if (!ScreenshotMgr::init()) + { + $_SESSION['error']['ss'] = Lang::main('intError'); + return false; + } + + if (!ScreenshotMgr::validateUpload()) + { + $_SESSION['error']['ss'] = ScreenshotMgr::$error; + return false; + } + + if (!ScreenshotMgr::loadUpload()) + { + $_SESSION['error']['ss'] = Lang::main('intError'); + return false; + } + + if (!ScreenshotMgr::tempSaveUpload([$this->destType, $this->destTypeId], $this->imgHash)) + { + $_SESSION['error']['ss'] = Lang::main('intError'); + return false; + } + + return true; + } +} + +?> diff --git a/endpoints/screenshot/complete.php b/endpoints/screenshot/complete.php new file mode 100644 index 00000000..0ea503bc --- /dev/null +++ b/endpoints/screenshot/complete.php @@ -0,0 +1,106 @@ + 3. =complete: store edited screenshot file and data + 4. =thankyou +*/ + +// filename: Username-type-typeId-[_original].jpg + +class ScreenshotCompleteResponse extends TextResponse +{ + use TrCommunityHelper; + + protected bool $requiresLogin = true; + + protected array $expectedPOST = array( + 'coords' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkCoords'] ], + 'screenshotalt' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']] + ); + + private int $destType = 0; + private int $destTypeId = 0; + private string $imgHash = ''; + + public function __construct(string $pageParam) + { + parent::__construct($pageParam); + + // get screenshot destination + // target delivered as screenshot=&.. (hash is optional) + if (!preg_match('/^screenshot=\w+&(-?\d+)\.(-?\d+)(\.(\w{16}))?$/i', $_SERVER['QUERY_STRING'] ?? '', $m, PREG_UNMATCHED_AS_NULL)) + $this->generate404(); + + [, $this->destType, $this->destTypeId, , $this->imgHash] = $m; + + // no such type or this type cannot receive screenshots + if (!Type::checkClassAttrib($this->destType, 'contribute', CONTRIBUTE_SS)) + $this->generate404(); + + // no such typeId + if (!Type::validateIds($this->destType, $this->destTypeId)) + $this->generate404(); + + // hash required for crop & complete + if (!$this->imgHash) + $this->generate404(); + } + + protected function generate() : void + { + if ($this->handleComplete()) + $this->forward('?screenshot=thankyou&'.$this->destType.'.'.$this->destTypeId); + else + $this->generate404(); + } + + private function handleComplete() : bool + { + if (!$this->assertPOST('coords')) + return false; + + ScreenshotMgr::init(); + + if (!ScreenshotMgr::loadFile(ScreenshotMgr::PATH_TEMP, User::$username.'-'.$this->destType.'-'.$this->destTypeId.'-'.$this->imgHash.'_original')) + return false; + + ScreenshotMgr::cropImg(...$this->_post['coords']); + + ['oWidth' => $w, 'oHeight' => $h] = ScreenshotMgr::calcImgDimensions(); + + // write to db + $newId = DB::Aowow()->query( + 'INSERT INTO ?_screenshots (`type`, `typeId`, `userIdOwner`, `date`, `width`, `height`, `caption`, `status`) VALUES (?d, ?d, ?d, UNIX_TIMESTAMP(), ?d, ?d, ?, 0)', + $this->destType, $this->destTypeId, + User::$id, + $w, $h, + $this->handleCaption($this->_post['screenshotalt']) + ); + if (!is_int($newId)) // 0 is valid, NULL or FALSE is not + { + trigger_error('ScreenshotCompleteResponse - screenshot query failed', E_USER_ERROR); + return false; + } + + // write to file + return ScreenshotMgr::writeImage(ScreenshotMgr::PATH_PENDING, $newId); + } + + protected static function checkCoords(string $val) : ?array + { + if (preg_match('/^[01]\.[0-9]{3}(,[01]\.[0-9]{3}){3}$/', $val)) + return explode(',', $val); + + return null; + } +} + +?> diff --git a/endpoints/screenshot/crop.php b/endpoints/screenshot/crop.php new file mode 100644 index 00000000..cabae726 --- /dev/null +++ b/endpoints/screenshot/crop.php @@ -0,0 +1,92 @@ + 2. =crop: user edites upload + 2.1. just show edit page + 2.2. user submits coords and description to =complete + 3. =complete: store edited screenshot file and data + 4. =thankyou +*/ + +// filename: Username-type-typeId-[_original].jpg + +class ScreenshotCropResponse extends TemplateResponse +{ + protected bool $requiresLogin = true; + + protected string $template = 'screenshot'; + protected string $pageName = 'screenshot'; + + protected array $scripts = [[SC_JS_FILE, 'js/Cropper.js'], [SC_CSS_FILE, 'css/Cropper.css']]; + + public ?Markup $infobox = null; + public array $cropper = []; + public int $destType = 0; + public int $destTypeId = 0; + public string $imgHash = ''; + + public function __construct(string $pageParam) + { + parent::__construct($pageParam); + + // get screenshot destination + // target delivered as screenshot=&.. (hash is optional) + if (!preg_match('/^screenshot=\w+&(-?\d+)\.(-?\d+)(\.(\w{16}))?$/i', $_SERVER['QUERY_STRING'] ?? '', $m, PREG_UNMATCHED_AS_NULL)) + $this->generateError(); + + [, $this->destType, $this->destTypeId, , $this->imgHash] = $m; + + // no such type or this type cannot receive screenshots + if (!Type::checkClassAttrib($this->destType, 'contribute', CONTRIBUTE_SS)) + $this->generateError(); + + // no such typeId + if (!Type::validateIds($this->destType, $this->destTypeId)) + $this->generateError(); + + // hash required for crop & complete + if (!$this->imgHash) + $this->generateError(); + } + + protected function generate() : void + { + $this->h1 = Lang::screenshot('submission'); + $fileBase = User::$username.'-'.$this->destType.'-'.$this->destTypeId.'-'.$this->imgHash; + + array_unshift($this->title, $this->h1); + + ScreenshotMgr::init(); + + if (!ScreenshotMgr::loadFile(ScreenshotMgr::PATH_TEMP, $fileBase.'_original')) + { + $_SESSION['error']['ss'] = Lang::main('intError'); + $this->forward('?'.Type::getFileString($this->destType).'='.$this->destTypeId.'#submit-a-screenshot'); + } + + $dims = ScreenshotMgr::calcImgDimensions(); + + $this->cropper = $dims + array( + 'url' => Cfg::get('STATIC_URL').'/uploads/screenshots/temp/'.$fileBase.'.jpg', + 'parent' => 'ss-container', + 'minCrop' => ScreenshotMgr::$MIN_SIZE, // optional; defaults to 150 - min selection size (a square) + 'type' => $this->destType, // only used to check against NPC: 15384 [OLDWorld Trigger (DO NOT DELETE)] for U_GROUP_MODERATOR | U_GROUP_EDITOR. If successful drops minCrop constraint + 'typeId' => $this->destTypeId // i guess this was used to upload arbitrary imagery for articles, blog posts, etc + ); + + // target + $this->infobox = new Markup(Lang::screenshot('displayOn', [Lang::typeName($this->destType), Type::getFileString($this->destType), $this->destTypeId]), ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0'); + $this->extendGlobalIds($this->destType, $this->destTypeId); + + parent::generate(); + } +} + +?> diff --git a/endpoints/screenshot/thankyou.php b/endpoints/screenshot/thankyou.php new file mode 100644 index 00000000..53b62bfc --- /dev/null +++ b/endpoints/screenshot/thankyou.php @@ -0,0 +1,66 @@ + 4. =thankyou +*/ + +// filename: Username-type-typeId-[_original].jpg + +class ScreenshotThankyouResponse extends TemplateResponse +{ + protected bool $requiresLogin = true; + + protected string $template = 'text-page-generic'; + protected string $pageName = 'screenshot'; + + private int $destType = 0; + private int $destTypeId = 0; + + public function __construct(string $pageParam) + { + parent::__construct($pageParam); + + // get screenshot destination + // target delivered as screenshot=&.. (hash is optional) + if (!preg_match('/^screenshot=\w+&(-?\d+)\.(-?\d+)(\.(\w{16}))?$/i', $_SERVER['QUERY_STRING'] ?? '', $m, PREG_UNMATCHED_AS_NULL)) + $this->generateError(); + + [, $this->destType, $this->destTypeId, , $imgHash] = $m; + + // no such type or this type cannot receive screenshots + if (!Type::checkClassAttrib($this->destType, 'contribute', CONTRIBUTE_SS)) + $this->generateError(); + + // no such typeId + if (!Type::validateIds($this->destType, $this->destTypeId)) + $this->generateError(); + + // only accept/expect hash for crop & complete + if ($imgHash) + $this->generateError(); + } + + protected function generate() : void + { + $this->h1 = Lang::screenshot('submission'); + + array_unshift($this->title, $this->h1); + + $this->extraHTML = Lang::screenshot('thanks', 'contrib').'

'; + $this->extraHTML .= Lang::screenshot('thanks', 'goBack', [Type::getFileString($this->destType), $this->destTypeId])."

\n"; + $this->extraHTML .= ''.Lang::screenshot('thanks', 'note').''; + + parent::generate(); + } +} + +?> diff --git a/endpoints/top-users/top-users.php b/endpoints/top-users/top-users.php index 1de7c60c..b342baa1 100644 --- a/endpoints/top-users/top-users.php +++ b/endpoints/top-users/top-users.php @@ -56,7 +56,7 @@ class TopusersBaseResponse extends TemplateResponse GROUP BY a.`id` ORDER BY reputation DESC LIMIT ?d', - SITEREP_ACTION_COMMENT, SITEREP_ACTION_UPLOAD, SITEREP_ACTION_GOOD_REPORT, + SITEREP_ACTION_COMMENT, SITEREP_ACTION_SUBMIT_SCREENSHOT, SITEREP_ACTION_GOOD_REPORT, $time ?: DBSIMPLE_SKIP, Cfg::get('SQL_LIMIT_SEARCH') ); diff --git a/includes/ajaxHandler/admin.class.php b/includes/ajaxHandler/admin.class.php index 78af780d..899c607e 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 = ['screenshots', 'siteconfig', 'weight-presets', 'spawn-override', 'guide', 'comment']; + protected $validParams = ['siteconfig', 'weight-presets', 'spawn-override', 'guide', 'comment']; protected $_get = array( 'action' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkTextLine' ], 'id' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AjaxHandler::checkIdListUnsigned'], @@ -37,27 +37,7 @@ class AjaxAdmin extends AjaxHandler if (!$this->params) return; - if ($this->params[0] == 'screenshots' && $this->_get['action']) - { - if (!User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_SCREENSHOT)) - return; - - if ($this->_get['action'] == 'list') - $this->handler = 'ssList'; - else if ($this->_get['action'] == 'manage') - $this->handler = 'ssManage'; - else if ($this->_get['action'] == 'editalt') - $this->handler = 'ssEditAlt'; - else if ($this->_get['action'] == 'approve') - $this->handler = 'ssApprove'; - else if ($this->_get['action'] == 'sticky') - $this->handler = 'ssSticky'; - else if ($this->_get['action'] == 'delete') - $this->handler = 'ssDelete'; - else if ($this->_get['action'] == 'relocate') - $this->handler = 'ssRelocate'; - } - else if ($this->params[0] == 'siteconfig' && $this->_get['action']) + if ($this->params[0] == 'siteconfig' && $this->_get['action']) { if (!User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN)) return; @@ -100,227 +80,6 @@ class AjaxAdmin extends AjaxHandler } } - // get all => null (optional) - // evaled response .. UNK - protected function ssList() : string - { - // ssm_screenshotPages - // ssm_numPagesFound - - $pages = CommunityContent::getScreenshotPagesForManager($this->_get['all'], $nPages); - $buff = 'ssm_screenshotPages = '.Util::toJSON($pages).";\n"; - $buff .= 'ssm_numPagesFound = '.$nPages.';'; - - return $buff; - } - - // get: [type => type, typeId => typeId] || [user => username] - // evaled response .. UNK - protected function ssManage() : string - { - $res = []; - - if ($this->_get['type'] && $this->_get['type'] && $this->_get['typeid'] && $this->_get['typeid']) - $res = CommunityContent::getScreenshotsForManager($this->_get['type'], $this->_get['typeid']); - else if ($this->_get['user']) - if ($uId = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE LOWER(`username`) = LOWER(?)', $this->_get['user'])) - $res = CommunityContent::getScreenshotsForManager(0, 0, $uId); - - return 'ssm_screenshotData = '.Util::toJSON($res); - } - - // get: id => SSid - // resp: '' - protected function ssEditAlt() : void - { - // doesn't need to be htmlEscaped, ths javascript does that - if ($this->_get['id'] && $this->_post['alt'] !== null) - DB::Aowow()->query('UPDATE ?_screenshots SET caption = ? WHERE id = ?d', trim($this->_post['alt']), $this->_get['id'][0]); - } - - // get: id => comma-separated SSids - // resp: '' - protected function ssApprove() : void - { - if (!$this->reqGET('id')) - { - trigger_error('AjaxAdmin::ssApprove - screenshotId empty', E_USER_ERROR); - return; - } - - // create resized and thumb version of screenshot - $resized = [772, 618]; - $thumb = [150, 150]; - $path = 'static/uploads/screenshots/%s/%d.jpg'; - - foreach ($this->_get['id'] as $id) - { - // must not be already approved - if ($ssEntry = DB::Aowow()->selectRow('SELECT userIdOwner, date, type, typeId FROM ?_screenshots WHERE (status & ?d) = 0 AND id = ?d', CC_FLAG_APPROVED, $id)) - { - // should also error-log - if (!file_exists(sprintf($path, 'pending', $id))) - { - trigger_error('AjaxAdmin::ssApprove - screenshot #'.$id.' exists in db but not as file', E_USER_ERROR); - continue; - } - - $srcImg = imagecreatefromjpeg(sprintf($path, 'pending', $id)); - $srcW = imagesx($srcImg); - $srcH = imagesy($srcImg); - - // write thumb - $scale = min(1.0, min($thumb[0] / $srcW, $thumb[1] / $srcH)); - $destW = $srcW * $scale; - $destH = $srcH * $scale; - $destImg = imagecreatetruecolor($destW, $destH); - - imagefill($destImg, 0, 0, imagecolorallocate($destImg, 255, 255, 255)); - imagecopyresampled($destImg, $srcImg, 0, 0, 0, 0, $destW, $destH, $srcW, $srcH); - - imagejpeg($destImg, sprintf($path, 'thumb', $id), 100); - - // write resized (only if required) - if ($srcW > $resized[0] || $srcH > $resized[1]) - { - $scale = min(1.0, min($resized[0] / $srcW, $resized[1] / $srcH)); - $destW = $srcW * $scale; - $destH = $srcH * $scale; - $destImg = imagecreatetruecolor($destW, $destH); - - imagefill($destImg, 0, 0, imagecolorallocate($destImg, 255, 255, 255)); - imagecopyresampled($destImg, $srcImg, 0, 0, 0, 0, $destW, $destH, $srcW, $srcH); - - imagejpeg($destImg, sprintf($path, 'resized', $id), 100); - } - - imagedestroy($srcImg); - - // move screenshot from pending to normal - rename(sprintf($path, 'pending', $id), sprintf($path, 'normal', $id)); - - // set as approved in DB and gain rep (once!) - DB::Aowow()->query('UPDATE ?_screenshots SET status = ?d, userIdApprove = ?d WHERE id = ?d', CC_FLAG_APPROVED, User::$id, $id); - Util::gainSiteReputation($ssEntry['userIdOwner'], SITEREP_ACTION_UPLOAD, ['id' => $id, 'what' => 1, 'date' => $ssEntry['date']]); - // flag DB entry as having screenshots - if ($tbl = Type::getClassAttrib($ssEntry['type'], 'dataTable')) - DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags | ?d WHERE id = ?d', CUSTOM_HAS_SCREENSHOT, $ssEntry['typeId']); - } - else - trigger_error('AjaxAdmin::ssApprove - screenshot #'.$id.' not in db or already approved', E_USER_ERROR); - } - - return; - } - - // get: id => comma-separated SSids - // resp: '' - protected function ssSticky() : void - { - if (!$this->reqGET('id')) - { - trigger_error('AjaxAdmin::ssSticky - screenshotId empty', E_USER_ERROR); - return; - } - - // approve soon to be sticky screenshots - $this->ssApprove(); - - // this one is a bit strange: as far as i've seen, the only thing a 'sticky' screenshot does is show up in the infobox - // this also means, that only one screenshot per page should be sticky - // so, handle it one by one and the last one affecting one particular type/typId-key gets the cake - foreach ($this->_get['id'] as $id) - { - // reset all others - DB::Aowow()->query('UPDATE ?_screenshots a, ?_screenshots b SET a.status = a.status & ~?d WHERE a.type = b.type AND a.typeId = b.typeId AND a.id <> b.id AND b.id = ?d', CC_FLAG_STICKY, $id); - - // toggle sticky status - DB::Aowow()->query('UPDATE ?_screenshots SET `status` = IF(`status` & ?d, `status` & ~?d, `status` | ?d) WHERE id = ?d AND `status` & ?d', CC_FLAG_STICKY, CC_FLAG_STICKY, CC_FLAG_STICKY, $id, CC_FLAG_APPROVED); - } - } - - // get: id => comma-separated SSids - // resp: '' - // 2 steps: 1) remove from sight, 2) remove from disk - protected function ssDelete() : void - { - if (!$this->reqGET('id')) - { - trigger_error('AjaxAdmin::ssDelete - screenshotId empty', E_USER_ERROR); - return; - } - - $path = 'static/uploads/screenshots/%s/%d.jpg'; - - foreach ($this->_get['id'] as $id) - { - // irrevocably remove already deleted files - if (User::isInGroup(U_GROUP_ADMIN) && DB::Aowow()->selectCell('SELECT 1 FROM ?_screenshots WHERE status & ?d AND id = ?d', CC_FLAG_DELETED, $id)) - { - DB::Aowow()->query('DELETE FROM ?_screenshots WHERE id = ?d', $id); - if (file_exists(sprintf($path, 'pending', $id))) - unlink(sprintf($path, 'pending', $id)); - - continue; - } - - // move pending or normal to pending - if (file_exists(sprintf($path, 'normal', $id))) - rename(sprintf($path, 'normal', $id), sprintf($path, 'pending', $id)); - - // remove resized and thumb - if (file_exists(sprintf($path, 'thumb', $id))) - unlink(sprintf($path, 'thumb', $id)); - - if (file_exists(sprintf($path, 'resized', $id))) - unlink(sprintf($path, 'resized', $id)); - } - - // flag as deleted if not aready - $oldEntries = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, GROUP_CONCAT(typeId) FROM ?_screenshots WHERE id IN (?a) GROUP BY `type`', $this->_get['id']); - DB::Aowow()->query('UPDATE ?_screenshots SET status = ?d, userIdDelete = ?d WHERE id IN (?a)', CC_FLAG_DELETED, User::$id, $this->_get['id']); - // deflag db entry as having screenshots - foreach ($oldEntries as $type => $typeIds) - { - $typeIds = explode(',', $typeIds); - $toUnflag = DB::Aowow()->selectCol('SELECT typeId AS ARRAY_KEY, IF(BIT_OR(`status`) & ?d, 1, 0) AS hasMore FROM ?_screenshots WHERE `type` = ?d AND typeId IN (?a) GROUP BY typeId HAVING hasMore = 0', CC_FLAG_APPROVED, $type, $typeIds); - if ($toUnflag && ($tbl = Type::getClassAttrib($type, 'dataTable'))) - DB::Aowow()->query('UPDATE '.$tbl.' SET cuFlags = cuFlags & ~?d WHERE id IN (?a)', CUSTOM_HAS_SCREENSHOT, array_keys($toUnflag)); - } - } - - // get: id => ssId, typeid => typeId (but not type..?) - // resp: '' - protected function ssRelocate() : void - { - if (!$this->reqGET('id', 'typeid')) - { - trigger_error('AjaxAdmin::ssRelocate - screenshotId or typeId empty', E_USER_ERROR); - return; - } - - $id = $this->_get['id'][0]; - [$type, $oldTypeId] = array_values(DB::Aowow()->selectRow('SELECT type, typeId FROM ?_screenshots WHERE id = ?d', $id)); - $typeId = (int)$this->_get['typeid']; - - $tc = Type::newList($type, [['id', $typeId]]); - if ($tc && !$tc->error) - { - // move screenshot - DB::Aowow()->query('UPDATE ?_screenshots SET typeId = ?d WHERE id = ?d', $typeId, $id); - - // flag target as having screenshot - DB::Aowow()->query('UPDATE '.$tc::$dataTable.' SET cuFlags = cuFlags | ?d WHERE id = ?d', CUSTOM_HAS_SCREENSHOT, $typeId); - - // deflag source for having had screenshots (maybe) - $ssInfo = DB::Aowow()->selectRow('SELECT IF(BIT_OR(~status) & ?d, 1, 0) AS hasMore FROM ?_screenshots WHERE `status`& ?d AND `type` = ?d AND typeId = ?d', CC_FLAG_DELETED, CC_FLAG_APPROVED, $type, $oldTypeId); - if($ssInfo || !$ssInfo['hasMore']) - DB::Aowow()->query('UPDATE '.$tc::$dataTable.' SET cuFlags = cuFlags & ~?d WHERE id = ?d', CUSTOM_HAS_SCREENSHOT, $oldTypeId); - } - else - trigger_error('AjaxAdmin::ssRelocate - invalid typeId #'.$typeId.' for type #'.$type, E_USER_ERROR); - } - protected function confAdd() : string { $key = trim($this->_get['key']); diff --git a/includes/cfg.class.php b/includes/cfg.class.php index ce5c8ff5..2ddffd10 100644 --- a/includes/cfg.class.php +++ b/includes/cfg.class.php @@ -206,7 +206,6 @@ class Cfg return 'empty value given for required config'; DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $value, $key); - self::$store[$key][self::IDX_VALUE] = $value; // validate change @@ -251,6 +250,7 @@ class Cfg return 'configuration option not found'; [$oldValue, $flags, , $default, ] = self::$store[$key]; + if ($flags & self::FLAG_INTERNAL) return 'can\'t set an internal option directly'; @@ -446,6 +446,17 @@ class Cfg { return (($_SERVER['HTTPS'] ?? 'off') != 'off') || (self::$store['force_ssl'][self::IDX_VALUE] ?? 0); } + + private static function screenshot_min_size(int|string $value, ?string &$msg = '') : bool + { + if ($value < 200) + { + $msg .= 'Value must be at least 200 (px).'; + return false; + } + + return true; + } } ?> diff --git a/includes/components/communitycontent.class.php b/includes/components/communitycontent.class.php index 12e37385..8b04d7d5 100644 --- a/includes/components/communitycontent.class.php +++ b/includes/components/communitycontent.class.php @@ -236,129 +236,6 @@ class CommunityContent return $replies; } - public static function getScreenshotsForManager($type, $typeId, $userId = 0) - { - $screenshots = DB::Aowow()->select( - 'SELECT s.`id`, a.`username` AS "user", s.`date`, s.`width`, s.`height`, s.`type`, s.`typeId`, s.`caption`, s.`status`, s.`status` AS "flags" - FROM ?_screenshots s - LEFT JOIN ?_account a ON s.`userIdOwner` = a.`id` - WHERE - { s.`type` = ?d} - { AND s.`typeId` = ?d} - { s.`userIdOwner` = ?d} - LIMIT 100', - $userId ? DBSIMPLE_SKIP : $type, - $userId ? DBSIMPLE_SKIP : $typeId, - $userId ? $userId : DBSIMPLE_SKIP - ); - - $num = []; - foreach ($screenshots as $s) - { - if (empty($num[$s['type']][$s['typeId']])) - $num[$s['type']][$s['typeId']] = 1; - else - $num[$s['type']][$s['typeId']]++; - } - - // format data to meet requirements of the js - foreach ($screenshots as $idx => &$s) - { - $s['date'] = date(Util::$dateFormatInternal, $s['date']); - - $s['name'] = "Screenshot #".$s['id']; // what should we REALLY name it? - - if (isset($screenshots[$idx - 1])) - $s['prev'] = $idx - 1; - - if (isset($screenshots[$idx + 1])) - $s['next'] = $idx + 1; - - // order gives priority for 'status' - if (!($s['flags'] & CC_FLAG_APPROVED)) - { - $s['pending'] = 1; - $s['status'] = 0; - } - else - $s['status'] = 100; - - if ($s['flags'] & CC_FLAG_STICKY) - { - $s['sticky'] = 1; - $s['status'] = 105; - } - - if ($s['flags'] & CC_FLAG_DELETED) - { - $s['deleted'] = 1; - $s['status'] = 999; - } - - // something todo with massSelect .. am i doing this right? - if ($num[$s['type']][$s['typeId']] == 1) - $s['unique'] = 1; - - if (!$s['user']) - unset($s['user']); - } - - return $screenshots; - } - - public static function getScreenshotPagesForManager($all, &$nFound) - { - // i GUESS .. ss_getALL ? everything : pending - $nFound = 0; - $pages = DB::Aowow()->select( - 'SELECT s.`type`, s.`typeId`, COUNT(1) AS "count", MIN(s.`date`) AS "date" - FROM ?_screenshots s - { WHERE (s.`status` & ?d) = 0 } - GROUP BY s.`type`, s.`typeId`', - $all ? DBSIMPLE_SKIP : CC_FLAG_APPROVED | CC_FLAG_DELETED - ); - - if ($pages) - { - // limit to one actually existing type each - foreach (array_unique(array_column($pages, 'type')) as $t) - { - $ids = []; - foreach ($pages as $row) - if ($row['type'] == $t) - $ids[] = $row['typeId']; - - if (!$ids) - continue; - - $obj = Type::newList($t, [Cfg::get('SQL_LIMIT_NONE'), ['id', $ids]]); - if (!$obj || $obj->error) - continue; - - foreach ($pages as &$p) - if ($p['type'] == $t) - if ($obj->getEntry($p['typeId'])) - $p['name'] = $obj->getField('name', true); - } - - foreach ($pages as &$p) - { - if (empty($p['name'])) - { - trigger_error('Screenshot linked to nonexistent type/typeId combination: '.$p['type'].'/'.$p['typeId'], E_USER_NOTICE); - unset($p); - } - else - { - $nFound += $p['count']; - $p['date'] = date(Util::$dateFormatInternal, $p['date']); - } - } - } - - return $pages; - } - public static function getComments(int $type, int $typeId) : array { diff --git a/includes/components/imageupload.class.php b/includes/components/imageupload.class.php new file mode 100644 index 00000000..e8fa79a5 --- /dev/null +++ b/includes/components/imageupload.class.php @@ -0,0 +1,306 @@ + self::loadFromJPG(), + self::MIME_PNG => self::loadFromPNG(), + self::MIME_WEBP => self::loadFromWEBP(), + default => false + }; + } + + public static function loadFile(string $path, string $nameBase) : bool + { + self::$fileName = sprintf($path, $nameBase); + + if (!file_exists(self::$fileName)) + { + trigger_error('ImageUpload::loadFile - image ('.self::$fileName.') not found', E_USER_ERROR); + self::$fileName = ''; + return false; + } + + // we are using only jpg internally + return self::loadFromJPG(); + } + + public static function calcImgDimensions() : array + { + if (!self::$img) + return []; + + $oSize = $rSize = [imagesx(self::$img), imagesy(self::$img)]; + $rel = $oSize[0] / $oSize[1]; + + // check for oversize and refit to crop-screen + if ($rel >= 1.5 && $oSize[0] > self::CROP_W) + $rSize = [self::CROP_W, self::CROP_W / $rel]; + else if ($rel < 1.5 && $oSize[1] > self::CROP_H) + $rSize = [self::CROP_H * $rel, self::CROP_H]; + + // r: resized; o: original + // r: x <= 488 && y <= 325 while x proportional to y + return array( + 'oWidth' => $oSize[0], + 'rWidth' => $rSize[0], + 'oHeight' => $oSize[1], + 'rHeight' => $rSize[1] + ); + } + + public static function tempSaveUpload(array $tmpNameParts, ?string &$uid) : bool + { + if (!self::$img || !$tmpNameParts) + return false; + + $uid = Util::createHash(16); + + $nameBase = User::$username.'-'.implode('-', $tmpNameParts).'-'.$uid; + + // use this image for work + if (!self::writeImage(static::$tmpPath, $nameBase.'_original')) + return false; + + ['oWidth' => $oW, 'rWidth' => $rW, 'oHeight' => $oH, 'rHeight' => $rH] = self::calcImgDimensions(); + + // use this image to display in cropper + $res = imagecreatetruecolor($rW, $rH); + if (!$res) + { + trigger_error('ImageUpload::tempSaveUpload - imagecreate failed', E_USER_ERROR); + return false; + } + + if (!imagecopyresampled($res, self::$img, 0, 0, 0, 0, $rW, $rH, $oW, $oH)) + { + trigger_error('ImageUpload::tempSaveUpload - imagecopy failed', E_USER_ERROR); + return false; + } + + self::$img = $res; + unset($res); + + return self::writeImage(static::$tmpPath, $nameBase); + } + + public static function cropImg(float $scaleX, float $scaleY, float $scaleW, float $scaleH) : bool + { + if (!self::$img) + return false; + + $x = (int)(imagesx(self::$img) * $scaleX); + $y = (int)(imagesy(self::$img) * $scaleY); + $w = (int)(imagesx(self::$img) * $scaleW); + $h = (int)(imagesy(self::$img) * $scaleH); + + $destImg = imagecreatetruecolor($w, $h); + if (!$destImg) + return false; + + // imagefill($destImg, 0, 0, imagecolorallocate($destImg, 255, 255, 255)); + imagecopy($destImg, self::$img, 0, 0, $x, $y, $w, $h); + + self::$img = $destImg; + imagedestroy($destImg); + + return true; + } + + public static function writeImage(string $path, string $file) : bool + { + if (!self::$img) + return false; + + if (imagejpeg(self::$img, sprintf($path, $file), self::JPEG_QUALITY)) + return true; + + trigger_error('ImageUpload::writeImage - write failed', E_USER_ERROR); + return false; + } + + private static function setMimeType() : bool + { + if (!self::$hasUpload) + return false; + + $mime = (new \finfo(FILEINFO_MIME))?->file(self::$fileName); + + if ($mime && stripos($mime, 'image/png') === 0) + self::$mimeType = self::MIME_PNG; + else if ($mime && stripos($mime, 'image/webp') === 0) + self::$mimeType = self::MIME_WEBP; + else if ($mime && preg_match('/^image\/jpe?g/i', $mime)) + self::$mimeType = self::MIME_JPG; + else + trigger_error('ImageUpload::setMimeType - uploaded file is of type: '.$mime, E_USER_WARNING); + + return self::$mimeType != self::MIME_UNK; + } + + private static function loadFromPNG() : bool + { + // straight self::$img = imagecreatefrompng(self::$fileName); causes issues when transforming the alpha channel + // this roundabout way through imagealphablending() avoids that + $image = imagecreatefrompng(self::$fileName); + if (!$image) + return false; + + self::$img = imagecreatetruecolor(imagesx($image), imagesy($image)) ?: null; + if (!self::$img) + return false; + + imagealphablending(self::$img, true); + imagecopy(self::$img, $image, 0, 0, 0, 0, imagesx($image), imagesy($image)); + imagedestroy($image); + + return true; + } + + private static function loadFromJPG() : bool + { + self::$img = imagecreatefromjpeg(self::$fileName) ?: null; + + return !is_null(self::$img); + } + + private static function loadFromWEBP() : bool + { + $image = imagecreatefromwebp(self::$fileName); + if (!$image) + return false; + + self::$img = imagecreatetruecolor(imagesx($image), imagesy($image)) ?: null; + if (!self::$img) + return false; + + imagealphablending(self::$img, true); + imagecopy(self::$img, $image, 0, 0, 0, 0, imagesx($image), imagesy($image)); + imagedestroy($image); + + return true; + } + + protected static function resizeAndWrite(int $limitW, int $limitH, string $path, string $file) : bool + { + $srcW = imagesx(self::$img); + $srcH = imagesy(self::$img); + + // already small enough + if ($srcW < $limitW && $srcH < $limitH) + return true; + + $scale = min(1.0, $limitW / $srcW, $limitH / $srcH); + $destW = $srcW * $scale; + $destH = $srcH * $scale; + + $destImg = imagecreatetruecolor($destW, $destH); + + // imagefill($destImg, 0, 0, imagecolorallocate($destImg, 255, 255, 255)); + imagecopyresampled($destImg, self::$img, 0, 0, 0, 0, $destW, $destH, $srcW, $srcH); + + return imagejpeg($destImg, sprintf($path, $file), self::JPEG_QUALITY); + } +} + +?> diff --git a/includes/components/response/textresponse.class.php b/includes/components/response/textresponse.class.php index caa5acb7..aef1843d 100644 --- a/includes/components/response/textresponse.class.php +++ b/includes/components/response/textresponse.class.php @@ -86,10 +86,7 @@ trait TrCommunityHelper $caption = trim(preg_replace('/\s{2,}/', ' ', $caption)); // shorten to fit db - $caption = substr($caption, 0, 200); - - // jsEscape just in case - return Util::jsEscape($caption); + return substr($caption, 0, 200); } } diff --git a/includes/components/screenshotmgr.class.php b/includes/components/screenshotmgr.class.php new file mode 100644 index 00000000..5a5ff4a1 --- /dev/null +++ b/includes/components/screenshotmgr.class.php @@ -0,0 +1,230 @@ + self::MAX_W || $is[1] > self::MAX_H) + self::$error = Lang::screenshot('error', 'selectSS'); + } + else + self::$error = Lang::screenshot('error', 'selectSS'); + + if (!self::$error) + return true; + + self::$fileName = ''; + return false; + } + + public static function createThumbnail(string $fileName) : bool + { + if (!self::$img) + return false; + + return static::resizeAndWrite(self::DIMS_THUMB[0], self::DIMS_THUMB[1], self::PATH_THUMB, $fileName); + } + + public static function createResized(string $fileName) : bool + { + if (!self::$img) + return false; + + return self::resizeAndWrite(self::DIMS_RESIZED[0], self::DIMS_RESIZED[1], self::PATH_RESIZED, $fileName); + } + + + /*************/ + /* Admin Mgr */ + /*************/ + + public static function getScreenshots(int $type = 0, int $typeId = 0, $userId = 0, ?int &$nFound = 0) : array + { + $screenshots = DB::Aowow()->select( + 'SELECT s.`id`, a.`username` AS "user", s.`date`, s.`width`, s.`height`, s.`type`, s.`typeId`, s.`caption`, s.`status`, s.`status` AS "flags" + FROM ?_screenshots s + LEFT JOIN ?_account a ON s.`userIdOwner` = a.`id` + WHERE + { s.`type` = ?d } + { AND s.`typeId` = ?d } + { s.`userIdOwner` = ?d } + { LIMIT ?d }', + $userId ? DBSIMPLE_SKIP : $type, + $userId ? DBSIMPLE_SKIP : $typeId, + $userId ? $userId : DBSIMPLE_SKIP, + $userId || $type ? DBSIMPLE_SKIP : 100 + ); + + $num = []; + foreach ($screenshots as $s) + { + if (empty($num[$s['type']][$s['typeId']])) + $num[$s['type']][$s['typeId']] = 1; + else + $num[$s['type']][$s['typeId']]++; + } + + $nFound = 0; + + // format data to meet requirements of the js + foreach ($screenshots as $i => &$s) + { + $nFound++; + + $s['date'] = date(Util::$dateFormatInternal, $s['date']); + + $s['name'] = "Screenshot #".$s['id']; // what should we REALLY name it? + + if ($i > 0) + $s['prev'] = $i - 1; + + if (($i + 1) < count($screenshots)) + $s['next'] = $i + 1; + + // order gives priority for 'status' + if (!($s['flags'] & CC_FLAG_APPROVED)) + { + $s['pending'] = 1; + $s['status'] = self::STATUS_PENDING; + } + else + $s['status'] = self::STATUS_APPROVED; + + if ($s['flags'] & CC_FLAG_STICKY) + { + $s['sticky'] = 1; + $s['status'] = self::STATUS_STICKY; + } + + if ($s['flags'] & CC_FLAG_DELETED) + { + $s['deleted'] = 1; + $s['status'] = self::STATUS_DELETED; + } + + // something todo with massSelect .. am i doing this right? + if ($num[$s['type']][$s['typeId']] == 1) + $s['unique'] = 1; + + if (!$s['user']) + unset($s['user']); + } + + return $screenshots; + } + + public static function getPages(?bool $all, ?int &$nFound) : array + { + // i GUESS .. ss_getALL ? everything : pending + $nFound = 0; + $pages = DB::Aowow()->select( + 'SELECT s.`type`, s.`typeId`, COUNT(1) AS "count", MIN(s.`date`) AS "date" + FROM ?_screenshots s + { WHERE (s.`status` & ?d) = 0 } + GROUP BY s.`type`, s.`typeId`', + $all ? DBSIMPLE_SKIP : CC_FLAG_APPROVED | CC_FLAG_DELETED + ); + + if ($pages) + { + // limit to one actually existing type each + foreach (array_unique(array_column($pages, 'type')) as $t) + { + $ids = []; + foreach ($pages as $row) + if ($row['type'] == $t) + $ids[] = $row['typeId']; + + if (!$ids) + continue; + + $obj = Type::newList($t, [Cfg::get('SQL_LIMIT_NONE'), ['id', $ids]]); + if (!$obj || $obj->error) + continue; + + foreach ($pages as &$p) + if ($p['type'] == $t) + if ($obj->getEntry($p['typeId'])) + $p['name'] = $obj->getField('name', true); + } + + foreach ($pages as &$p) + { + if (empty($p['name'])) + { + trigger_error('ScreenshotMgr::getPages - screenshot linked to nonexistent type/typeId combination: '.$p['type'].'/'.$p['typeId'], E_USER_NOTICE); + unset($p); + } + else + { + $nFound += $p['count']; + $p['date'] = date(Util::$dateFormatInternal, $p['date']); + } + } + } + + return $pages; + } +} + +?> diff --git a/includes/defines.php b/includes/defines.php index d0c124cf..49e18275 100644 --- a/includes/defines.php +++ b/includes/defines.php @@ -90,7 +90,7 @@ define('SITEREP_ACTION_DAILYVISIT', 2); // Daily visit define('SITEREP_ACTION_COMMENT', 3); // Posted comment define('SITEREP_ACTION_UPVOTED', 4); // Your comment was upvoted define('SITEREP_ACTION_DOWNVOTED', 5); // Your comment was downvoted -define('SITEREP_ACTION_UPLOAD', 6); // Submitted screenshot (suggested video) +define('SITEREP_ACTION_SUBMIT_SCREENSHOT', 6); // Submitted screenshot (suggested video) // Cast vote // Uploaded data define('SITEREP_ACTION_GOOD_REPORT', 9); // Report accepted diff --git a/includes/utilities.php b/includes/utilities.php index abb5bd21..5ead86dc 100644 --- a/includes/utilities.php +++ b/includes/utilities.php @@ -644,7 +644,8 @@ abstract class Util return $success; } - public static function createHash($length = 40) // just some random numbers for unsafe identification purpose + // just some random numbers for unsafe identification purpose + public static function createHash(int $length = 40) : string { static $seed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; $hash = ''; @@ -725,12 +726,12 @@ abstract class Util $x['sourceB'] = $miscData['voterId']; $x['amount'] = $action == SITEREP_ACTION_UPVOTED ? Cfg::get('REP_REWARD_UPVOTED') : Cfg::get('REP_REWARD_DOWNVOTED'); break; - case SITEREP_ACTION_UPLOAD: + case SITEREP_ACTION_SUBMIT_SCREENSHOT: if (empty($miscData['id']) || empty($miscData['what'])) return false; $x['sourceA'] = $miscData['id']; // screenshotId or videoId - $x['sourceB'] = $miscData['what']; // screenshot:1 or video:NYD + $x['sourceB'] = $miscData['what']; // screenshot:1 $x['amount'] = Cfg::get('REP_REWARD_UPLOAD'); break; case SITEREP_ACTION_GOOD_REPORT: // NYI diff --git a/pages/admin.php b/pages/admin.php index 412c5805..a69209a0 100644 --- a/pages/admin.php +++ b/pages/admin.php @@ -35,14 +35,6 @@ class AdminPage extends GenericPage { switch ($pageParam) { - case 'screenshots': - $this->reqUGroup = U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_SCREENSHOT; - $this->generator = 'handleScreenshots'; - $this->tpl = 'admin/screenshots'; - - array_push($this->path, 1, 5); - $this->name = 'Screenshot Manager'; - break; case 'phpinfo': $this->reqUGroup = U_GROUP_ADMIN | U_GROUP_DEV; $this->generator = 'handlePhpInfo'; @@ -202,44 +194,6 @@ class AdminPage extends GenericPage } } - private function handleScreenshots() : void - { - $this->addScript( - [SC_JS_FILE, 'js/screenshot.js'], - [SC_CSS_STRING, '.layout {margin: 0px 25px; max-width: inherit; min-width: 1200px; }'], - [SC_CSS_STRING, '#highlightedRow { background-color: #322C1C; }'] - ); - - $ssGetAll = $this->_get['all']; - $ssPages = []; - $ssData = []; - $nMatches = 0; - - if ($this->_get['type'] && $this->_get['typeid']) - { - $ssData = CommunityContent::getScreenshotsForManager($this->_get['type'], $this->_get['typeid']); - $nMatches = count($ssData); - } - else if ($this->_get['user']) - { - if (mb_strlen($this->_get['user']) >= 3) - { - if ($uId = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE LOWER(`username`) = LOWER(?)', $this->_get['user'])) - { - $ssData = CommunityContent::getScreenshotsForManager(0, 0, $uId); - $nMatches = count($ssData); - } - } - } - else - $ssPages = CommunityContent::getScreenshotPagesForManager($ssGetAll, $nMatches); - - $this->getAll = $ssGetAll; - $this->ssPages = $ssPages; - $this->ssData = $ssData; - $this->ssNFound = $nMatches; // ssm_numPagesFound - } - private function handleWeightPresets() : void { $this->addScript( diff --git a/pages/screenshot.php b/pages/screenshot.php deleted file mode 100644 index 725ef31d..00000000 --- a/pages/screenshot.php +++ /dev/null @@ -1,396 +0,0 @@ -[_original].jpg - -class ScreenshotPage extends GenericPage -{ - const MAX_W = 488; - const MAX_H = 325; - - protected $infobox = []; - protected $cropper = []; - protected $extraHTML = null; - - protected $tpl = 'screenshot'; - protected $scripts = [[SC_JS_FILE, 'js/Cropper.js'], [SC_CSS_FILE, 'css/Cropper.css']]; - protected $reqAuth = true; - protected $tabId = 0; - - private $tmpPath = 'static/uploads/temp/'; - private $pendingPath = 'static/uploads/screenshots/pending/'; - private $destination = null; - private $minSize = 200; - private $command = ''; - - protected $validCats = ['add', 'crop', 'complete', 'thankyou']; - protected $destType = 0; - protected $destTypeId = 0; - protected $imgHash = ''; - - protected $_post = array( - 'coords' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\ScreenshotPage::checkCoords'], - 'screenshotalt' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkTextBlob'] - ); - - public function __construct($pageCall, $pageParam) - { - parent::__construct($pageCall, $pageParam); - - $this->name = Lang::screenshot('submission'); - $this->command = $pageParam; - - if ($ms = Cfg::get('SCREENSHOT_MIN_SIZE')) - $this->minSize = abs($ms); - else - trigger_error('config error: Invalid value for minimum screenshot dimensions. Value forced to '.$this->minSize, E_USER_WARNING); - - // get screenshot destination - // target delivered as screenshot=&.. (hash is optional) - if (preg_match('/^screenshot=\w+&(-?\d+)\.(-?\d+)(\.(\w{16}))?$/i', $_SERVER['QUERY_STRING'] ?? '', $m)) - { - // no such type or this type cannot receive screenshots - if (!Type::checkClassAttrib($m[1], 'contribute', CONTRIBUTE_SS)) - $this->error(); - - $this->destination = Type::newList($m[1], [['id', intVal($m[2])]]); - - // no such typeId - if (!$this->destination || $this->destination->error) - $this->error(); - - // only accept/expect hash for crop & complete - if (empty($m[4]) && ($this->command == 'crop' || $this->command == 'complete')) - $this->error(); - else if (!empty($m[4]) && ($this->command == 'add' || $this->command == 'thankyou')) - $this->error(); - else if (!empty($m[4])) - $this->imgHash = $m[4]; - - $this->destType = intVal($m[1]); - $this->destTypeId = intVal($m[2]); - } - else - $this->error(); - } - - protected function generateContent() : void - { - switch ($this->command) - { - case 'add': - if ($this->handleAdd()) - header('Location: ?screenshot=crop&'.$this->destType.'.'.$this->destTypeId.'.'.$this->imgHash, true, 302); - else - header('Location: ?'.Type::getFileString($this->destType).'='.$this->destTypeId.'#submit-a-screenshot', true, 302); - die(); - case 'crop': - if (!$this->handleCrop()) - header('Location: ?'.Type::getFileString($this->destType).'='.$this->destTypeId.'#submit-a-screenshot', true, 302); - break; - case 'complete': - if ($_ = $this->handleComplete()) - $this->notFound(Lang::main('nfPageTitle'), sprintf(Lang::main('intError2'), '#'.$_)); - else - header('Location: ?screenshot=thankyou&'.$this->destType.'.'.$this->destTypeId, true, 302); - die(); - case 'thankyou': - $this->tpl = 'list-page-generic'; - $this->handleThankyou(); - break; - } - } - - - /*******************/ - /* command handler */ - /*******************/ - - private function handleAdd() : bool - { - $this->imgHash = Util::createHash(16); - - if (!is_writable($this->tmpPath)) - { - trigger_error('ScreenshotPage::handleAdd - temp upload directory not writable', E_USER_ERROR); - $_SESSION['error']['ss'] = Lang::main('intError'); - return false; - } - - if (!User::canUploadScreenshot()) - { - $_SESSION['error']['ss'] = Lang::screenshot('error', 'notAllowed'); - return false; - } - - if ($_ = $this->validateScreenshot($isPNG)) - { - $_SESSION['error']['ss'] = $_; - return false; - } - - $im = $isPNG ? $this->loadFromPNG() : $this->loadFromJPG(); - if (!$im) - { - $_SESSION['error']['ss'] = Lang::main('intError'); - return false; - } - - $oSize = $rSize = [imagesx($im), imagesy($im)]; - $rel = $oSize[0] / $oSize[1]; - - // check for oversize and refit to crop-screen - if ($rel >= 1.5 && $oSize[0] > self::MAX_W) - $rSize = [self::MAX_W, self::MAX_W / $rel]; - else if ($rel < 1.5 && $oSize[1] > self::MAX_H) - $rSize = [self::MAX_H * $rel, self::MAX_H]; - - $success = true; - - // use this image for work - if (!$this->writeImage($im, $oSize[0], $oSize[1], $this->ssName().'_original')) - $success = false; - - // use this image to display - if (!$this->writeImage($im, $rSize[0], $rSize[1], $this->ssName())) - $success = false; - - return $success; - } - - private function handleCrop() : bool - { - $tmpFile = $this->tmpPath.$this->ssName().'_original.jpg'; - - if (!file_exists($tmpFile)) - { - trigger_error('ScreenshotPage::handleCrop - temp image ('.$tmpFile.') not found', E_USER_ERROR); - $_SESSION['error']['ss'] = Lang::main('intError'); - return false; - } - - $im = imagecreatefromjpeg($tmpFile); - if (!$im) - { - trigger_error('ScreenshotPage::handleCrop - imagecreate failed ('.$tmpFile.')', E_USER_ERROR); - $_SESSION['error']['ss'] = Lang::main('intError'); - return false; - } - - $oSize = $rSize = [imagesx($im), imagesy($im)]; - $rel = $oSize[0] / $oSize[1]; - - // check for oversize and refit to crop-screen - if ($rel >= 1.5 && $oSize[0] > self::MAX_W) - $rSize = [self::MAX_W, self::MAX_W / $rel]; - else if ($rel < 1.5 && $oSize[1] > self::MAX_H) - $rSize = [self::MAX_H * $rel, self::MAX_H]; - - // r: resized; o: original - // r: x <= 488 && y <= 325 while x proportional to y - // mincrop is optional and specifies the minimum resulting image size - $this->cropper = [ - 'url' => Cfg::get('STATIC_URL').'/uploads/temp/'.$this->ssName().'.jpg', - 'parent' => 'ss-container', - 'oWidth' => $oSize[0], - 'rWidth' => $rSize[0], - 'oHeight' => $oSize[1], - 'rHeight' => $rSize[1], - 'type' => $this->destType, // only used to check against NPC: 15384 [OLDWorld Trigger (DO NOT DELETE)] - 'typeId' => $this->destTypeId // i guess this was used to upload arbitrary imagery - ]; - - // minimum dimensions - if (!User::isInGroup(U_GROUP_STAFF)) - $this->cropper['minCrop'] = $this->minSize; - - // target - $this->infobox = sprintf(Lang::screenshot('displayOn'), Lang::typeName($this->destType), Type::getFileString($this->destType), $this->destTypeId); - $this->extendGlobalIds($this->destType, $this->destTypeId); - - return true; - } - - private function handleComplete() : int - { - // check tmp file - $fullPath = $this->tmpPath.$this->ssName().'_original.jpg'; - if (!file_exists($fullPath)) - return 1; - - // check post data - if (!$this->_post['coords']) - return 2; - - $dims = $this->_post['coords']; - if (count($dims) != 4) - return 3; - - Util::checkNumeric($dims, NUM_CAST_FLOAT); - - // actually crop the image - $srcImg = imagecreatefromjpeg($fullPath); - - $x = (int)(imagesx($srcImg) * $dims[0]); - $y = (int)(imagesy($srcImg) * $dims[1]); - $w = (int)(imagesx($srcImg) * $dims[2]); - $h = (int)(imagesy($srcImg) * $dims[3]); - - $destImg = imagecreatetruecolor($w, $h); - - imagefill($destImg, 0, 0, imagecolorallocate($destImg, 255, 255, 255)); - imagecopy($destImg, $srcImg, 0, 0, $x, $y, $w, $h); - imagedestroy($srcImg); - - // write to db - $newId = DB::Aowow()->query( - 'INSERT INTO ?_screenshots (`type`, `typeId`, `userIdOwner`, `date`, `width`, `height`, `caption`, `status`) VALUES (?d, ?d, ?d, UNIX_TIMESTAMP(), ?d, ?d, ?, 0)', - $this->destType, $this->destTypeId, - User::$id, - $w, $h, - $this->_post['screenshotalt'] ?? '' - ); - - // write to file - if (is_int($newId)) // 0 is valid, NULL or FALSE is not - imagejpeg($destImg, $this->pendingPath.$newId.'.jpg', 100); - else - return 6; - - return 0; - } - - private function handleThankyou() : void - { - $this->extraHTML = Lang::screenshot('thanks', 'contrib').'

'; - $this->extraHTML .= sprintf(Lang::screenshot('thanks', 'goBack'), Type::getFileString($this->destType), $this->destTypeId)."

\n"; - $this->extraHTML .= ''.Lang::screenshot('thanks', 'note').''; - } - - - /**********/ - /* helper */ - /**********/ - - private function loadFromPNG() // : resource/gd - { - $image = imagecreatefrompng($_FILES['screenshotfile']['tmp_name']); - $bg = imagecreatetruecolor(imagesx($image), imagesy($image)); - - imagefill($bg, 0, 0, imagecolorallocate($bg, 255, 255, 255)); - imagealphablending($bg, true); - imagecopy($bg, $image, 0, 0, 0, 0, imagesx($image), imagesy($image)); - imagedestroy($image); - - return $bg; - } - - private function loadFromJPG() // : resource/gd - { - return imagecreatefromjpeg($_FILES['screenshotfile']['tmp_name']); - } - - private function writeImage(/*resource/gd*/ $im, int $w, int $h, string $file) : bool - { - if ($res = imagecreatetruecolor($w, $h)) - { - if (imagecopyresampled($res, $im, 0, 0, 0, 0, $w, $h, imagesx($im), imagesy($im))) - { - if (imagejpeg($res, $this->tmpPath.$file.'.jpg', 100)) - return true; - else - trigger_error('ScreenshotPage::writeImage - write failed', E_USER_ERROR); - } - else - trigger_error('ScreenshotPage::writeImage - imagecopy failed', E_USER_ERROR); - } - else - trigger_error('ScreenshotPage::writeImage - imagecreate failed', E_USER_ERROR); - - return false; - } - - private function validateScreenshot(?bool &$isPNG = false) : string - { - // no upload happened or some error occured - if (!$_FILES || empty($_FILES['screenshotfile'])) - return Lang::screenshot('error', 'selectSS'); - - switch ($_FILES['screenshotfile']['error']) // 0 is fine - { - case UPLOAD_ERR_INI_SIZE: // 1 - case UPLOAD_ERR_FORM_SIZE: // 2 - trigger_error('validateScreenshot - the file exceeds the maximum size of '.ini_get('upload_max_filesize'), E_USER_WARNING); - return Lang::screenshot('error', 'selectSS'); - case UPLOAD_ERR_PARTIAL: // 3 - trigger_error('validateScreenshot - upload was interrupted', E_USER_WARNING); - return Lang::screenshot('error', 'selectSS'); - case UPLOAD_ERR_NO_FILE: // 4 - trigger_error('validateScreenshot - no file was received', E_USER_WARNING); - return Lang::screenshot('error', 'selectSS'); - case UPLOAD_ERR_NO_TMP_DIR: // 6 - trigger_error('validateScreenshot - temporary upload directory is not set', E_USER_ERROR); - return Lang::main('intError'); - case UPLOAD_ERR_CANT_WRITE: // 7 - trigger_error('validateScreenshot - could not write temporary file to disk', E_USER_ERROR); - return Lang::main('intError'); - case UPLOAD_ERR_EXTENSION: // 8 - trigger_error('validateScreenshot - a php extension stopped the file upload.', E_USER_ERROR); - return Lang::main('intError'); - } - - // points to invalid file (hack attempt) - if (!is_uploaded_file($_FILES['screenshotfile']['tmp_name'])) - { - trigger_error('validateScreenshot - uploaded file not in upload directory', E_USER_ERROR); - return Lang::main('intError'); - } - - // check if file is an image; allow jpeg, png - $finfo = new \finfo(FILEINFO_MIME); // fileInfo appends charset information and other nonsense - $mime = $finfo->file($_FILES['screenshotfile']['tmp_name']); - if (preg_match('/^image\/(png|jpe?g)/i', $mime, $m)) - $isPNG = $m[0] == 'image/png'; - else - return Lang::screenshot('error', 'unkFormat'); - - // invalid file - $is = getimagesize($_FILES['screenshotfile']['tmp_name']); - if (!$is) - return Lang::screenshot('error', 'selectSS'); - - // size-missmatch: 4k UHD upper limit; 150px lower limit - if ($is[0] < $this->minSize || $is[1] < $this->minSize) - return Lang::screenshot('error', 'tooSmall'); - else if ($is[0] > 3840 || $is[1] > 2160) - return Lang::screenshot('error', 'selectSS'); - - return ''; - } - - private function ssName() : string - { - return $this->imgHash ? User::$username.'-'.$this->destType.'-'.$this->destTypeId.'-'.$this->imgHash : ''; - } - - protected static function checkCoords(string $val) : array - { - if (preg_match('/^[01]\.[0-9]{3}(,[01]\.[0-9]{3}){3}$/', $val)) - return explode(',', $val); - - return []; - } - - protected function generatePath() : void { } - protected function generateTitle() : void - { - array_unshift($this->title, Lang::screenshot('submission')); - } -} - -?> diff --git a/setup/updates/1758578400_05.sql b/setup/updates/1758578400_05.sql new file mode 100644 index 00000000..d36973d1 --- /dev/null +++ b/setup/updates/1758578400_05.sql @@ -0,0 +1,3 @@ +DELETE FROM `aowow_config` WHERE `key` = 'screenshot_min_size'; +INSERT INTO `aowow_config` (`key`, `value`, `default`, `cat`, `flags`, `comment`) VALUES + ('screenshot_min_size', 200, 200, 1, 1153, "minimum dimensions of uploaded screenshots in px (yes, it's square, no it cant go below 200)"); diff --git a/setup/updates/1758578400_06.sql b/setup/updates/1758578400_06.sql new file mode 100644 index 00000000..9a8c8a9d --- /dev/null +++ b/setup/updates/1758578400_06.sql @@ -0,0 +1,2 @@ +ALTER TABLE `aowow_screenshots` + MODIFY COLUMN `caption` varchar(200) DEFAULT NULL; diff --git a/static/js/Cropper.js b/static/js/Cropper.js index 2e853483..040547b4 100644 --- a/static/js/Cropper.js +++ b/static/js/Cropper.js @@ -322,4 +322,4 @@ Cropper.mouseMove = function (ev) { this.moveSelection(left, top, width - left, height - top); } -}; \ No newline at end of file +}; diff --git a/static/js/screenshot.js b/static/js/screenshot.js index 08486484..e4ab32eb 100644 --- a/static/js/screenshot.js +++ b/static/js/screenshot.js @@ -47,19 +47,16 @@ function ss_Refresh(openNext, type, typeId) { ssm_UpdatePages(); - if (openNext) { + if (openNext) ss_Manage($WH.ge('pages-container').firstChild.firstChild, ssm_screenshotPages[0].type, ssm_screenshotPages[0].typeId, true); - } - else if (type && typeId) { + else if (type && typeId) ss_Manage(null, type, typeId, true); - } } else { $WH.ee($WH.ge('show-all-pages')); $WH.ge('pages-container').innerHTML = 'NO SCREENZSHOT NEEDS 2 BE APPRVED NOW KTHX. :)'; - if (type && typeId) { + if (type && typeId) ss_Manage(null, type, typeId, true); - } } } }); @@ -73,11 +70,9 @@ function ss_Manage(_this, type, typeId, openNext) { eval(xhr.responseText); ssm_numPending = 0; - for (var i in ssm_screenshotData) { - if (ssm_screenshotData[i].pending) { + for (var i in ssm_screenshotData) + if (ssm_screenshotData[i].pending) ssm_numPending++; - } - } var nRows = ssm_screenshotData.length; $WH.ge('screenshotTotal').innerHTML = nRows + ' total' + (nRows == 100 ? ' (limit reached)' : ''); @@ -85,15 +80,13 @@ function ss_Manage(_this, type, typeId, openNext) { ssm_UpdateList(openNext); ssm_UpdateMassLinks(); - if (ss_managedRow != null) { + if (ss_managedRow != null) ss_ColorizeRow('transparent'); - } ss_managedRow = _this; - if (ss_managedRow != null) { + if (ss_managedRow != null) ss_ColorizeRow('#282828'); - } } }); } @@ -120,13 +113,15 @@ function ss_ManageUser() { method: 'get', onSuccess: function (xhr) { eval(xhr.responseText); + var nRows = ssm_screenshotData.length; $WH.ge('screenshotTotal').innerHTML = nRows + ' total' + (nRows == 100 ? ' (limit reached)' : ''); + ssm_UpdateList(); ssm_UpdateMassLinks(); - if (ss_managedRow != null) { + + if (ss_managedRow != null) ss_ColorizeRow('transparent'); - } } }); @@ -134,33 +129,28 @@ function ss_ManageUser() { } function ss_ColorizeRow(color) { - for (var i = 0; i < ss_managedRow.childNodes.length; ++i) { + for (var i = 0; i < ss_managedRow.childNodes.length; ++i) ss_managedRow.childNodes[i].style.backgroundColor = color; - } } function ssm_GetScreenshot(id) { - for (var i in ssm_screenshotData) { - if (ssm_screenshotData[i].id == id) { + for (var i in ssm_screenshotData) + if (ssm_screenshotData[i].id == id) return ssm_screenshotData[i]; - } - } return null; } function ssm_View(row, id) { - if (ssm_ViewedRow != null) { + if (ssm_ViewedRow != null) ssm_ColorizeRow('transparent'); - } ssm_ViewedRow = row; ssm_ColorizeRow('#282828'); var screenshot = ssm_GetScreenshot(id); - if (screenshot != null) { + if (screenshot != null) ScreenshotManager.show(screenshot); - } } function ssm_ColorizeRow(color) { @@ -248,23 +238,20 @@ function ssm_UpdatePages(UNUSED) { } function ssm_UpdateList(openNext) { - var tsl = $WH.ge('theScreenshotsList'); + var tbl = $WH.ge('theScreenshotsList'); var tBody = false; var i = 1; - while (tsl.childNodes.length > i) { - if (tsl.childNodes[i].nodeName == 'TR' && tBody) { - $WH.de(tsl.childNodes[i]); - } - else if (tsl.childNodes[i].nodeName == 'TR') { + while (tbl.childNodes.length > i) { + if (tbl.childNodes[i].nodeName == 'TR' && tBody) + $WH.de(tbl.childNodes[i]); + else if (tbl.childNodes[i].nodeName == 'TR') tBody = true; - } - else { + else i++; - } } - var now = new Date(); + var now = new Date(); var ssId = 0; for (var i in ssm_screenshotData) { var screenshot = ssm_screenshotData[i]; @@ -299,15 +286,13 @@ function ssm_UpdateList(openNext) { var a = $WH.ce('a'); a.href = '?' + g_types[screenshot.type] + '=' + screenshot.typeId + '#screenshots:id=' + screenshot.id; a.target = '_blank'; - a.onclick = function (e) { - $WH.sp(e); - }; + a.onclick = function (e) { $WH.sp(e); }; $WH.ae(a, $WH.ct(screenshot.id)); $WH.ae(td, a); } - else { + else $WH.ae(td, $WH.ct(screenshot.id)); - } + $WH.ae(tr, td); td = $WH.ce('td'); @@ -336,9 +321,9 @@ function ssm_UpdateList(openNext) { var a = $WH.ce('a'); a.href = 'javascript:;'; - a.onclick = function (id, e) { + a.onclick = function (ss, e) { $WH.sp(e); - (ssm_ShowEdit.bind(this, id))() + (ssm_ShowEdit.bind(this, ss))(); }.bind(a, screenshot); $WH.ae(a, $WH.ct('Edit')); $WH.ae(sp, a); @@ -346,9 +331,9 @@ function ssm_UpdateList(openNext) { a = $WH.ce('a'); a.href = 'javascript:;'; - a.onclick = function (id, e) { + a.onclick = function (ss, e) { $WH.sp(e); - (ssm_Clear.bind(this, id))() + (ssm_Clear.bind(this, ss))(); }.bind(a, screenshot); $WH.ae(a, $WH.ct('Clear')); $WH.ae(sp, a); @@ -364,9 +349,7 @@ function ssm_UpdateList(openNext) { a = $WH.ce('a'); a.href = '?user=' + screenshot.user; a.target = '_blank'; - a.onclick = function (e) { - $WH.sp(e); - }; + a.onclick = function (e) { $WH.sp(e); }; $WH.ae(a, $WH.ct(screenshot.user)); $WH.ae(td, a); $WH.ae(tr, td); @@ -384,6 +367,7 @@ function ssm_UpdateList(openNext) { (ssm_UpdateMassLinks.bind(this))(); }.bind(cb); $WH.ae(td, cb); + $WH.ae(td, $WH.ct(' ')); if (screenshot.status != 999) { @@ -392,23 +376,21 @@ function ssm_UpdateList(openNext) { return false; }.bind(tr, screenshot.id); - if (screenshot.id == ssId && openNext) { + if (screenshot.id == ssId && openNext) ssm_View(tr, screenshot.id); - } if (screenshot.pending) { a = $WH.ce('a'); a.href = 'javascript:;'; a.onclick = function (e) { $WH.sp(e); - (ssm_Approve.bind(this, false))() + (ssm_Approve.bind(this, false))(); }.bind(screenshot); $WH.ae(a, $WH.ct('Approve')); $WH.ae(td, a); } - else { + else $WH.ae(td, $WH.ct('Approve')); - } $WH.ae(td, makePipe()); @@ -422,9 +404,9 @@ function ssm_UpdateList(openNext) { $WH.ae(a, $WH.ct('Make sticky')); $WH.ae(td, a); } - else { + else $WH.ae(td, $WH.ct('Make sticky')); - } + $WH.ae(td, makePipe()); a = $WH.ce('a'); @@ -435,6 +417,7 @@ function ssm_UpdateList(openNext) { }.bind(screenshot); $WH.ae(a, $WH.ct('Delete')); $WH.ae(td, a); + $WH.ae(td, makePipe()); a = $WH.ce('a'); @@ -449,24 +432,24 @@ function ssm_UpdateList(openNext) { } $WH.ae(tr, td); - $WH.ae(tsl, tr); + $WH.ae(tbl, tr); } } function ssm_UpdateMassLinks() { - var buff = ''; + var idBuff = ''; var i = 0; var tSL = $WH.ge('theScreenshotsList'); var inp = $WH.gE(tSL, 'input'); $WH.array_walk(inp, function (x) { if (x.checked) { - buff += x.value + ','; + idBuff += x.value + ','; ++i; } }); - buff = $WH.rtrim(buff, ','); + idBuff = $WH.rtrim(idBuff, ','); var selCnt = $WH.ge('withselected'); if (i > 0) { @@ -477,23 +460,22 @@ function ssm_UpdateMassLinks() { var b = $WH.ge('massdelete'); var a = $WH.ge('masssticky'); - c.href = '?admin=screenshots&action=approve&id=' + buff; + c.href = '?admin=screenshots&action=approve&id=' + idBuff; c.onclick = ssm_ConfirmMassApprove; - b.href = '?admin=screenshots&action=delete&id=' + buff; + b.href = '?admin=screenshots&action=delete&id=' + idBuff; b.onclick = ssm_ConfirmMassDelete; - a.href = '?admin=screenshots&action=sticky&id=' + buff; + a.href = '?admin=screenshots&action=sticky&id=' + idBuff; a.onclick = ssm_ConfirmMassSticky; } - else { + else selCnt.style.display = 'none'; - } } function ssm_MassSelect(action) { - var tSL = $WH.ge('theScreenshotsList'); - var inp = $WH.gE(tSL, 'input'); + var tbl = $WH.ge('theScreenshotsList'); + var inp = $WH.gE(tbl, 'input'); switch (parseInt(action)) { case 1: @@ -527,12 +509,10 @@ function ssm_MassSelect(action) { function ssm_ShowEdit(screenshot, isAlt) { var node; - if (isAlt) { - node = $WH.ge('alt2-' + screenshot.id) - } - else { - node = $WH.ge('alt-' + screenshot.id) - } + if (isAlt) + node = $WH.ge('alt2-' + screenshot.id); + else + node = $WH.ge('alt-' + screenshot.id); var sp = $WH.gE(node, 'span')[0]; var div = $WH.ce('div'); @@ -548,28 +528,27 @@ function ssm_ShowEdit(screenshot, isAlt) { var btn = $WH.ce('input'); btn.type = 'button'; btn.value = 'Update'; - btn.onclick = function (i, j, k) { - if (!j) { - $WH.sp(k); - } + btn.onclick = function (ss, isAlt, e) { + if (!isAlt) + $WH.sp(e); - (ssm_Edit.bind(this, i, j))(); + (ssm_Edit.bind(this, ss, isAlt))(); }.bind(btn, screenshot, isAlt); div.appendChild(btn); - var c = $WH.ce('span'); - c.appendChild($WH.ct(' ')); - div.appendChild(c); + var sp2 = $WH.ce('span'); + sp2.appendChild($WH.ct(' ')); + div.appendChild(sp2); btn = $WH.ce('input'); btn.type = 'button'; btn.value = 'Cancel'; - btn.onclick = function (i, j, k) { - if (!j) { - $WH.sp(k); + btn.onclick = function (ss, isAlt, e) { + if (!isAlt) { + $WH.sp(e); } - (ssm_CancelEdit.bind(this, i, j))(); + (ssm_CancelEdit.bind(this, ss, isAlt))(); }.bind(btn, screenshot, isAlt); div.appendChild(btn); @@ -583,12 +562,10 @@ function ssm_ShowEdit(screenshot, isAlt) { function ssm_CancelEdit(screenshot, isAlt) { var node; - if (isAlt) { + if (isAlt) node = $WH.ge('alt2-' + screenshot.id); - } - else { + else node = $WH.ge('alt-' + screenshot.id); - } var sp = $WH.gE(node, 'span')[1]; sp.style.display = ''; @@ -610,41 +587,39 @@ function ssm_Edit(screenshot, isAlt) { var desc = node.firstChild.childNodes; if (desc[0].value == screenshot.caption) { ssm_CancelEdit(screenshot, isAlt); - return + return; } + screenshot.caption = desc[0].value; ssm_CancelEdit(screenshot, isAlt); node = node.firstChild; - while (node.childNodes.length > 0) { + while (node.childNodes.length > 0) node.removeChild(node.firstChild); - } + $WH.ae(node, $WH.ct(screenshot.caption)); new Ajax('?admin=screenshots&action=editalt&id=' + screenshot.id, { method: 'POST', params: 'alt=' + $WH.urlencode(screenshot.caption) - }) + }); } function ssm_Clear(screenshot, isAlt) { var node; - if (isAlt) { + if (isAlt) node = $WH.ge('alt2-' + screenshot.id); - } - else { + else node = $WH.ge('alt-' + screenshot.id); - } var sp = $WH.gE(node, 'span'); var a = $WH.gE(sp[1], 'a'); sp = sp[0]; - if (screenshot.caption == '') { + if (screenshot.caption == '') return; - } screenshot.caption = ''; sp.innerHTML = 'NULL'; @@ -652,67 +627,64 @@ function ssm_Clear(screenshot, isAlt) { new Ajax('?admin=screenshots&action=editalt&id=' + screenshot.id, { method: 'POST', params: 'alt=' + $WH.urlencode('') - }) + }); } function ssm_Approve(openNext) { - var _self = this; - new Ajax('?admin=screenshots&action=approve&id=' + _self.id, { + var ss = this; + new Ajax('?admin=screenshots&action=approve&id=' + ss.id, { method: 'get', onSuccess: function (x) { Lightbox.hide(); - if (ssm_numPending == 1 && _self.pending) { + if (ssm_numPending == 1 && ss.pending) ss_Refresh(true); - } else { ss_Refresh(); - ss_Manage(ss_managedRow, _self.type, _self.typeId, openNext, 0); + ss_Manage(ss_managedRow, ss.type, ss.typeId, openNext, 0); } } - }) + }); } function ssm_Sticky(openNext) { - var _self = this; - new Ajax('?admin=screenshots&action=sticky&id=' + _self.id, { + var ss = this; + new Ajax('?admin=screenshots&action=sticky&id=' + ss.id, { method: 'get', onSuccess: function (x) { Lightbox.hide(); - if (ssm_numPending == 1 && _self.pending) { + if (ssm_numPending == 1 && ss.pending) ss_Refresh(true); - } else { ss_Refresh(); - ss_Manage(ss_managedRow, _self.type, _self.typeId, openNext, 0); + ss_Manage(ss_managedRow, ss.type, ss.typeId, openNext, 0); } } - }) + }); } function ssm_Delete(openNext) { - var _self = this; - new Ajax('?admin=screenshots&action=delete&id=' + _self.id, { + var ss = this; + new Ajax('?admin=screenshots&action=delete&id=' + ss.id, { method: 'get', onSuccess: function (x) { Lightbox.hide(); - if (ssm_numPending == 1 && _self.pending) { + if (ssm_numPending == 1 && ss.pending) ss_Refresh(true); - } else { ss_Refresh(); - ss_Manage(ss_managedRow, _self.type, _self.typeId, openNext, 0); + ss_Manage(ss_managedRow, ss.type, ss.typeId, openNext, 0); } } }); } function ssm_Relocate(typeId) { - var _self = this; - new Ajax('?admin=screenshots&action=relocate&id=' + _self.id + '&typeid=' + typeId, { + var ss = this; + new Ajax('?admin=screenshots&action=relocate&id=' + ss.id + '&typeid=' + typeId, { method: 'get', onSuccess: function (x) { ss_Refresh(); - ss_Manage(ss_managedRow, _self.type, typeId); + ss_Manage(ss_managedRow, ss.type, typeId); } }); } @@ -733,10 +705,10 @@ var ScreenshotManager = new function () { aCover, aOriginal, divFrom, + spCaption, divCaption, - __div, h2Name, - u, + controlsCOPY, aEdit, aClear, spApprove, @@ -753,17 +725,14 @@ var ScreenshotManager = new function () { desiredScale = Math.min(772 / screenshot.width, 618 / screenshot.height); scale = Math.min(772 / screenshot.width, availHeight / screenshot.height); } - else { + else desiredScale = scale = 1; - } - if (desiredScale > 1) { + if (desiredScale > 1) desiredScale = 1; - } - if (scale > 1) { + if (scale > 1) scale = 1; - } imgWidth = Math.round(scale * screenshot.width); imgHeight = Math.round(scale * screenshot.height); @@ -778,14 +747,12 @@ var ScreenshotManager = new function () { } function render(resizing) { - if (resizing && (scale == desiredScale) && $WH.g_getWindowSize().h > container.offsetHeight) { + if (resizing && (scale == desiredScale) && $WH.g_getWindowSize().h > container.offsetHeight) return; - } container.style.visibility = 'hidden'; - var - resized = (screenshot.width > 772 || screenshot.height > 618); + var resized = (screenshot.width > 772 || screenshot.height > 618); computeDimensions(0); @@ -798,43 +765,39 @@ var ScreenshotManager = new function () { if (!resizing) { aOriginal.href = g_staticUrl + '/uploads/screenshots/' + (screenshot.pending ? 'pending' : 'normal') + '/' + screenshot.id + '.jpg'; - var hasFrom1 = screenshot.date && screenshot.user; - if (hasFrom1) { + var hasFrom = screenshot.date && screenshot.user; + if (hasFrom) { var postedOn = new Date(screenshot.date), - elapsed = (g_serverTime - postedOn) / 1000; + elapsed = (g_serverTime - postedOn) / 1000; var a = divFrom.firstChild.childNodes[1]; a.href = '?user=' + screenshot.user; a.innerHTML = screenshot.user; var s = divFrom.firstChild.childNodes[3]; - $WH.ee(s); g_formatDate(s, elapsed, postedOn); divFrom.firstChild.style.display = ''; } - else { + else divFrom.firstChild.style.display = 'none'; - } - divFrom.style.display = (hasFrom1 ? '' : 'none'); + divFrom.style.display = (hasFrom ? '' : 'none'); var hasCaption = (screenshot.caption != null && screenshot.caption.length); if (hasCaption) { var html = ''; - if (hasCaption) { + if (hasCaption) html += '' + Markup.toHtml(screenshot.caption, { mode: Markup.MODE_SIGNATURE }) + ''; - } - divCaption.innerHTML = html; - } - else { - divCaption.innerHTML = 'NULL'; + spCaption.innerHTML = html; } + else + spCaption.innerHTML = 'NULL'; - __div.id = 'alt2-' + screenshot.id; + divCaption.id = 'alt2-' + screenshot.id; aEdit.onclick = ssm_ShowEdit.bind(aEdit, screenshot, true); aClear.onclick = ssm_Clear.bind(aClear, screenshot, true); @@ -851,24 +814,22 @@ var ScreenshotManager = new function () { Lightbox.reveal(); - if (divCaption.offsetHeight > 18) { - computeDimensions(divCaption.offsetHeight - 18); - } + if (spCaption.offsetHeight > 18) + computeDimensions(spCaption.offsetHeight - 18); + container.style.visibility = 'visible'; } function nextScreenshot() { - if (screenshot.next !== undefined) { + if (screenshot.next !== undefined) screenshot = ssm_screenshotData[screenshot.next]; - } onRender(); } function prevScreenshot() { - if (screenshot.prev !== undefined) { + if (screenshot.prev !== undefined) screenshot = ssm_screenshotData[screenshot.prev]; - } onRender(); } @@ -890,7 +851,6 @@ var ScreenshotManager = new function () { dest.className = 'screenshotviewer'; screen = $WH.ce('div'); - screen.className = 'screenshotviewer-screen'; aPrev = $WH.ce('a'); @@ -914,12 +874,14 @@ var ScreenshotManager = new function () { aCover.className = 'screenshotviewer-cover'; aCover.href = 'javascript:;'; aCover.onclick = Lightbox.hide; + var foo = $WH.ce('span'); $WH.ae(foo, $WH.ce('b')); $WH.ae(aCover, foo); $WH.ae(screen, aPrev); $WH.ae(screen, aNext); $WH.ae(screen, aCover); + var _div = $WH.ce('div'); _div.className = 'text'; h2Name = $WH.ce('h2'); @@ -937,28 +899,38 @@ var ScreenshotManager = new function () { _div.style.paddingTop = '6px'; _div.style.cssFloat = _div.style.styleFloat = 'right'; _div.className = 'bigger-links'; + aApprove = $WH.ce('a'); aApprove.href = 'javascript:;'; $WH.ae(aApprove, $WH.ct('Approve')); $WH.ae(_div, aApprove); + spApprove = $WH.ce('span'); spApprove.style.display = 'none'; $WH.ae(spApprove, $WH.ct('Approve')); $WH.ae(_div, spApprove); + $WH.ae(_div, makePipe()); + aMakeSticky = $WH.ce('a'); aMakeSticky.href = 'javascript:;'; $WH.ae(aMakeSticky, $WH.ct('Make sticky')); $WH.ae(_div, aMakeSticky); + $WH.ae(_div, makePipe()); + aDelete = $WH.ce('a'); aDelete.href = 'javascript:;'; $WH.ae(aDelete, $WH.ct('Delete')); $WH.ae(_div, aDelete); - u = _div; + + controlsCOPY = _div; + $WH.ae(dest, _div); + divFrom = $WH.ce('div'); divFrom.className = 'screenshotviewer-from'; + var sp = $WH.ce('span'); $WH.ae(sp, $WH.ct(LANG.lvscreenshot_from)); $WH.ae(sp, $WH.ce('a')); @@ -966,9 +938,11 @@ var ScreenshotManager = new function () { $WH.ae(sp, $WH.ce('span')); $WH.ae(divFrom, sp); $WH.ae(dest, divFrom); + _div = $WH.ce('div'); _div.className = 'clear'; $WH.ae(dest, _div); + var aClose = $WH.ce('a'); aClose.className = 'screenshotviewer-close'; aClose.href = 'javascript:;'; @@ -983,23 +957,27 @@ var ScreenshotManager = new function () { $WH.ae(aOriginal, $WH.ce('span')); $WH.ae(dest, aOriginal); - __div = $WH.ce('div'); - divCaption = $WH.ce('span'); - divCaption.style.paddingRight = '8px'; - $WH.ae(__div, divCaption); + divCaption = $WH.ce('div'); + spCaption = $WH.ce('span'); + spCaption.style.paddingRight = '8px'; + $WH.ae(divCaption, spCaption); + var sp = $WH.ce('span'); sp.style.whiteSpace = 'nowrap'; aEdit = $WH.ce('a'); aEdit.href = 'javascript:;'; $WH.ae(aEdit, $WH.ct('Edit')); $WH.ae(sp, aEdit); + $WH.ae(sp, makePipe()); + aClear = $WH.ce('a'); aClear.href = 'javascript:;'; $WH.ae(aClear, $WH.ct('Clear')); $WH.ae(sp, aClear); - $WH.ae(__div, sp); - $WH.ae(dest, __div); + $WH.ae(divCaption, sp); + $WH.ae(dest, divCaption); + _div = $WH.ce('div'); _div.className = 'clear'; $WH.ae(dest, _div); @@ -1033,7 +1011,7 @@ var ScreenshotManager = new function () { } else { container.className = ''; - lightboxComponents = []; + lightboxComponents = []; while (container.firstChild) { lightboxComponents.push(container.firstChild); @@ -1056,7 +1034,7 @@ var ScreenshotManager = new function () { var img = $WH.ce('img'); img.src = g_staticUrl + '/images/ui/misc/progress-anim.gif'; - img.width = 126; + img.width = 126; img.height = 22; $WH.ae(div, img); @@ -1067,25 +1045,26 @@ var ScreenshotManager = new function () { }, 150); loadingImage = new Image(); - loadingImage.onload = (function (screen, timer) { + loadingImage.onload = (function (ss, timer) { clearTimeout(timer); - screen.width = this.width; - screen.height = this.height; - loadingImage = null; + ss.width = this.width; + ss.height = this.height; + loadingImage = null; restoreLightbox(); render(); }).bind(loadingImage, screenshot, lightboxTimer); + loadingImage.onerror = (function (timer) { clearTimeout(timer); loadingImage = null; Lightbox.hide(); restoreLightbox(); }).bind(loadingImage, lightboxTimer); + loadingImage.src = (screenshot.url ? screenshot.url : g_staticUrl + '/uploads/screenshots/' + (screenshot.pending ? 'pending' : 'normal') + '/' + screenshot.id + '.jpg'); } - else { + else render(); - } } function cancelImageLoading() { @@ -1101,15 +1080,13 @@ var ScreenshotManager = new function () { } function restoreLightbox() { - if (!lightboxComponents) { + if (!lightboxComponents) return; - } $WH.ee(container); container.className = 'screenshotviewer'; - for (var i = 0; i < lightboxComponents.length; ++i) { + for (var i = 0; i < lightboxComponents.length; ++i) $WH.ae(container, lightboxComponents[i]); - } lightboxComponents = null; } diff --git a/template/localized/ssReminder_0.tpl.php b/template/localized/ssReminder_0.tpl.php index 90b4a874..021b7148 100644 --- a/template/localized/ssReminder_0.tpl.php +++ b/template/localized/ssReminder_0.tpl.php @@ -1,6 +1,6 @@ - + -

Reminder

+

Reminder

Your screenshot will not be approved if it doesn't correspond to the following guidelines.
    diff --git a/template/localized/ssReminder_2.tpl.php b/template/localized/ssReminder_2.tpl.php new file mode 100644 index 00000000..60e2755c --- /dev/null +++ b/template/localized/ssReminder_2.tpl.php @@ -0,0 +1,12 @@ + + +

    Rappel

    + Votre capture d'écran ne sera pas approuvée si elle ne respecte pas les directives suivantes. + +
      +
    • Assurez-vous d'augmenter vos paramètres graphiques pour que la capture soit de bonne qualité !
    • +
    • Les captures d'écran du visualiseur de modèles sont supprimées immédiatement (cela inclut généralement la sélection de personnage).
    • +
    • N'incluez pas le texte à l'écran ni le cercle de sélection d'un PNJ.
    • +
    • N'incluez aucune interface utilisateur dans la capture si possible.
    • +
    • Utilisez l'outil de recadrage pour vous concentrer autant que possible sur l'objet et réduire l'environnement inutile, afin de mieux mettre en valeur l'objet sur la vignette qui apparaîtra sur la page de l'objet.
    • +
    diff --git a/template/localized/ssReminder_3.tpl.php b/template/localized/ssReminder_3.tpl.php index b02686cf..3737a045 100644 --- a/template/localized/ssReminder_3.tpl.php +++ b/template/localized/ssReminder_3.tpl.php @@ -1,6 +1,6 @@ - + -

    Hinweis

    +

    Hinweis

    Euer Screenshot wird nicht zugelassen werden, wenn er nicht unseren Richtlinien entspricht.
      diff --git a/template/localized/ssReminder_4.tpl.php b/template/localized/ssReminder_4.tpl.php index 094d7bd2..8ae87b64 100644 --- a/template/localized/ssReminder_4.tpl.php +++ b/template/localized/ssReminder_4.tpl.php @@ -1,12 +1,12 @@ - + -

      提醒

      - 你的截图将不会 通过审查假设不符合下列准则。 +

      提醒

      + 如果您的截图不符合以下指南,将不会被通过审核。
        -
      • Be sure to turn up your graphics settings to make sure the shot looks good!
      • -
      • Model viewer shots are deleted on sight (this also includes character select, typically).
      • -
      • Don't include the onscreen text and the selection circle of a NPC.
      • -
      • Don't include any UI in the shot if you can help it.
      • -
      • Use the screenshot cropping tool to focus on the item as much as possible and reduce any unnecessary surrounding, as to better show off the item in question when reduced to the thumbnail that will be present on the item's page.
      • +
      • 请确保将您的图形设置调高,以保证截图质量!
      • +
      • 使用模型查看器的截图会被直接删除(包括角色选择界面)。
      • +
      • 请勿包含NPC的屏幕文字选择圈
      • +
      • 如有可能,请勿在截图中包含任何用户界面元素。
      • +
      • 请使用截图裁剪工具尽量聚焦于物品本身,减少不必要的背景,以便在物品页面的缩略图中更好地展示该物品。
      diff --git a/template/localized/ssReminder_6.tpl.php b/template/localized/ssReminder_6.tpl.php new file mode 100644 index 00000000..803078e8 --- /dev/null +++ b/template/localized/ssReminder_6.tpl.php @@ -0,0 +1,12 @@ + + +

      Recordatorio

      + Su captura de pantalla no será aprobada si no cumple con las siguientes directrices. + +
        +
      • ¡Asegúrese de subir la configuración gráfica para que la imagen se vea bien!
      • +
      • Las capturas de model viewer se eliminan automáticamente (esto también incluye la selección de personaje, normalmente).
      • +
      • No incluya el texto en pantalla ni el círculo de selección de un PNJ.
      • +
      • No incluya ninguna interfaz de usuario en la imagen si puede evitarlo.
      • +
      • Utilice la herramienta de recorte de capturas para centrarse en el objeto tanto como sea posible y reducir el entorno innecesario, para mostrar mejor el objeto en la miniatura que aparecerá en la página del objeto.
      • +
      diff --git a/template/localized/ssReminder_8.tpl.php b/template/localized/ssReminder_8.tpl.php new file mode 100644 index 00000000..6e4814f0 --- /dev/null +++ b/template/localized/ssReminder_8.tpl.php @@ -0,0 +1,12 @@ + + +

      Напоминание

      + Ваш скриншот не будет одобрен, если он не соответствует следующим рекомендациям. + +
        +
      • Обязательно увеличьте свои настройки графики, чтобы скриншот выглядел хорошо!
      • +
      • Скриншоты из просмотрщика моделей удаляются сразу (это также касается выбора персонажа).
      • +
      • Не включайте текст на экране и круг выделения NPC.
      • +
      • Не включайте никакой интерфейс пользователя в скриншот, если это возможно.
      • +
      • Используйте инструмент обрезки скриншотов, чтобы максимально сфокусироваться на предмете и уменьшить ненужное окружение, чтобы лучше показать предмет на миниатюре, которая будет на странице предмета.
      • +
      diff --git a/template/pages/admin/screenshots.tpl.php b/template/pages/admin/screenshots.tpl.php index 65c8b339..f2034ba8 100644 --- a/template/pages/admin/screenshots.tpl.php +++ b/template/pages/admin/screenshots.tpl.php @@ -1,7 +1,8 @@ - - -brick('header'); ?> +brick('header'); +?>
      @@ -12,7 +13,7 @@ $this->brick('announcement'); $this->brick('pageTemplate'); ?>
      -

      name; ?>

      +

      h1; ?>

      @@ -24,13 +25,7 @@ $this->brick('pageTemplate'); @@ -42,20 +37,20 @@ endforeach; @@ -124,10 +119,10 @@ if ($this->getAll): echo " var ss_getAll = true;\n"; endif; if ($this->ssPages): - echo " var ssm_screenshotPages = ".Util::toJSON($this->ssPages).";\n"; + echo " var ssm_screenshotPages = ".$this->json($this->ssPages).";\n"; echo " ssm_UpdatePages();\n"; elseif ($this->ssData): - echo " var ssm_screenshotData = ".Util::toJSON($this->ssData).";\n"; + echo " var ssm_screenshotData = ".$this->json($this->ssData).";\n"; echo " ssm_UpdateList();\n"; endif; ?> diff --git a/template/pages/screenshot.tpl.php b/template/pages/screenshot.tpl.php index beb03f03..819607b7 100644 --- a/template/pages/screenshot.tpl.php +++ b/template/pages/screenshot.tpl.php @@ -1,7 +1,10 @@ - +brick('header'); ?> + use \Aowow\Lang; + $this->brick('header'); +?>
      @@ -15,12 +18,12 @@ $this->brick('infobox'); ?>
      -

      name; ?>

      +

      h1; ?>

      Page: #
      Menu
      PagesScreenshots: