- implemented screenshot handling, inclding

* editing after upload
  * reputation gain for approved uploads
  * moderator interface (albeit a bit wonky)

I still do not speak French, Spanish or Russian, so translations are
appreciated
This commit is contained in:
Sarjuuk
2014-11-12 19:35:03 +01:00
parent f413089328
commit 0b46d15a2a
22 changed files with 1591 additions and 34 deletions

View File

@@ -17,7 +17,9 @@ AddDefaultCharset utf8
CharsetRecodeMultipartForms Off
</IfModule>
# 5MB should be enough for the largest screenshots in the land
php_value default_charset UTF-8
php_value upload_max_filesize 5M
RewriteEngine on
# RewriteBase /~user/localPath/ # enable if the rules do not work, when they should

View File

@@ -640,7 +640,28 @@ class AjaxHandler
if (empty($this->get['action']) || !$this->params)
return null;
if ($this->params[0] == 'siteconfig')
if ($this->params[0] == 'screenshots')
{
if (!User::isInGroup(U_GROUP_STAFF | U_GROUP_SCREENSHOT)) // comment_mod, handleSSmod, vi_mod ?
return null;
switch ($this->get['action'])
{
case 'list': // get all => null (optional)
case 'manage': // get: [type => type, typeId => typeId] || [user => username]
case 'editalt': // get: id => ssId; post: alt => caption
case 'approve': // get: id => ssId || ,-separated id-list
case 'sticky': // get: id => ssId || ,-separated id-list
case 'delete': // get: id => ssId || ,-separated id-list
case 'relocate': // get: id => ssId, typeid => typeId (but not type..?)
$fn = 'admin_handleSS'.ucfirst($this->get['action']);
return $this->$fn();
break;
default:
return null;
}
}
else if ($this->params[0] == 'siteconfig')
{
if (!User::isInGroup(U_GROUP_DEV | U_GROUP_ADMIN))
return null;
@@ -651,7 +672,7 @@ class AjaxHandler
if (empty($this->get['id']))
return 'invalid configuration option given';
if (DB::Aowow()->query('DELETE FROM ?_config WHERE `key` = ? AND (`flags` & ?d) = 0', $this->get['id'], CON_FLAG_PERSISTANT))
if (DB::Aowow()->query('DELETE FROM ?_config WHERE `key` = ? AND (`flags` & ?d) = 0', $this->get['id'], CON_FLAG_PERSISTENT))
return '';
else
return 'option name is either protected or was not found';
@@ -1073,6 +1094,198 @@ class AjaxHandler
// hey, still here? you're not a Tauren/Nelf as bear or cat, are you?
return DB::Aowow()->selectCell('SELECT IF(?d == 1, IFNULL(displayIdA, displayIdH), IFNULL(displayIdH, displayIdA)) FROM ?_shapeshiftform WHERE id = ?d', Util::sideByRaceMask(1 << ($char['race'] - 1)), $form);
}
// get all => null (optional)
// evaled response .. UNK
private function admin_handleSSList()
{
// ssm_screenshotPages
// ssm_numPagesFound
$pages = CommunityContent::getScreenshotPagesForManager(isset($this->get['all']), $nPages);
$buff = 'ssm_screenshotPages = '.json_encode($pages, JSON_NUMERIC_CHECK).";\n";
$buff .= 'ssm_numPagesFound = '.$nPages.';';
return $buff;
}
// get: [type => type, typeId => typeId] || [user => username]
// evaled response .. UNK
private function admin_handleSSManage()
{
$res = [];
if (!empty($this->get['type']) && intVal($this->get['type']) && !empty($this->get['typeid']) && intVal($this->get['typeid']))
$res = CommunityContent::getScreenshotsForManager($this->get['type'], $this->get['typeid']);
else if (!empty($this->get['user']) && strlen(urldecode($this->get['user'])) > 2)
if ($uId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE displayName = ?', strtolower(urldecode($this->get['user']))))
$res = CommunityContent::getScreenshotsForManager(0, 0, $uId);
return 'ssm_screenshotData = '.json_encode($res, JSON_NUMERIC_CHECK);
}
// get: id => SSid
// resp: ''
private function admin_handleSSEditalt()
{
if (empty($_GET['id']) || empty($this->post['alt']))
return '';
// doesn't need to be htmlEscaped, ths javascript does that
DB::Aowow()->query('UPDATE ?_screenshots SET caption = ? WHERE id = ?d', $this->post['alt'], $_GET['id']);
return '';
}
// get: id => comma-separated SSids
// resp: ''
private function admin_handleSSApprove($override = [])
{
if (empty($_GET['id']))
return '';
$ids = $override ?: array_map('intval', explode(',', $_GET['id']));
// create resized and thumb version of screenshot
$resized = [772, 618];
$thumb = [150, 150];
$path = 'static/uploads/screenshots/%s/%d.jpg';
foreach ($ids as $id)
{
// must not be already approved
if ($_ = DB::Aowow()->selectCell('SELECT uploader FROM ?_screenshots WHERE (status & ?d) = 0 AND id = ?d', CC_FLAG_APPROVED, $id))
{
// should also error-log
if (!file_exists(sprintf($path, 'pending', $id)))
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, approvedBy = ?d WHERE id = ?d', CC_FLAG_APPROVED, User::$id, $id);
Util::gainSiteReputation($_, SITEREP_ACTION_UPLOAD, ['id' => $id, 'what' => 1]);
}
}
return '';
}
// get: id => comma-separated SSids
// resp: ''
private function admin_handleSSSticky()
{
if (empty($_GET['id']))
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
$ids = array_map('intval', explode(',', $_GET['id']));
foreach ($ids 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);
// approve this one (if not already)
$this->admin_handleSSApprove([$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);
}
return '';
}
// get: id => comma-separated SSids
// resp: ''
// 2 steps: 1) remove from sight, 2) remove from disk
private function admin_handleSSDelete()
{
if (empty($_GET['id']))
return '';
$path = 'static/uploads/screenshots/%s/%d.jpg';
$ids = array_map('intval', explode(',', $_GET['id']));
foreach ($ids as $id)
{
// irrevocably remove already deleted files
if (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
DB::Aowow()->query('UPDATE ?_screenshots SET status = ?d, deletedBy = ?d WHERE id IN (?a)', CC_FLAG_DELETED, User::$id, $ids);
return '';
}
// get: id => ssId, typeid => typeId (but not type..?)
// resp: ''
private function admin_handleSSRelocate()
{
if (empty($this->get['id']) || empty($this->get['typeid']))
return '';
$type = DB::Aowow()->selectCell('SELECT type FROM ?_screenshots WHERE id = ?d', $this->get['id']);
$typeId = (int)$this->get['typeid'];
if (!(new Util::$typeClasses[$type]([['id', $typeId]]))->error)
DB::Aowow()->query('UPDATE ?_screenshots SET typeId = ?d WHERE id = ?d', $typeId, $this->get['id']);
return '';
}
}
?>

View File

@@ -218,6 +218,128 @@ class CommunityContent
return $replies;
}
public static function getScreenshotsForManager($type, $typeId, $userId = 0)
{
$screenshots = DB::Aowow()->select('
SELECT s.id, a.displayName AS user, s.date, s.width, s.height, s.type, s.typeId, s.caption, s.status, s.status AS "flags"
FROM ?_screenshots s, ?_account a
WHERE
s.uploader = a.id AND
{ s.type = ?d}
{ AND s.typeId = ?d}
{ s.uploader = ?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;
}
return $screenshots;
}
public static function getScreenshotPagesForManager($all, &$nFound)
{
// i GUESS .. ss_getALL ? everything : unapproved
$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
);
if ($pages)
{
// limit to one actually existing type each
$types = array_intersect(array_unique(array_column($pages, 'type')), array_keys(Util::$typeClasses));
foreach ($types as $t)
{
$ids = [];
foreach ($pages as $row)
if ($row['type'] == $t)
$ids[] = $row['typeId'];
if (!$ids)
continue;
$cnd = [['id', $ids]];
if ($t == TYPE_WORLDEVENT) // FKIN HOLIDAYS
array_push($cnd, ['holidayId', $ids], 'OR');
$tClass = new Util::$typeClasses[$t]($cnd);
foreach ($pages as &$p)
if ($p['type'] == $t)
if ($tClass->getEntry($p['typeId']))
$p['name'] = $tClass->getField('name', true);
}
foreach ($pages as &$p)
{
if (empty($p['name']))
{
Util::addNote(U_GROUP_STAFF | U_GROUP_SCREENSHOT, 'AdminPage::handleScreenshots() - Screenshot linked to nonexistant type/typeId combination '.$p['type'].'/'.$p['typeId']);
unset($p);
}
else
{
$nFound += $p['count'];
$p['date'] = date(Util::$dateFormatInternal, $p['date']);
}
}
}
return $pages;
}
private static function getComments($type, $typeId)
{
@@ -300,11 +422,14 @@ class CommunityContent
private static function getScreenshots($type, $typeId)
{
$screenshots = DB::Aowow()->Query("
SELECT s.id, a.displayName AS user, s.date, s.width, s.height, s.type, s.typeId, s.caption, IF(s.status & 0x4, 1, 0) AS 'sticky'
SELECT s.id, a.displayName AS user, s.date, s.width, s.height, s.type, s.typeId, s.caption, IF(s.status & ?d, 1, 0) AS 'sticky'
FROM ?_screenshots s, ?_account a
WHERE s.type = ? AND s.typeId = ? AND s.status & 0x2 AND s.uploader = a.id",
WHERE s.type = ? AND s.typeId = ? AND s.status & ?d AND (s.status & ?d) = 0 AND s.uploader = a.id",
CC_FLAG_STICKY,
$type,
$typeId
$typeId,
CC_FLAG_APPROVED,
CC_FLAG_DELETED
);
// format data to meet requirements of the js

View File

@@ -91,7 +91,7 @@ define('CON_FLAG_TYPE_STRING', 0x08); //
define('CON_FLAG_OPT_LIST', 0x10); // single option
define('CON_FLAG_BITMASK', 0x20); // multiple options
define('CON_FLAG_PHP', 0x40); // applied with ini_set() [restrictions apply!]
define('CON_FLAG_PERSISTANT', 0x80); // can not be deleted
define('CON_FLAG_PERSISTENT', 0x80); // can not be deleted
// Auth Result
define('AUTH_OK', 0);

View File

@@ -1479,7 +1479,7 @@ class Util
$x['sourceB'] = $miscData['voterId'];
$x['amount'] = $action == SITEREP_ACTION_UPVOTED ? CFG_REP_REWARD_UPVOTED : CFG_REP_REWARD_DOWNVOTED;
break;
case SITEREP_ACTION_UPLOAD: // NYI
case SITEREP_ACTION_UPLOAD:
if (empty($miscData['id']) || empty($miscData['what']))
return false;

View File

@@ -70,6 +70,7 @@ switch ($pageCall)
case 'quests':
case 'race':
case 'races':
case 'screenshot': // prepare uploaded screenshots
case 'search': // tool: searches
case 'skill':
case 'skills':

View File

@@ -50,6 +50,7 @@ $lang = array(
'aboutUs' => "Über Aowow",
'and' => " und ",
'or' => " oder ",
'back' => "Zurück",
// filter
'extSearch' => "Erweiterte Suche",
@@ -136,9 +137,36 @@ $lang = array(
'dateFmtLong' => "d.m.Y \u\m H:i",
// error
'intError' => "Ein interner Fehler ist aufgetreten.",
'intError2' => "Ein interner Fehler ist aufgetreten. (%s)",
'genericError' => "Ein Fehler trat auf; aktualisiert die Seite und versucht es nochmal. Wenn der Fehler bestehen bleibt, bitte meldet es bei <a href='#contact'>feedback</a>", # LANG.genericerror
'bannedRating' => "Ihr wurdet davon gesperrt, Kommentare zu bewerten.", # LANG.tooltip_banned_rating
'tooManyVotes' => "Ihr habt die tägliche Grenze für erlaubte Bewertungen erreicht. Kommt morgen mal wieder!" # LANG.tooltip_too_many_votes
'tooManyVotes' => "Ihr habt die tägliche Grenze für erlaubte Bewertungen erreicht. Kommt morgen mal wieder!", # LANG.tooltip_too_many_votes
// screenshots
'prepError' => "Bei der Aufbereitung eures Screenshots ist ein Fehler aufgetreten",
'cropHint' => "Schneidet das Bild zu, indem ihr die Auswahl verschiebt.<br>Bitte beachtet <a href=\"?help=screenshots-tips-tricks\">Screenshots: Tipps & Tricks</a> für eine optimale Darstellung.",
'caption' => "Kurzbeschreibung",
'originalSize' => "Originalgröße",
'targetSize' => "Zielgröße",
'minSize' => "Mindestgröße",
'displayOn' => "Hochgeladen für: %s[br][%s=%d]",
'ssEdit' => "Screenshot bearbeiten",
'ssUpload' => "Screenshot hochladen",
'ssSubmit' => "Screenshot einsenden",
'ssErrors' => array(
'noUpload' => "Die Datei wurde nicht hochgeladen!",
'maxSize' => "Die Datei überschreitet die max. Größe von %s!",
'interrupted' => "Der Vorgang wurde unterbrochen!",
'noFile' => "Es wurde keine Datei empfangen!",
'noDest' => "Die Seite auf welcher der Screenshot angezeigt werden sollte existiert nicht!",
'notAllowed' => "Es ist euch nicht erlaubt einen Screenshot hochzuladen!",
'noImage' => "Die hochgeladene Datei ist kein Bild!",
'wrongFormat' => "Das Bild muss im PNG oder JPG-Format sein!",
'load' => "Das Bild konnte nicht geladen werden!",
'tooSmall' => "Die Abmessungen sind zu klein! (kleiner als %d x %d)",
'tooLarge' => "Die Abmessungen sind zu groß! (größer als %d x %d)"
)
),
'game' => array(
'achievement' => "Erfolg",
@@ -333,8 +361,6 @@ $lang = array(
'passMismatch' => "Die eingegebenen Kennworte stimmen nicht überein.",
'nameInUse' => "Es existiert bereits ein Konto mit diesem Namen.",
'mailInUse' => "Diese E-Mail-Adresse ist bereits mit einem Konto verbunden.",
'intError' => "Ein interner Fehler ist aufgetreten.",
'intError2' => "Ein interner Fehler ist aufgetreten. (%s)",
'isRecovering' => "Dieses Konto wird bereits wiederhergestellt. Folgt den Anweisungen in der Nachricht oder wartet %s bis das Token verfällt.",
'passCheckFail' => "Die Kennwörter stimmen nicht überein.", // message_passwordsdonotmatch
'newPassDiff' => "Euer neues Kennwort muss sich von eurem alten Kennwort unterscheiden." // message_newpassdifferent

View File

@@ -45,6 +45,7 @@ $lang = array(
'aboutUs' => "About us & contact",
'and' => " and ",
'or' => " or ",
'back' => "Back",
// filter
'extSearch' => "Extended search",
@@ -131,9 +132,36 @@ $lang = array(
'dateFmtLong' => "Y/m/d \a\\t H:i",
// error
'intError' => "An internal error has occurred.",
'intError2' => "An internal error has occurred. (%s)",
'genericError' => "An error has occurred; refresh the page and try again. If the error persists email <a href=\"#contact\">feedback</a>", # LANG.genericerror
'bannedRating' => "You have been banned from rating comments.", # LANG.tooltip_banned_rating
'tooManyVotes' => "You have reached the daily voting cap. Come back tomorrow!" # LANG.tooltip_too_many_votes
'tooManyVotes' => "You have reached the daily voting cap. Come back tomorrow!", # LANG.tooltip_too_many_votes
// screenshots
'prepError' => "An error occured preparing your screenshot",
'cropHint' => "Crop the image by dragging the selection.<br>Please refer to <a href=\"?help=screenshots-tips-tricks\">Screenshots: Tips & Tricks</a> for an optimal layout.",
'caption' => "Caption",
'originalSize' => "Original size",
'targetSize' => "Target size",
'minSize' => "Minimum size",
'displayOn' => "Displayed on: %s[br][%s=%d]",
'ssEdit' => "Edit uploaded screenshot",
'ssUpload' => "Screenshot Upload",
'ssSubmit' => "Submit Screenshot",
'ssErrors' => array(
'noUpload' => "The file was not uploaded!",
'maxSize' => "The file exceeds the maximum size of %s!",
'interrupted' => "The upload process was interrupted!",
'noFile' => "The file was not received!",
'noDest' => "The page this screenshot should be displayed on, does not exist!",
'notAllowed' => "You are not allowed to upload screenshots!",
'noImage' => "The uploaded file is not an image file!",
'wrongFormat' => "The image file must be a png or jpg!",
'load' => "The image file could not be loaded!",
'tooSmall' => "The image size is too small! (lower than %d x %d)",
'tooLarge' => "The image size is too large! (greater than %d x %d)"
)
),
'game' => array(
'achievement' => "achievement",
@@ -328,8 +356,6 @@ $lang = array(
'passMismatch' => "The passwords you entered do not match.",
'nameInUse' => "That username is already taken.",
'mailInUse' => "That email is already registered to an account.",
'intError' => "An internal error occured.",
'intError2' => "An internal error occured. (%s)",
'isRecovering' => "This account is already recovering. Follow the instructions in your email or wait %s for the token to expire.",
'passCheckFail' => "Passwords do not match.", // message_passwordsdonotmatch
'newPassDiff' => "Your new password must be different than your previous one." // message_newpassdifferent

View File

@@ -50,6 +50,7 @@ $lang = array(
'aboutUs' => "Sobre Aowow",
'and' => " y ",
'or' => " o ",
'back' => "Arrière",
// filter
'extSearch' => "Extender búsqueda",
@@ -136,9 +137,36 @@ $lang = array(
'dateFmtLong' => "d/m/Y \a \l\a\s H:i",
// error
'intError' => "[An internal error occured.]",
'intError2' => "[An internal error occured. (%s)]",
'genericError' => "Ha ocurrido un error; refresca la página e inténtalo de nuevo. Si el error persiste manda un correo a <a href='#contact'>feedback</a>", # LANG.genericerror
'bannedRating' => "Has sido baneado y no podrás valorar comentarios.", # LANG.tooltip_banned_rating
'tooManyVotes' => "Has alcanzado el límite diario de votos. Vuelve mañana." # LANG.tooltip_too_many_votes
'tooManyVotes' => "Has alcanzado el límite diario de votos. Vuelve mañana.", # LANG.tooltip_too_many_votes
// screenshots
'prepError' => "[An error occured preparing your screenshot]",
'cropHint' => "[Crop the image by dragging the selection.<br>Please refer to <a href=\"?help=screenshots-tips-tricks\">Screenshots: Tips & Tricks</a> for an optimal layout.]",
'caption' => "[Caption]",
'originalSize' => "[Original size]",
'targetSize' => "[Target size]",
'minSize' => "[Minimum size]",
'displayOn' => "[Displayed on: %s[br][%s=%d]]",
'ssEdit' => "[Edit uploaded screenshot]",
'ssUpload' => "[Screenshot Upload]",
'ssSubmit' => "[Submit Screenshot]",
'ssErrors' => array(
'noUpload' => "[The file was not uploaded!]",
'maxSize' => "[The file exceeds the maximum size of %s!]",
'interrupted' => "[The upload process was interrupted!]",
'noFile' => "[The file was not received!]",
'noDest' => "[The page this screenshot should be displayed on, does not exist!]",
'notAllowed' => "[You are not allowed to upload screenshots!]",
'noImage' => "[The uploaded file is not an image file!]",
'wrongFormat' => "[The image file must be a png or jpg!]",
'load' => "[The image file could not be loaded!]",
'tooSmall' => "[The image size is too small! (lower than %d x %d)]",
'tooLarge' => "[The image size is too large! (greater than %d x %d)]"
)
),
'game' => array(
'achievement' => "logro",
@@ -334,8 +362,6 @@ $lang = array(
'passMismatch' => "The passwords you entered do not match.",
'nameInUse' => "That username is already taken.",
'mailInUse' => "That email is already registered to an account.",
'intError' => "An internal error occured.",
'intError2' => "An internal error occured. (%s)",
'isRecovering' => "This account is already recovering. Follow the instructions in your email or wait %s for the token to expire.",
'passCheckFail' => "Las contraseñas no son iguales.", // message_passwordsdonotmatch
'newPassDiff' => "Su nueva contraseña tiene que ser diferente a Su contraseña anterior." // message_newpassdifferent

View File

@@ -50,6 +50,7 @@ $lang = array(
'aboutUs' => "À propos de Aowow",
'and' => " et ",
'or' => " ou ",
'back' => "Redro",
// filter
'extSearch' => "Recherche avancée",
@@ -136,9 +137,36 @@ $lang = array(
'dateFmtLong' => "Y-m-d à H:i",
// error
'intError' => "[An internal error occured.]",
'intError2' => "[An internal error occured. (%s)]",
'genericError' => "Une erreur est survenue; Actualisez la page et essayez à nouveau. Si l'erreur persiste, envoyez un email à <a href='#contact'>feedback</a>", # LANG.genericerror
'bannedRating' => "Vous avez été banni du score des commentaires.", # LANG.tooltip_banned_rating
'tooManyVotes' => "Vous avez voté trop souvent aujourd'hui! Revenez demain." # LANG.tooltip_too_many_votes
'tooManyVotes' => "Vous avez voté trop souvent aujourd'hui! Revenez demain.", # LANG.tooltip_too_many_votes
// screenshots
'prepError' => "[An error occured preparing your screenshot]",
'cropHint' => "[Crop the image by dragging the selection.<br>Please refer to <a href=\"?help=screenshots-tips-tricks\">Screenshots: Tips & Tricks</a> for an optimal layout.]",
'caption' => "[Caption]",
'originalSize' => "[Original size]",
'targetSize' => "[Target size]",
'minSize' => "[Minimum size]",
'displayOn' => "[Displayed on: %s[br][%s=%d]]",
'ssEdit' => "[Edit uploaded screenshot]",
'ssUpload' => "[Screenshot Upload]",
'ssSubmit' => "[Submit Screenshot]",
'ssErrors' => array(
'noUpload' => "[The file was not uploaded!]",
'maxSize' => "[The file exceeds the maximum size of %s!]",
'interrupted' => "[The upload process was interrupted!]",
'noFile' => "[The file was not received!]",
'noDest' => "[The page this screenshot should be displayed on, does not exist!]",
'notAllowed' => "[You are not allowed to upload screenshots!]",
'noImage' => "[The uploaded file is not an image file!]",
'wrongFormat' => "[The image file must be a png or jpg!]",
'load' => "[The image file could not be loaded!]",
'tooSmall' => "[The image size is too small! (lower than %d x %d)]",
'tooLarge' => "[The image size is too large! (greater than %d x %d)]"
)
),
'game' => array(
'achievement' => "haut fait",
@@ -333,8 +361,6 @@ $lang = array(
'passMismatch' => "The passwords you entered do not match.",
'nameInUse' => "That username is already taken.",
'mailInUse' => "That email is already registered to an account.",
'intError' => "An internal error occured.",
'intError2' => "An internal error occured. (%s)",
'isRecovering' => "This account is already recovering. Follow the instructions in your email or wait %s for the token to expire.",
'passCheckFail' => "Les mots de passe ne correspondent pas.", // message_passwordsdonotmatch
'newPassDiff' => "Votre nouveau mot de passe doit être différent de l'ancien." // message_newpassdifferent

View File

@@ -50,6 +50,7 @@ $lang = array(
'aboutUs' => "О Aowow",
'and' => " и ",
'or' => " или ",
'back' => "Назад",
// filter
'extSearch' => "Расширенный поиск",
@@ -136,9 +137,36 @@ $lang = array(
'dateFmtLong' => "Y-m-d в H:i",
// error
'intError' => "[An internal error occured.]",
'intError2' => "[An internal error occured. (%s)]",
'genericError' => "Произошла ошибка; обновите страницу и попробуйте снова. Если ситуация повторяется, отправьте сообщение на <a href='#contact'>feedback</a>", # LANG.genericerror
'bannedRating' => "Вам была заблокирована возможность оценивать комментарии.", # LANG.tooltip_banned_rating
'tooManyVotes' => "Вы сегодня проголосовали слишком много раз! Вы сможете продолжить завтра." # LANG.tooltip_too_many_votes
'tooManyVotes' => "Вы сегодня проголосовали слишком много раз! Вы сможете продолжить завтра.", # LANG.tooltip_too_many_votes
// screenshots
'prepError' => "[An error occured preparing your screenshot]",
'cropHint' => "[Crop the image by dragging the selection.<br>Please refer to <a href=\"?help=screenshots-tips-tricks\">Screenshots: Tips & Tricks</a> for an optimal layout.]",
'caption' => "[Caption]",
'originalSize' => "[Original size]",
'targetSize' => "[Target size]",
'minSize' => "[Minimum size]",
'displayOn' => "[Displayed on: %s[br][%s=%d]]",
'ssEdit' => "[Edit uploaded screenshot]",
'ssUpload' => "[Screenshot Upload]",
'ssSubmit' => "[Submit Screenshot]",
'ssErrors' => array(
'noUpload' => "[The file was not uploaded!]",
'maxSize' => "[The file exceeds the maximum size of %s!]",
'interrupted' => "[The upload process was interrupted!]",
'noFile' => "[The file was not received!]",
'noDest' => "[The page this screenshot should be displayed on, does not exist!]",
'notAllowed' => "[You are not allowed to upload screenshots!]",
'noImage' => "[The uploaded file is not an image file!]",
'wrongFormat' => "[The image file must be a png or jpg!]",
'load' => "[The image file could not be loaded!]",
'tooSmall' => "[The image size is too small! (lower than %d x %d)]",
'tooLarge' => "[The image size is too large! (greater than %d x %d)]"
)
),
'game' => array(
'achievement' => "достижение",
@@ -333,8 +361,6 @@ $lang = array(
'passMismatch' => "The passwords you entered do not match.",
'nameInUse' => "That username is already taken.",
'mailInUse' => "That email is already registered to an account.",
'intError' => "An internal error occured.",
'intError2' => "An internal error occured. (%s)",
'isRecovering' => "This account is already recovering. Follow the instructions in your email or wait %s for the token to expire.",
'passCheckFail' => "Пароли не совпадают.", // message_passwordsdonotmatch
'newPassDiff' => "Прежний и новый пароли не должны совпадать." // message_newpassdifferent

View File

@@ -323,7 +323,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
{
case AUTH_OK:
if (!User::$ip)
return Lang::$account['intError'];
return Lang::$main['intError'];
// reset account status, update expiration
DB::Aowow()->query('UPDATE ?_account SET prevIP = IF(curIp = ?, prevIP, curIP), curIP = IF(curIp = ?, curIP, ?), allowExpire = ?d, status = 0, statusTimer = 0, token = "" WHERE user = ?',
@@ -354,7 +354,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
return sprintf(Lang::$account['loginExceeded'], Util::formatTime(CFG_FAILED_AUTH_EXCLUSION * 1000));
case AUTH_INTERNAL_ERR:
User::destroy();
return Lang::$account['intError'];
return Lang::$main['intError'];
default:
return;
}
@@ -391,7 +391,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
// check ip
if (!User::$ip)
return Lang::$account['intError'];
return Lang::$main['intError'];
// limit account creation
$ip = DB::Aowow()->selectRow('SELECT ip, count, unbanDate FROM ?_account_bannedips WHERE type = 1 AND ip = ?', User::$ip);
@@ -420,7 +420,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
$token
);
if (!$id) // something went wrong
return Lang::$account['intError'];
return Lang::$main['intError'];
else if ($_ = $this->sendMail($email, Lang::$mail['accConfirm'][0], sprintf(Lang::$mail['accConfirm'][1], $token), CFG_ACCOUNT_CREATE_SAVE_DECAY))
{
// success:: update ip-bans
@@ -460,7 +460,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
return Lang::$account['newPassDiff'];
if (!DB::Aowow()->query('UPDATE ?_account SET passHash = ?, status = ?d WHERE id = ?d', User::hashcrypt($newPass), ACC_STATUS_OK, $uRow['id']))
return Lang::$account['intError'];
return Lang::$main['intError'];
}
private function doRecoverUser($target)
@@ -475,7 +475,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
private function initRecovery($type, $target, $delay, &$token)
{
if (!$type)
return Lang::$account['intError'];
return Lang::$main['intError'];
// check if already processing
if ($_ = DB::Aowow()->selectCell('SELECT statusTimer - UNIX_TIMESTAMP() FROM ?_account WHERE email = ? AND status <> ?d AND statusTimer > UNIX_TIMESTAMP()', $target, ACC_STATUS_OK))
@@ -484,7 +484,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
// create new token and write to db
$token = Util::createHash();
if (!DB::Aowow()->query('UPDATE ?_account SET token = ?, status = ?d, statusTimer = UNIX_TIMESTAMP() + ?d WHERE email = ?', $token, $type, $delay, $target))
return Lang::$account['intError'];
return Lang::$main['intError'];
}
private function sendMail($target, $subj, $msg, $delay = 300)
@@ -497,7 +497,7 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup
'X-Mailer: PHP/' . phpversion();
if (!mail($target, $subj, $msg, $header))
return sprintf(Lang::$account['intError2'], 'send mail');
return sprintf(Lang::$main['intError2'], 'send mail');
}
private function getNext($forHeader = false)

View File

@@ -19,6 +19,14 @@ class AdminPage extends GenericPage
{
switch ($pageParam)
{
case 'screenshots':
$this->reqUGroup = U_GROUP_STAFF | 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';
@@ -439,6 +447,45 @@ class AdminPage extends GenericPage
}
}
private function handleScreenshots()
{
$this->addJS('screenshot.js');
$this->addCSS(array(
['string' => '.layout {margin: 0px 25px; max-width: inherit; min-width: 1200px; }'],
['string' => '#highlightedRow { background-color: #322C1C; }']
));
$ssGetAll = isset($_GET['all']) && empty($_GET['all']);
$ssPages = [];
$ssData = [];
$nMatches = 0;
if (!empty($_GET['type']) && !empty($_GET['typeid']))
{
$ssData = CommunityContent::getScreenshotsForManager(intVal($_GET['type']), intVal($_GET['typeid']));
$nMatches = count($ssData);
}
else if (!empty($_GET['user']))
{
$name = urldecode($_GET['user']);
if (strlen($name) > 3)
{
if ($uId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE displayName = ?', ucFirst($name)))
{
$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 configAddRow($r)
{
$buff = '<tr>';
@@ -487,7 +534,7 @@ class AdminPage extends GenericPage
else
$buff .= '|<a class="icon-refresh tip disabled"></a>';
if (!($r['flags'] & CON_FLAG_PERSISTANT))
if (!($r['flags'] & CON_FLAG_PERSISTENT))
$buff .= '|<a class="icon-delete tip" onclick="cfg_remove.bind(this, \''.$key.'\')()" onmouseover="$WH.Tooltip.showAtCursor(event, \'Remove Setting\', 0, 0, \'q\')" onmousemove="$WH.Tooltip.cursorUpdate(event)" onmouseout="$WH.Tooltip.hide()"></a>';
$buff .= '<span class="status"></span></td></tr>';

301
pages/screenshot.php Normal file
View File

@@ -0,0 +1,301 @@
<?php
if (!defined('AOWOW_REVISION'))
die('illegal access');
// filename: Username-type-typeId-<hash>[_original].jpg
class ScreenshotPage extends GenericPage
{
protected $tpl = 'screenshot';
protected $js = ['Cropper.js'];
protected $css = [['path' => 'Cropper.css']];
protected $reqAuth = true;
private $tmpPath = 'static/uploads/temp/';
private $pendingPath = 'static/uploads/screenshots/pending/';
private $destination = null;
protected $destType = 0;
protected $destTypeId = 0;
public function __construct($pageCall, $pageParam)
{
parent::__construct($pageCall, $pageParam);
$this->name = Lang::$main['ssEdit'];
$this->caption = @$_POST['screenshotcaption']; // do not htmlEscape. It's applied as textnode
// what are its other uses..? (finalize is custom)
if ($pageParam == 'finalize')
{
if (!$this->handleFinalize())
$this->error();
}
else if ($pageParam != 'add')
$this->error();
// get screenshot destination
foreach ($_GET as $k => $v)
{
if ($v) // taret delivered as empty type.typeId key
continue;
$x = explode('_', $k); // . => _ as array key
if (count($x) != 2)
continue;
// no such type
if (empty(Util::$typeClasses[$x[0]]))
continue;
$t = Util::$typeClasses[$x[0]];
$c = [['id', intVal($x[1])]];
if ($x[0] == TYPE_WORLDEVENT) // ohforfsake..
$c = array_merge($c, ['OR', ['holidayId', intVal($x[1])]]);
$this->destination = new $t($c);
// no such typeId
if ($this->destination->error)
continue;
$this->destType = intVal($x[0]);
$this->destTypeId = intVal($x[1]);
}
}
private function handleFinalize()
{
if (empty($_SESSION['ssUpload']))
return false;
// as its saved in session it should be valid
$file = $_SESSION['ssUpload']['file'];
// check tmp file
$fullPath = $this->tmpPath.$file.'_original.jpg';
if (!file_exists($fullPath))
return false;
// check post data
if (empty($_POST) || empty($_POST['selection']))
return false;
$dims = explode(',', $_POST['selection']);
if (count($dims) != 4)
return false;
Util::checkNumeric($dims);
// 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, uploader, date, width, height, caption) VALUES (?d, ?d, ?d, UNIX_TIMESTAMP(), ?d, ?d, ?)',
$_SESSION['ssUpload']['type'], $_SESSION['ssUpload']['typeId'],
User::$id,
$w, $h,
$this->caption
);
// write to file
if (is_int($newId)) // 0 is valid, NULL or FALSE is not
imagejpeg($destImg, $this->pendingPath.$newId.'.jpg', 100);
unset($_SESSION['ssUpload']);
header('Location: ?user='.User::$displayName.'#screenshots');
}
protected function generateContent()
{
$maxW = 488;
$maxH = 325;
$minCrop = CFG_SCREENSHOT_MIN_SIZE;
if ($minCrop <= 0)
{
Util::addNote(U_GROUP_DEV | U_GROUP_ADMIN, 'ScreenshotPage::generateContent() - config error: dimensions for uploaded screenshots egual or less than zero. Value forced to 200');
$minCrop = 200;
}
if (!$this->destType)
{
$this->error = Lang::$main['ssErrors']['noDest'];
return;
}
if (User::$banStatus & ACC_BAN_SCREENSHOT)
{
$this->error = Lang::$main['ssErrors']['notAllowed'];
return;
}
if ($_ = $this->validateScreenshot($isPNG))
{
$this->error = $_;
return;
}
$im = $isPNG ? $this->loadFromPNG() : $this->loadFromJPG();
if (!$im)
{
$this->error = Lang::$main['ssErrors']['load'];
return;
}
$name = User::$displayName.'-'.$this->destType.'-'.$this->destTypeId.'-'.Util::createHash(16);
$oSize = $rSize = [imagesx($im), imagesy($im)];
$rel = $oSize[0] / $oSize[1];
// not sure if this is the best way
$_SESSION['ssUpload'] = array(
'file' => $name,
'type' => $this->destType,
'typeId' => $this->destTypeId
);
// check for oversize and refit to crop-screen
if ($rel >= 1.5 && $oSize[0] > $maxW)
$rSize = [$maxW, $maxW / $rel];
else if ($rel < 1.5 && $oSize[1] > $maxH)
$rSize = [$maxH * $rel, $maxH];
$this->writeImage($im, $oSize, $name.'_original'); // use this image for work
$this->writeImage($im, $rSize, $name); // use this image to display
// 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' => $this->tmpPath.$name.'.jpg',
'parent' => 'ss-container',
'oWidth' => $oSize[0],
'rWidth' => $rSize[0],
'oHeight' => $oSize[1],
'rHeight' => $rSize[1],
];
$infobox = [];
// target
$infobox[] = sprintf(Lang::$main['displayOn'], Util::ucFirst(Lang::$game[Util::$typeStrings[$this->destType]]), Util::$typeStrings[$this->destType], $this->destTypeId);
$this->extendGlobalIds($this->destType, $this->destTypeId);
// dimensions
$infobox[] = Lang::$main['originalSize'].Lang::$main['colon'].$oSize[0].' x '.$oSize[1];
$infobox[] = Lang::$main['targetSize'].Lang::$main['colon'].'[span id=qf-newSize][/span]';
// minimum dimensions
if (!User::isInGroup(U_GROUP_STAFF))
{
$infobox[] = Lang::$main['minSize'].Lang::$main['colon'].$minCrop.' x '.$minCrop;
$this->cropper['minCrop'] = $minCrop;
}
$this->infobox = '[ul][li]'.implode('[/li][li]', $infobox).'[/li][/ul]';
}
private function loadFromPNG()
{
$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()
{
return imagecreatefromjpeg($_FILES['screenshotfile']['tmp_name']);
}
private function writeImage($im, $dims, $file)
{
if ($res = imagecreatetruecolor($dims[0], $dims[1]))
if (imagecopyresampled($res, $im, 0, 0, 0, 0, $dims[0], $dims[1], imagesx($im), imagesy($im)))
if (imagejpeg($res, $this->tmpPath.$file.'.jpg', 100))
return true;
return false;
}
private function validateScreenshot(&$isPNG = false)
{
// no upload happened or some error occured
if (!$_FILES || empty($_FILES['screenshotfile']))
return Lang::$main['ssErrors']['noUpload'];
switch ($_FILES['screenshotfile']['error'])
{
case 1:
return sprintf(Lang::$main['ssErrors']['maxSize'], ini_get('upload_max_filesize'));;
case 3:
return Lang::$main['ssErrors']['interrupted'];
case 4:
return Lang::$main['ssErrors']['noFile'];
case 6:
Util::addNote(U_GROUP_ADMIN, 'ScreenshotPage::validateScreenshot() - temporary upload directory is not set');
return Lang::$main['intError'];
case 7:
Util::addNote(U_GROUP_ADMIN, 'ScreenshotPage::validateScreenshot() - could not write temporary file to disk');
return Lang::$main['genericError'];
}
// points to invalid file (hack attempt)
if (!is_uploaded_file($_FILES['screenshotfile']['tmp_name']))
{
Util::addNote(U_GROUP_ADMIN, 'ScreenshotPage::validateScreenshot() - uploaded file not in upload directory');
return Lang::$main['genericError'];
}
// invalid file
$is = getimagesize($_FILES['screenshotfile']['tmp_name']);
if (!$is || empty($is['mime']))
return Lang::$main['ssErrors']['notImage'];
// allow jpeg, png
switch ($is['mime'])
{
case 'image/png':
$isPNG = true;
case 'image/jpg':
case 'image/jpeg':
break;
default:
return Lang::$main['ssErrors']['wrongFormat'];
}
// size-missmatch: 4k UHD upper limit; 150px lower limit
if ($is[0] < 150 || $is[1] < 150)
return sprintf(Lang::$main['ssErrors']['tooSmall'], 150, 150);
if ($is[0] > 3840 || $is[1] > 2160)
return sprintf(Lang::$main['ssErrors']['tooLarge'], 150, 150);
return null;
}
protected function generatePath() { }
protected function generateTitle()
{
array_unshift($this->title, Lang::$main['ssUpload']);
}
}
?>

View File

@@ -0,0 +1,16 @@
DROP TABLE IF EXISTS `aowow_screenshots`;
CREATE TABLE IF NOT EXISTS `aowow_screenshots` (
`id` int(16) unsigned NOT NULL AUTO_INCREMENT,
`type` tinyint(4) unsigned NOT NULL,
`typeId` mediumint(9) NOT NULL,
`uploader` int(16) unsigned NOT NULL,
`date` int(32) unsigned NOT NULL,
`width` smallint(5) unsigned NOT NULL,
`height` smallint(5) unsigned NOT NULL,
`caption` varchar(250) DEFAULT NULL,
`status` tinyint(3) unsigned NOT NULL COMMENT 'see defines.php - CC_FLAG_*',
`approvedBy` int(16) unsigned DEFAULT NULL,
`deletedBy` int(16) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `type` (`type`,`typeId`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

129
static/css/Cropper.css Normal file
View File

@@ -0,0 +1,129 @@
.cropper {
width:488px;
height:325px;
border:3px solid #404040;
text-align:center;
background:black;
}
.canvas {
position:relative;
margin:0 auto;
}
.selection {
position:absolute;
cursor:move;
}
.selection .opac {
position:absolute;
left:0;
top:0;
width:100%;
height:100%;
background-color:#369;
filter:alpha(opacity=40);
opacity:.4;
}
.selection div {
position:absolute;
font-size:1px;
}
.selection .grabw {
left:-4px;
top:0;
width:9px;
height:100%;
cursor:w-resize;
}
.selection .grabe {
right:-4px;
top:0;
width:9px;
height:100%;
cursor:w-resize;
}
.selection .grabn {
left:0;
top:-4px;
width:100%;
height:9px;
cursor:n-resize;
}
.selection .grabs {
left:0;
bottom:-4px;
width:100%;
height:9px;
cursor:n-resize;
}
.selection .grabnw {
left:-3px;
top:-3px;
width:10px;
height:10px;
cursor:nw-resize;
}
.selection .grabne {
right:-3px;
top:-3px;
width:10px;
height:10px;
cursor:ne-resize;
}
.selection .grabsw {
left:-3px;
bottom:-3px;
width:10px;
height:10px;
cursor:ne-resize;
}
.selection .grabse {
right:-3px;
bottom:-3px;
width:10px;
height:10px;
cursor:nw-resize;
}
.selection .hborder {
left:0;
top:0;
width:100%;
height:1px;
background:url(//wowimg.zamimg.com/images/ui/misc/selection-h.gif) repeat-x top;
}
.selection .hborder2 {
left:0;
bottom:0;
width:100%;
height:1px;
background:url(//wowimg.zamimg.com/images/ui/misc/selection-h.gif) repeat-x bottom;
}
.selection .vborder {
left:0;
top:0;
width:1px;
height:100%;
background:url(//wowimg.zamimg.com/images/ui/misc/selection-v.gif) repeat-y left;
}
.selection .vborder2 {
right:0;
top:0;
width:1px;
height:100%;
background:url(//wowimg.zamimg.com/images/ui/misc/selection-v.gif) repeat-y right;
}

325
static/js/Cropper.js Normal file
View File

@@ -0,0 +1,325 @@
function Cropper(opts) {
var
canvas,
div,
_;
$WH.cO(this, opts);
if (this.parent) {
this.parent = $WH.ge(this.parent);
}
else {
return;
}
this.locked = 0;
this.selection = { node: $WH.ce('div') };
this.canvas = { node: $WH.ce('div') };
if (this.minCrop == null) {
this.minCrop = [150, 150];
}
else if (!isNaN(this.minCrop)) {
this.minCrop = [(this.minCrop || 150), (this.minCrop || 150)];
}
this.minCrop[0] = Math.ceil(this.minCrop[0] * this.rWidth / this.oWidth);
this.minCrop[1] = Math.ceil(this.minCrop[1] * this.rHeight / this.oHeight);
canvas = this.container = $WH.ce('div');
canvas.className = 'cropper';
canvas = this.canvas.node;
canvas.className = 'canvas';
if (this.rHeight < 325 && !this.blog) {
canvas.style.marginTop = Math.floor((325 - this.rHeight) / 2) + 'px'
}
canvas.style.width = this.rWidth + 'px';
canvas.style.height = this.rHeight + 'px';
canvas.style.backgroundImage = 'url(' + this.url + ')';
$WH.ns(canvas);
var
f = (this.minCrop[0] > this.minCrop[1] ? 0 : 1),
e = (this.oWidth > this.oHeight ? 0 : 1),
h = [this.oWidth, this.oHeight],
g = this.minCrop[f] / this.minCrop[1 - f],
l = [];
if (Math.floor(h[e] / 2) >= this.minCrop[e]) {
l[f] = 100;
}
else {
l[f] = Math.ceil(this.minCrop[f] / h[f] * 100 * 1000) / 1000;
}
l[1 - f] = l[f] / g;
if ((this.type == 1 && this.typeId == 15384) && g_user.roles & (U_GROUP_MODERATOR | U_GROUP_EDITOR)) {
this.minCrop[0] = 1;
this.minCrop[1] = 1;
l = [100, 100];
}
canvas = this.selection.node;
canvas.className = 'selection';
canvas.style.width = l[0] + '%';
canvas.style.height = l[1] + '%';
canvas.style.left = (100 - l[0]) / 2 + '%';
canvas.style.top = (100 - l[1]) / 2 + '%';
div = $WH.ce('div');
div.className = 'opac';
div.onmousedown = Cropper.lock.bind(this, 5);
canvas.appendChild(div);
_ = ['hborder', 'hborder2', 'vborder', 'vborder2'];
for (var i = 0, len = _.length; i < len; ++i) {
div = $WH.ce('div');
div.className = _[i];
canvas.appendChild(div);
}
_ = [['w', 4], ['e', 6], ['n', 8], ['s', 2], ['nw', 7], ['ne', 9], ['sw', 1], ['se', 3]];
for (var i = 0, len = _.length; i < len; ++i) {
div = $WH.ce('div');
div.className = 'grab' + _[i][0];
div.onmousedown = Cropper.lock.bind(this, _[i][1]);
canvas.appendChild(div);
}
this.canvas.node.appendChild(this.selection.node);
this.container.appendChild(this.canvas.node);
this.parent.appendChild(this.container);
$WH.aE(document, 'mousedown', Cropper.mouseDown.bind(this));
$WH.aE(document, 'mouseup', Cropper.mouseUp.bind(this));
$WH.aE(document, 'mousemove', Cropper.mouseMove.bind(this));
}
Cropper.prototype = {
refreshCoords: function () {
this.selection.coords = $WH.ac(this.selection.node);
this.selection.size = [this.selection.node.offsetWidth, this.selection.node.offsetHeight];
this.canvas.coords = $WH.ac(this.canvas.node);
},
getCoords: function () {
this.refreshCoords();
var
left = this.selection.coords[0] - this.canvas.coords[0],
top = this.selection.coords[1] - this.canvas.coords[1],
width = this.selection.size[0],
height = this.selection.size[1];
var
w = this.rWidth,
h = this.rHeight;
return [
(left / w).toFixed(3),
(top / h).toFixed(3),
(width / w).toFixed(3),
(height / h).toFixed(3)
].join(',');
},
moveSelection: function (left, top, width, height) {
this.selection.node.style.left = left + 'px';
this.selection.node.style.top = top + 'px';
this.selection.node.style.width = width + 'px';
this.selection.node.style.height = height + 'px';
},
selectAll: function () {
this.moveSelection(0, 0, this.rWidth, this.rHeight);
}
};
Cropper.lock = function (locked) {
this.locked = locked;
return false;
};
Cropper.mouseDown = function (ev) {
ev = $WH.$E(ev);
this.drag = 1;
this.anchorX = ev.clientX;
this.anchorY = ev.clientY;
this.refreshCoords();
};
Cropper.mouseUp = function (ev) {
ev = $WH.$E(ev);
this.drag = this.locked = 0;
};
Cropper.mouseMove = function (ev) {
if (this.drag && this.locked) {
ev = $WH.$E(ev);
var
left = this.selection.coords[0] - this.canvas.coords[0],
top = this.selection.coords[1] - this.canvas.coords[1],
width = left + this.selection.size[0],
height = top + this.selection.size[1];
var
x = (ev.clientX - this.anchorX),
y = (ev.clientY - this.anchorY);
var locks = null;
if (this.locked == 5) {
left += x;
width += x;
top += y;
height += y;
}
else {
if (this.locked == 1 || this.locked == 4 || this.locked == 7) {
left += Math.max(x, -left);
left = Math.min(left, this.rWidth);
if (this.locked == 4) {
locks = 'x';
}
else {
locks = 'xy';
}
if (Math.abs(left - width) < this.minCrop[0]) {
if (left > width) {
left = width + this.minCrop[0];
}
else {
left = width - this.minCrop[0];
}
}
}
else if (this.locked == 3 || this.locked == 6 || this.locked == 9) {
width += Math.min(x, this.rWidth - width);
width = Math.max(width, 0);
if (this.locked == 6) {
locks = 'x';
}
else {
locks = 'xy';
}
if (Math.abs(left - width) < this.minCrop[0]) {
if (width > left) {
width = left + this.minCrop[0];
}
else {
width = left - this.minCrop[0];
}
}
}
if (this.locked == 1 || this.locked == 2 || this.locked == 3) {
height += Math.min(y, this.rHeight - height);
height = Math.max(height, 0);
if (this.locked == 2) {
locks = 'y';
}
else {
locks = 'xy';
}
if (Math.abs(top - height) < this.minCrop[1]) {
if (height > top) {
height = top + this.minCrop[1];
}
else {
height = top - this.minCrop[1];
}
}
}
else if (this.locked == 7 || this.locked == 8 || this.locked == 9) {
top += Math.max(y, -top);
top = Math.min(top, this.rHeight);
if (this.locked == 8) {
locks = 'y';
}
else {
locks = 'xy';
}
if (Math.abs(top - height) < this.minCrop[1]) {
if (top > height) {
top = height + this.minCrop[1];
}
else {
top = height - this.minCrop[1];
}
}
}
}
if (left > width) {
var _ = left;
left = width;
width = _;
}
if (top > height) {
var _ = top;
top = height;
height = _;
}
if (this.constraint) {
var
absX = Math.abs(x),
absY = Math.abs(y);
if (locks == 'x' || (locks == 'xy' && absX > absY)) {
var b = width - left;
var k = this.constraint[1] / this.constraint[0];
var p = b * k;
height = top + p;
}
else if (locks == 'y' || (locks == 'xy' && absX < absY)) {
var p = height - top;
var k = this.constraint[0] / this.constraint[1];
var b = p * k;
width = left + b;
}
}
if (left < 0) {
width -= left;
left = 0;
}
if (top < 0) {
height -= top;
top = 0;
}
if (width > this.rWidth) {
left -= (width - this.rWidth);
width = this.rWidth;
}
if (height > this.rHeight) {
top -= (height - this.rHeight);
height = this.rHeight;
}
this.moveSelection(left, top, width - left, height - top);
}
};

1
static/js/screenshot.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
<title><?php echo htmlentities(implode(' - ', $this->title)); ?></title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="SHORTCUT ICON" href="<?php echo STATIC_URL; ?>/images/logos/favicon.ico" />
<link rel="search" type="application/opensearchdescription+xml" href="<?php echo STATIC_URL; ?>/download/searchplugins/aowow.xml" title="Aowow" />
<link rel="stylesheet" type="text/css" href="<?php echo STATIC_URL.'/css/basic.css?'.AOWOW_REVISION; ?>" />
@@ -8,7 +8,7 @@
<link rel="stylesheet" type="text/css" href="<?php echo STATIC_URL.'/css/aowow.css?'.AOWOW_REVISION; ?>" />
<link rel="stylesheet" type="text/css" href="<?php echo STATIC_URL.'/css/locale_'.User::$localeString.'.css?'.AOWOW_REVISION; ?>" />
<?php
if (User::isInGroup(U_GROUP_STAFF)):
if (User::isInGroup(U_GROUP_STAFF | U_GROUP_SCREENSHOT | U_GROUP_VIDEO)):
echo ' <link rel="stylesheet" type="text/css" href="'.STATIC_URL.'/css/staff.css?'.AOWOW_REVISION."\" />\n";
endif;
@@ -40,7 +40,7 @@ endif;
<script src="<?php echo STATIC_URL.'/js/locale.js?'.AOWOW_REVISION; ?>" type="text/javascript"></script>
<script src="<?php echo STATIC_URL.'/js/Markup.js?'.AOWOW_REVISION; ?>" type="text/javascript"></script>
<?php
if (User::isInGroup(U_GROUP_STAFF)):
if (User::isInGroup(U_GROUP_STAFF | U_GROUP_SCREENSHOT | U_GROUP_VIDEO)):
echo ' <script src="'.STATIC_URL.'/js/staff.js?'.AOWOW_REVISION."\" type=\"text/javascript\"></script>\n";
endif;

View File

@@ -36,12 +36,22 @@ if (!empty($this->type) && !empty($this->typeId)):
?>
<tr><th id="infobox-screenshots"><?php echo Lang::$main['screenshots']; ?></th></tr>
<tr><td><div class="infobox-spacer"></div><div id="infobox-sticky-ss"></div></td></tr>
<?php
if (User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_VIDEO) || !empty($this->community['vi'])):
?>
<tr><th id="infobox-videos"><?php echo Lang::$main['videos']; ?></th></tr>
<tr><td><div class="infobox-spacer"></div><div id="infobox-sticky-vi"></div></td></tr>
<?php
endif;
?>
</table>
<script type="text/javascript">ss_appendSticky()</script>
<?php
if (User::isInGroup(U_GROUP_ADMIN | U_GROUP_BUREAU | U_GROUP_VIDEO) || !empty($this->community['vi'])):
?>
<script type="text/javascript">vi_appendSticky()</script>
<?php
endif;
else:
echo " </table>\n";
endif;

View File

@@ -0,0 +1,138 @@
<?php $this->brick('header'); ?>
<div class="main" id="main">
<div class="main-precontents" id="main-precontents"></div>
<div class="main-contents" id="main-contents">
<?php
$this->brick('announcement');
$this->brick('pageTemplate');
?>
<div class="text">
<h1><?php echo $this->name; ?></h1>
<table>
<tr>
<td>User: </td>
<td colspan="2"><input type="text" id="usermanage" size="23"></td>
<td>&raquo;&nbsp;<a href="#" onClick="ss_ManageUser()">Search by User</a></td>
</tr>
<tr>
<td>Page: </td>
<td>
<select id="pagetype">
<?php
foreach (Util::$typeStrings as $i => $str):
if (isset(Lang::$game[$str])):
echo " <option value=\"".$i."\">".Util::ucFirst(Lang::$game[$str])."</option>\n";
endif;
endforeach;
?>
</select>
</td>
<td>#<input type="number" size="5" id="pagetypeid"></td>
<td>&raquo;&nbsp;<a href="#" onClick="ss_Manage(null, $('#pagetype').val(), $('#pagetypeid').val())">Search by Page</a></td>
</tr>
</table>
<hr />
<table style="width:100%;">
<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"><?php echo $this->ssNFound ? ' &ndash; <a href="?admin=screenshots&all">Show All</a> ('.$this->ssNFound.')' : null; ?></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>
<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>
</div>
</td>
<td id="pages-container" style="vertical-align: top;"></td>
<td id="data-container" style="vertical-align: top;"><table class="grid" id="theScreenshotsList"><thead><tr>
<th>Screenshot</th>
<th>Id</th>
<th>Caption</th>
<th>Date</th>
<th>Uploader</th>
<th>Status</th>
<th>Options</th>
</tr></thead></table></td>
</tr></tbody>
</table>
<script type="text/javascript">
var hasLoader = false;
function ajaxAnchor(el)
{
if (!el.href || hasLoader)
return;
$('#withselected').find('h4').append("&nbsp;").append(CreateAjaxLoader());
hasLoader = true;
new Ajax(el.href, {
method: 'get',
onSuccess: function(xhr) {
hasLoader = false;
$('#withselected img').remove();
var g = $WH.g_getGets();
if (g.type && g.typeid)
ss_Manage(null, g.type, g.typeid);
else if (g.user)
ss_ManageUser();
else
ss_Refresh();
}
});
}
$WH.ge('usermanage').onkeydown = function(e)
{
e = $WH.$E(e);
if (e.keyCode != 13)
return;
ss_ManageUser();
}
$WH.ge('pagetypeid').onkeydown = function(e)
{
e = $WH.$E(e);
var validKeys = [8, 9, 13, 35, 36, 37, 39, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57];
if (!e.ctrlKey && $WH.in_array(validKeys, e.keyCode) == -1)
return false;
if (e.keyCode == 13 && this.value != '')
ss_Manage();
return true;
}
<?php
if ($this->getAll):
echo " var ss_getAll = true;\n";
endif;
if ($this->ssPages):
echo " var ssm_screenshotPages = ".json_encode($this->ssPages, JSON_NUMERIC_CHECK).";\n";
echo " ssm_UpdatePages();\n";
elseif ($this->ssData):
echo " var ssm_screenshotData = ".json_encode($this->ssData, JSON_NUMERIC_CHECK).";\n";
echo " ssm_UpdateList();\n";
endif;
?>
ss_OnResize();
</script>
</div>
</div><!-- main-contents -->
</div><!-- main -->
<?php $this->brick('footer'); ?>

View File

@@ -0,0 +1,119 @@
<?php $this->brick('header'); ?>
<div class="main" id="main">
<div class="main-precontents" id="main-precontents"></div>
<div class="main-contents" id="main-contents">
<?php
$this->brick('announcement');
$this->brick('pageTemplate');
$this->brick('infobox');
if (isset($this->error)):
?>
<div class="pad3"></div>
<div class="inputbox">
<h1><?php echo Lang::$main['prepError']; ?></h1>
<div id="inputbox-error"><?php echo $this->error; ?><br><a href="<?php echo $this->destType ? '?'.Util::$typeStrings[$this->destType].'='.$this->destTypeId : 'javascript:history.back();'; ?>"><?php echo Lang::$main['back']; ?></a></div>
<?php
else:
?>
<div class="text">
<h1><?php echo $this->name; ?></h1>
<?php
if ($this->mode == 'add'):
?>
<div id="ss-container" style="float:right;"></div>
<div id="img-edit-form">
<span><?php echo Lang::$main['cropHint']; ?></span>
<div class="pad"></div>
<b><?php echo Lang::$main['caption'].Lang::$main['colon']; ?></b><br>
<form action="?screenshot=finalize" method="post">
<textarea style="resize: none;" name="screenshotcaption" cols="27" rows="4"><?php echo $this->caption; ?></textarea>
<div class="text-counter">text counter ph</div>
<div style="clear:right;"></div>
<input type="hidden" id="selection" name="selection" value=""></input>
<input type="submit" value="<?php echo Lang::$main['ssSubmit']; ?>"></input>
</form>
</div>
<script type="text/javascript">//<![CDATA[
var
cropper = new Cropper(<?php echo json_encode($this->cropper, JSON_NUMERIC_CHECK); ?>),
captionMaxLen = 200;
Body = $('#img-edit-form').find('textarea');
TextCounter = $('#img-edit-form').find('div.text-counter');
Form = $('#img-edit-form').find('form');
qfSizeDiv = $('.infobox').find('#qf-newSize');
$('body').mouseup(UpdateImageSelection);
Form.submit(function () { return submitSS(); });
UpdateTextCounter();
UpdateImageSelection();
Body.keyup(function (e) { return UpdateTextCounter(); });
Body.keypress(function (e) { // ENTER
if (e.keyCode == 13 || captionMaxLen - getCaptionLen() <= 0 )
return false;
});
Form.find('textarea').focus();
function getCaptionLen()
{
return Body.val().replace(/(\s+)/g, ' ').replace(/^\s*/, '').replace(/\s*$/, '').length;
}
function UpdateTextCounter()
{
var text = '(error)';
var cssClass = 'q0';
var chars = getCaptionLen();
var charsLeft = captionMaxLen - chars;
text = $WH.sprintf(charsLeft == 1 ? LANG.replylength4_format : LANG.replylength3_format, charsLeft);
if (charsLeft < 25)
cssClass = 'q10';
else if (charsLeft < 50)
cssClass = 'q5';
else if (charsLeft < 75)
cssClass = 'q11';
TextCounter.html(text).attr('class', cssClass);
}
function UpdateImageSelection()
{
dims = cropper.getCoords().split(',');
w = Math.floor(cropper.oWidth * dims[2]);
h = Math.floor(cropper.oHeight * dims[3]);
qfSizeDiv.html(w + ' x ' + h);
}
function submitSS()
{
$('#selection').val(cropper.getCoords());
return true;
}
//]]></script>
<div class="pad2"></div>
<?php
endif;
endif;
?>
</div>
</div><!-- main-contents -->
</div><!-- main -->
<?php $this->brick('footer'); ?>