Template/Update (Part 38)

* split Screenshot upload & management into separate endpoints
 * move shared functions to manager classes
 * cleanup javascript
 * move test for config screenshot min size to cfg class
This commit is contained in:
Sarjuuk
2025-08-13 23:19:57 +02:00
parent 3d3e2211e5
commit a369244908
35 changed files with 1546 additions and 1031 deletions

View File

@@ -0,0 +1,68 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminScreenshotsResponse extends TemplateResponse
{
protected int $requiredUserGroup = U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_SCREENSHOT;
protected string $template = 'admin/screenshots';
protected string $pageName = 'screenshots';
protected ?int $activeTab = parent::TAB_STAFF;
protected array $breadcrumb = [4, 1, 5]; // Staff > 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();
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Aowow;
use GdImage;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminScreenshotsActionApproveResponse extends TextResponse
{
protected int $requiredUserGroup = U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_SCREENSHOT;
protected array $expectedGET = array(
'id' => ['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);
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminScreenshotsActionDeleteResponse extends TextResponse
{
protected int $requiredUserGroup = U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_SCREENSHOT;
protected array $expectedGET = array(
'id' => ['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));
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminScreenshotsActionEditaltResponse extends TextResponse
{
use TrCommunityHelper;
protected int $requiredUserGroup = U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_SCREENSHOT;
protected array $expectedGET = array(
'id' => ['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']
);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminScreenshotsActionListResponse extends TextResponse
{
protected int $requiredUserGroup = U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_SCREENSHOT;
protected array $expectedGET = array(
'all' => ['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.';';
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminScreenshotsActionManageResponse extends TextResponse
{
protected int $requiredUserGroup = U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_SCREENSHOT;
protected array $expectedGET = array(
'type' => ['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);
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminScreenshotsActionRelocateResponse extends TextResponse
{
protected int $requiredUserGroup = U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_SCREENSHOT;
protected array $expectedGET = array(
'id' => ['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);
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminScreenshotsActionStickyResponse extends TextResponse
{
protected int $requiredUserGroup = U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_SCREENSHOT;
protected array $expectedGET = array(
'id' => ['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);
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
/*
-> 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-<hash>[_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=<command>&<type>.<typeId>.<hash:16> (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;
}
}
?>

View File

@@ -0,0 +1,106 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
/*
1. =add: receives user upload
2. =crop: user edites upload
-> 3. =complete: store edited screenshot file and data
4. =thankyou
*/
// filename: Username-type-typeId-<hash>[_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=<command>&<type>.<typeId>.<hash:16> (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;
}
}
?>

View File

@@ -0,0 +1,92 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
/*
1. =add: receives user upload
-> 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-<hash>[_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=<command>&<type>.<typeId>.<hash:16> (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();
}
}
?>

View File

@@ -0,0 +1,66 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
/*
1. =add: receives user upload
2. =crop: user edites upload
3. =complete: store edited screenshot file and data
-> 4. =thankyou
*/
// filename: Username-type-typeId-<hash>[_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=<command>&<type>.<typeId>.<hash:16> (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').'<br /><br />';
$this->extraHTML .= Lang::screenshot('thanks', 'goBack', [Type::getFileString($this->destType), $this->destTypeId])."<br /><br />\n";
$this->extraHTML .= '<i>'.Lang::screenshot('thanks', 'note').'</i>';
parent::generate();
}
}
?>

View File

@@ -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')
);

View File

@@ -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']);

View File

@@ -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;
}
}
?>

View File

@@ -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
{

View File

@@ -0,0 +1,306 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
abstract class ImageUpload
{
public const /* int */ MIME_UNK = 0;
public const /* int */ MIME_JPG = 1;
public const /* int */ MIME_PNG = 2;
public const /* int */ MIME_WEBP = 3;
// scale img down if larger than crop screen
private const /* int */ CROP_W = 488;
private const /* int */ CROP_H = 325;
protected const /* int */ JPEG_QUALITY = 85;
protected static int $mimeType = self::MIME_UNK;
protected static ?\GdImage $img = null;
protected static string $fileName = '';
protected static string $uploadFormField;
protected static string $tmpPath;
public static bool $hasUpload = false;
public static string $error = '';
public static function init() : bool
{
// active screenshot upload
self::$hasUpload = $_FILES && !empty($_FILES[static::$uploadFormField]);
return true;
}
public static function validateUpload() : bool
{
if (!self::$hasUpload)
return false;
switch ($_FILES[static::$uploadFormField]['error']) // 0 is fine
{
case UPLOAD_ERR_INI_SIZE: // 1
case UPLOAD_ERR_FORM_SIZE: // 2
trigger_error('ImageUpload::validateUpload - the file exceeds the maximum size of '.ini_get('upload_max_filesize'), E_USER_WARNING);
self::$error = Lang::main('intError');
return false;
case UPLOAD_ERR_PARTIAL: // 3
trigger_error('ImageUpload::validateUpload - upload was interrupted', E_USER_WARNING);
self::$error = Lang::screenshot('error', 'selectSS');
return false;
case UPLOAD_ERR_NO_FILE: // 4
trigger_error('ImageUpload::validateUpload - no file was received', E_USER_WARNING);
self::$error = Lang::screenshot('error', 'selectSS');
return false;
case UPLOAD_ERR_NO_TMP_DIR: // 6
trigger_error('ImageUpload::validateUpload - temporary upload directory is not set', E_USER_ERROR);
self::$error = Lang::main('intError');
return false;
case UPLOAD_ERR_CANT_WRITE: // 7
trigger_error('ImageUpload::validateUpload - could not write temporary file to disk', E_USER_ERROR);
self::$error = Lang::main('intError');
return false;
case UPLOAD_ERR_EXTENSION: // 8
trigger_error('ImageUpload::validateUpload - a php extension stopped the file upload.', E_USER_ERROR);
self::$error = Lang::main('intError');
return false;
}
self::$fileName = $_FILES[static::$uploadFormField]['tmp_name'];
// points to invalid file
if (!is_uploaded_file(self::$fileName))
{
trigger_error('ImageUpload::validateUpload - uploaded file not in upload directory', E_USER_ERROR);
self::$error = Lang::main('intError');
self::$fileName = '';
return false;
}
// check if file is an image
if (!self::setMimeType())
{
self::$error = Lang::screenshot('error', 'unkFormat');
self::$fileName = '';
return false;
}
if (!self::$error)
return true;
self::$fileName = '';
return false;
}
public static function loadUpload() : bool
{
if (!self::$hasUpload)
return false;
return match (self::$mimeType)
{
self::MIME_JPG => 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);
}
}
?>

View File

@@ -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);
}
}

View File

@@ -0,0 +1,230 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class ScreenshotMgr extends ImageUpload
{
// config value
public static int $MIN_SIZE; // 200
// 4k resolution
private const /* int */ MAX_W = 4096;
private const /* int */ MAX_H = 2160;
// as expected by js - this also makes the CC-flags functionally exclusive with each other
private const /* int */ STATUS_PENDING = 0;
private const /* int */ STATUS_DELETED = 999;
private const /* int */ STATUS_APPROVED = 100;
private const /* int */ STATUS_STICKY = 105;
private const /* array */ DIMS_RESIZED = [772, 618];
private const /* array */ DIMS_THUMB = [150, 150];
protected static string $uploadFormField = 'screenshotfile';
protected static string $tmpPath = self::PATH_TEMP;
public const /* string */ PATH_TEMP = 'static/uploads/screenshots/temp/%s.jpg';
public const /* string */ PATH_PENDING = 'static/uploads/screenshots/pending/%d.jpg';
public const /* string */ PATH_THUMB = 'static/uploads/screenshots/thumb/%d.jpg';
public const /* string */ PATH_RESIZED = 'static/uploads/screenshots/resized/%d.jpg';
public const /* string */ PATH_NORMAL = 'static/uploads/screenshots/normal/%d.jpg';
public static function init() : bool
{
self::$MIN_SIZE = Cfg::get('SCREENSHOT_MIN_SIZE');
$dirErr = false;
foreach (['TEMP', 'PENDING', 'THUMB', 'RESIZED', 'NORMAL'] as $p)
{
$path = constant('self::PATH_' . $p);
if (!is_writable(substr($path, 0, strrpos($path, '/'))))
{
trigger_error('ScreenshotMgr::init - directory '.substr($path, 0, strrpos($path, '/')).' not writable', E_USER_ERROR);
$dirErr = true;
}
}
if ($dirErr)
return false;
return parent::init();
}
public static function validateUpload() : bool
{
if (!parent::validateUpload())
return false;
// invalid file
if ($is = getimagesize(self::$fileName))
{
// image size out of bounds
if ($is[0] < self::$MIN_SIZE || $is[1] < self::$MIN_SIZE)
self::$error = Lang::screenshot('error', 'tooSmall');
else if ($is[0] > 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;
}
}
?>

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -1,396 +0,0 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
// filename: Username-type-typeId-<hash>[_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=<command>&<type>.<typeId>.<hash:16> (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').'<br><br>';
$this->extraHTML .= sprintf(Lang::screenshot('thanks', 'goBack'), Type::getFileString($this->destType), $this->destTypeId)."<br /><br />\n";
$this->extraHTML .= '<i>'.Lang::screenshot('thanks', 'note').'</i>';
}
/**********/
/* 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'));
}
}
?>

View File

@@ -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)");

View File

@@ -0,0 +1,2 @@
ALTER TABLE `aowow_screenshots`
MODIFY COLUMN `caption` varchar(200) DEFAULT NULL;

View File

@@ -322,4 +322,4 @@ Cropper.mouseMove = function (ev) {
this.moveSelection(left, top, width - left, height - top);
}
};
};

View File

@@ -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 = '<i class="q0">NULL</i>';
@@ -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 += '<span class="screenshotviewer-caption"><b>' + Markup.toHtml(screenshot.caption, { mode: Markup.MODE_SIGNATURE }) + '</b></span>';
}
divCaption.innerHTML = html;
}
else {
divCaption.innerHTML = '<i class="q0">NULL</i>';
spCaption.innerHTML = html;
}
else
spCaption.innerHTML = '<i class="q0">NULL</i>';
__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;
}

View File

@@ -1,6 +1,6 @@
<?php namespace Aowow; ?>
<?php namespace Aowow\Template; ?>
<h2><img src="<?=Cfg::get('STATIC_URL'); ?>/images/icons/bubble-big.gif" width="32" height="29" alt="" style="vertical-align:middle;margin-right:8px">Reminder</h2>
<h2><img src="<?=$this->gStaticUrl; ?>/images/icons/bubble-big.gif" width="32" height="29" alt="" style="vertical-align:middle;margin-right:8px">Reminder</h2>
Your screenshot will <b class="q10">not</b> be approved if it doesn't correspond to the following guidelines.
<ul>

View File

@@ -0,0 +1,12 @@
<?php namespace Aowow\Template; ?>
<h2><img src="<?=$this->gStaticUrl; ?>/images/icons/bubble-big.gif" width="32" height="29" alt="" style="vertical-align:middle;margin-right:8px">Rappel</h2>
Votre capture d'écran <b class="q10">ne</b> sera pas approuvée si elle ne respecte pas les directives suivantes.
<ul>
<li><div>Assurez-vous d'augmenter vos <u><b>paramètres graphiques</b></u> pour que la capture soit de bonne qualité !</div></li>
<li><div>Les captures d'écran du <u><b>visualiseur de modèles</b></u> sont supprimées immédiatement (cela inclut généralement la sélection de personnage).</div></li>
<li><div>N'incluez pas le <u><b>texte à l'écran</b></u> ni le <u><b>cercle de sélection</b></u> d'un PNJ.</div></li>
<li><div>N'incluez aucune <u><b>interface utilisateur</b></u> dans la capture si possible.</div></li>
<li><div>Utilisez l'<u><b>outil de recadrage</b></u> 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.</div></li>
</ul>

View File

@@ -1,6 +1,6 @@
<?php namespace Aowow; ?>
<?php namespace Aowow\Template; ?>
<h2><img src="<?=Cfg::get('STATIC_URL'); ?>/images/icons/bubble-big.gif" width="32" height="29" alt="" style="vertical-align:middle;margin-right:8px">Hinweis</h2>
<h2><img src="<?=$this->gStaticUrl; ?>/images/icons/bubble-big.gif" width="32" height="29" alt="" style="vertical-align:middle;margin-right:8px">Hinweis</h2>
Euer Screenshot wird <b class="q10">nicht</b> zugelassen werden, wenn er nicht unseren Richtlinien entspricht.
<ul>

View File

@@ -1,12 +1,12 @@
<?php namespace Aowow; ?>
<?php namespace Aowow\Template; ?>
<h2><img src="<?=Cfg::get('STATIC_URL'); ?>/images/icons/bubble-big.gif" width="32" height="29" alt="" style="vertical-align:middle;margin-right:8px">提醒</h2>
你的截图将<b class="q10">不会</b> 通过审查假设不符合下列准则
<h2><img src="<?=$this->gStaticUrl; ?>/images/icons/bubble-big.gif" width="32" height="29" alt="" style="vertical-align:middle;margin-right:8px">提醒</h2>
如果您的截图不符合以下指南,将<b class="q10">不会</b>通过审
<ul>
<li><div>Be sure to turn up your <u><b>graphics settings</b></u> to make sure the shot looks good!</div></li>
<li><div><u><b>Model viewer</b></u> shots are deleted on sight (this also includes character select, typically).</div></li>
<li><div>Don't include the <u><b>onscreen text</b></u> and the <u><b>selection circle</b></u> of a NPC.</div></li>
<li><div>Don't include any <u><b>UI</b></u> in the shot if you can help it.</div></li>
<li><div>Use the screenshot <u><b>cropping tool</b></u> 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.</div></li>
<li><div>请确保将您的<u><b>图形设置</b></u>调高,以保证截图质量!</div></li>
<li><div>使用<u><b>模型查看器</b></u>的截图会被直接删除(包括角色选择界面)。</div></li>
<li><div>请勿包含NPC的<u><b>屏幕文字</b></u>和<u><b>选择圈</b></u>。</div></li>
<li><div>如有可能,请勿在截图中包含任何<u><b>用户界面</b></u>元素。</div></li>
<li><div>请使用<u><b>截图裁剪工具</b></u>尽量聚焦于物品本身,减少不必要的背景,以便在物品页面的缩略图中更好地展示该物品。</div></li>
</ul>

View File

@@ -0,0 +1,12 @@
<?php namespace Aowow\Template; ?>
<h2><img src="<?=$this->gStaticUrl; ?>/images/icons/bubble-big.gif" width="32" height="29" alt="" style="vertical-align:middle;margin-right:8px">Recordatorio</h2>
Su captura de pantalla <b class="q10">no</b> será aprobada si no cumple con las siguientes directrices.
<ul>
<li><div>¡Asegúrese de subir la <u><b>configuración gráfica</b></u> para que la imagen se vea bien!</div></li>
<li><div>Las capturas de <u><b>model viewer</b></u> se eliminan automáticamente (esto también incluye la selección de personaje, normalmente).</div></li>
<li><div>No incluya el <u><b>texto en pantalla</b></u> ni el <u><b>círculo de selección</b></u> de un PNJ.</div></li>
<li><div>No incluya ninguna <u><b>interfaz de usuario</b></u> en la imagen si puede evitarlo.</div></li>
<li><div>Utilice la <u><b>herramienta de recorte de capturas</b></u> 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.</div></li>
</ul>

View File

@@ -0,0 +1,12 @@
<?php namespace Aowow\Template; ?>
<h2><img src="<?=$this->gStaticUrl; ?>/images/icons/bubble-big.gif" width="32" height="29" alt="" style="vertical-align:middle;margin-right:8px">Напоминание</h2>
Ваш скриншот <b class="q10">не</b> будет одобрен, если он не соответствует следующим рекомендациям.
<ul>
<li><div>Обязательно увеличьте свои <u><b>настройки графики</b></u>, чтобы скриншот выглядел хорошо!</div></li>
<li><div>Скриншоты из <u><b>просмотрщика моделей</b></u> удаляются сразу (это также касается выбора персонажа).</div></li>
<li><div>Не включайте <u><b>текст на экране</b></u> и <u><b>круг выделения</b></u> NPC.</div></li>
<li><div>Не включайте никакой <u><b>интерфейс пользователя</b></u> в скриншот, если это возможно.</div></li>
<li><div>Используйте <u><b>инструмент обрезки скриншотов</b></u>, чтобы максимально сфокусироваться на предмете и уменьшить ненужное окружение, чтобы лучше показать предмет на миниатюре, которая будет на странице предмета.</div></li>
</ul>

View File

@@ -1,7 +1,8 @@
<?php namespace Aowow; ?>
<?php $this->brick('header'); ?>
<?php
namespace Aowow\Template;
$this->brick('header');
?>
<div class="main" id="main">
<div class="main-precontents" id="main-precontents"></div>
<div class="main-contents" id="main-contents">
@@ -12,7 +13,7 @@ $this->brick('announcement');
$this->brick('pageTemplate');
?>
<div class="text">
<h1><?=$this->name; ?></h1>
<h1><?=$this->h1; ?></h1>
<table>
<tr>
@@ -24,13 +25,7 @@ $this->brick('pageTemplate');
<td>Page: </td>
<td>
<select id="pagetype">
<?php
foreach (Type::getFileStringsFor() as $i => $str):
if (Lang::game($str) && Type::checkClassAttrib($i, 'contribute', CONTRIBUTE_SS)):
echo " <option value=\"".$i."\">".Util::ucFirst(Lang::game($str))."</option>\n";
endif;
endforeach;
?>
<?=$this->makeOptionsList($this->pageTypes, null, 32); ?>
</select>
</td>
<td>#<input type="number" size="6" id="pagetypeid"></td>
@@ -42,20 +37,20 @@ endforeach;
<thead><tr><th style="width:135px;"><div>Menu</div></th><th style="width:400px;">Pages</th><th>Screenshots: <span id="screenshotTotal"></span></th></tr></thead>
<tbody><tr>
<td id="menu-container" style="vertical-align: top;">
<div id="show-all-pages"><?=($this->ssNFound ? ' &ndash; <a href="?admin=screenshots&all">Show All</a> ('.$this->ssNFound.')' : null); ?></div>
<div id="show-all-pages"><?=($this->ssNFound ? ' &ndash; <a href="?admin=screenshots&all">Show All</a> ('.$this->ssNFound.')' : ''); ?></div>
<h4>Mass Select</h4>
&ndash; <a href="#" onClick="ssm_MassSelect(1);">Select All</a><br>
&ndash; <a href="#" onClick="ssm_MassSelect(0);">Deselect All</a><br>
&ndash; <a href="#" onClick="ssm_MassSelect(-1);">Toggle Selection</a><br>
&ndash; <a href="#" onClick="ssm_MassSelect(2);">Select All Pending</a><br>
&ndash; <a href="#" onClick="ssm_MassSelect(5);">Select All Unique</a><br>
&ndash; <a href="#" onClick="ssm_MassSelect(3);">Select All Approved</a><br>
&ndash; <a href="#" onClick="ssm_MassSelect(4);">Select All Sticky</a><br>
&ndash; <a href="#" onClick="ssm_MassSelect(1);">Select All</a><br />
&ndash; <a href="#" onClick="ssm_MassSelect(0);">Deselect All</a><br />
&ndash; <a href="#" onClick="ssm_MassSelect(-1);">Toggle Selection</a><br />
&ndash; <a href="#" onClick="ssm_MassSelect(2);">Select All Pending</a><br />
&ndash; <a href="#" onClick="ssm_MassSelect(5);">Select All Unique</a><br />
&ndash; <a href="#" onClick="ssm_MassSelect(3);">Select All Approved</a><br />
&ndash; <a href="#" onClick="ssm_MassSelect(4);">Select All Sticky</a><br />
<div id="withselected" style="display:none;">
<h4>Mass Action <b>(0)</b></h4>
&ndash; <a href="#" id="massapprove">Approve All</a><br>
&ndash; <a href="#" id="massdelete">Delete All</a><br>
&ndash; <a href="#" id="masssticky">Sticky All</a><br>
&ndash; <a href="#" id="massapprove">Approve All</a><br />
&ndash; <a href="#" id="massdelete">Delete All</a><br />
&ndash; <a href="#" id="masssticky">Sticky All</a><br />
</div>
</td>
<td id="pages-container" style="vertical-align: top;"></td>
@@ -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;
?>

View File

@@ -1,7 +1,10 @@
<?php namespace Aowow; ?>
<?php
namespace Aowow\Template;
<?php $this->brick('header'); ?>
use \Aowow\Lang;
$this->brick('header');
?>
<div class="main" id="main">
<div class="main-precontents" id="main-precontents"></div>
<div class="main-contents" id="main-contents">
@@ -15,12 +18,12 @@ $this->brick('infobox');
?>
<div class="text">
<h1><?=$this->name; ?></h1>
<h1><?=$this->h1; ?></h1>
<span><?=Lang::screenshot('cropHint'); ?></span>
<div class="pad"></div>
<div id="ss-container"></div><script type="text/javascript">//<![CDATA[
var myCropper = new Cropper(<?=Util::toJSON($this->cropper); ?>);
var myCropper = new Cropper(<?=$this->json($this->cropper); ?>);
//]]></script>
<div class="pad"></div>