diff --git a/.htaccess b/.htaccess
index 664ca4c6..f9875a4d 100644
--- a/.htaccess
+++ b/.htaccess
@@ -17,7 +17,9 @@ AddDefaultCharset utf8
CharsetRecodeMultipartForms Off
+# 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
diff --git a/includes/ajaxHandler.class.php b/includes/ajaxHandler.class.php
index 557ab3ac..fbc1c5bb 100644
--- a/includes/ajaxHandler.class.php
+++ b/includes/ajaxHandler.class.php
@@ -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 '';
+ }
}
?>
diff --git a/includes/community.class.php b/includes/community.class.php
index 7bcadb13..42439b83 100644
--- a/includes/community.class.php
+++ b/includes/community.class.php
@@ -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
diff --git a/includes/defines.php b/includes/defines.php
index 9d4f3c82..77a02974 100644
--- a/includes/defines.php
+++ b/includes/defines.php
@@ -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);
diff --git a/includes/utilities.php b/includes/utilities.php
index d9625aa2..c239d8fd 100644
--- a/includes/utilities.php
+++ b/includes/utilities.php
@@ -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;
diff --git a/index.php b/index.php
index 8848ab7a..b53b8506 100644
--- a/index.php
+++ b/index.php
@@ -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':
diff --git a/localization/locale_dede.php b/localization/locale_dede.php
index 2b1da5df..5be497cf 100644
--- a/localization/locale_dede.php
+++ b/localization/locale_dede.php
@@ -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 feedback", # 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.
Bitte beachtet Screenshots: Tipps & Tricks 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
diff --git a/localization/locale_enus.php b/localization/locale_enus.php
index 8b22b969..d52f652c 100644
--- a/localization/locale_enus.php
+++ b/localization/locale_enus.php
@@ -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 feedback", # 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.
Please refer to Screenshots: Tips & Tricks 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
diff --git a/localization/locale_eses.php b/localization/locale_eses.php
index a93ac559..e0bf8ef5 100644
--- a/localization/locale_eses.php
+++ b/localization/locale_eses.php
@@ -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 feedback", # 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.
Please refer to Screenshots: Tips & Tricks 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
diff --git a/localization/locale_frfr.php b/localization/locale_frfr.php
index 1cf1f189..89baf887 100644
--- a/localization/locale_frfr.php
+++ b/localization/locale_frfr.php
@@ -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 à feedback", # 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.
Please refer to Screenshots: Tips & Tricks 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
diff --git a/localization/locale_ruru.php b/localization/locale_ruru.php
index 4e872d6d..a87d98f0 100644
--- a/localization/locale_ruru.php
+++ b/localization/locale_ruru.php
@@ -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' => "Произошла ошибка; обновите страницу и попробуйте снова. Если ситуация повторяется, отправьте сообщение на feedback", # 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.
Please refer to Screenshots: Tips & Tricks 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
diff --git a/pages/account.php b/pages/account.php
index 07b2369f..739208bd 100644
--- a/pages/account.php
+++ b/pages/account.php
@@ -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)
diff --git a/pages/admin.php b/pages/admin.php
index 689e5ca2..f189ff0e 100644
--- a/pages/admin.php
+++ b/pages/admin.php
@@ -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 = '
';
@@ -487,7 +534,7 @@ class AdminPage extends GenericPage
else
$buff .= '|';
- if (!($r['flags'] & CON_FLAG_PERSISTANT))
+ if (!($r['flags'] & CON_FLAG_PERSISTENT))
$buff .= '|';
$buff .= '
';
diff --git a/pages/screenshot.php b/pages/screenshot.php
new file mode 100644
index 00000000..20117fb9
--- /dev/null
+++ b/pages/screenshot.php
@@ -0,0 +1,301 @@
+[_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']);
+ }
+}
+
+?>
diff --git a/setup/updates/03_screenshots.sql b/setup/updates/03_screenshots.sql
new file mode 100644
index 00000000..179ca830
--- /dev/null
+++ b/setup/updates/03_screenshots.sql
@@ -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;
diff --git a/static/css/Cropper.css b/static/css/Cropper.css
new file mode 100644
index 00000000..6ae9c313
--- /dev/null
+++ b/static/css/Cropper.css
@@ -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;
+}
diff --git a/static/js/Cropper.js b/static/js/Cropper.js
new file mode 100644
index 00000000..2e853483
--- /dev/null
+++ b/static/js/Cropper.js
@@ -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);
+ }
+};
\ No newline at end of file
diff --git a/static/js/screenshot.js b/static/js/screenshot.js
new file mode 100644
index 00000000..8515855a
--- /dev/null
+++ b/static/js/screenshot.js
@@ -0,0 +1 @@
+var ss_managedRow = null;
var ss_getAll = false; // never changed (maybe with ?admin=screenshots&all)
var ssm_ViewedRow = null;
var ssm_screenshotData = [];
var ssm_screenshotPages = [];
var ssm_numPagesFound = 0;
var ssm_numPages = 0; // never accessed
var ssm_numPending = 0;
var ssm_statuses = {
0 : 'Pending',
999: 'Deleted',
100: 'Approved',
105: 'Sticky'
};
function makePipe() {
var sp = $WH.ce('span');
$WH.ae(sp, $WH.ct(' '));
var sm = $WH.ce('small');
sm.className = 'q0';
$WH.ae(sm, $WH.ct('|'));
$WH.ae(sp, sm);
$WH.ae(sp, $WH.ct(' '));
return sp;
}
function ss_OnResize() {
var _ = Math.max(100, Math.min($WH.g_getWindowSize().h - 50, 700));
$WH.ge('menu-container').style.height = $WH.ge('pages-container').style.height = _ + 'px';
$WH.ge('data-container').style.height = _ + 'px';
}
$WH.aE(window, 'resize', ss_OnResize);
function ss_Refresh(openNext, type, typeId) {
new Ajax('?admin=screenshots&action=list' + (ss_getAll ? '&all' : ''), {
method: 'get',
onSuccess: function (xhr) {
eval(xhr.responseText);
if (ssm_screenshotPages.length > 0) {
$WH.ge('show-all-pages').innerHTML = ' – Show All (' + ssm_numPagesFound + ')';
ssm_UpdatePages();
if (openNext) {
ss_Manage($WH.ge('pages-container').firstChild.firstChild, ssm_screenshotPages[0].type, ssm_screenshotPages[0].typeId, true);
}
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) {
ss_Manage(null, type, typeId, true);
}
}
}
});
}
function ss_Manage(_this, type, typeId, openNext) {
new Ajax('?admin=screenshots&action=manage&type=' + type + '&typeid=' + typeId, {
method: 'get',
onSuccess: function (xhr) {
eval(xhr.responseText);
ssm_numPending = 0;
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)' : '');
ssm_UpdateList(openNext);
ssm_UpdateMassLinks();
if (ss_managedRow != null) {
ss_ColorizeRow('transparent');
}
ss_managedRow = _this;
if (ss_managedRow != null) {
ss_ColorizeRow('#282828');
}
}
});
}
function ss_ManageUser() {
var username = $WH.ge('usermanage');
username.value = $WH.trim(username.value);
if (username.value.length < 4) {
alert('Username must be at least 4 characters long.');
username.focus();
return false;
}
if (username.value.match(/[^a-z0-9]/i) != null) {
alert('Username can only contain letters and numbers.');
username.focus();
return false;
}
new Ajax('?admin=screenshots&action=manage&user=' + username.value, {
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) {
ss_ColorizeRow('transparent');
}
}
});
return true;
}
function ss_ColorizeRow(color) {
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) {
return ssm_screenshotData[i];
}
}
return null;
}
function ssm_View(row, id) {
if (ssm_ViewedRow != null) {
ssm_ColorizeRow('transparent');
}
ssm_ViewedRow = row;
ssm_ColorizeRow('#282828');
var screenshot = ssm_GetScreenshot(id);
if (screenshot != null) {
ScreenshotManager.show(screenshot);
}
}
function ssm_ColorizeRow(color) {
for (var i = 0; i < ssm_ViewedRow.childNodes.length; ++i) {
ssm_ViewedRow.childNodes[i].style.backgroundColor = color;
}
}
function ssm_ConfirmMassApprove() {
ajaxAnchor(this); // sarjuuk custom - there has to be something in place or we are manually using a script for ajax
return false;
// return true;
}
function ssm_ConfirmMassDelete() {
if (confirm('Delete selected screenshot(s)?')) // sarjuuk custom - see above
ajaxAnchor(this);
return false;
// return confirm('Delete selected screenshot(s)?');
}
function ssm_ConfirmMassSticky() {
if (confirm('Sticky selected screenshot(s)?')) // sarjuuk custom - see above
ajaxAnchor(this);
return false;
// return confirm('Sticky selected screenshot(s)?');
}
function ssm_UpdatePages(UNUSED) {
var pc = $WH.ge('pages-container');
$WH.ee(pc);
var tbl = $WH.ce('table');
tbl.className = 'grid';
tbl.style.width = '400px';
var tr = $WH.ce('tr');
var th = $WH.ce('th');
$WH.ae(th, $WH.ct('Page'));
$WH.ae(tr, th);
th = $WH.ce('th');
$WH.ae(th, $WH.ct('Submitted'));
$WH.ae(tr, th);
th = $WH.ce('th');
th.align = 'right';
$WH.ae(th, $WH.ct('#'));
$WH.ae(tr, th);
$WH.ae(tbl, tr);
var now = new Date();
for (var i in ssm_screenshotPages) {
var ssPage = ssm_screenshotPages[i];
tr = $WH.ce('tr');
tr.onclick = ss_Manage.bind(tr, tr, ssPage.type, ssPage.typeId, true, i);
var td = $WH.ce('td');
var a = $WH.ce('a');
a.href = '?' + g_types[ssPage.type] + '=' + ssPage.typeId;
a.target = '_blank';
$WH.ae(a, $WH.ct(ssPage.name));
$WH.ae(td, a);
$WH.ae(tr, td);
td = $WH.ce('td');
var elapsed = new Date(ssPage.date);
$WH.ae(td, $WH.ct(g_formatTimeElapsed((now.getTime() - elapsed.getTime()) / 1000) + ' ago'));
$WH.ae(tr, td);
td = $WH.ce('td');
td.align = 'right';
$WH.ae(td, $WH.ct(ssPage.count));
$WH.ae(tr, td);
$WH.ae(tbl, tr);
}
$WH.ae(pc, tbl);
}
function ssm_UpdateList(openNext) {
var tsl = $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') {
tBody = true;
}
else {
i++;
}
}
var now = new Date();
var ssId = 0;
for (var i in ssm_screenshotData) {
var screenshot = ssm_screenshotData[i];
var tr = $WH.ce('tr');
if (ssId == 0 && screenshot.pending) {
ssId = screenshot.id;
tr.id = 'highlightedRow';
}
var td = $WH.ce('td');
td.align = 'center';
var a = $WH.ce('a');
a.href = g_staticUrl + '/uploads/screenshots/' + (screenshot.status != 999 && !screenshot.pending ? 'normal' : 'pending') + '/' + screenshot.id + '.jpg';
a.target = '_blank';
a.onclick = function (id, e) {
$WH.sp(e);
(ssm_View.bind(this, id))();
return false;
}.bind(tr, screenshot.id);
var img = $WH.ce('img');
img.src = g_staticUrl + '/uploads/screenshots/' + (screenshot.status != 999 && !screenshot.pending ? 'thumb' : 'pending') + '/' + screenshot.id + '.jpg';
img.height = 50;
$WH.ae(a, img);
$WH.ae(td, a);
$WH.ae(tr, td);
td = $WH.ce('td');
if (screenshot.status != 999 && !screenshot.pending) {
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);
};
$WH.ae(a, $WH.ct(screenshot.id));
$WH.ae(td, a);
}
else {
$WH.ae(td, $WH.ct(screenshot.id));
}
$WH.ae(tr, td);
td = $WH.ce('td');
td.id = 'alt-' + screenshot.id;
var sp = $WH.ce('span');
sp.style.paddingRight = '8px';
if (screenshot.caption) {
var sp2 = $WH.ce('span');
sp2.className = 'q2';
var b = $WH.ce('b');
$WH.ae(b, $WH.ct(screenshot.caption));
$WH.ae(sp2, b);
$WH.ae(sp, sp2);
}
else {
var it = $WH.ce('i');
it.className = 'q0';
$WH.ae(it, $WH.ct('NULL'));
$WH.ae(sp, it);
}
$WH.ae(td, sp);
sp = $WH.ce('span');
sp.style.whiteSpace = 'nowrap';
var a = $WH.ce('a');
a.href = 'javascript:;';
a.onclick = function (id, e) {
$WH.sp(e);
(ssm_ShowEdit.bind(this, id))()
}.bind(a, screenshot);
$WH.ae(a, $WH.ct('Edit'));
$WH.ae(sp, a);
$WH.ae(sp, makePipe());
a = $WH.ce('a');
a.href = 'javascript:;';
a.onclick = function (id, e) {
$WH.sp(e);
(ssm_Clear.bind(this, id))()
}.bind(a, screenshot);
$WH.ae(a, $WH.ct('Clear'));
$WH.ae(sp, a);
$WH.ae(td, sp);
$WH.ae(tr, td);
td = $WH.ce('td');
var elapsed = new Date(screenshot.date);
$WH.ae(td, $WH.ct(g_formatTimeElapsed((now.getTime() - elapsed.getTime()) / 1000) + ' ago'));
$WH.ae(tr, td);
td = $WH.ce('td');
a = $WH.ce('a');
a.href = '?user=' + screenshot.user;
a.target = '_blank';
a.onclick = function (e) {
$WH.sp(e);
};
$WH.ae(a, $WH.ct(screenshot.user));
$WH.ae(td, a);
$WH.ae(tr, td);
td = $WH.ce('td');
$WH.ae(td, $WH.ct(ssm_statuses[screenshot.status]));
$WH.ae(tr, td);
td = $WH.ce('td');
var cb = $WH.ce('input');
cb.type = 'checkbox';
cb.value = screenshot.id;
cb.onclick = function (e) {
$WH.sp(e);
(ssm_UpdateMassLinks.bind(this))();
}.bind(cb);
$WH.ae(td, cb);
$WH.ae(td, $WH.ct(' '));
if (screenshot.status != 999) {
tr.onclick = function (id) {
ssm_View(this, id);
return false;
}.bind(tr, screenshot.id);
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))()
}.bind(screenshot);
$WH.ae(a, $WH.ct('Approve'));
$WH.ae(td, a);
}
else {
$WH.ae(td, $WH.ct('Approve'));
}
$WH.ae(td, makePipe());
if (screenshot.status != 105) {
a = $WH.ce('a');
a.href = 'javascript:;';
a.onclick = function (e) {
$WH.sp(e);
(ssm_Sticky.bind(this, false))();
}.bind(screenshot);
$WH.ae(a, $WH.ct('Make sticky'));
$WH.ae(td, a);
}
else {
$WH.ae(td, $WH.ct('Make sticky'));
}
$WH.ae(td, makePipe());
a = $WH.ce('a');
a.href = 'javascript:;';
a.onclick = function (e) {
$WH.sp(e);
(ssm_Delete.bind(this, false))();
}.bind(screenshot);
$WH.ae(a, $WH.ct('Delete'));
$WH.ae(td, a);
$WH.ae(td, makePipe());
a = $WH.ce('a');
a.href = 'javascript:;';
a.onclick = function (e) {
$WH.sp(e);
var id = prompt('Enter the ID to move this screenshot to:');
(ssm_Relocate.bind(this, id))();
}.bind(screenshot);
$WH.ae(a, $WH.ct('Relocate'));
$WH.ae(td, a);
}
$WH.ae(tr, td);
$WH.ae(tsl, tr);
}
}
function ssm_UpdateMassLinks() {
var buff = '';
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 + ',';
++i;
}
});
buff = $WH.rtrim(buff, ',');
var selCnt = $WH.ge('withselected');
if (i > 0) {
selCnt.style.display = '';
$WH.gE(selCnt, 'b')[0].firstChild.nodeValue = '(' + i + ')';
var c = $WH.ge('massapprove');
var b = $WH.ge('massdelete');
var a = $WH.ge('masssticky');
c.href = '?admin=screenshots&action=approve&id=' + buff;
c.onclick = ssm_ConfirmMassApprove;
b.href = '?admin=screenshots&action=delete&id=' + buff;
b.onclick = ssm_ConfirmMassDelete;
a.href = '?admin=screenshots&action=sticky&id=' + buff;
a.onclick = ssm_ConfirmMassSticky;
}
else {
selCnt.style.display = 'none';
}
}
function ssm_MassSelect(action) {
var tSL = $WH.ge('theScreenshotsList');
var inp = $WH.gE(tSL, 'input');
switch (parseInt(action)) {
case 1:
$WH.array_walk(inp, function (x) { x.checked = true });
break;
case 0:
$WH.array_walk(inp, function (x) { x.checked = false });
break;
case -1:
$WH.array_walk(inp, function (x) { x.checked = !x.checked });
break;
case 2:
$WH.array_walk(inp, function (x) { x.checked = ssm_GetScreenshot(x.value).status == 0 });
break;
case 5:
$WH.array_walk(inp, function (x) { x.checked = ssm_GetScreenshot(x.value).unique == 1 && ssm_GetScreenshot(x.value).status == 0 });
break;
case 3:
$WH.array_walk(inp, function (x) { x.checked = ssm_GetScreenshot(x.value).status == 100 });
break;
case 4:
$WH.array_walk(inp, function (x) { x.checked = ssm_GetScreenshot(x.value).status == 105 });
break;
default:
return;
}
ssm_UpdateMassLinks();
}
function ssm_ShowEdit(screenshot, isAlt) {
var node;
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');
div.style.whiteSpace = 'nowrap';
var iCaption = $WH.ce('input');
iCaption.type = 'text';
iCaption.value = screenshot.caption;
iCaption.maxLength = 200;
iCaption.size = 35;
iCaption.onclick = function (e) { $WH.sp(e); } // sarjuuk - custom to inhibit screenshot popup, when clicking into input element
div.appendChild(iCaption);
var btn = $WH.ce('input');
btn.type = 'button';
btn.value = 'Update';
btn.onclick = function (i, j, k) {
if (!j) {
$WH.sp(k);
}
(ssm_Edit.bind(this, i, j))();
}.bind(btn, screenshot, isAlt);
div.appendChild(btn);
var c = $WH.ce('span');
c.appendChild($WH.ct(' '));
div.appendChild(c);
btn = $WH.ce('input');
btn.type = 'button';
btn.value = 'Cancel';
btn.onclick = function (i, j, k) {
if (!j) {
$WH.sp(k);
}
(ssm_CancelEdit.bind(this, i, j))();
}.bind(btn, screenshot, isAlt);
div.appendChild(btn);
sp.style.display = 'none';
sp.nextSibling.style.display = 'none';
node.insertBefore(div, sp);
iCaption.focus()
}
function ssm_CancelEdit(screenshot, isAlt) {
var node;
if (isAlt) {
node = $WH.ge('alt2-' + screenshot.id);
}
else {
node = $WH.ge('alt-' + screenshot.id);
}
var sp = $WH.gE(node, 'span')[1];
sp.style.display = '';
sp.nextSibling.style.display = '';
node.removeChild(node.firstChild);
}
function ssm_Edit(screenshot, isAlt) {
var node;
if (isAlt) {
node = $WH.ge('alt2-' + screenshot.id);
}
else {
node = $WH.ge('alt-' + screenshot.id);
}
var desc = node.firstChild.childNodes;
if (desc[0].value == screenshot.caption) {
ssm_CancelEdit(screenshot, isAlt);
return
}
screenshot.caption = desc[0].value;
ssm_CancelEdit(screenshot, isAlt);
node = node.firstChild;
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) {
node = $WH.ge('alt2-' + screenshot.id);
}
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 == '') {
return;
}
screenshot.caption = '';
sp.innerHTML = 'NULL';
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, {
method: 'get',
onSuccess: function (x) {
Lightbox.hide();
if (ssm_numPending == 1 && _self.pending) {
ss_Refresh(true);
}
else {
ss_Refresh();
ss_Manage(ss_managedRow, _self.type, _self.typeId, openNext, 0);
}
}
})
}
function ssm_Sticky(openNext) {
var _self = this;
new Ajax('?admin=screenshots&action=sticky&id=' + _self.id, {
method: 'get',
onSuccess: function (x) {
Lightbox.hide();
if (ssm_numPending == 1 && _self.pending) {
ss_Refresh(true);
}
else {
ss_Refresh();
ss_Manage(ss_managedRow, _self.type, _self.typeId, openNext, 0);
}
}
})
}
function ssm_Delete(openNext) {
var _self = this;
new Ajax('?admin=screenshots&action=delete&id=' + _self.id, {
method: 'get',
onSuccess: function (x) {
Lightbox.hide();
if (ssm_numPending == 1 && _self.pending) {
ss_Refresh(true);
}
else {
ss_Refresh();
ss_Manage(ss_managedRow, _self.type, _self.typeId, openNext, 0);
}
}
});
}
function ssm_Relocate(typeId) {
var _self = this;
new Ajax('?admin=screenshots&action=relocate&id=' + _self.id + '&typeid=' + typeId, {
method: 'get',
onSuccess: function (x) {
ss_Refresh();
ss_Manage(ss_managedRow, _self.type, typeId);
}
});
}
var ScreenshotManager = new function () {
var
screenshot,
pos,
imgWidth,
imgHeight,
scale,
desiredScale,
container,
screen,
imgDiv,
aPrev,
aNext,
aCover,
aOriginal,
divFrom,
divCaption,
__div,
h2Name,
u,
aEdit,
aClear,
spApprove,
aApprove,
aMakeSticky,
aDelete,
loadingImage,
lightboxComponents;
function computeDimensions(captionExtraHeight) {
var availHeight = Math.max(50, Math.min(618, $WH.g_getWindowSize().h - 122 - captionExtraHeight));
if (screenshot.id) {
desiredScale = Math.min(772 / screenshot.width, 618 / screenshot.height);
scale = Math.min(772 / screenshot.width, availHeight / screenshot.height);
}
else {
desiredScale = scale = 1;
}
if (desiredScale > 1) {
desiredScale = 1;
}
if (scale > 1) {
scale = 1;
}
imgWidth = Math.round(scale * screenshot.width);
imgHeight = Math.round(scale * screenshot.height);
var lbWidth = Math.max(480, imgWidth);
Lightbox.setSize(lbWidth + 20, imgHeight + 116 + captionExtraHeight);
if (captionExtraHeight) {
imgDiv.firstChild.width = imgWidth;
imgDiv.firstChild.height = imgHeight;
}
}
function render(resizing) {
if (resizing && (scale == desiredScale) && $WH.g_getWindowSize().h > container.offsetHeight) {
return;
}
container.style.visibility = 'hidden';
var
resized = (screenshot.width > 772 || screenshot.height > 618);
computeDimensions(0);
var url = g_staticUrl + '/uploads/screenshots/' + (screenshot.pending ? 'pending' : 'normal') + '/' + screenshot.id + '.jpg';
var html = '
';
imgDiv.innerHTML = html;
if (!resizing) {
aOriginal.href = g_staticUrl + '/uploads/screenshots/' + (screenshot.pending ? 'pending' : 'normal') + '/' + screenshot.id + '.jpg';
var hasFrom1 = screenshot.date && screenshot.user;
if (hasFrom1) {
var
postedOn = new Date(screenshot.date),
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 {
divFrom.firstChild.style.display = 'none';
}
divFrom.style.display = (hasFrom1 ? '' : 'none');
var hasCaption = (screenshot.caption != null && screenshot.caption.length);
if (hasCaption) {
var html = '';
if (hasCaption) {
html += '' + Markup.toHtml(screenshot.caption, { mode: Markup.MODE_SIGNATURE }) + '';
}
divCaption.innerHTML = html;
}
else {
divCaption.innerHTML = 'NULL';
}
__div.id = 'alt2-' + screenshot.id;
aEdit.onclick = ssm_ShowEdit.bind(aEdit, screenshot, true);
aClear.onclick = ssm_Clear.bind(aClear, screenshot, true);
if (screenshot.next !== undefined) {
aPrev.style.display = aNext.style.display = '';
aCover.style.display = 'none';
}
else {
aPrev.style.display = aNext.style.display = 'none';
aCover.style.display = '';
}
}
Lightbox.reveal();
if (divCaption.offsetHeight > 18) {
computeDimensions(divCaption.offsetHeight - 18);
}
container.style.visibility = 'visible';
}
function nextScreenshot() {
if (screenshot.next !== undefined) {
screenshot = ssm_screenshotData[screenshot.next];
}
onRender();
}
function prevScreenshot() {
if (screenshot.prev !== undefined) {
screenshot = ssm_screenshotData[screenshot.prev];
}
onRender();
}
function onResize() {
render(1);
}
function onHide() {
aApprove.onclick = aMakeSticky.onclick = aDelete.onclick = null;
cancelImageLoading();
}
function onShow(dest, first, opt) {
screenshot = opt;
container = dest;
if (first) {
dest.className = 'screenshotviewer';
screen = $WH.ce('div');
screen.className = 'screenshotviewer-screen';
aPrev = $WH.ce('a');
aNext = $WH.ce('a');
aPrev.className = 'screenshotviewer-prev';
aNext.className = 'screenshotviewer-next';
aPrev.href = 'javascript:;';
aNext.href = 'javascript:;';
var foo = $WH.ce('span');
$WH.ae(foo, $WH.ce('b'));
$WH.ae(aPrev, foo);
var foo = $WH.ce('span');
$WH.ae(foo, $WH.ce('b'));
$WH.ae(aNext, foo);
aPrev.onclick = prevScreenshot;
aNext.onclick = nextScreenshot;
aCover = $WH.ce('a');
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');
h2Name.className = 'first';
$WH.ae(h2Name, $WH.ct(screenshot.name));
$WH.ae(_div, h2Name);
$WH.ae(dest, _div);
imgDiv = $WH.ce('div');
$WH.ae(screen, imgDiv);
$WH.ae(dest, screen);
var _div = $WH.ce('div');
_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;
$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'));
$WH.ae(sp, $WH.ct(' '));
$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:;';
aClose.onclick = Lightbox.hide;
$WH.ae(aClose, $WH.ce('span'));
$WH.ae(dest, aClose);
aOriginal = $WH.ce('a');
aOriginal.className = 'screenshotviewer-original';
aOriginal.href = 'javascript:;';
aOriginal.target = '_blank';
$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);
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);
_div = $WH.ce('div');
_div.className = 'clear';
$WH.ae(dest, _div);
}
else {
$WH.ee(h2Name);
$WH.ae(h2Name, $WH.ct(screenshot.name));
}
onRender();
}
function onRender() {
if (screenshot.pending) {
aApprove.onclick = ssm_Approve.bind(screenshot, true);
aMakeSticky.onclick = ssm_Sticky.bind(screenshot, true);
aDelete.onclick = ssm_Delete.bind(screenshot, true);
}
else {
aMakeSticky.onclick = ssm_Sticky.bind(screenshot, true);
aDelete.onclick = ssm_Delete.bind(screenshot, true);
}
aApprove.style.display = screenshot.pending ? '' : 'none';
spApprove.style.display = screenshot.pending ? 'none' : '';
if (!screenshot.width || !screenshot.height) {
if (loadingImage) {
loadingImage.onload = null;
loadingImage.onerror = null;
}
else {
container.className = '';
lightboxComponents = [];
while (container.firstChild) {
lightboxComponents.push(container.firstChild);
$WH.de(container.firstChild);
}
}
var lightboxTimer = setTimeout(function () {
screenshot.width = 126;
screenshot.height = 22;
computeDimensions(0);
screenshot.width = null;
screenshot.height = null;
var div = $WH.ce('div');
div.style.margin = '0 auto';
div.style.width = '126px';
var img = $WH.ce('img');
img.src = g_staticUrl + '/images/ui/misc/progress-anim.gif';
img.width = 126;
img.height = 22;
$WH.ae(div, img);
$WH.ae(container, div);
Lightbox.reveal();
container.style.visiblity = 'visible';
}, 150);
loadingImage = new Image();
loadingImage.onload = (function (screen, timer) {
clearTimeout(timer);
screen.width = this.width;
screen.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 {
render();
}
}
function cancelImageLoading() {
if (!loadingImage) {
return;
}
loadingImage.onload = null;
loadingImage.onerror = null;
loadingImage = null;
restoreLightbox();
}
function restoreLightbox() {
if (!lightboxComponents) {
return;
}
$WH.ee(container);
container.className = 'screenshotviewer';
for (var i = 0; i < lightboxComponents.length; ++i) {
$WH.ae(container, lightboxComponents[i]);
}
lightboxComponents = null;
}
this.show = function (opt) {
Lightbox.show('screenshotmanager', {
onShow: onShow,
onHide: onHide,
onResize: onResize
}, opt);
}
};
\ No newline at end of file
diff --git a/template/bricks/head.tpl.php b/template/bricks/head.tpl.php
index fcab4eb3..d29f2094 100644
--- a/template/bricks/head.tpl.php
+++ b/template/bricks/head.tpl.php
@@ -1,6 +1,6 @@
title)); ?>
-
+
@@ -8,7 +8,7 @@
\n";
endif;
@@ -40,7 +40,7 @@ endif;
\n";
endif;
diff --git a/template/bricks/infobox.tpl.php b/template/bricks/infobox.tpl.php
index 919bcdf2..e4ff0f5c 100644
--- a/template/bricks/infobox.tpl.php
+++ b/template/bricks/infobox.tpl.php
@@ -36,12 +36,22 @@ if (!empty($this->type) && !empty($this->typeId)):
?>
|
|
+community['vi'])):
+?>
|
|
+
+community['vi'])):
+?>
\n";
endif;
diff --git a/template/pages/admin/screenshots.tpl.php b/template/pages/admin/screenshots.tpl.php
new file mode 100644
index 00000000..64125a93
--- /dev/null
+++ b/template/pages/admin/screenshots.tpl.php
@@ -0,0 +1,138 @@
+brick('header'); ?>
+
+
+
+
+
+brick('announcement');
+
+$this->brick('pageTemplate');
+?>
+
+
name; ?>
+
+
+
+
+ Menu | Pages | Screenshots: |
+
+
+ |
+
+ | Screenshot |
+ Id |
+ Caption |
+ Date |
+ Uploader |
+ Status |
+ Options |
+
|
+
+
+
+
+
+
+
+
+brick('footer'); ?>
diff --git a/template/pages/screenshot.tpl.php b/template/pages/screenshot.tpl.php
new file mode 100644
index 00000000..78435cc1
--- /dev/null
+++ b/template/pages/screenshot.tpl.php
@@ -0,0 +1,119 @@
+brick('header'); ?>
+
+
+
+
+
+brick('announcement');
+
+$this->brick('pageTemplate');
+
+$this->brick('infobox');
+
+if (isset($this->error)):
+?>
+
+
+
+
+
+brick('footer'); ?>