diff --git a/endpoints/admin/videos.php b/endpoints/admin/videos.php new file mode 100644 index 00000000..10beece8 --- /dev/null +++ b/endpoints/admin/videos.php @@ -0,0 +1,68 @@ + Content > Videos + + protected array $scripts = array( + [SC_JS_FILE, 'js/video.js'], + [SC_CSS_STRING, '.layout {margin: 0px 25px; max-width: inherit; min-width: 1200px; }'], + [SC_CSS_STRING, '#highlightedRow { background-color: #322C1C; }'] + ); + protected array $expectedGET = array( + 'action' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']], + 'all' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkEmptySet']], + 'type' => ['filter' => FILTER_VALIDATE_INT ], + 'typeid' => ['filter' => FILTER_VALIDATE_INT ], + 'user' => ['filter' => FILTER_CALLBACK, 'options' => 'urldecode' ] + ); + + public ?bool $getAll = null; + public array $viPages = []; + public array $viData = []; + public int $viNFound = 0; + public array $pageTypes = []; + + protected function generate() : void + { + $this->h1 = 'Video Manager'; + + // types that can have videos + foreach (Type::getClassesFor(0, 'contribute', CONTRIBUTE_SS) as $type => $obj) + $this->pageTypes[$type] = Util::ucWords(Lang::game(Type::getFileString($type))); + + $viGetAll = $this->_get['all']; + $viPages = []; + $viData = []; + $nMatches = 0; + + if ($this->_get['type'] && $this->_get['typeid']) + $viData = VideoMgr::getVideos($this->_get['type'], $this->_get['typeid'], nFound: $nMatches); + else if ($this->_get['user']) + { + if (mb_strlen($this->_get['user']) >= 3) + if ($uId = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE LOWER(`username`) = LOWER(?)', $this->_get['user'])) + $viData = VideoMgr::getVideos(userId: $uId, nFound: $nMatches); + } + else + $viPages = VideoMgr::getPages($viGetAll, $nMatches); + + $this->getAll = $viGetAll; + $this->viPages = $viPages; + $this->viData = $viData; + $this->viNFound = $nMatches; // ssm_numPagesFound + + parent::generate(); + } +} diff --git a/endpoints/admin/videos_approve.php b/endpoints/admin/videos_approve.php new file mode 100644 index 00000000..32e866e9 --- /dev/null +++ b/endpoints/admin/videos_approve.php @@ -0,0 +1,46 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdListUnsigned']] + ); + + protected function generate() : void + { + if (!$this->assertGET('id')) + { + trigger_error('AdminVideosActionApproveResponse - videoId empty', E_USER_ERROR); + return; + } + + $viEntries = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `userIdOwner`, `date`, `type`, `typeId` FROM ?_videos WHERE (`status` & ?d) = 0 AND `id` IN (?a)', CC_FLAG_APPROVED, $this->_get['id']); + foreach ($viEntries as $id => $viData) + { + // set as approved in DB + DB::Aowow()->query('UPDATE ?_videos SET `status` = ?d, `userIdApprove` = ?d WHERE `id` = ?d', CC_FLAG_APPROVED, User::$id, $id); + + // gain siterep + Util::gainSiteReputation($viData['userIdOwner'], SITEREP_ACTION_SUGGEST_VIDEO, ['id' => $id, 'what' => 1, 'date' => $viData['date']]); + + // flag DB entry as having videos + if ($tbl = Type::getClassAttrib($viData['type'], 'dataTable')) + DB::Aowow()->query('UPDATE ?# SET `cuFlags` = `cuFlags` | ?d WHERE `id` = ?d', $tbl, CUSTOM_HAS_VIDEO, $viData['typeId']); + + unset($viEntries[$id]); + } + + if (!$viEntries) + trigger_error('AdminVideosActionApproveResponse - video(s) # '.implode(', ', array_keys($viEntries)).' not in db or already approved', E_USER_WARNING); + } +} diff --git a/endpoints/admin/videos_delete.php b/endpoints/admin/videos_delete.php new file mode 100644 index 00000000..d3b6c19e --- /dev/null +++ b/endpoints/admin/videos_delete.php @@ -0,0 +1,43 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkEmptySet']] + ); + + // 2 steps: 1) remove from sight, 2) remove from disk + protected function generate() : void + { + if (!$this->assertGET('id')) + { + trigger_error('AdminVideosActionDeleteResponse - videoId empty', E_USER_ERROR); + return; + } + + // irrevocably purge files already flagged as deleted (should only exist as pending) + if (User::isInGroup(U_GROUP_ADMIN)) + DB::Aowow()->selectCell('SELECT 1 FROM ?_videos WHERE `status` & ?d AND `id` IN (?a)', CC_FLAG_DELETED, $this->_get['id']); + + // flag as deleted if not aready + $oldEntries = DB::Aowow()->selectCol('SELECT `type` AS ARRAY_KEY, GROUP_CONCAT(`typeId`) FROM ?_videos WHERE `id` IN (?a) GROUP BY `type`', $this->_get['id']); + DB::Aowow()->query('UPDATE ?_videos SET `status` = ?d, `userIdDelete` = ?d WHERE (`status` & ?d) = 0 AND `id` IN (?a)', CC_FLAG_DELETED, User::$id, CC_FLAG_DELETED, $this->_get['id']); + + // deflag db entry as having videos + foreach ($oldEntries as $type => $typeIds) + { + $typeIds = explode(',', $typeIds); + $toUnflag = DB::Aowow()->selectCol('SELECT `typeId` AS ARRAY_KEY, IF(BIT_OR(`status`) & ?d, 1, 0) AS "hasMore" FROM ?_videos WHERE `type` = ?d AND `typeId` IN (?a) GROUP BY `typeId` HAVING `hasMore` = 0', CC_FLAG_APPROVED, $type, $typeIds); + if ($toUnflag && ($tbl = Type::getClassAttrib($type, 'dataTable'))) + DB::Aowow()->query('UPDATE ?# SET cuFlags = cuFlags & ~?d WHERE id IN (?a)', $tbl, CUSTOM_HAS_VIDEO, array_keys($toUnflag)); + } + } +} diff --git a/endpoints/admin/videos_edittitle.php b/endpoints/admin/videos_edittitle.php new file mode 100644 index 00000000..d9c92dfe --- /dev/null +++ b/endpoints/admin/videos_edittitle.php @@ -0,0 +1,31 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdListUnsigned']] + ); + protected array $expectedPOST = array( + 'title' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']] + ); + + protected function generate() : void + { + if (!$this->assertGET('id')) + return; + + $caption = $this->handleCaption($this->_post['title']); + + DB::Aowow()->query('UPDATE ?_videos SET `caption` = ? WHERE `id` = ?d', $caption, $this->_get['id'][0]); + } +} diff --git a/endpoints/admin/videos_list.php b/endpoints/admin/videos_list.php new file mode 100644 index 00000000..7b02d12b --- /dev/null +++ b/endpoints/admin/videos_list.php @@ -0,0 +1,23 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkEmptySet']] + ); + + protected function generate() : void + { + $pages = VideoMgr::getPages($this->_get['all'], $nPages); + $this->result = 'vim_videoPages = '.Util::toJSON($pages).";\n"; + $this->result .= 'vim_numPagesFound = '.$nPages.';'; + } +} diff --git a/endpoints/admin/videos_manage.php b/endpoints/admin/videos_manage.php new file mode 100644 index 00000000..0bd4e31e --- /dev/null +++ b/endpoints/admin/videos_manage.php @@ -0,0 +1,31 @@ + ['filter' => FILTER_VALIDATE_INT ], + 'typeid' => ['filter' => FILTER_VALIDATE_INT ], + 'user' => ['filter' => FILTER_CALLBACK, 'options' => 'urldecode'] + ); + + protected function generate() : void + { + $res = []; + + if ($this->_get['type'] && $this->_get['typeid']) + $res = VideoMgr::getVideos($this->_get['type'], $this->_get['typeid']); + else if ($this->_get['user']) + if ($uId = DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE LOWER(`username`) = LOWER(?)', $this->_get['user'])) + $res = VideoMgr::getVideos(userId: $uId); + + $this->result = 'vim_videoData = '.Util::toJSON($res); + } +} diff --git a/endpoints/admin/videos_order.php b/endpoints/admin/videos_order.php new file mode 100644 index 00000000..f11f64eb --- /dev/null +++ b/endpoints/admin/videos_order.php @@ -0,0 +1,57 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdListUnsigned'] ], + 'move' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_range' => -1, 'max_range' => 1]] // -1 = up, 1 = down + ); + + protected function generate() : void + { + if (!$this->assertGET('id', 'move') || $this->_get['move'] === 0) + { + trigger_error('AdminVideosActionOrderResponse - id or move empty', E_USER_ERROR); + return; + } + + $id = $this->_get['id'][0]; + + $videos = DB::Aowow()->selectCol('SELECT a.`id` AS ARRAY_KEY, a.`pos` FROM ?_videos a, ?_videos b WHERE a.`type` = b.`type` AND a.`typeId` = b.`typeId` AND (a.`status` & ?d) = 0 AND b.`id` = ?d ORDER BY a.`pos` ASC', CC_FLAG_DELETED, $id); + if (!$videos || count($videos) == 1) + { + trigger_error('AdminVideosActionOrderResponse - not enough videos to sort', E_USER_WARNING); + return; + } + + $dir = $this->_get['move']; + $curPos = $videos[$id]; + + if ($dir == -1 && $curPos == 0) + { + trigger_error('AdminVideosActionOrderResponse - video #'.$id.' already in top position', E_USER_WARNING); + return; + } + + if ($dir == 1 && $curPos + 1 == count($videos)) + { + trigger_error('AdminVideosActionOrderResponse - video #'.$id.' already in bottom position', E_USER_WARNING); + return; + } + + $oldKey = array_search($curPos + $dir, $videos); + $videos[$oldKey] -= $dir; + $videos[$id] += $dir; + + foreach ($videos as $id => $pos) + DB::Aowow()->query('UPDATE ?_videos SET `pos` = ?d WHERE `id` = ?d', $pos, $id); + } +} diff --git a/endpoints/admin/videos_relocate.php b/endpoints/admin/videos_relocate.php new file mode 100644 index 00000000..aa2bd6a1 --- /dev/null +++ b/endpoints/admin/videos_relocate.php @@ -0,0 +1,49 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdListUnsigned']], + 'typeid' => ['filter' => FILTER_VALIDATE_INT ] + // (but not type..?) + ); + + protected function generate() : void + { + if (!$this->assertGET('id', 'typeid')) + { + trigger_error('AdminVideosActionRelocateResponse - videoId or typeId empty', E_USER_ERROR); + return; + } + + $id = $this->_get['id'][0]; + [$type, $oldTypeId] = array_values(DB::Aowow()->selectRow('SELECT `type`, `typeId` FROM ?_videos WHERE `id` = ?d', $id)); + $typeId = $this->_get['typeid']; + + if (Type::validateIds($type, $typeId)) + { + $tbl = Type::getClassAttrib($type, 'dataTable'); + + // move video + DB::Aowow()->query('UPDATE ?_videos SET `typeId` = ?d WHERE `id` = ?d', $typeId, $id); + + // flag target as having video + DB::Aowow()->query('UPDATE ?# SET `cuFlags` = `cuFlags` | ?d WHERE `id` = ?d', $tbl, CUSTOM_HAS_VIDEO, $typeId); + + // deflag source for having had videos (maybe) + $viInfo = DB::Aowow()->selectRow('SELECT IF(BIT_OR(~`status`) & ?d, 1, 0) AS "hasMore" FROM ?_videos WHERE `status`& ?d AND `type` = ?d AND `typeId` = ?d', CC_FLAG_DELETED, CC_FLAG_APPROVED, $type, $oldTypeId); + if ($viInfo || !$viInfo['hasMore']) + DB::Aowow()->query('UPDATE ?# SET `cuFlags` = `cuFlags` & ~?d WHERE `id` = ?d', $tbl, CUSTOM_HAS_VIDEO, $oldTypeId); + } + else + trigger_error('AdminVideosActionRelocateResponse - invalid typeId #'.$typeId.' for type #'.$type, E_USER_ERROR); + } +} diff --git a/endpoints/admin/videos_sticky.php b/endpoints/admin/videos_sticky.php new file mode 100644 index 00000000..ea79ac4b --- /dev/null +++ b/endpoints/admin/videos_sticky.php @@ -0,0 +1,56 @@ + ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIdListUnsigned']] + ); + + protected function generate() : void + { + if (!$this->assertGET('id')) + { + trigger_error('AdminVideosActionStickyResponse - videoId empty', E_USER_ERROR); + return; + } + + // this one is a bit strange: as far as i've seen, the only thing a 'sticky' video does is show up in the infobox + // this also means, that only one video per page should be sticky + // so, handle it one by one and the last one affecting one particular type/typId-key gets the cake + $viEntries = DB::Aowow()->select('SELECT `id` AS ARRAY_KEY, `userIdOwner`, `date`, `type`, `typeId`, `status` FROM ?_videos WHERE (`status` & ?d) = 0 AND `id` IN (?a)', CC_FLAG_DELETED, $this->_get['id']); + foreach ($viEntries as $id => $viData) + { + // approve yet unapproved videos + if (!($viData['status'] & CC_FLAG_APPROVED)) + { + // set as approved in DB + DB::Aowow()->query('UPDATE ?_videos SET `status` = ?d, `userIdApprove` = ?d WHERE `id` = ?d', CC_FLAG_APPROVED, User::$id, $id); + + // gain siterep + Util::gainSiteReputation($viData['userIdOwner'], SITEREP_ACTION_SUGGEST_VIDEO, ['id' => $id, 'what' => 1, 'date' => $viData['date']]); + + // flag DB entry as having videos + if ($tbl = Type::getClassAttrib($viData['type'], 'dataTable')) + DB::Aowow()->query('UPDATE ?# SET `cuFlags` = `cuFlags` | ?d WHERE `id` = ?d', $tbl, CUSTOM_HAS_VIDEO, $viData['typeId']); + } + + // reset all others + DB::Aowow()->query('UPDATE ?_videos a, ?_videos b SET a.`status` = a.`status` & ~?d WHERE a.`type` = b.`type` AND a.`typeId` = b.`typeId` AND a.`id` <> b.`id` AND b.`id` = ?d', CC_FLAG_STICKY, $id); + + // toggle sticky status + DB::Aowow()->query('UPDATE ?_videos SET `status` = IF(`status` & ?d, `status` & ~?d, `status` | ?d) WHERE `id` = ?d AND `status` & ?d', CC_FLAG_STICKY, CC_FLAG_STICKY, CC_FLAG_STICKY, $id, CC_FLAG_APPROVED); + + unset($viEntries[$id]); + } + + if ($viEntries) + trigger_error('AdminVideosActionStickyResponse - video(s) # '.implode(', ', array_keys($viEntries)).' not in db or flagged as deleted', E_USER_WARNING); + } +} diff --git a/endpoints/video/add.php b/endpoints/video/add.php new file mode 100644 index 00000000..36c67ebc --- /dev/null +++ b/endpoints/video/add.php @@ -0,0 +1,124 @@ + 1. =add: receives user upload + 1.1. checks and processing on the upload + 1.2. forward to =confirm or blank response + 2. =confirm: user edites upload + 3. =complete: store edited video file and data + 4. =thankyou +*/ + +class VideoAddResponse extends TextResponse +{ + protected bool $requiresLogin = true; + + protected array $expectedPOST = array( + 'videourl' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']] + ); + + private string $videoHash = ''; + private int $destType = 0; + private int $destTypeId = 0; + + public function __construct(string $pageParam) + { + parent::__construct($pageParam); + + // get video destination + // target delivered as video=&.. (hash is optional) + if (!preg_match('/^video=\w+&(-?\d+)\.(-?\d+)(\.(\w{16}))?$/i', $_SERVER['QUERY_STRING'] ?? '', $m, PREG_UNMATCHED_AS_NULL)) + $this->generate404(); + + [, $this->destType, $this->destTypeId, , $videoHash] = $m; + + // no such type or this type cannot receive videos + if (!Type::checkClassAttrib($this->destType, 'contribute', CONTRIBUTE_VI)) + $this->generate404(); + + // no such typeId + if (!Type::validateIds($this->destType, $this->destTypeId)) + $this->generate404(); + + // only accept/expect hash for confirm & complete + if ($videoHash) + $this->generate404(); + } + + protected function generate() : void + { + if ($this->handleAdd()) + $this->redirectTo = '?video=confirm&'.$this->destType.'.'.$this->destTypeId.'.'.$this->videoHash; + else if ($this->destType && $this->destTypeId) + $this->redirectTo = '?'.Type::getFileString($this->destType).'='.$this->destTypeId.'#suggest-a-video'; + else + $this->generate404(); + } + + private function handleAdd() : bool + { + if (!User::canSuggestVideo()) + { + $_SESSION['error']['vi'] = Lang::video('error', 'notAllowed'); + return false; + } + + if (!$this->assertPOST('videourl')) + { + $_SESSION['error']['vi'] = Lang::video('error', 'selectVI'); + return false; + } + + $videoId = ''; + if (preg_match('/^https?:\/\/(www\.)?youtu(\.be|be\.com\/watch\?v=)([a-zA-Z0-9_-]{11})/', $this->_post['videourl'], $m)) + $videoId = $m[3]; + else + { + $_SESSION['error']['vi'] = Lang::video('error', 'selectVI'); + return false; + } + + $curl = curl_init('https://youtube.com/oembed?format=json&url=https://www.youtube.com/watch?v='.$videoId); + if (!$curl) + { + trigger_error('VideoAddResponse - curl_init fail', E_USER_ERROR); + $_SESSION['error']['vi'] = Lang::main('intError'); + return false; + } + + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + $ytOembed = curl_exec($curl); + $status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE); + curl_close($curl); + + if ($status == 401) + { + $_SESSION['error']['vi'] = Lang::video('error', 'isPrivate'); + return false; + } + else if ($status != 200) // 404, 500 seen .. does it matter why its inaccessible? + { + $_SESSION['error']['vi'] = Lang::video('error', 'noExist'); + return false; + } + + $videoInfo = json_decode($ytOembed); + $videoInfo->id = $videoId; + + if (!VideoMgr::saveSuggestion($videoInfo, $this->destType, $this->destTypeId, $this->videoHash)) + { + $_SESSION['error']['ss'] = Lang::main('intError'); + return false; + } + + return true; + } +} + +?> diff --git a/endpoints/video/complete.php b/endpoints/video/complete.php new file mode 100644 index 00000000..46cf9d0b --- /dev/null +++ b/endpoints/video/complete.php @@ -0,0 +1,92 @@ + 3. =complete: store edited video file and data + 4. =thankyou +*/ + +class VideoCompleteResponse extends TextResponse +{ + use TrCommunityHelper; + + protected bool $requiresLogin = true; + + protected array $expectedPOST = array( + 'caption' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']] + ); + + private string $videoHash = ''; + private int $destType = 0; + private int $destTypeId = 0; + + public function __construct(string $pageParam) + { + parent::__construct($pageParam); + + // get video destination + // target delivered as video=&.. (hash is optional) + if (!preg_match('/^video=\w+&(-?\d+)\.(-?\d+)(\.(\w{16}))?$/i', $_SERVER['QUERY_STRING'] ?? '', $m, PREG_UNMATCHED_AS_NULL)) + $this->generate404(); + + [, $this->destType, $this->destTypeId, , $this->videoHash] = $m; + + // no such type or this type cannot receive videos + if (!Type::checkClassAttrib($this->destType, 'contribute', CONTRIBUTE_VI)) + $this->generate404(); + + // no such typeId + if (!Type::validateIds($this->destType, $this->destTypeId)) + $this->generate404(); + } + + protected function generate() : void + { + if ($this->handleComplete()) + $this->forward('?video=thankyou&'.$this->destType.'.'.$this->destTypeId); + else + $this->generate404(); + } + + private function handleComplete() : bool + { + if (!VideoMgr::loadSuggestion($videoInfo, $this->destType, $this->destTypeId, $this->videoHash)) + $this->generate404(); + + $pos = DB::Aowow()->selectCell('SELECT MAX(`pos`) FROM ?_videos WHERE `type` = ?d AND `typeId` = ?d AND (`status` & ?d) = 0', $this->destType, $this->destTypeId, CC_FLAG_DELETED); + if (!is_int($pos)) + $pos = -1; + + // write to db + $newId = DB::Aowow()->query( + 'INSERT INTO ?_videos (`type`, `typeId`, `userIdOwner`, `date`, `videoId`, `pos`, `url`, `width`, `height`, `name`, `caption`, `status`) VALUES (?d, ?d, ?d, UNIX_TIMESTAMP(), ?, ?d, ?, ?d, ?d, ?, ?, 0)', + $this->destType, $this->destTypeId, User::$id, + $videoInfo->id, + $pos + 1, + $videoInfo->thumbnail_url, + $videoInfo->thumbnail_width, + $videoInfo->thumbnail_height, + $videoInfo->title, + $this->handleCaption($this->_post['caption']) + ); + + if (!is_int($newId)) // 0 is valid, NULL or FALSE is not + { + trigger_error('VideoCompleteResponse - video query failed', E_USER_ERROR); + return false; + } + + VideoMgr::dropTempFile(); + + return true; + } +} + +?> diff --git a/endpoints/video/confirm.php b/endpoints/video/confirm.php new file mode 100644 index 00000000..055670ef --- /dev/null +++ b/endpoints/video/confirm.php @@ -0,0 +1,81 @@ + 2. =crop: user edites upload + 2.1. just show edit page + 2.2. user submits coords and description to =complete + 3. =complete: store edited video file and data + 4. =thankyou +*/ + +class VideoConfirmResponse extends TemplateResponse +{ + protected bool $requiresLogin = true; + + protected string $template = 'video'; + protected string $pageName = 'video'; + + public ?Markup $infobox = null; + public string $videoHash = ''; + public int $destType = 0; + public int $destTypeId = 0; + public string $url = ''; + public int $width = 0; + public int $height = 0; + public array $video = []; + public string $viTitle = ''; + + public function __construct(string $pageParam) + { + parent::__construct($pageParam); + + // get video destination + // target delivered as video=&.. (hash is optional) + if (!preg_match('/^video=\w+&(-?\d+)\.(-?\d+)(\.(\w{16}))?$/i', $_SERVER['QUERY_STRING'] ?? '', $m, PREG_UNMATCHED_AS_NULL)) + $this->generateError(); + + [, $this->destType, $this->destTypeId, , $this->videoHash] = $m; + + // no such type or this type cannot receive videos + if (!Type::checkClassAttrib($this->destType, 'contribute', CONTRIBUTE_VI)) + $this->generateError(); + + // no such typeId + if (!Type::validateIds($this->destType, $this->destTypeId)) + $this->generateError(); + } + + protected function generate() : void + { + $this->h1 = Lang::video('submission'); + array_unshift($this->title, $this->h1); + + if (!VideoMgr::loadSuggestion($videoInfo, $this->destType, $this->destTypeId, $this->videoHash)) + $this->generateError(); + + $this->viTitle = $videoInfo->title; + $this->url = $videoInfo->thumbnail_url; + $this->width = $videoInfo->thumbnail_width; + $this->height = $videoInfo->thumbnail_height; + $this->video = [[ + 'videoType' => VideoMgr::TYPE_YOUTUBE, + 'videoId' => $videoInfo->id, + 'caption' => $videoInfo->title + ]]; + + // target + $this->infobox = new Markup(Lang::screenshot('displayOn', [Lang::typeName($this->destType), Type::getFileString($this->destType), $this->destTypeId]), ['allow' => Markup::CLASS_STAFF, 'dbpage' => true], 'infobox-contents0'); + $this->extendGlobalIds($this->destType, $this->destTypeId); + + parent::generate(); + } +} + +?> diff --git a/endpoints/video/thankyou.php b/endpoints/video/thankyou.php new file mode 100644 index 00000000..c90e7922 --- /dev/null +++ b/endpoints/video/thankyou.php @@ -0,0 +1,60 @@ + 4. =thankyou +*/ + +class VideoThankyouResponse extends TemplateResponse +{ + protected bool $requiresLogin = true; + + protected string $template = 'text-page-generic'; + protected string $pageName = 'video'; + + private int $destType = 0; + private int $destTypeId = 0; + + public function __construct(string $pageParam) + { + parent::__construct($pageParam); + + // get video destination + // target delivered as video=&. + if (!preg_match('/^video=\w+&(-?\d+)\.(-?\d+)$/i', $_SERVER['QUERY_STRING'] ?? '', $m, PREG_UNMATCHED_AS_NULL)) + $this->generateError(); + + [, $this->destType, $this->destTypeId] = $m; + + // no such type or this type cannot receive videos + if (!Type::checkClassAttrib($this->destType, 'contribute', CONTRIBUTE_VI)) + $this->generateError(); + + // no such typeId + if (!Type::validateIds($this->destType, $this->destTypeId)) + $this->generateError(); + } + + protected function generate() : void + { + $this->h1 = Lang::video('submission'); + + array_unshift($this->title, $this->h1); + + $this->extraHTML = Lang::video('thanks', 'contrib').'

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

\n"; + $this->extraHTML .= ''.Lang::video('thanks', 'note').''; + + parent::generate(); + } +} + +?> diff --git a/includes/components/videomgr.class.php b/includes/components/videomgr.class.php new file mode 100644 index 00000000..a9b6e07f --- /dev/null +++ b/includes/components/videomgr.class.php @@ -0,0 +1,229 @@ +id . PHP_EOL); + fwrite($tmpFile, $videoInfo->title . PHP_EOL); + fwrite($tmpFile, $videoInfo->thumbnail_url . PHP_EOL); + fwrite($tmpFile, $videoInfo->thumbnail_height . PHP_EOL); + fwrite($tmpFile, $videoInfo->thumbnail_width . PHP_EOL); + + return fclose($tmpFile); + } + + public static function loadSuggestion(?\stdClass &$videoInfo, int $destType, int $destTypeId, ?string $uid) : bool + { + self::$tmpFile = sprintf(self::PATH_TEMP, User::$username.'-'.$destType.'-'.$destTypeId.'-'.$uid); + + if (!file_exists(self::$tmpFile)) + return false; + + if ($info = file(self::$tmpFile, FILE_IGNORE_NEW_LINES)) + { + $videoInfo = new \stdClass; + $videoInfo->id = $info[0]; + $videoInfo->title = $info[1]; + $videoInfo->thumbnail_url = $info[2]; + $videoInfo->thumbnail_height = (int)$info[3]; + $videoInfo->thumbnail_width = (int)$info[4]; + + return true; + } + + return false; + } + + public static function dropTempFile() + { + if (!self::$tmpFile || !file_exists(self::$tmpFile)) + return; + + unlink(self::$tmpFile); + } + + + /*************/ + /* Admin Mgr */ + /*************/ + + public static function getVideos(int $type = 0, int $typeId = 0, $userId = 0, ?int &$nFound = 0) : array + { + /* VideoData + * caption: caption + * date: isodate + * height: ytPreviewImgHeight? + * width: ytPreviewImgWidth? + * id: id + * next: idx || null + * prev: idx || null + * name: ytTitle? + * pending: bool + * status: statusCode + * type: dbType + * typeId: typeId + * user: userName + * url: ytPreviewImg? + * videoType: always 1 + * videoId: videoId + * unique: bool || null + */ + + $videos = DB::Aowow()->select( + 'SELECT v.`id`, a.`username` AS "user", v.`date`, v.`videoId`, v.`type`, v.`typeId`, v.`caption`, v.`status` AS "flags", v.`url`, v.`name` + FROM ?_videos v + LEFT JOIN ?_account a ON v.`userIdOwner` = a.`id` + WHERE + { v.`type` = ?d } + { AND v.`typeId` = ?d } + { v.`userIdOwner` = ?d } + { LIMIT ?d } + ORDER BY `type`, `typeId`, `pos` ASC', + $userId ? DBSIMPLE_SKIP : $type, + $userId ? DBSIMPLE_SKIP : $typeId, + $userId ? $userId : DBSIMPLE_SKIP, + $userId || $type ? DBSIMPLE_SKIP : 100 + ); + + $num = []; + foreach ($videos as $v) + { + if (empty($num[$v['type']][$v['typeId']])) + $num[$v['type']][$v['typeId']] = 1; + else + $num[$v['type']][$v['typeId']]++; + } + + $nFound = 0; + + // format data to meet requirements of the js + foreach ($videos as $i => &$v) + { + $nFound++; + + $v['date'] = date(Util::$dateFormatInternal, $v['date']); + $v['videoType'] = self::TYPE_YOUTUBE; + + if ($i > 0) + $v['prev'] = $i - 1; + + if (($i + 1) < count($videos)) + $v['next'] = $i + 1; + + // order gives priority for 'status' + if (!($v['flags'] & CC_FLAG_APPROVED)) + { + $v['pending'] = 1; + $v['status'] = self::STATUS_PENDING; + } + else + $v['status'] = self::STATUS_APPROVED; + + if ($v['flags'] & CC_FLAG_STICKY) + { + $v['sticky'] = 1; + $v['status'] = self::STATUS_STICKY; + } + + if ($v['flags'] & CC_FLAG_DELETED) + { + $v['deleted'] = 1; + $v['status'] = self::STATUS_DELETED; + } + + // something todo with massSelect .. am i doing this right? + if ($num[$v['type']][$v['typeId']] == 1) + $v['unique'] = 1; + + if (!$v['user']) + unset($v['user']); + } + + return $videos; + } + + public static function getPages(?bool $all, ?int &$nFound) : array + { + // i GUESS .. vi_getALL ? everything : pending + $nFound = 0; + $pages = DB::Aowow()->select( + 'SELECT v.`type`, v.`typeId`, COUNT(1) AS "count", MIN(v.`date`) AS "date" + FROM ?_videos v + { WHERE (v.`status` & ?d) = 0 } + GROUP BY v.`type`, v.`typeId`', + $all ? DBSIMPLE_SKIP : CC_FLAG_APPROVED | CC_FLAG_DELETED + ); + + if ($pages) + { + // limit to one actually existing type each + foreach (array_unique(array_column($pages, 'type')) as $t) + { + $ids = []; + foreach ($pages as $row) + if ($row['type'] == $t) + $ids[] = $row['typeId']; + + if (!$ids) + continue; + + $obj = Type::newList($t, [Cfg::get('SQL_LIMIT_NONE'), ['id', $ids]]); + if (!$obj || $obj->error) + continue; + + foreach ($pages as &$p) + if ($p['type'] == $t) + if ($obj->getEntry($p['typeId'])) + $p['name'] = $obj->getField('name', true); + } + + foreach ($pages as &$p) + { + if (empty($p['name'])) + { + trigger_error('VideoMgr::getPages - video linked to nonexistent type/typeId combination: '.$p['type'].'/'.$p['typeId'], E_USER_NOTICE); + unset($p); + } + else + { + $nFound += $p['count']; + $p['date'] = date(Util::$dateFormatInternal, $p['date']); + } + } + } + + return $pages; + } +} + +?> diff --git a/includes/defines.php b/includes/defines.php index 49e18275..a9f440ef 100644 --- a/includes/defines.php +++ b/includes/defines.php @@ -90,7 +90,7 @@ define('SITEREP_ACTION_DAILYVISIT', 2); // Daily visit define('SITEREP_ACTION_COMMENT', 3); // Posted comment define('SITEREP_ACTION_UPVOTED', 4); // Your comment was upvoted define('SITEREP_ACTION_DOWNVOTED', 5); // Your comment was downvoted -define('SITEREP_ACTION_SUBMIT_SCREENSHOT', 6); // Submitted screenshot (suggested video) +define('SITEREP_ACTION_SUBMIT_SCREENSHOT', 6); // Submitted screenshot // Cast vote // Uploaded data define('SITEREP_ACTION_GOOD_REPORT', 9); // Report accepted @@ -98,7 +98,7 @@ define('SITEREP_ACTION_BAD_REPORT', 10); // Report declined // Copper Achievement // Silver Achievement // Gold Achievement - // Test 1 +define('SITEREP_ACTION_SUGGEST_VIDEO', 14); // repurposed, originally: Test 1 // Test 2 define('SITEREP_ACTION_ARTICLE', 16); // Guide approved (article approved) define('SITEREP_ACTION_USER_WARNED', 17); // Moderator Warning diff --git a/includes/utilities.php b/includes/utilities.php index 5ead86dc..10d84995 100644 --- a/includes/utilities.php +++ b/includes/utilities.php @@ -727,12 +727,12 @@ abstract class Util $x['amount'] = $action == SITEREP_ACTION_UPVOTED ? Cfg::get('REP_REWARD_UPVOTED') : Cfg::get('REP_REWARD_DOWNVOTED'); break; case SITEREP_ACTION_SUBMIT_SCREENSHOT: + case SITEREP_ACTION_SUGGEST_VIDEO: if (empty($miscData['id']) || empty($miscData['what'])) return false; $x['sourceA'] = $miscData['id']; // screenshotId or videoId - $x['sourceB'] = $miscData['what']; // screenshot:1 - $x['amount'] = Cfg::get('REP_REWARD_UPLOAD'); + $x['amount'] = $action == SITEREP_ACTION_SUBMIT_SCREENSHOT ? Cfg::get('REP_REWARD_SUBMIT_SCREENSHOT') : Cfg::get('REP_REWARD_SUGGEST_VIDEO'); break; case SITEREP_ACTION_GOOD_REPORT: // NYI case SITEREP_ACTION_BAD_REPORT: diff --git a/localization/lang.class.php b/localization/lang.class.php index 1bde86d4..e496c36b 100644 --- a/localization/lang.class.php +++ b/localization/lang.class.php @@ -16,6 +16,7 @@ class Lang private static $maps; private static $profiler; private static $screenshot; + private static $video; private static $privileges; private static $smartAI; private static $unit; diff --git a/localization/locale_dede.php b/localization/locale_dede.php index 9369227d..c3e0007b 100644 --- a/localization/locale_dede.php +++ b/localization/locale_dede.php @@ -278,6 +278,20 @@ $lang = array( 'notAllowed' => "Es ist euch nicht erlaubt einen Screenshot hochzuladen!", ) ), + 'video' => array( + 'submission' => "Video-Einsendung", + 'thanks' => array( + 'contrib' => "Vielen Dank für Euren Beitrag!", + 'goBack' => 'Klickt hier, um zu der vorherigen Seite zurückzukehren.', + 'note' => "Hinweis: Euer Video muss zunächst zugelassen werden, bevor es auf der Seite erscheint. Dies kann bis zu 72 Stunden dauern." + ), + 'error' => array( + 'isPrivate' => "Das vorgeschlagene Video ist privat.", + 'noExist' => "An der eingereichten Url existiert kein Video.", + 'selectVI' => "Bitte gebt gültige Videoinformationen ein.", + 'notAllowed' => "Es ist euch nicht erlaubt Videos vorzuschlagen!" + ) + ), 'game' => array( // type strings 'npc' => "NPC", // 1 diff --git a/localization/locale_enus.php b/localization/locale_enus.php index 4628acc2..e4a7fe76 100644 --- a/localization/locale_enus.php +++ b/localization/locale_enus.php @@ -278,6 +278,20 @@ $lang = array( 'notAllowed' => "You are not allowed to upload screenshots!", ) ), + 'video' => array( + 'submission' => "Video Suggestion", + 'thanks' => array( + 'contrib' => "Thanks a lot for your contribution!", + 'goBack' => 'Click here to go back to the page you came from.', + 'note' => "Note: Your video will need to be approved before appearing on the site. This can take up to 72 hours." + ), + 'error' => array( + 'isPrivate' => "The suggested video is private.", + 'noExist' => "No video found at the provided Url.", + 'selectVI' => "Please enter valid video information.", // message_novideo + 'notAllowed' => "You are not allowed to suggest videos!", + ) + ), 'game' => array( // type strings 'npc' => "NPC", diff --git a/localization/locale_eses.php b/localization/locale_eses.php index 82ba9a10..95a75707 100644 --- a/localization/locale_eses.php +++ b/localization/locale_eses.php @@ -278,6 +278,20 @@ $lang = array( 'notAllowed' => "¡No estás permitido para subir capturas de pantalla!", ) ), + 'video' => array( + 'submission' => "Sugerencia de video", + 'thanks' => array( + 'contrib' => "¡Muchísimas gracias por tu aportación!", + 'goBack' => 'aquí vuelve a la página de la que viniste.', + 'note' => "Nota: Tu video tiene que ser aprobado antes de que pueda aparecer en el sitio. Esto puede tomar hasta 72 horas." + ), + 'error' => array( + 'isPrivate' => "El video sugerido es privado.", + 'noExist' => "No se encontró ningún video en la URL proporcionada.", + 'selectVI' => "Por favor, introduce información válida del vídeo.", // message_novideo + 'notAllowed' => "¡No tienes permiso para sugerir videos!", + ) + ), 'game' => array( // type strings 'npc' => "PNJ", diff --git a/localization/locale_frfr.php b/localization/locale_frfr.php index 96e90a94..7123dde1 100644 --- a/localization/locale_frfr.php +++ b/localization/locale_frfr.php @@ -278,6 +278,20 @@ $lang = array( 'notAllowed' => "Vous n'êtes pas autorisés à exporter des captures d'écran.", ) ), + 'video' => array( + 'submission' => "Suggestion de vidéo", + 'thanks' => array( + 'contrib' => "Merci beaucoup de votre contribution!", + 'goBack' => 'ici pour retourner à la page d\'où vous venez.', + 'note' => "Note : Votre vidéo devra être approuvée avant d'apparaître sur le site. Cela peut prendre jusqu'à 72 heures." + ), + 'error' => array( + 'isPrivate' => "La vidéo suggérée est privée.", + 'noExist' => "Aucune vidéo trouvée à l'URL fournie.", + 'selectVI' => "Veuillez entrer des informations valides pour la vidéo.", // message_novideo + 'notAllowed' => "Vous n'êtes pas autorisé à suggérer des vidéos!", + ) + ), 'game' => array( // type strings 'npc' => "PNJ", diff --git a/localization/locale_ruru.php b/localization/locale_ruru.php index 930b776a..6e0de9a1 100644 --- a/localization/locale_ruru.php +++ b/localization/locale_ruru.php @@ -278,6 +278,20 @@ $lang = array( 'notAllowed' => "[You are not allowed to upload screenshots!]", ) ), + 'video' => array( + 'submission' => "Предложить видео", + 'thanks' => array( + 'contrib' => "Спасибо за ваш вклад!", + 'goBack' => 'здесь чтобы перейти к предыдущей странице.', + 'note' => "Примечание: Ваше видео должно быть одобрено, прежде чем появится на сайте. Это может занять до 72 часов." + ), + 'error' => array( + 'isPrivate' => "Предложенное видео является приватным.", + 'noExist' => "Видео по предоставленной ссылке не найдено.", + 'selectVI' => "введите корректную информацию о видео.", // message_novideo + 'notAllowed' => "У вас нет прав предлагать видео!", + ) + ), 'game' => array( // type strings 'npc' => "НИП", diff --git a/localization/locale_zhcn.php b/localization/locale_zhcn.php index a5c5912c..42e090cb 100644 --- a/localization/locale_zhcn.php +++ b/localization/locale_zhcn.php @@ -278,7 +278,22 @@ $lang = array( 'notAllowed' => "你不允许上传截图!", ) ), + 'video' => array( + 'submission' => "视频建议", + 'thanks' => array( + 'contrib' => "非常感谢你的贡献!", + 'goBack' => '点击这里返回上一页。', + 'note' => "注意:您的视频需要经过审核后才能显示在网站上。这需要最多72小时。" + ), + 'error' => array( + 'isPrivate' => "建议的视频为私有。", + 'noExist' => "在提供的链接中未找到视频。", + 'selectVI' => "请输入有效的视频信息。", // message_novideo + 'notAllowed' => "您没有权限建议视频!", + ) + ), 'game' => array( + // type strings 'npc' => "NPC", 'npcs' => "NPC", 'object' => "对象", diff --git a/setup/updates/1758578400_07.sql b/setup/updates/1758578400_07.sql new file mode 100644 index 00000000..4ba7523f --- /dev/null +++ b/setup/updates/1758578400_07.sql @@ -0,0 +1,8 @@ +-- `key` is too small for our new configs +ALTER TABLE `aowow_config` + MODIFY COLUMN `key` varchar(50) NOT NULL; + +-- split generic upload in ss / vi +UPDATE `aowow_config` SET `key` = 'rep_reward_submit_screenshot', `comment` = 'uploaded screenshot was approved' WHERE `key` = 'rep_reward_upload'; +DELETE FROM `aowow_config` WHERE `key` = 'rep_reward_suggest_video'; +INSERT INTO `aowow_config` VALUES ('rep_reward_suggest_video', '10', '10', 5, 129, 'suggested video was approved'); diff --git a/setup/updates/1758578400_08.sql b/setup/updates/1758578400_08.sql new file mode 100644 index 00000000..f7ad1861 --- /dev/null +++ b/setup/updates/1758578400_08.sql @@ -0,0 +1,8 @@ +-- update video storage +ALTER TABLE `aowow_videos` + ADD COLUMN `pos` tinyint unsigned NOT NULL AFTER `videoId`, + ADD COLUMN `url` varchar(64) NOT NULL COMMENT 'preview thumb' AFTER `pos`, + ADD COLUMN `width` smallint unsigned NOT NULL AFTER `url`, + ADD COLUMN `height` smallint unsigned NOT NULL AFTER `width`, + ADD COLUMN `name` varchar(64) DEFAULT NULL AFTER `height`, + MODIFY COLUMN `caption` varchar(200) DEFAULT NULL; diff --git a/setup/updates/1758578400_09.sql b/setup/updates/1758578400_09.sql new file mode 100644 index 00000000..4af1c23d --- /dev/null +++ b/setup/updates/1758578400_09.sql @@ -0,0 +1,4 @@ +-- update article affected by cfg change +UPDATE `aowow_acticles` SET + `article` = '[b]Reputation[/b] is a rough measurement of how much you participate in the community--it is earned by convincing your peers that you know what you’re talking about. Our community puts just as much work as our developers do into making our site as awesome as it is and reputation is meant as a way for you to track just how much work you\'re putting into us.\r\n\r\nThe primary means of gaining reputation is by posting quality comments on database entries (which are then voted up by other site members) and by general contributions to the site which can include actions like data and screenshot submissions. Whenever you leave a comment on a database entry, your peers can then vote on these comments, and those votes will cause you to gain reputation. You can also earn reputation by voting on other users\' comments and by sending in reports!\r\n\r\nBy being a good-standing and contributing user you will be able to earn both reputation and achievements for many of the same actions!\r\n\r\n[h3]Reputation Gains[/h3]\r\n[div style=\"max-width:400px\"][table class=grid]\r\n[tr][td][url=?account=signup]Registering[/url] an account[/td]\r\n[td align=right class=no-wrap]CFG_REP_REWARD_REGISTER reputation[/td]\r\n[/tr]\r\n[tr][td]Daily visit[/td]\r\n[td align=right class=no-wrap]CFG_REP_REWARD_DAILYVISIT reputation[/td]\r\n[/tr]\r\n[tr][td]Posting a comment[/td]\r\n[td align=right class=no-wrap]CFG_REP_REWARD_COMMENT reputation[/td]\r\n[/tr]\r\n[tr][td]Your comment was voted up (each upvote)[/td]\r\n[td align=right class=no-wrap]CFG_REP_REWARD_UPVOTED reputation[/td]\r\n[/tr]\r\n[tr][td]Submitting a screenshot[/td]\r\n[td align=right class=no-wrap]REP_REWARD_SUBMIT_SCREENSHOT reputation[/td]\r\n[/tr]\r\n[tr][td]Suggesting a video[/td]\r\n[td align=right class=no-wrap]REP_REWARD_SUGGEST_VIDEO reputation[/td]\r\n[/tr]\r\n[tr][td]Submitting a guide (approved)[/td]\r\n[td align=right class=no-wrap]CFG_REP_REWARD_ARTICLE reputation[/td]\r\n[/tr]\r\n[tr][td]Filing a report (accepted)[/td]\r\n[td align=right class=no-wrap]CFG_REP_REWARD_GOOD_REPORT reputation[/td]\r\n[/tr]\r\n[/table][/div]\r\n\r\n\r\n[h3]Site Privileges[/h3]\r\nThe higher your reputation level, the more privileges you gain. Earn a high enough reputation to unlock additional rewards, in the form of new privileges around the site!\r\n[pad]\r\n[div style=\"max-width:400px\"][table class=grid]\r\n[tr][td]Post comments[/td]\r\n[td align=right class=no-wrap]CFG_REP_REQ_COMMENT reputation[/td]\r\n[/tr]\r\n[tr][td]Upvote on comments[/td]\r\n[td align=right class=no-wrap]CFG_REP_REQ_UPVOTE reputation[/td]\r\n[/tr]\r\n[tr][td]Downvote on comments[/td]\r\n[td align=right class=no-wrap]CFG_REP_REQ_DOWNVOTE reputation[/td]\r\n[/tr]\r\n[tr][td]More votes per day[/td]\r\n[td align=right class=no-wrap]CFG_REP_REQ_VOTEMORE_BASE reputation[/td]\r\n[/tr]\r\n[tr][td]Comment votes worth more[/td]\r\n[td align=right class=no-wrap]CFG_REP_REQ_SUPERVOTE reputation[/td]\r\n[/tr]\r\n[/table][/div]\r\n[pad]\r\n[url=?privileges]Check out full details on site privileges you can earn![/url]\r\n' +WHERE `url` = 'reputation' AND `locale` = 0; diff --git a/static/js/global.js b/static/js/global.js index 91d49851..453f644e 100644 --- a/static/js/global.js +++ b/static/js/global.js @@ -2831,9 +2831,10 @@ var vi_siteurls = { 1: 'https://www.youtube.com/watch?v=$1' // YouTube }; -var vi_sitevalidation = { - 1: /^https?:\/\/www\.youtube\.com\/watch\?v=([^& ]{11})/ // YouTube -}; +var vi_sitevalidation = [ + /https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([^& ]{11})/i, + /https?:\/\/(?:www\.)?youtu\.be\/([^& ]{11})/i +]; function vi_submitAVideo() { tabsContribute.focus(2); @@ -2882,7 +2883,7 @@ function vi_appendSticky() { }; var img = $WH.ce('img'); - img.src = $WH.sprintf(vi_thumbnails[video.videoType], video.videoId); + img.src = $WH.sprintf(vi_thumbnails[video.videoType].replace(/\/default\.jpg/, '/mqdefault.jpg'), video.videoId); img.className = 'border'; $WH.ae(a, img); @@ -3227,7 +3228,7 @@ var VideoViewer = new function() { aCover.onclick = Lightbox.hide; var foo = $WH.ce('span'); var b = $WH.ce('b'); - $WH.ae(b, $WH.ct(LANG.close)); + // $WH.ae(b, $WH.ct(LANG.close)); $WH.ae(foo, b); $WH.ae(aCover, foo); @@ -3312,7 +3313,7 @@ var VideoViewer = new function() { onShow: onShow, onHide: onHide, onResize: onResize - },opt); + }, opt); return false; } diff --git a/static/js/locale_dede.js b/static/js/locale_dede.js index 7ee34a44..32d67761 100644 --- a/static/js/locale_dede.js +++ b/static/js/locale_dede.js @@ -13,7 +13,7 @@ var l_reputation_names = [ "Bronzeerfolg", "Silbererfolg", "Golderfolg", - 'Test 1', + 'Video vorgeschlagen', // aowow - originally: Test 1 'Test 2', "Leitfaden zugelassen", "Warnung durch Moderator", diff --git a/static/js/locale_enus.js b/static/js/locale_enus.js index a06c0c6f..4aacd372 100644 --- a/static/js/locale_enus.js +++ b/static/js/locale_enus.js @@ -13,7 +13,7 @@ var l_reputation_names = [ "Copper Achievement", "Silver Achievement", "Gold Achievement", - 'Test 1', + 'Video suggested', // aowow - originally: Test 1 'Test 2', "Guide approved", "Moderator Warning", diff --git a/static/js/locale_eses.js b/static/js/locale_eses.js index e4f82e5a..6cbf3d68 100644 --- a/static/js/locale_eses.js +++ b/static/js/locale_eses.js @@ -13,7 +13,7 @@ var l_reputation_names = [ "Logro de Cobre", "Logro de Plata", "Logro de Oro", - 'Test 1', + "Vídeo sugerido", // aowow - originally: Test 1 'Test 2', "Guía aprobada", "Aviso de moderador", diff --git a/static/js/locale_frfr.js b/static/js/locale_frfr.js index 42128086..747196f1 100644 --- a/static/js/locale_frfr.js +++ b/static/js/locale_frfr.js @@ -13,7 +13,7 @@ var l_reputation_names = [ "Haut-fait de bronze", "Haut-fait d'argent", "Haut-fait d'or", - 'Test 1', + "Vidéo suggérée", // aowow - originally: Test 1 'Test 2', "Guide approuvé", "Avertissement d'un modérateur", diff --git a/static/js/locale_ruru.js b/static/js/locale_ruru.js index 8851c21b..0211ba2c 100644 --- a/static/js/locale_ruru.js +++ b/static/js/locale_ruru.js @@ -13,7 +13,7 @@ var l_reputation_names = [ "Бронзовое достижение", "Серебряное достижение", "Золотое достижение", - "Test 1", + "Видео предложено", // aowow - originally: Test 1 "Test 2", "Гайд одобрен", "Пожаловаться модератору", diff --git a/static/js/locale_zhcn.js b/static/js/locale_zhcn.js index 53d7cfda..186bb754 100644 --- a/static/js/locale_zhcn.js +++ b/static/js/locale_zhcn.js @@ -13,7 +13,7 @@ var l_reputation_names = [ "铜牌成就", "银牌成就", "金牌成就", - 'Test 1', + '建议视频', // aowow - originally: Test 1 'Test 2', "指南通过审核", "管理员警告", diff --git a/static/js/video.js b/static/js/video.js new file mode 100644 index 00000000..545ca01c --- /dev/null +++ b/static/js/video.js @@ -0,0 +1,1135 @@ +var vi_managedRow = null; +var vi_getAll = false; +var vim_ViewedRow = null; +var vim_videoData = []; +var vim_videoPages = []; +var vim_numPagesFound = 0; +var vim_numPages = 0; +var vim_numPending = 0; +var vim_statuses = { + 0 : 'Pending', + 999: 'Deleted', + 100: 'Approved', + 105: 'Sticky' +}; + +function makePipe() { + var sp = $WH.ce('span'); + $WH.ae(sp, $WH.ct(' ')); + + var b = $WH.ce('small'); + b.className = 'q0'; + $WH.ae(b, $WH.ct('|')); + + $WH.ae(sp, b); + $WH.ae(sp, $WH.ct(' ')); + + return sp; +} + +function vi_OnResize() { + var a = Math.max(100, Math.min($WH.g_getWindowSize().h - 50, 700)); + + $WH.ge('menu-container').style.height = $WH.ge('pages-container').style.height = a + 'px'; + $WH.ge('data-container').style.height = a + 'px'; +} + +$WH.aE(window, 'resize', vi_OnResize); + +function vi_Refresh(openNext, type, typeId) { + new Ajax('?admin=videos&action=list' + (vi_getAll ? '&all': ''), { + method: 'get', + onSuccess: function (xhr) { + eval(xhr.responseText); + + if (vim_videoPages.length > 0) { + $WH.ge('show-all-pages').innerHTML = ' – Show All (' + vim_numPagesFound + ')'; + + vim_UpdatePages(); + + if (openNext) + vi_Manage($WH.ge('pages-container').firstChild.firstChild, vim_videoPages[0].type, vim_videoPages[0].typeId, true); + else if (type && typeId) + vi_Manage(null, type, typeId, true); + } + else { + $WH.ee($WH.ge('show-all-pages')); + $WH.ge('pages-container').innerHTML = 'NO VIDEOZ NEEDS 2 BE APPRVED NOW KTHX. :)'; + if (type && typeId) + vi_Manage(null, type, typeId, true); + } + } + }) +} + +function vi_Manage(_this, type, typeId, openNext) { + new Ajax('?admin=videos&action=manage&type=' + type + '&typeid=' + typeId, { + method: 'get', + onSuccess: function (xhr) { + + eval(xhr.responseText); + vim_numPending = 0; + + for (var i in vim_videoData) + if (vim_videoData[i].pending) + vim_numPending++; + + var nRows = vim_videoData.length; + $WH.ge('videoTotal').innerHTML = nRows + ' total' + (nRows == 100 ? ' (limit reached)' : ''); + + vim_UpdateList(openNext); + vim_UpdateMassLinks(); + + if (vi_managedRow != null) + vi_ColorizeRow('transparent'); + + vi_managedRow = _this; + + if (vi_managedRow != null) + vi_ColorizeRow('#282828'); + } + }); +} + +function vi_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=videos&action=manage&user=' + username.value, { + method: 'get', + onSuccess: function (xhr) { + eval(xhr.responseText); + + var nRows = vim_videoData.length; + $WH.ge('videoTotal').innerHTML = nRows + ' total' + (nRows == 100 ? ' (limit reached)' : ''); + + vim_UpdateList(); + vim_UpdateMassLinks(); + + if (vi_managedRow != null) + vi_ColorizeRow('transparent'); + } + }); + + return true +} + +function vi_ColorizeRow(color) { + for (var i = 0; i < vi_managedRow.childNodes.length; ++i) + vi_managedRow.childNodes[i].style.backgroundColor = color; +} + +function vim_GetVideo(id) { + for (var i in vim_videoData) + if (vim_videoData[i].id == id) + return vim_videoData[i]; + + return null +} + +function vim_View(row, id) { + if (vim_ViewedRow != null) + vim_ColorizeRow('transparent'); + + vim_ViewedRow = row; + vim_ColorizeRow('#282828'); + + var video = vim_GetVideo(id); + if (video != null) + VideoManager.show(video); +} + +function vim_ColorizeRow(color) { + for (var i = 0; i < vim_ViewedRow.childNodes.length; ++i) + vim_ViewedRow.childNodes[i].style.backgroundColor = color; +} + +function vim_ConfirmMassApprove() { + ajaxAnchor(this); // aowow custom - same endpoint gets used as ajax and page .. what? + + return false; + // return true; +} + +function vim_ConfirmMassDelete() { + if (confirm('Delete selected video(s)?')) // aowow custom - see above + ajaxAnchor(this); + + return false; + // return confirm('Delete selected video(s)?'); +} + +function vim_ConfirmMassSticky() { + if (confirm('Sticky selected video(s)?')) // aowow custom - see above + ajaxAnchor(this); + + return false; + // return confirm('Sticky selected video(s)?'); +} + +function vim_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 vim_videoPages) { + var viPage = vim_videoPages[i]; + tr = $WH.ce('tr'); + tr.onclick = vi_Manage.bind(tr, tr, viPage.type, viPage.typeId, true, i); + + var td = $WH.ce('td'); + var a = $WH.ce('a'); + a.href = '?' + g_types[viPage.type] + '=' + viPage.typeId; + a.target = '_blank'; + $WH.ae(a, $WH.ct(viPage.name)); + $WH.ae(td, a); + $WH.ae(tr, td); + + td = $WH.ce('td'); + var elapsed = new Date(viPage.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(viPage.count)); + $WH.ae(tr, td); + + $WH.ae(tbl, tr); + } + + $WH.ae(pc, tbl); +} + +function vim_UpdateList(k) { + var tbl = $WH.ge('theVideosList'); + var tBody = false; + var i = 1; + + while (tbl.childNodes.length > i) { + if (tbl.childNodes[i].nodeName == 'TR' && tBody) + $WH.de(tbl.childNodes[i]); + else if (tbl.childNodes[i].nodeName == 'TR') + tBody = true; + else + i++; + } + + var now = new Date(); + var viId = 0; + for (var i in vim_videoData) { + var video = vim_videoData[i]; + var tr = $WH.ce('tr'); + if (viId == 0 && video.pending) { + viId = video.id; + tr.id = 'highlightedRow'; + } + + var td = $WH.ce('td'); + td.align = 'center'; + + // if (video.status != 999 && !video.pending) { // Aowow - removed + var a = $WH.ce('a'); + a.href = $WH.sprintf(vi_siteurls[video.videoType], video.videoId); + a.target = '_blank'; + a.onclick = function (id, e) { + $WH.sp(e); + (vim_View.bind(null, this, id))(); + return false; + }.bind(tr, video.id); + + var previewImg = $WH.ce('img'); + previewImg.src = $WH.sprintf(vi_thumbnails[video.videoType], video.videoId); + previewImg.height = 50; + $WH.ae(a, previewImg); + $WH.ae(td, a); + // } + $WH.ae(tr, td); + + td = $WH.ce('td'); + if (video.status != 999 && !video.pending) { + var a = $WH.ce('a'); + a.href = '?' + g_types[video.type] + '=' + video.typeId + '#videos:id=' + video.id; + a.target = '_blank'; + a.onclick = function (a) { $WH.sp(a); }; + $WH.ae(a, $WH.ct(video.id)); + $WH.ae(td, a); + } + else + $WH.ae(td, $WH.ct(video.id)); + + $WH.ae(tr, td); + + td = $WH.ce('td'); + td.id = 'title-' + video.id; + + var sp = $WH.ce('span'); + sp.style.paddingRight = '8px'; + if (video.caption) { + var sp2 = $WH.ce('span'); + sp2.className = 'q2'; + var b = $WH.ce('b'); + $WH.ae(b, $WH.ct(video.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 (vi, e) { + $WH.sp(e); + (vim_ShowEdit.bind(this, vi))(); + }.bind(a, video); + $WH.ae(a, $WH.ct('Edit')); + $WH.ae(sp, a); + $WH.ae(sp, makePipe()); + + a = $WH.ce('a'); + a.href = 'javascript:;'; + a.onclick = function (vi, e) { + $WH.sp(e); + (vim_Clear.bind(this, vi))(); + }.bind(a, video); + $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(video.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=' + video.user; + a.target = '_blank'; + a.onclick = function (a) { $WH.sp(a); }; + $WH.ae(a, $WH.ct(video.user)); + $WH.ae(td, a); + $WH.ae(tr, td); + + td = $WH.ce('td'); + $WH.ae(td, $WH.ct(vim_statuses[video.status])); + $WH.ae(tr, td); + + td = $WH.ce('td'); + var cb = $WH.ce('input'); + cb.type = 'checkbox'; + cb.value = video.id; + cb.onclick = function (e) { + $WH.sp(e); + (vim_UpdateMassLinks.bind(this))(); + }.bind(cb); + $WH.ae(td, cb); + + $WH.ae(td, $WH.ct(' ')); + + if (video.status != 999) { + tr.onclick = function (id) { + vim_View(this, id); + return false; + }.bind(tr, video.id); + + if (video.id == viId && k) + vim_View(tr, video.id); + + if (video.pending) { + a = $WH.ce('a'); + a.href = 'javascript:;'; + a.onclick = function (e) { + $WH.sp(e); + (vim_Approve.bind(this, false))(); + }.bind(video); + $WH.ae(a, $WH.ct('Approve')); + $WH.ae(td, a); + } + else + $WH.ae(td, $WH.ct('Approve')); + + $WH.ae(td, makePipe()); + + if (video.status != 105) { + a = $WH.ce('a'); + a.href = 'javascript:;'; + a.onclick = function (e) { + $WH.sp(e); + (vim_Sticky.bind(this, false))(); + }.bind(video); + $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); + (vim_Delete.bind(this, false))(); + }.bind(video); + $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 a = prompt('Enter the ID to move this video to:'); + (vim_Relocate.bind(this, a))(); + }.bind(video); + $WH.ae(a, $WH.ct('Relocate')); + $WH.ae(td, a); + + $WH.ae(td, makePipe()); + + if (i > 0) { + a = $WH.ce('a'); + a.href = 'javascript:;'; + a.onclick = function (e) { + $WH.sp(e); + (vim_Move.bind(this, -1))() + }.bind(video); + $WH.ae(a, $WH.ct('Move up')); + $WH.ae(td, a); + } + else + $WH.ae(td, $WH.ct('Move up')); + + $WH.ae(td, makePipe()); + + if (i < vim_videoData.length - 1) { + a = $WH.ce('a'); + a.href = 'javascript:;'; + a.onclick = function (e) { + $WH.sp(e); + (vim_Move.bind(this, 1))(); + }.bind(video); + $WH.ae(a, $WH.ct('Move down')); + $WH.ae(td, a); + } + else + $WH.ae(td, $WH.ct('Move down')); + } + + $WH.ae(tr, td); + $WH.ae(tbl, tr); + } +} + +function vim_UpdateMassLinks() { + var idBuff = ''; + var i = 0; + var e = $WH.ge('theVideosList'); + var inp = $WH.gE(e, 'input'); + + $WH.array_walk(inp, function (i) { + if (i.checked) { + idBuff += i.value + ','; ++i + } + }); + + idBuff = $WH.rtrim(idBuff, ','); + + 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=videos&action=approve&id=' + idBuff; + c.onclick = vim_ConfirmMassApprove; + + b.href = '?admin=videos&action=delete&id=' + idBuff; + b.onclick = vim_ConfirmMassDelete; + + a.href = '?admin=videos&action=sticky&id=' + idBuff; + a.onclick = vim_ConfirmMassSticky; + } + else + selCnt.style.display = 'none'; +} + +function vim_MassSelect(action) { + var tbl = $WH.ge('theVideosList'); + var inp = $WH.gE(tbl, '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 = vim_GetVideo(x.value).status == 0; }); + break; + case 5: + $WH.array_walk(inp, function (x) { x.checked = vim_GetVideo(x.value).unique == 1 && vim_GetVideo(x.value).status == 0; }); + break; + case 3: + $WH.array_walk(inp, function (x) { x.checked = vim_GetVideo(x.value).status == 100; }); + break; + case 4: + $WH.array_walk(inp, function (x) { x.checked = vim_GetVideo(x.value).status == 105; }); + break; + default: + return; + } + + vim_UpdateMassLinks(); +} + +function vim_ShowEdit(video, isAlt) { + var node; + if (isAlt) + node = $WH.ge('title2-' + video.id); + else + node = $WH.ge('title-' + video.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 = video.caption; + iCaption.maxLength = 200; + iCaption.size = 35; + iCaption.onclick = function (e) { $WH.sp(e); } // aowow - 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 (vi, isAlt, e) { + if (!isAlt) + $WH.sp(e); + + (vim_Edit.bind(this, vi, isAlt))(); + }.bind(btn, video, isAlt); + div.appendChild(btn); + + var sp2 = $WH.ce('span'); + sp2.appendChild($WH.ct(' ')); + div.appendChild(sp2); + + btn = $WH.ce('input'); + btn.type = 'button'; + btn.value = 'Cancel'; + btn.onclick = function (vi, isAlt, e) { + if (!isAlt) + $WH.sp(e); + + (vim_CancelEdit.bind(this, vi, isAlt))(); + }.bind(btn, video, isAlt); + div.appendChild(btn); + + sp.style.display = 'none'; + sp.nextSibling.style.display = 'none'; + node.insertBefore(div, sp); + + iCaption.focus(); +} + +function vim_CancelEdit(video, isAlt) { + var node; + if (isAlt) + node = $WH.ge('title2-' + video.id); + else + node = $WH.ge('title-' + video.id); + + var b = $WH.gE(node, 'span')[1]; + b.style.display = ''; + b.nextSibling.style.display = ''; + + node.removeChild(node.firstChild); +} + +function vim_Edit(video, isAlt) { + var node; + if (isAlt) + node = $WH.ge('title2-' + video.id); + else + node = $WH.ge('title-' + video.id); + + var desc = node.firstChild.childNodes; + if (desc[0].value == video.caption) { + vim_CancelEdit(video, isAlt); + return; + } + + video.caption = desc[0].value; + + vim_CancelEdit(video, isAlt); + + node = node.firstChild; + while (node.childNodes.length > 0) + node.removeChild(node.firstChild); + + $WH.ae(node, $WH.ct(video.caption)); + + new Ajax('?admin=videos&action=edittitle&id=' + video.id, { + method: 'POST', + params: 'title=' + $WH.urlencode(video.caption) + }); +} + +function vim_Clear(video, isAlt) { + var node; + if (isAlt) + node = $WH.ge('title2-' + video.id); + else + node = $WH.ge('title-' + video.id); + + var sp = $WH.gE(node, 'span'); + var a = $WH.gE(sp[1], 'a'); + sp = sp[0]; + + if (video.caption == '') + return; + + video.caption = ''; + sp.innerHTML = "NULL"; + + new Ajax('?admin=videos&action=edittitle&id=' + video.id, { + method: 'POST', + params: 'title=' + $WH.urlencode('') + }); +} + +function vim_Approve(openNext) { + var vi = this; + new Ajax('?admin=videos&action=approve&id=' + vi.id, { + method: 'get', + onSuccess: function (x) { + Lightbox.hide(); + if (vim_numPending == 1 && vi.pending) + vi_Refresh(true); + else { + vi_Refresh(); + vi_Manage(vi_managedRow, vi.type, vi.typeId, openNext, 0); + } + } + }); +} + +function vim_Sticky(openNext) { + var vi = this; + new Ajax('?admin=videos&action=sticky&id=' + vi.id, { + method: 'get', + onSuccess: function (x) { + Lightbox.hide(); + if (vim_numPending == 1 && vi.pending) + vi_Refresh(true); + else { + vi_Refresh(); + vi_Manage(vi_managedRow, vi.type, vi.typeId, openNext, 0); + } + } + }); +} + +function vim_Delete(openNext) { + var vi = this; + new Ajax('?admin=videos&action=delete&id=' + vi.id, { + method: 'get', + onSuccess: function (x) { + Lightbox.hide(); + if (vim_numPending == 1 && vi.pending) + vi_Refresh(true); + else { + vi_Refresh(); + vi_Manage(vi_managedRow, vi.type, vi.typeId, openNext, 0); + } + } + }); +} + +function vim_Relocate(typeid) { + var vi = this; + new Ajax('?admin=videos&action=relocate&id=' + vi.id + '&typeid=' + typeid, { + method: 'get', + onSuccess: function (x) { + vi_Refresh(); + vi_Manage(vi_managedRow, vi.type, typeid); + } + }); +} + +function vim_Move(direction) { + var vi = this; + new Ajax('?admin=videos&action=order&id=' + vi.id + '&move=' + direction, { + method: 'get', + onSuccess: function (x) { + vi_Refresh(); + vi_Manage(vi_managedRow, vi.type, vi.typeId); + } + }); +} + +var VideoManager = new +function () { + var + video, + pos, + prevImgWidth, + prevImgHeight, + scale, + desiredScale, + container, screen, + prevImgDiv, + aPrev, aNext, aCover, + aOriginal, + divFrom, + spCaption, + divCaption, + h2Name, + controlsCOPY, + 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 (video.id) { + desiredScale = Math.min(772 / video.width, 618 / video.height); + scale = Math.min(772 / video.width, availHeight / video.height) + } + else + desiredScale = scale = 1; + + if (desiredScale > 1) + desiredScale = 1; + + if (scale > 1) + scale = 1; + + prevImgWidth = Math.round(scale * video.width); + prevImgHeight = Math.round(scale * video.height); + var M = Math.max(480, prevImgWidth); + + Lightbox.setSize(M + 20, prevImgHeight + 116 + captionExtraHeight); + + if (captionExtraHeight) { + prevImgDiv.firstChild.width = prevImgWidth; + prevImgDiv.firstChild.height = prevImgHeight; + } + } + + function render(resizing) { + if (resizing && (scale == desiredScale) && $WH.g_getWindowSize().h > container.offsetHeight) + return; + + container.style.visibility = 'hidden'; + + var resized = (video.width > 772 || video.height > 618); + + computeDimensions(0); + + // Aowow - /uploads/videos/ not seen on server + // var url = g_staticUrl + '/uploads/videos/' + (video.pending ? 'pending' : 'normal') + '/' + video.id + '.jpg'; + var url = video.url; + + var html = ''; + + spCaption.innerHTML = html; + } + else + spCaption.innerHTML = "NULL"; + + divCaption.id = 'title2-' + video.id; + + aEdit.onclick = vim_ShowEdit.bind(aEdit, video, true); + aClear.onclick = vim_Clear.bind(aClear, video, true); + + if (video.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 (spCaption.offsetHeight > 18) + computeDimensions(spCaption.offsetHeight - 18); + + container.style.visibility = 'visible'; + } + + function nextVideo() { + if (video.next !== undefined) + video = vim_videoData[video.next]; + + onRender(); + } + + function prevVideo() { + if (video.prev !== undefined) + video = vim_videoData[video.prev]; + + onRender(); + } + function onResize() { + render(1); + } + + function onHide() { + aApprove.onclick = aMakeSticky.onclick = aDelete.onclick = null; + cancelImageLoading(); + } + + function onShow(dest, first, opt) { + video = 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 = prevVideo; + aNext.onclick = nextVideo; + + 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(video.name)); + $WH.ae(_div, h2Name); + $WH.ae(dest, _div); + + prevImgDiv = $WH.ce('div'); + $WH.ae(screen, prevImgDiv); + + $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); + + controlsCOPY = _div; + + $WH.ae(dest, _div); + + divFrom = $WH.ce('div'); + divFrom.className = 'screenshotviewer-from'; + + var sp = $WH.ce('span'); + $WH.ae(sp, $WH.ct(LANG.lvvideo_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); + + divCaption = $WH.ce('div'); + spCaption = $WH.ce('span'); + spCaption.style.paddingRight = '8px'; + $WH.ae(divCaption, spCaption); + + var sp = $WH.ce('span'); + sp.style.whiteSpace = 'nowrap'; + aEdit = $WH.ce('a'); + aEdit.href = 'javascript:;'; + $WH.ae(aEdit, $WH.ct('Edit')); + $WH.ae(sp, aEdit); + + $WH.ae(sp, makePipe()); + + aClear = $WH.ce('a'); + aClear.href = 'javascript:;'; + $WH.ae(aClear, $WH.ct('Clear')); + $WH.ae(sp, aClear); + $WH.ae(divCaption, sp); + $WH.ae(dest, divCaption); + + _div = $WH.ce('div'); + _div.className = 'clear'; + $WH.ae(dest, _div); + } + else { + $WH.ee(h2Name); + $WH.ae(h2Name, $WH.ct(video.name)); + } + + onRender(); + } + function onRender() { + if (video.pending) { + aApprove.onclick = vim_Approve.bind(video, true); + aMakeSticky.onclick = vim_Sticky.bind(video, true); + aDelete.onclick = vim_Delete.bind(video, true); + } + else { + aMakeSticky.onclick = vim_Sticky.bind(video, true); + aDelete.onclick = vim_Delete.bind(video, true); + } + aApprove.style.display = video.pending ? '' : 'none'; + spApprove.style.display = video.pending ? 'none' : ''; + + if (!video.width || !video.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 () { + video.width = 126; + video.height = 22; + + computeDimensions(0); + + video.width = null; + video.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 (vi, timer) { + clearTimeout(timer); + vi.width = this.width; + vi.height = this.height; + loadingImage = null; + restoreLightbox(); + render() + }).bind(loadingImage, video, lightboxTimer); + + loadingImage.onerror = (function (timer) { + clearTimeout(timer); + loadingImage = null; + Lightbox.hide(); + restoreLightbox() + }).bind(loadingImage, lightboxTimer); + + loadingImage.src = (video.url ? video.url : g_staticUrl + '/uploads/videos/' + (video.pending ? 'pending' : 'normal') + '/' + video.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 K = 0; K < lightboxComponents.length; ++K) + $WH.ae(container, lightboxComponents[K]); + + lightboxComponents = null; + } + + this.show = function (opt) { + Lightbox.show('videomanager', { + onShow: onShow, + onHide: onHide, + onResize: onResize + }, opt); + } +}; diff --git a/template/pages/admin/videos.tpl.php b/template/pages/admin/videos.tpl.php new file mode 100644 index 00000000..da89e0a6 --- /dev/null +++ b/template/pages/admin/videos.tpl.php @@ -0,0 +1,135 @@ +brick('header'); +?> +
+
+
+ +brick('announcement'); + +$this->brick('pageTemplate'); +?> +
+

h1; ?>

+ + + + + + + + + + + + + +
User: » Search by User
Page: + + #» Search by Page
+
+ + + + + + + +
Menu
PagesVideos:
+ + + + + + + +
VideoIdTitleDateUploaderStatusOptions
+ + +
+
+
+ +brick('footer'); ?> diff --git a/template/pages/video.tpl.php b/template/pages/video.tpl.php new file mode 100644 index 00000000..f3cded7d --- /dev/null +++ b/template/pages/video.tpl.php @@ -0,0 +1,82 @@ +brick('header'); +?> +
+
+
+ +brick('announcement'); + +$this->brick('pageTemplate'); + +$this->brick('infobox'); + +?> +
+

h1; ?>

+ +

viTitle;?>

+
+
+ + +
+ +
+
+
+ + +
+ + +
+
+
+ +brick('footer'); ?>