Compare commits

...

7 commits

72 changed files with 3431 additions and 2613 deletions

View file

@ -1,92 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\{Photos as PhotosRepo, Albums, Clubs};
class Photos implements Handler
{
protected $user;
protected $photos;
function __construct(?User $user)
{
$this->user = $user;
$this->photos = new PhotosRepo;
}
function getPhotos(int $page = 1, int $album = 0, callable $resolve, callable $reject)
{
if($album == 0) {
$photos = $this->photos->getEveryUserPhoto($this->user, $page, 24);
$count = $this->photos->getUserPhotosCount($this->user);
} else {
$album = (new Albums)->get($album);
if(!$album || $album->isDeleted())
$reject(55, "Invalid .");
if($album->getOwner() instanceof User) {
if($album->getOwner()->getId() != $this->user->getId())
$reject(555, "Access to album denied");
} else {
if(!$album->getOwner()->canBeModifiedBy($this->user))
$reject(555, "Access to album denied");
}
$photos = $album->getPhotos($page, 24);
$count = $album->size();
}
$arr = [
"count" => $count,
"items" => [],
];
foreach($photos as $photo) {
$res = json_decode(json_encode($photo->toVkApiStruct()), true);
$arr["items"][] = $res;
}
$resolve($arr);
}
function getAlbums(int $club, callable $resolve, callable $reject)
{
$albumsRepo = (new Albums);
$count = $albumsRepo->getUserAlbumsCount($this->user);
$albums = $albumsRepo->getUserAlbums($this->user, 1, $count);
$arr = [
"count" => $count,
"items" => [],
];
foreach($albums as $album) {
$res = ["id" => $album->getId(), "name" => $album->getName()];
$arr["items"][] = $res;
}
if($club > 0) {
$cluber = (new Clubs)->get($club);
if(!$cluber || !$cluber->canBeModifiedBy($this->user))
$reject(1337, "Invalid (club), or you can't modify him");
$clubCount = (new Albums)->getClubAlbumsCount($cluber);
$clubAlbums = (new Albums)->getClubAlbums($cluber, 1, $clubCount);
foreach($clubAlbums as $albumr) {
$res = ["id" => $albumr->getId(), "name" => $albumr->getName()];
$arr["items"][] = $res;
}
$arr["count"] = $arr["count"] + $clubCount;
}
$resolve($arr);
}
}

View file

@ -1,156 +0,0 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\{User, Post};
use openvk\Web\Models\Repositories\{Videos, Comments, Clubs};
use Chandler\MVC\Routing\Router;
class Video implements Handler
{
protected $user;
protected $videos;
protected $comments;
protected $groups;
function __construct(?User $user)
{
$this->user = $user;
$this->videos = new Videos;
$this->comments = new Comments;
$this->groups = new Clubs;
}
function getVideo(int $id, callable $resolve, callable $reject)
{
$video = $this->videos->get($id);
if(!$video || $video->isDeleted()) {
$reject(2, "Video does not exists");
}
if(method_exists($video, "canBeViewedBy") && !$video->canBeViewedBy($this->user)) {
$reject(4, "Access to video denied");
}
if(!$video->getOwner()->getPrivacyPermission('videos.read', $this->user)) {
$reject(8, "Access to video denied: this user chose to hide his videos");
}
$prevVideo = NULL;
$nextVideo = NULL;
$lastVideo = $this->videos->getLastVideo($video->getOwner());
if($video->getVirtualId() - 1 != 0) {
for($i = $video->getVirtualId(); $i != 0; $i--) {
$maybeVideo = (new Videos)->getByOwnerAndVID($video->getOwner()->getId(), $i);
if(!is_null($maybeVideo) && !$maybeVideo->isDeleted() && $maybeVideo->getId() != $video->getId()) {
if(method_exists($maybeVideo, "canBeViewedBy") && !$maybeVideo->canBeViewedBy($this->user)) {
continue;
}
$prevVideo = $maybeVideo;
break;
}
}
}
if(is_null($lastVideo) || $lastVideo->getId() == $video->getId()) {
$nextVideo = NULL;
} else {
for($i = $video->getVirtualId(); $i <= $lastVideo->getVirtualId(); $i++) {
$maybeVideo = (new Videos)->getByOwnerAndVID($video->getOwner()->getId(), $i);
if(!is_null($maybeVideo) && !$maybeVideo->isDeleted() && $maybeVideo->getId() != $video->getId()) {
if(method_exists($maybeVideo, "canBeViewedBy") && !$maybeVideo->canBeViewedBy($this->user)) {
continue;
}
$nextVideo = $maybeVideo;
break;
}
}
}
$res = [
"id" => $video->getId(),
"title" => $video->getName(),
"owner" => $video->getOwner()->getId(),
"commentsCount" => $video->getCommentsCount(),
"description" => $video->getDescription(),
"type" => $video->getType(),
"name" => $video->getOwner()->getCanonicalName(),
"pretty_id" => $video->getPrettyId(),
"virtual_id" => $video->getVirtualId(),
"published" => (string)$video->getPublicationTime(),
"likes" => $video->getLikesCount(),
"has_like" => $video->hasLikeFrom($this->user),
"author" => $video->getOwner()->getCanonicalName(),
"canBeEdited" => $video->getOwner()->getId() == $this->user->getId(),
"isProcessing" => $video->getType() == 0 && $video->getURL() == "/assets/packages/static/openvk/video/rendering.mp4",
"prevVideo" => !is_null($prevVideo) ? $prevVideo->getId() : null,
"nextVideo" => !is_null($nextVideo) ? $nextVideo->getId() : null,
];
if($video->getType() == 1) {
$res["embed"] = $video->getVideoDriver()->getEmbed();
} else {
$res["url"] = $video->getURL();
}
$resolve($res);
}
function shareVideo(int $owner, int $vid, int $type, string $message, int $club, bool $signed, bool $asGroup, callable $resolve, callable $reject)
{
$video = $this->videos->getByOwnerAndVID($owner, $vid);
if(!$video || $video->isDeleted()) {
$reject(16, "Video does not exists");
}
if(method_exists($video, "canBeViewedBy") && !$video->canBeViewedBy($this->user)) {
$reject(32, "Access to video denied");
}
if(!$video->getOwner()->getPrivacyPermission('videos.read', $this->user)) {
$reject(8, "Access to video denied: this user chose to hide his videos");
}
$flags = 0;
$nPost = new Post;
$nPost->setOwner($this->user->getId());
if($type == 0) {
$nPost->setWall($this->user->getId());
} else {
$club = $this->groups->get($club);
if(!$club || $club->isDeleted() || !$club->canBeModifiedBy($this->user)) {
$reject(64, "Can't do repost to this club");
}
if($asGroup)
$flags |= 0b10000000;
if($signed)
$flags |= 0b01000000;
$nPost->setWall($club->getId() * -1);
}
$nPost->setContent($message);
$nPost->setFlags($flags);
$nPost->save();
$nPost->attach($video);
$res = [
"id" => $nPost->getId(),
"pretty_id" => $nPost->getPrettyId(),
];
$resolve($res);
}
}

View file

@ -80,67 +80,4 @@ class Wall implements Handler
$resolve($post->getId());
}
function getMyNotes(callable $resolve, callable $reject)
{
$count = $this->notes->getUserNotesCount($this->user);
$myNotes = $this->notes->getUserNotes($this->user, 1, $count);
$arr = [
"count" => $count,
"closed" => $this->user->getPrivacySetting("notes.read"),
"items" => [],
];
foreach($myNotes as $note) {
$arr["items"][] = [
"id" => $note->getId(),
"name" => ovk_proc_strtr($note->getName(), 30),
#"preview" => $note->getPreview()
];
}
$resolve($arr);
}
function getVideos(int $page = 1, callable $resolve, callable $reject)
{
$videos = $this->videos->getByUser($this->user, $page, 8);
$count = $this->videos->getUserVideosCount($this->user);
$arr = [
"count" => $count,
"items" => [],
];
foreach($videos as $video) {
$res = json_decode(json_encode($video->toVkApiStruct($this->user)), true);
$res["video"]["author_name"] = $video->getOwner()->getCanonicalName();
$arr["items"][] = $res;
}
$resolve($arr);
}
function searchVideos(int $page = 1, string $query, callable $resolve, callable $reject)
{
$dbc = $this->videos->find($query);
$videos = $dbc->page($page, 8);
$count = $dbc->size();
$arr = [
"count" => $count,
"items" => [],
];
foreach($videos as $video) {
$res = json_decode(json_encode($video->toVkApiStruct($this->user)), true);
$res["video"]["author_name"] = $video->getOwner()->getCanonicalName();
$arr["items"][] = $res;
}
$resolve($arr);
}
}

View file

@ -190,4 +190,42 @@ final class Account extends VKAPIRequestHandler
return $settings_list;
}
function sendVotes(int $receiver, int $value, string $message = ""): object
{
$this->requireUser();
$this->willExecuteWriteAction();
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
$this->fail(-105, "Commerce is disabled on this instance");
if($receiver < 0)
$this->fail(-248, "Invalid receiver id");
if($value < 1)
$this->fail(-248, "Invalid value");
if(iconv_strlen($message) > 255)
$this->fail(-249, "Message is too long");
if($this->getUser()->getCoins() < $value)
$this->fail(-252, "Not enough votes");
$receiver_entity = (new \openvk\Web\Models\Repositories\Users)->get($receiver);
if(!$receiver_entity || $receiver_entity->isDeleted())
$this->fail(-250, "Invalid receiver");
if($receiver_entity->getId() === $this->getUser()->getId())
$this->fail(-251, "Can't transfer votes to yourself");
$this->getUser()->setCoins($this->getUser()->getCoins() - $value);
$this->getUser()->save();
$receiver_entity->setCoins($receiver_entity->getCoins() + $value);
$receiver_entity->save();
(new \openvk\Web\Models\Entities\Notifications\CoinsTransferNotification($receiver_entity, $this->getUser(), $value, $message))->emit();
return (object) ['votes' => $this->getUser()->getCoins()];
}
}

View file

@ -11,6 +11,12 @@ final class Groups extends VKAPIRequestHandler
{
$this->requireUser();
# InfoApp fix
if($filter == "admin" && ($user_id != 0 && $user_id != $this->getUser()->getId())) {
$this->fail(15, 'Access denied: filter admin is available only for current user');
}
$clbs = [];
if($user_id == 0) {
foreach($this->getUser()->getClubs($offset, $filter == "admin", $count, true) as $club)
$clbs[] = $club;

View file

@ -48,7 +48,7 @@ final class Newsfeed extends VKAPIRequestHandler
return $response;
}
function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0)
function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $rss = 0)
{
$this->requireUser();
@ -74,6 +74,25 @@ final class Newsfeed extends VKAPIRequestHandler
$rposts = [];
$ids = [];
if($rss == 1) {
$channel = new \Bhaktaraz\RSSGenerator\Channel();
$channel->title("Global Feed — " . OPENVK_ROOT_CONF['openvk']['appearance']['name'])
->description('OVK Global feed')
->url(ovk_scheme(true) . $_SERVER["HTTP_HOST"] . "/feed/all");
foreach($posts as $item) {
$post = (new PostsRepo)->get($item->id);
if(!$post || $post->isDeleted()) {
continue;
}
$output = $post->toRss();
$output->appendTo($channel);
}
return $channel;
}
foreach($posts as $post) {
$rposts[] = (new PostsRepo)->get($post->id)->getPrettyId();
$ids[] = $post->id;

View file

@ -427,7 +427,7 @@ final class Photos extends VKAPIRequestHandler
$this->fail(15, "Access denied");
$photos = array_slice(iterator_to_array($album->getPhotos(1, $count + $offset)), $offset);
$res["count"] = sizeof($photos);
$res["count"] = $album->size();
foreach($photos as $photo) {
if(!$photo || $photo->isDeleted()) continue;
@ -638,15 +638,15 @@ final class Photos extends VKAPIRequestHandler
$this->fail(4, "This method doesn't works with clubs");
$user = (new UsersRepo)->get($owner_id);
if(!$user)
$this->fail(4, "Invalid user");
if(!$user->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(21, "This user chose to hide his albums.");
$photos = array_slice(iterator_to_array((new PhotosRepo)->getEveryUserPhoto($user, 1, $count + $offset)), $offset);
$photos = (new PhotosRepo)->getEveryUserPhoto($user, $offset, $count);
$res = [
"count" => (new PhotosRepo)->getUserPhotosCount($user),
"items" => [],
];

View file

@ -11,25 +11,56 @@ use openvk\Web\Models\Repositories\Comments as CommentsRepo;
final class Video extends VKAPIRequestHandler
{
function get(int $owner_id, string $videos = "", int $offset = 0, int $count = 30, int $extended = 0): object
function get(int $owner_id = 0, string $videos = "", string $fields = "", int $offset = 0, int $count = 30, int $extended = 0): object
{
$this->requireUser();
if(!empty($videos)) {
$vids = explode(',', $videos);
foreach($vids as $vid)
{
$profiles = [];
$groups = [];
foreach($vids as $vid) {
$id = explode("_", $vid);
$items = [];
$video = (new VideosRepo)->getByOwnerAndVID(intval($id[0]), intval($id[1]));
if($video) {
$items[] = $video->getApiStructure($this->getUser());
if($video && !$video->isDeleted()) {
$out_video = $video->getApiStructure($this->getUser())->video;
$items[] = $out_video;
if($out_video['owner_id']) {
if($out_video['owner_id'] > 0)
$profiles[] = $out_video['owner_id'];
else
$groups[] = abs($out_video['owner_id']);
}
}
}
if($extended == 1) {
$profiles = array_unique($profiles);
$groups = array_unique($groups);
$profilesFormatted = [];
$groupsFormatted = [];
foreach($profiles as $prof) {
$profile = (new UsersRepo)->get($prof);
$profilesFormatted[] = $profile->toVkApiStruct($this->getUser(), $fields);
}
foreach($groups as $gr) {
$group = (new ClubsRepo)->get($gr);
$groupsFormatted[] = $group->toVkApiStruct($this->getUser(), $fields);
}
return (object) [
"count" => sizeof($items),
"items" => $items,
"profiles" => $profilesFormatted,
"groups" => $groupsFormatted,
];
}
return (object) [
"count" => count($items),
"items" => $items
@ -46,12 +77,46 @@ final class Video extends VKAPIRequestHandler
if(!$user->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(21, "This user chose to hide his videos.");
$videos = (new VideosRepo)->getByUser($user, $offset + 1, $count);
$videos = (new VideosRepo)->getByUserLimit($user, $offset, $count);
$videosCount = (new VideosRepo)->getUserVideosCount($user);
$items = [];
foreach ($videos as $video) {
$items[] = $video->getApiStructure($this->getUser());
$profiles = [];
$groups = [];
foreach($videos as $video) {
$video = $video->getApiStructure($this->getUser())->video;
$items[] = $video;
if($video['owner_id']) {
if($video['owner_id'] > 0)
$profiles[] = $video['owner_id'];
else
$groups[] = abs($video['owner_id']);
}
}
if($extended == 1) {
$profiles = array_unique($profiles);
$groups = array_unique($groups);
$profilesFormatted = [];
$groupsFormatted = [];
foreach($profiles as $prof) {
$profile = (new UsersRepo)->get($prof);
$profilesFormatted[] = $profile->toVkApiStruct($this->getUser(), $fields);
}
foreach($groups as $gr) {
$group = (new ClubsRepo)->get($gr);
$groupsFormatted[] = $group->toVkApiStruct($this->getUser(), $fields);
}
return (object) [
"count" => $videosCount,
"items" => $items,
"profiles" => $profilesFormatted,
"groups" => $groupsFormatted,
];
}
return (object) [

View file

@ -20,7 +20,7 @@ use openvk\Web\Models\Repositories\Audios as AudiosRepo;
final class Wall extends VKAPIRequestHandler
{
function get(int $owner_id, string $domain = "", int $offset = 0, int $count = 30, int $extended = 0, string $filter = "all"): object
function get(int $owner_id, string $domain = "", int $offset = 0, int $count = 30, int $extended = 0, string $filter = "all", int $rss = 0): object
{
$this->requireUser();
@ -86,6 +86,8 @@ final class Wall extends VKAPIRequestHandler
break;
}
$iteratorv = iterator_to_array($iteratorv);
foreach($iteratorv as $post) {
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
@ -217,6 +219,20 @@ final class Wall extends VKAPIRequestHandler
$attachments = NULL; # free attachments so it will not clone everythingg
}
if($rss == 1) {
$channel = new \Bhaktaraz\RSSGenerator\Channel();
$channel->title($wallOnwer->getCanonicalName() . "" . OPENVK_ROOT_CONF['openvk']['appearance']['name'])
->description('Wall of ' . $wallOnwer->getCanonicalName())
->url(ovk_scheme(true) . $_SERVER["HTTP_HOST"] . "/wall" . $wallOnwer->getRealId());
foreach($iteratorv as $item) {
$output = $item->toRss();
$output->appendTo($channel);
}
return $channel;
}
if($extended == 1) {
$profiles = array_unique($profiles);
$groups = array_unique($groups);
@ -284,12 +300,16 @@ final class Wall extends VKAPIRequestHandler
foreach($psts as $pst) {
$id = explode("_", $pst);
$post = (new PostsRepo)->getPostById(intval($id[0]), intval($id[1]));
$post = (new PostsRepo)->getPostById(intval($id[0]), intval($id[1]), true);
if($post && !$post->isDeleted()) {
if(!$post->canBeViewedBy($this->getUser()))
continue;
if($post->getSuggestionType() != 0 && !$post->canBeEditedBy($this->getUser())) {
continue;
}
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
$attachments = [];
$repost = []; // чел высрал семь сигарет 😳 помянем 🕯
@ -473,7 +493,7 @@ final class Wall extends VKAPIRequestHandler
];
}
function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0, string $attachments = "", int $post_id = 0): object
function post(string $owner_id, string $message = "", string $copyright = "", int $from_group = 0, int $signed = 0, string $attachments = "", int $post_id = 0): object
{
$this->requireUser();
$this->willExecuteWriteAction();
@ -551,7 +571,17 @@ final class Wall extends VKAPIRequestHandler
if($signed == 1)
$flags |= 0b01000000;
if(empty($message) && empty($attachments))
$parsed_attachments = parseAttachments($attachments, ['photo', 'video', 'note', 'poll', 'audio']);
$final_attachments = [];
$should_be_suggested = $owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2;
foreach($parsed_attachments as $attachment) {
if($attachment && !$attachment->isDeleted() && $attachment->canBeViewedBy($this->getUser()) &&
!(method_exists($attachment, 'getVoters') && $attachment->getOwner()->getId() != $this->getUser()->getId())) {
$final_attachments[] = $attachment;
}
}
if((empty($message) && (empty($attachments) || sizeof($final_attachments) < 1)))
$this->fail(100, "Required parameter 'message' missing.");
try {
@ -569,7 +599,7 @@ final class Wall extends VKAPIRequestHandler
} catch(\Throwable) {}
}
if($owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2)
if($should_be_suggested)
$post->setSuggested(1);
$post->save();
@ -577,134 +607,70 @@ final class Wall extends VKAPIRequestHandler
$this->fail(100, "One of the parameters specified was missing or invalid");
}
# TODO use parseAttachments
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
# Аттачи такого вида: [тип][id владельца]_[id вложения]
# Пример: photo1_1
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Too many attachments");
preg_match_all("/poll/m", $attachments, $matches, PREG_SET_ORDER, 0);
if(sizeof($matches) > 1)
$this->fail(85, "Too many polls");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
elseif(str_contains($attac, "note"))
$attachmentType = "note";
elseif(str_contains($attac, "poll"))
$attachmentType = "poll";
elseif(str_contains($attac, "audio"))
$attachmentType = "audio";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Invalid photo");
if(!$attacc->getOwner()->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(43, "Access to photo denied");
$post->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if(!$attacc->getOwner()->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(43, "Access to video denied");
$post->attach($attacc);
} elseif($attachmentType == "note") {
$attacc = (new NotesRepo)->getNoteById($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Note does not exist");
if(!$attacc->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
$this->fail(11, "Access to note denied");
$post->attach($attacc);
} elseif($attachmentType == "poll") {
$attacc = (new PollsRepo)->get($attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Poll does not exist");
if($attacc->getOwner()->getId() != $this->getUser()->getId())
$this->fail(43, "You do not have access to this poll");
$post->attach($attacc);
} elseif($attachmentType == "audio") {
$attacc = (new AudiosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Audio does not exist");
$post->attach($attacc);
}
}
foreach($final_attachments as $attachment) {
$post->attach($attachment);
}
if($wall > 0 && $wall !== $this->user->identity->getId())
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
if($owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2) {
$suggsCount = (new PostsRepo)->getSuggestedPostsCount($wallOwner->getId());
if($suggsCount % 10 == 0) {
$managers = $wallOwner->getManagers();
$owner = $wallOwner->getOwner();
(new NewSuggestedPostsNotification($owner, $wallOwner))->emit();
foreach($managers as $manager) {
(new NewSuggestedPostsNotification($manager->getUser(), $wallOwner))->emit();
}
}
return (object)["post_id" => "on_view"];
}
return (object)["post_id" => $post->getVirtualId()];
}
function repost(string $object, string $message = "", int $group_id = 0) {
function repost(string $object, string $message = "", string $attachments = "", int $group_id = 0, int $as_group = 0, int $signed = 0) {
$this->requireUser();
$this->willExecuteWriteAction();
$postArray;
if(preg_match('/wall((?:-?)[0-9]+)_([0-9]+)/', $object, $postArray) == 0)
if(preg_match('/(wall|video|photo)((?:-?)[0-9]+)_([0-9]+)/', $object, $postArray) == 0)
$this->fail(100, "One of the parameters specified was missing or invalid: object is incorrect");
$post = (new PostsRepo)->getPostById((int) $postArray[1], (int) $postArray[2]);
if(!$post || $post->isDeleted()) $this->fail(100, "One of the parameters specified was missing or invalid");
$parsed_attachments = parseAttachments($attachments, ['photo', 'video', 'note', 'audio']);
$final_attachments = [];
foreach($parsed_attachments as $attachment) {
if($attachment && !$attachment->isDeleted() && $attachment->canBeViewedBy($this->getUser()) &&
!(method_exists($attachment, 'getVoters') && $attachment->getOwner()->getId() != $this->getUser()->getId())) {
$final_attachments[] = $attachment;
}
}
if(!$post->canBeViewedBy($this->getUser()))
$this->fail(15, "Access denied");
$repost_entity = NULL;
$repost_type = $postArray[1];
switch($repost_type) {
default:
case 'wall':
$repost_entity = (new PostsRepo)->getPostById((int) $postArray[2], (int) $postArray[3]);
break;
case 'photo':
$repost_entity = (new PhotosRepo)->getByOwnerAndVID((int) $postArray[2], (int) $postArray[3]);
break;
case 'video':
$repost_entity = (new VideosRepo)->getByOwnerAndVID((int) $postArray[2], (int) $postArray[3]);
break;
}
if(!$repost_entity || $repost_entity->isDeleted() || !$repost_entity->canBeViewedBy($this->getUser())) $this->fail(100, "One of the parameters specified was missing or invalid");
$nPost = new Post;
$nPost->setOwner($this->user->getId());
if($group_id > 0) {
$club = (new ClubsRepo)->get($group_id);
$club = (new ClubsRepo)->get($group_id);
if(!$club)
$this->fail(42, "Invalid group");
if(!$club->canBeModifiedBy($this->user))
$this->fail(16, "Access to group denied");
$nPost->setWall($group_id * -1);
$nPost->setWall($club->getRealId());
$flags = 0;
if($as_group === 1 || $signed === 1)
$flags |= 0b10000000;
if($signed === 1)
$flags |= 0b01000000;
$nPost->setFlags($flags);
} else {
$nPost->setWall($this->user->getId());
}
@ -712,16 +678,27 @@ final class Wall extends VKAPIRequestHandler
$nPost->setContent($message);
$nPost->setApi_Source_Name($this->getPlatform());
$nPost->save();
$nPost->attach($post);
if($post->getOwner(false)->getId() !== $this->user->getId() && !($post->getOwner() instanceof Club))
(new RepostNotification($post->getOwner(false), $post, $this->user))->emit();
$nPost->attach($repost_entity);
foreach($parsed_attachments as $attachment) {
$nPost->attach($attachment);
}
if($repost_type == 'wall' && $repost_entity->getOwner(false)->getId() !== $this->user->getId() && !($repost_entity->getOwner() instanceof Club))
(new RepostNotification($repost_entity->getOwner(false), $repost_entity, $this->user))->emit();
$repost_count = 1;
if($repost_type == 'wall') {
$repost_count = $repost_entity->getRepostCount();
}
return (object) [
"success" => 1, // 👍
"post_id" => $nPost->getVirtualId(),
"reposts_count" => $post->getRepostCount(),
"likes_count" => $post->getLikesCount()
"pretty_id" => $nPost->getPrettyId(),
"reposts_count" => $repost_count,
"likes_count" => $repost_entity->getLikesCount()
];
}
@ -767,7 +744,7 @@ final class Wall extends VKAPIRequestHandler
"date" => $comment->getPublicationTime()->timestamp(),
"text" => $comment->getText(false),
"post_id" => $post->getVirtualId(),
"owner_id" => $post->isPostedOnBehalfOfGroup() ? $post->getOwner()->getId() * -1 : $post->getOwner()->getId(),
"owner_id" => method_exists($post, 'isPostedOnBehalfOfGroup') && $post->isPostedOnBehalfOfGroup() ? $post->getOwner()->getId() * -1 : $post->getOwner()->getId(),
"parents_stack" => [],
"attachments" => $attachments,
"thread" => [
@ -833,6 +810,13 @@ final class Wall extends VKAPIRequestHandler
foreach($comment->getChildren() as $attachment) {
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
$attachments[] = $this->getApiPhoto($attachment);
} elseif($attachment instanceof \openvk\Web\Models\Entities\Video) {
$attachments[] = $attachment->getApiStructure();
} elseif($attachment instanceof \openvk\Web\Models\Entities\Note) {
$attachments[] = [
'type' => 'note',
'note' => $attachment->toVkApiStruct()
];
} elseif($attachment instanceof \openvk\Web\Models\Entities\Audio) {
$attachments[] = [
"type" => "audio",
@ -847,7 +831,7 @@ final class Wall extends VKAPIRequestHandler
"date" => $comment->getPublicationTime()->timestamp(),
"text" => $comment->getText(false),
"post_id" => $comment->getTarget()->getVirtualId(),
"owner_id" => $comment->getTarget()->isPostedOnBehalfOfGroup() ? $comment->getTarget()->getOwner()->getId() * -1 : $comment->getTarget()->getOwner()->getId(),
"owner_id" => method_exists($comment->getTarget(), 'isPostedOnBehalfOfGroup') && $comment->getTarget()->isPostedOnBehalfOfGroup() ? $comment->getTarget()->getOwner()->getId() * -1 : $comment->getTarget()->getOwner()->getId(),
"parents_stack" => [],
"attachments" => $attachments,
"likes" => [
@ -899,7 +883,16 @@ final class Wall extends VKAPIRequestHandler
if($post->getTargetWall() < 0)
$club = (new ClubsRepo)->get(abs($post->getTargetWall()));
if(empty($message) && empty($attachments)) {
$parsed_attachments = parseAttachments($attachments, ['photo', 'video', 'note', 'audio']);
$final_attachments = [];
foreach($parsed_attachments as $attachment) {
if($attachment && !$attachment->isDeleted() && $attachment->canBeViewedBy($this->getUser()) &&
!(method_exists($attachment, 'getVoters') && $attachment->getOwner()->getId() != $this->getUser()->getId())) {
$final_attachments[] = $attachment;
}
}
if((empty($message) && (empty($attachments) || sizeof($final_attachments) < 1))) {
$this->fail(100, "Required parameter 'message' missing.");
}
@ -920,55 +913,8 @@ final class Wall extends VKAPIRequestHandler
$this->fail(1, "ошибка про то что коммент большой слишком");
}
if(!empty($attachments)) {
$attachmentsArr = explode(",", $attachments);
if(sizeof($attachmentsArr) > 10)
$this->fail(50, "Error: too many attachments");
foreach($attachmentsArr as $attac) {
$attachmentType = NULL;
if(str_contains($attac, "photo"))
$attachmentType = "photo";
elseif(str_contains($attac, "video"))
$attachmentType = "video";
elseif(str_contains($attac, "audio"))
$attachmentType = "audio";
else
$this->fail(205, "Unknown attachment type");
$attachment = str_replace($attachmentType, "", $attac);
$attachmentOwner = (int)explode("_", $attachment)[0];
$attachmentId = (int)end(explode("_", $attachment));
$attacc = NULL;
if($attachmentType == "photo") {
$attacc = (new PhotosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Photo does not exists");
if(!$attacc->getOwner()->getPrivacyPermission('photos.read', $this->getUser()))
$this->fail(11, "Access to photo denied");
$comment->attach($attacc);
} elseif($attachmentType == "video") {
$attacc = (new VideosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Video does not exists");
if(!$attacc->getOwner()->getPrivacyPermission('videos.read', $this->getUser()))
$this->fail(11, "Access to video denied");
$comment->attach($attacc);
} elseif($attachmentType == "audio") {
$attacc = (new AudiosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
if(!$attacc || $attacc->isDeleted())
$this->fail(100, "Audio does not exist");
$comment->attach($attacc);
}
}
foreach($final_attachments as $attachment) {
$comment->attach($attachment);
}
if($post->getOwner()->getId() !== $this->user->getId())
@ -1019,11 +965,24 @@ final class Wall extends VKAPIRequestHandler
}
}
function edit(int $owner_id, int $post_id, string $message = "", string $attachments = "", string $copyright = NULL) {
function edit(int $owner_id, int $post_id, string $message = "", string $attachments = "", string $copyright = NULL, int $explicit = -1, int $from_group = 0, int $signed = 0) {
$this->requireUser();
$this->willExecuteWriteAction();
$post = (new PostsRepo)->getPostById($owner_id, $post_id);
$parsed_attachments = parseAttachments($attachments, ['photo', 'video', 'note', 'audio', 'poll']);
$final_attachments = [];
foreach($parsed_attachments as $attachment) {
if($attachment && !$attachment->isDeleted() && $attachment->canBeViewedBy($this->getUser()) &&
!(method_exists($attachment, 'getVoters') && $attachment->getOwner()->getId() != $this->getUser()->getId())) {
$final_attachments[] = $attachment;
}
}
if(empty($message) && sizeof($final_attachments) < 1) {
$this->fail(-66, "Post will be empty, don't saving.");
}
$post = (new PostsRepo)->getPostById($owner_id, $post_id, true);
if(!$post || $post->isDeleted())
$this->fail(102, "Invalid post");
@ -1031,36 +990,43 @@ final class Wall extends VKAPIRequestHandler
if(!$post->canBeEditedBy($this->getUser()))
$this->fail(7, "Access to editing denied");
if(!empty($message))
if(!empty($message) || (empty($message) && sizeof($final_attachments) > 0))
$post->setContent($message);
$post->setEdited(time());
if(!is_null($copyright) && !empty($copyright)) {
try {
$post->setSource($copyright);
} catch(\Throwable) {}
if($copyright == 'remove') {
$post->resetSource();
} else {
try {
$post->setSource($copyright);
} catch(\Throwable) {}
}
}
if($explicit != -1) {
$post->setNsfw($explicit == 1);
}
$wallOwner = ($owner_id > 0 ? (new UsersRepo)->get($owner_id) : (new ClubsRepo)->get($owner_id * -1));
$flags = 0;
if($from_group == 1 && $wallOwner instanceof Club && $wallOwner->canBeModifiedBy($this->getUser()))
$flags |= 0b10000000;
/*if($signed == 1)
$flags |= 0b01000000;*/
$post->setFlags($flags);
$post->save(true);
# todo добавить такое в веб версию
if(!empty($attachments)) {
$attachs = parseAttachments($attachments);
$newAttachmentsCount = sizeof($attachs);
if($attachments == 'remove' || sizeof($final_attachments) > 0) {
foreach($post->getChildren() as $att) {
if(!($att instanceof Post)) {
$post->detach($att);
}
}
$postsAttachments = iterator_to_array($post->getChildren());
if(sizeof($postsAttachments) >= 10)
$this->fail(15, "Post have too many attachments");
if(($newAttachmentsCount + sizeof($postsAttachments)) > 10)
$this->fail(158, "Post will have too many attachments");
foreach($attachs as $attach) {
if($attach && !$attach->isDeleted())
$post->attach($attach);
else
$this->fail(52, "One of the attachments is invalid");
foreach($final_attachments as $attachment) {
$post->attach($attachment);
}
}
@ -1072,8 +1038,16 @@ final class Wall extends VKAPIRequestHandler
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
$parsed_attachments = parseAttachments($attachments, ['photo', 'video', 'note', 'audio']);
$final_attachments = [];
foreach($parsed_attachments as $attachment) {
if($attachment && !$attachment->isDeleted() && $attachment->canBeViewedBy($this->getUser()) &&
!(method_exists($attachment, 'getVoters') && $attachment->getOwner()->getId() != $this->getUser()->getId())) {
$final_attachments[] = $attachment;
}
}
if(empty($message) && empty($attachments))
if(empty($message) && sizeof($final_attachments) < 1)
$this->fail(100, "Required parameter 'message' missing.");
if(!$comment || $comment->isDeleted())
@ -1082,29 +1056,16 @@ final class Wall extends VKAPIRequestHandler
if(!$comment->canBeEditedBy($this->getUser()))
$this->fail(15, "Access to editing comment denied");
if(!empty($message))
if(!empty($message) || (empty($message) && sizeof($final_attachments) > 0))
$comment->setContent($message);
$comment->setEdited(time());
$comment->save(true);
if(!empty($attachments)) {
$attachs = parseAttachments($attachments);
$newAttachmentsCount = sizeof($attachs);
$postsAttachments = iterator_to_array($comment->getChildren());
if(sizeof($postsAttachments) >= 10)
$this->fail(15, "Post have too many attachments");
if(($newAttachmentsCount + sizeof($postsAttachments)) > 10)
$this->fail(158, "Post will have too many attachments");
foreach($attachs as $attach) {
if($attach && !$attach->isDeleted())
$comment->attach($attach);
else
$this->fail(52, "One of the attachments is invalid");
if(sizeof($final_attachments) > 0) {
$comment->unwire();
foreach($final_attachments as $attachment) {
$comment->attach($attachment);
}
}

View file

@ -87,6 +87,7 @@ class Album extends MediaCollection
$res = (object) [];
$res->id = $this->getPrettyId();
$res->vid = $this->getId();
$res->thumb_id = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getPrettyId() : 0;
$res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName();

View file

@ -39,7 +39,7 @@ class BannedLink extends RowModel
function getRegexpRule(): string
{
return addslashes("/" . $this->getDomain() . $this->getRawRegexp() . "/");
return "/^" . $this->getDomain() . "\/" . $this->getRawRegexp() . "$/i";
}
function getRawRegexp(): string

View file

@ -308,7 +308,7 @@ class Photo extends Media
$res->date = $res->created = $this->getPublicationTime()->timestamp();
if($photo_sizes) {
$res->sizes = $this->getVkApiSizes();
$res->sizes = array_values($this->getVkApiSizes());
$res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule");
$res->src = $res->photo_130 = $this->getURLBySizeId("tiny");
$res->src_big = $res->photo_604 = $this->getURLBySizeId("normal");

View file

@ -103,6 +103,11 @@ class Post extends Postable
$this->stateChanges("source", $source);
}
function resetSource()
{
$this->stateChanges("source", NULL);
}
function getVkApiCopyright(): object
{
return (object)[
@ -369,5 +374,46 @@ class Post extends Postable
return $user->getId() == $this->getOwner(false)->getId();
}
function toRss(): \Bhaktaraz\RSSGenerator\Item
{
$domain = ovk_scheme(true).$_SERVER["HTTP_HOST"];
$description = $this->getText(false);
$description_html = $description;
$url = $domain."/wall".$this->getPrettyId();
$author = $this->getOwner();
$author_name = htmlspecialchars($author->getCanonicalName(), ENT_DISALLOWED | ENT_XHTML);
if($this->isExplicit())
$description_html .= "<br /><b>".tr('contains_nsfw').".</b><br />";
foreach($this->getChildren() as $child) {
if($child instanceof Photo) {
$child_page = $domain.$child->getPageURL();
$child_url = $child->getURLBySizeId('large');
$description_html .= "<br /><a href='$child_page'><img src='$child_url'></a>";
} elseif($child instanceof Video) {
$child_page = $domain.'/video'.$child->getPrettyId();
$description_html .= "<br /><a href='$child_page'>Video</a>";
} elseif($child instanceof Audio) {
$description_html .= "<br />Audio";
}
}
$description_html .= "<br />".tr('author').": <img width='15px' src='".$author->getAvatarURL()."'><a href='".$author->getURL()."'>" . $author_name . "</a>";
if($this->hasSource()) {
$description_html .= "<br />".tr('source').": ".htmlspecialchars($this->getSource(), ENT_DISALLOWED | ENT_XHTML);
}
$item = new \Bhaktaraz\RSSGenerator\Item();
$item->title(str_replace("\n", "", ovk_proc_strtr($description, 79)))
->url($url)
->guid($url)
->creator($author_name)
->pubDate($this->getPublicationTime()->timestamp())
->content(str_replace("\n", "<br />", $description_html));
return $item;
}
use Traits\TRichText;
}

View file

@ -134,8 +134,13 @@ class Report extends RowModel
function getContentName(): string
{
if (method_exists($this->getContentObject(), "getCanonicalName"))
return $this->getContentObject()->getCanonicalName();
$content_object = $this->getContentObject();
if(!$content_object) {
return 'unknown';
}
if (method_exists($content_object, "getCanonicalName"))
return $content_object->getCanonicalName();
return $this->getContentType() . " #" . $this->getContentId();
}

View file

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits;
use openvk\Web\Models\Entities\{Attachable, Photo};
use openvk\Web\Models\Entities\{Attachable, Photo, Video};
use openvk\Web\Util\Makima\Makima;
use Chandler\Database\DatabaseConnection;
@ -36,10 +36,10 @@ trait TAttachmentHost
if($h < 0)
$h = $w;
$children = $this->getChildren();
$children = iterator_to_array($this->getChildren());
$skipped = $photos = $result = [];
foreach($children as $child) {
if($child instanceof Photo) {
if($child instanceof Photo || $child instanceof Video && $child->getDimensions()) {
$photos[] = $child;
continue;
}

View file

@ -34,9 +34,23 @@ class Video extends Media
if(sizeof($durations[1]) === 0)
throw new \DomainException("$filename does not contain any meaningful video streams");
foreach($durations[1] as $duration)
if(floatval($duration) < 1.0)
$length = 0;
foreach($durations[1] as $duration) {
$duration = floatval($duration);
if($duration < 1.0)
throw new \DomainException("$filename does not contain any meaningful video streams");
else
$length = max($length, $duration);
}
$this->stateChanges("length", (int) round($length, 0, PHP_ROUND_HALF_EVEN));
preg_match('%width=([0-9\.]++)%', $streams, $width);
preg_match('%height=([0-9\.]++)%', $streams, $height);
if(!empty($width) && !empty($height)) {
$this->stateChanges("width", $width[1]);
$this->stateChanges("height", $height[1]);
}
try {
if(!is_dir($dirId = dirname($this->pathFromHash($hash))))
@ -118,6 +132,7 @@ class Video extends Media
function getApiStructure(?User $user = NULL): object
{
$fromYoutube = $this->getType() == Video::TYPE_EMBED;
$dimensions = $this->getDimensions();
$res = (object)[
"type" => "video",
"video" => [
@ -130,7 +145,7 @@ class Video extends Media
"comments" => $this->getCommentsCount(),
"date" => $this->getPublicationTime()->timestamp(),
"description" => $this->getDescription(),
"duration" => 0, // я хуй знает как получить длину видео
"duration" => $this->getLength(),
"image" => [
[
"url" => $this->getThumbnailURL(),
@ -139,8 +154,8 @@ class Video extends Media
"with_padding" => 1
]
],
"width" => 640,
"height" => 480,
"width" => $dimensions ? $dimensions[0] : 640,
"height" => $dimensions ? $dimensions[1] : 480,
"id" => $this->getVirtualId(),
"owner_id" => $this->getOwner()->getId(),
"user_id" => $this->getOwner()->getId(),
@ -155,6 +170,7 @@ class Video extends Media
"repeat" => 0,
"type" => "video",
"views" => 0,
"is_processed" => $this->isProcessed(),
"reposts" => [
"count" => 0,
"user_reposted" => 0
@ -225,6 +241,74 @@ class Video extends Media
return $video;
}
function fillDimensions()
{
$hash = $this->getRecord()->hash;
$path = $this->pathFromHash($hash);
if(!file_exists($path)) {
$this->stateChanges("width", 0);
$this->stateChanges("height", 0);
$this->stateChanges("length", 0);
$this->save();
return false;
}
$streams = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute($error);
$durations = [];
preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);
$length = 0;
foreach($durations[1] as $duration) {
$duration = floatval($duration);
if($duration < 1.0)
continue;
else
$length = max($length, $duration);
}
$this->stateChanges("length", (int) round($length, 0, PHP_ROUND_HALF_EVEN));
preg_match('%width=([0-9\.]++)%', $streams, $width);
preg_match('%height=([0-9\.]++)%', $streams, $height);
if(!empty($width) && !empty($height)) {
$this->stateChanges("width", $width[1]);
$this->stateChanges("height", $height[1]);
}
$this->save();
return true;
}
function getDimensions()
{
if($this->getType() == Video::TYPE_EMBED) return [320, 180];
$width = $this->getRecord()->width;
$height = $this->getRecord()->height;
if(!$width) return NULL;
return $width != 0 ? [$width, $height] : NULL;
}
function getLength()
{
return $this->getRecord()->length;
}
function getFormattedLength(): string
{
$len = $this->getLength();
if(!$len) return "00:00";
$mins = floor($len / 60);
$secs = $len - ($mins * 60);
return (
str_pad((string) $mins, 2, "0", STR_PAD_LEFT)
. ":" .
str_pad((string) $secs, 2, "0", STR_PAD_LEFT)
);
}
function canBeViewedBy(?User $user = NULL): bool
{
if($this->isDeleted() || $this->getOwner()->isDeleted()) {
@ -248,7 +332,7 @@ class Video extends Media
$res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName();
$res->description = $this->getDescription();
$res->duration = "22";
$res->duration = $this->getLength();
$res->link = "/video".$this->getOwner()->getId()."_".$this->getVirtualId();
$res->image = $this->getThumbnailURL();
$res->date = $this->getPublicationTime()->timestamp();

View file

@ -3,6 +3,7 @@ namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection as DB;
use Nette\Database\Table\{ActiveRow, Selection};
use openvk\Web\Models\Entities\BannedLink;
use function Symfony\Component\Translation\t;
class BannedLinks
{
@ -43,7 +44,7 @@ class BannedLinks
function isDomainBanned(string $domain): bool
{
return sizeof($this->bannedLinks->where(["link" => $domain, "regexp_rule" => ""])) > 0;
return sizeof($this->bannedLinks->where(["domain" => $domain, "regexp_rule" => ""])) > 0;
}
function genLinks($rules): \Traversable
@ -57,12 +58,14 @@ class BannedLinks
foreach($links as $link)
if (preg_match($link->getRegexpRule(), $uri))
yield $link->getId();
else if ($this->isDomainBanned($link->getDomain()))
yield $link->getId();
}
function check(string $url): ?array
{
$uri = strstr(str_replace(["https://", "http://"], "", $url), "/", true);
$domain = str_replace("www.", "", $uri);
$uri = str_replace(["https://", "http://"], "", $url);
$domain = explode("/", str_replace("www.", "", $uri))[0];
$rules = $this->getByDomain($domain);
if (is_null($rules))

View file

@ -33,7 +33,7 @@ class Photos
return new Photo($photo);
}
function getEveryUserPhoto(User $user, int $page = 1, ?int $perPage = NULL): \Traversable
function getEveryUserPhoto(User $user, int $offset = 0, int $limit = 10): \Traversable
{
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
$photos = $this->photos->where([
@ -41,7 +41,7 @@ class Photos
"deleted" => 0
])->order("id DESC");
foreach($photos->page($page, $perPage) as $photo) {
foreach($photos->limit($limit, $offset) as $photo) {
yield new Photo($photo);
}
}

View file

@ -41,6 +41,13 @@ class Videos
yield new Video($video);
}
function getByUserLimit(User $user, int $offset = 0, int $limit = 10): \Traversable
{
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
foreach($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->limit($limit, $offset)->order("created DESC") as $video)
yield new Video($video);
}
function getUserVideosCount(User $user): int
{
return sizeof($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0]));
@ -49,7 +56,7 @@ class Videos
function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream
{
$query = "%$query%";
$result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0);
$result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0)->where("unlisted", 0);
$order_str = 'id';
switch($order['type']) {

View file

@ -481,7 +481,7 @@ final class AdminPresenter extends OpenVKPresenter
if ($link) {
$link->setDomain($new_domain ?? $this->postParam("link"));
$link->setReason($new_reason);
$link->setRegexp_rule($this->postParam("regexp"));
$link->setRegexp_rule(mb_strlen(trim($this->postParam("regexp"))) > 0 ? $this->postParam("regexp") : "");
$link->save();
} else {
if (!$new_domain)
@ -490,7 +490,7 @@ final class AdminPresenter extends OpenVKPresenter
$link = new BannedLink;
$link->setDomain($new_domain);
$link->setReason($new_reason);
$link->setRegexp_rule($this->postParam("regexp"));
$link->setRegexp_rule(mb_strlen(trim($this->postParam("regexp"))) > 0 ? $this->postParam("regexp") : "");
$link->setInitiator($this->user->identity->getId());
$link->save();

View file

@ -33,6 +33,7 @@ final class AudioPresenter extends OpenVKPresenter
function renderList(?int $owner = NULL, ?string $mode = "list"): void
{
$this->assertUserLoggedIn();
$this->template->_template = "Audio/List.xml";
$page = (int)($this->queryParam("p") ?? 1);
$audios = [];
@ -501,6 +502,7 @@ final class AudioPresenter extends OpenVKPresenter
function renderPlaylist(int $owner_id, int $virtual_id): void
{
$this->assertUserLoggedIn();
$playlist = $this->audios->getPlaylistByOwnerAndVID($owner_id, $virtual_id);
$page = (int)($this->queryParam("p") ?? 1);
if (!$playlist || $playlist->isDeleted())

View file

@ -7,7 +7,7 @@ final class AwayPresenter extends OpenVKPresenter
{
function renderAway(): void
{
$checkBanEntries = (new BannedLinks)->check($this->queryParam("to") . "/");
$checkBanEntries = (new BannedLinks)->check($this->queryParam("to"));
if (OPENVK_ROOT_CONF["openvk"]["preferences"]["susLinks"]["warnings"])
if (sizeof($checkBanEntries) > 0)
$this->pass("openvk!Away->view", $checkBanEntries[0]);

View file

@ -27,6 +27,11 @@ final class CommentPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error"), tr("forbidden"));
if(!is_null($this->user)) $comment->toggleLike($this->user->identity);
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->returnJson([
'success' => true,
]);
}
$this->redirect($_SERVER["HTTP_REFERER"]);
}
@ -71,63 +76,23 @@ final class CommentPresenter extends OpenVKPresenter
}
}
$photos = [];
if(!empty($this->postParam("photos"))) {
$un = rtrim($this->postParam("photos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$photo || $photo->isDeleted())
continue;
$photos[] = $photo;
}
$horizontal_attachments = [];
$vertical_attachments = [];
if(!empty($this->postParam("horizontal_attachments"))) {
$horizontal_attachments_array = array_slice(explode(",", $this->postParam("horizontal_attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]);
if(sizeof($horizontal_attachments_array) > 0) {
$horizontal_attachments = parseAttachments($horizontal_attachments_array, ['photo', 'video']);
}
}
$videos = [];
if(!empty($this->postParam("videos"))) {
$un = rtrim($this->postParam("videos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$video = (new Videos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$video || $video->isDeleted())
continue;
$videos[] = $video;
}
if(!empty($this->postParam("vertical_attachments"))) {
$vertical_attachments_array = array_slice(explode(",", $this->postParam("vertical_attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]);
if(sizeof($vertical_attachments_array) > 0) {
$vertical_attachments = parseAttachments($vertical_attachments_array, ['audio', 'note']);
}
}
$audios = [];
if(!empty($this->postParam("audios"))) {
$un = rtrim($this->postParam("audios"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$audio = (new Audios)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$audio || $audio->isDeleted())
continue;
$audios[] = $audio;
}
}
}
if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1 && sizeof($audios) < 1)
if(empty($this->postParam("text")) && sizeof($horizontal_attachments) < 1 && sizeof($vertical_attachments) < 1)
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_empty"));
try {
@ -143,15 +108,21 @@ final class CommentPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_too_big"));
}
foreach($photos as $photo)
$comment->attach($photo);
foreach($horizontal_attachments as $horizontal_attachment) {
if(!$horizontal_attachment || $horizontal_attachment->isDeleted() || !$horizontal_attachment->canBeViewedBy($this->user->identity)) {
continue;
}
if(sizeof($videos) > 0)
foreach($videos as $vid)
$comment->attach($vid);
$comment->attach($horizontal_attachment);
}
foreach($audios as $audio)
$comment->attach($audio);
foreach($vertical_attachments as $vertical_attachment) {
if(!$vertical_attachment || $vertical_attachment->isDeleted() || !$vertical_attachment->canBeViewedBy($this->user->identity)) {
continue;
}
$comment->attach($vertical_attachment);
}
if($entity->getOwner()->getId() !== $this->user->identity->getId())
if(($owner = $entity->getOwner()) instanceof User)

View file

@ -121,9 +121,9 @@ final class InternalAPIPresenter extends OpenVKPresenter
{
if($attachment instanceof \openvk\Web\Models\Entities\Photo)
{
$response[] = [
"url" => $attachment->getURLBySizeId('normal'),
"id" => $attachment->getPrettyId()
$response[$attachment->getPrettyId()] = [
"url" => $attachment->getURLBySizeId('larger'),
"id" => $attachment->getPrettyId(),
];
}
}
@ -133,4 +133,35 @@ final class InternalAPIPresenter extends OpenVKPresenter
]);
}
}
function renderGetPostTemplate(int $owner_id, int $post_id) {
if($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
exit("ты‍ не по адресу");
}
$type = $this->queryParam("type", false);
if($type == "post") {
$post = (new Posts)->getPostById($owner_id, $post_id, true);
} else {
$post = (new Comments)->get($post_id);
}
if(!$post || !$post->canBeEditedBy($this->user->identity)) {
exit('');
}
header("Content-Type: text/plain");
if($type == 'post') {
$this->template->_template = 'components/post.xml';
$this->template->post = $post;
$this->template->commentSection = false;
} elseif($type == 'comment') {
$this->template->_template = 'components/comment.xml';
$this->template->comment = $post;
} else {
exit('');
}
}
}

View file

@ -256,7 +256,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$userValidated = 1;
$cacheTime = 0; # Force no cache
if($this->user->identity->onlineStatus() == 0 && !($this->user->identity->isDeleted() || $this->user->identity->isBanned())) {
if(!property_exists($this, 'silent') && $this->user->identity->onlineStatus() == 0 && !($this->user->identity->isDeleted() || $this->user->identity->isBanned())) {
$this->user->identity->setOnline(time());
$this->user->identity->setClient_name(NULL);
$this->user->identity->save(false);

View file

@ -176,6 +176,7 @@ final class PhotosPresenter extends OpenVKPresenter
$this->template->cCount = $photo->getCommentsCount();
$this->template->cPage = (int) ($this->queryParam("p") ?? 1);
$this->template->comments = iterator_to_array($photo->getComments($this->template->cPage));
$this->template->owner = $photo->getOwner();
}
function renderAbsolutePhoto($id): void
@ -287,7 +288,8 @@ final class PhotosPresenter extends OpenVKPresenter
"id" => $photo->getId(),
"vid" => $photo->getVirtualId(),
"owner" => $photo->getOwner()->getId(),
"link" => $photo->getURL()
"link" => $photo->getURL(),
"pretty_id" => $photo->getPrettyId(),
];
} catch(ISE $ex) {
$name = $album->getName();
@ -354,4 +356,26 @@ final class PhotosPresenter extends OpenVKPresenter
$this->flash("succ", tr("photo_is_deleted"), tr("photo_is_deleted_desc"));
$this->redirect($redirect);
}
function renderLike(int $wall, int $post_id): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$this->assertNoCSRF();
$photo = $this->photos->getByOwnerAndVID($wall, $post_id);
if(!$photo || $photo->isDeleted() || !$photo->canBeViewedBy($this->user->identity)) $this->notFound();
if(!is_null($this->user)) {
$photo->toggleLike($this->user->identity);
}
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->returnJson([
'success' => true,
]);
}
$this->redirect("$_SERVER[HTTP_REFERER]");
}
}

View file

@ -10,6 +10,7 @@ use WhichBrowser;
final class VKAPIPresenter extends OpenVKPresenter
{
protected $silent = true;
private function logRequest(string $object, string $method): void
{
$date = date(DATE_COOKIE);
@ -222,9 +223,13 @@ final class VKAPIPresenter extends OpenVKPresenter
if(!is_callable([$handler, $method]))
$this->badMethod($object, $method);
$has_rss = false;
$route = new \ReflectionMethod($handler, $method);
$params = [];
foreach($route->getParameters() as $parameter) {
if($parameter->getName() == 'rss')
$has_rss = true;
$val = $this->requestParam($parameter->getName());
if(is_null($val)) {
if($parameter->allowsNull())
@ -260,15 +265,27 @@ final class VKAPIPresenter extends OpenVKPresenter
$this->fail($ex->getCode(), $ex->getMessage(), $object, $method);
}
$result = json_encode([
"response" => $res,
]);
$result = NULL;
if($callback) {
$result = $callback . '(' . $result . ')';
header('Content-Type: application/javascript');
} else
header("Content-Type: application/json");
if($this->queryParam("rss") == '1' && $has_rss) {
$feed = new \Bhaktaraz\RSSGenerator\Feed();
$res->appendTo($feed);
$result = strval($feed);
header("Content-Type: application/rss+xml;charset=UTF-8");
} else {
$result = json_encode([
"response" => $res,
]);
if($callback) {
$result = $callback . '(' . $result . ')';
header('Content-Type: application/javascript');
} else {
header("Content-Type: application/json");
}
}
$size = strlen($result);
header("Content-Length: $size");

View file

@ -62,6 +62,7 @@ final class VideosPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error"), tr("video_uploads_disabled"));
if($_SERVER["REQUEST_METHOD"] === "POST") {
$is_ajax = (int)($this->postParam('ajax') ?? '0') == 1;
if(!empty($this->postParam("name"))) {
$video = new Video;
$video->setOwner($this->user->id);
@ -75,18 +76,29 @@ final class VideosPresenter extends OpenVKPresenter
else if(!empty($this->postParam("link")))
$video->setLink($this->postParam("link"));
else
$this->flashFail("err", tr("no_video_error"), tr("no_video_description"));
$this->flashFail("err", tr("no_video_error"), tr("no_video_description"), 10, $is_ajax);
} catch(\DomainException $ex) {
$this->flashFail("err", tr("error_video"), tr("file_corrupted"));
$this->flashFail("err", tr("error_video"), tr("file_corrupted"), 10, $is_ajax);
} catch(ISE $ex) {
$this->flashFail("err", tr("error_video"), tr("link_incorrect"));
$this->flashFail("err", tr("error_video"), tr("link_incorrect"), 10, $is_ajax);
}
if((int)($this->postParam("unlisted") ?? '0') == 1) {
$video->setUnlisted(true);
}
$video->save();
if($is_ajax) {
$object = $video->getApiStructure();
$this->returnJson([
'payload' => $object->video,
]);
}
$this->redirect("/video" . $video->getPrettyId());
} else {
$this->flashFail("err", tr("error_video"), tr("no_name_error"));
$this->flashFail("err", tr("error_video"), tr("no_name_error"), 10, $is_ajax);
}
}
}
@ -152,6 +164,12 @@ final class VideosPresenter extends OpenVKPresenter
$video->toggleLike($this->user->identity);
}
$this->returnJson(["success" => true]);
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->returnJson([
'success' => true,
]);
}
$this->redirect("$_SERVER[HTTP_REFERER]");
}
}

View file

@ -282,22 +282,19 @@ final class WallPresenter extends OpenVKPresenter
if($this->postParam("force_sign") === "on")
$flags |= 0b01000000;
$photos = [];
$horizontal_attachments = [];
$vertical_attachments = [];
if(!empty($this->postParam("horizontal_attachments"))) {
$horizontal_attachments_array = array_slice(explode(",", $this->postParam("horizontal_attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]);
if(sizeof($horizontal_attachments_array) > 0) {
$horizontal_attachments = parseAttachments($horizontal_attachments_array, ['photo', 'video']);
}
}
if(!empty($this->postParam("photos"))) {
$un = rtrim($this->postParam("photos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$photo || $photo->isDeleted())
continue;
$photos[] = $photo;
}
if(!empty($this->postParam("vertical_attachments"))) {
$vertical_attachments_array = array_slice(explode(",", $this->postParam("vertical_attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]);
if(sizeof($vertical_attachments_array) > 0) {
$vertical_attachments = parseAttachments($vertical_attachments_array, ['audio', 'note']);
}
}
@ -312,61 +309,10 @@ final class WallPresenter extends OpenVKPresenter
$this->flashFail("err", tr("failed_to_publish_post"), "Poll format invalid");
}
$note = NULL;
if(!is_null($this->postParam("note")) && $this->postParam("note") != "none") {
$note = (new Notes)->get((int)$this->postParam("note"));
if(!$note || $note->isDeleted() || $note->getOwner()->getId() != $this->user->id) {
$this->flashFail("err", tr("error"), tr("error_attaching_note"));
}
if($note->getOwner()->getPrivacySetting("notes.read") < 1) {
$this->flashFail("err", " ");
}
}
$videos = [];
if(!empty($this->postParam("videos"))) {
$un = rtrim($this->postParam("videos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$video = (new Videos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$video || $video->isDeleted())
continue;
$videos[] = $video;
}
}
}
$audios = [];
if(!empty($this->postParam("audios"))) {
$un = rtrim($this->postParam("audios"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$audio = (new Audios)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$audio || $audio->isDeleted())
continue;
$audios[] = $audio;
}
}
}
if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1 && sizeof($audios) < 1 && !$poll && !$note)
if(empty($this->postParam("text")) && sizeof($horizontal_attachments) < 1 && sizeof($vertical_attachments) < 1 && !$poll)
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big"));
$should_be_suggested = $wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2;
try {
$post = new Post;
$post->setOwner($this->user->id);
@ -383,7 +329,7 @@ final class WallPresenter extends OpenVKPresenter
} catch(\Throwable) {}
}
if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2)
if($should_be_suggested)
$post->setSuggested(1);
$post->save();
@ -391,22 +337,25 @@ final class WallPresenter extends OpenVKPresenter
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big"));
}
foreach($photos as $photo)
$post->attach($photo);
foreach($horizontal_attachments as $horizontal_attachment) {
if(!$horizontal_attachment || $horizontal_attachment->isDeleted() || !$horizontal_attachment->canBeViewedBy($this->user->identity)) {
continue;
}
if(sizeof($videos) > 0)
foreach($videos as $vid)
$post->attach($vid);
$post->attach($horizontal_attachment);
}
foreach($vertical_attachments as $vertical_attachment) {
if(!$vertical_attachment || $vertical_attachment->isDeleted() || !$vertical_attachment->canBeViewedBy($this->user->identity)) {
continue;
}
$post->attach($vertical_attachment);
}
if(!is_null($poll))
$post->attach($poll);
if(!is_null($note))
$post->attach($note);
foreach($audios as $audio)
$post->attach($audio);
if($wall > 0 && $wall !== $this->user->identity->getId())
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
@ -414,9 +363,7 @@ final class WallPresenter extends OpenVKPresenter
if($wall > 0)
$excludeMentions[] = $wall;
if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2) {
# Чтобы не было упоминаний из предложки
} else {
if(!$should_be_suggested) {
$mentions = iterator_to_array($post->resolveMentions($excludeMentions));
foreach($mentions as $mentionee)
@ -424,18 +371,7 @@ final class WallPresenter extends OpenVKPresenter
(new MentionNotification($mentionee, $post, $post->getOwner(), strip_tags($post->getText())))->emit();
}
if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2) {
$suggsCount = $this->posts->getSuggestedPostsCount($wallOwner->getId());
if($suggsCount % 10 == 0) {
$managers = $wallOwner->getManagers();
$owner = $wallOwner->getOwner();
(new NewSuggestedPostsNotification($owner, $wallOwner))->emit();
foreach($managers as $manager)
(new NewSuggestedPostsNotification($manager->getUser(), $wallOwner))->emit();
}
if($should_be_suggested) {
$this->redirect("/club".$wallOwner->getId()."/suggested");
} else {
$this->redirect($wallOwner->getURL());
@ -487,6 +423,12 @@ final class WallPresenter extends OpenVKPresenter
$post->toggleLike($this->user->identity);
}
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->returnJson([
'success' => true,
]);
}
$this->redirect("$_SERVER[HTTP_REFERER]#postGarter=" . $post->getId());
}

View file

@ -17,6 +17,7 @@
{script "js/node_modules/umbrellajs/umbrella.min.js"}
{script "js/l10n.js"}
{script "js/openvk.cls.js"}
{script "js/utils.js"}
{script "js/node_modules/dashjs/dist/dash.all.min.js"}
<script src="/assets/packages/static/openvk/js/node_modules/cropperjs/dist/cropper.js" type="module"></script>
{script "js/al_music.js"}
@ -26,6 +27,7 @@
{css "js/node_modules/tippy.js/dist/border.css"}
{css "js/node_modules/tippy.js/dist/svg-arrow.css"}
{css "js/node_modules/tippy.js/themes/light.css"}
{css "js/node_modules/jquery-ui/themes/base/resizable.css"}
{script "js/node_modules/@popperjs/core/dist/umd/popper.min.js"}
{script "js/node_modules/tippy.js/dist/tippy-bundle.umd.min.js"}
{script "js/node_modules/handlebars/dist/handlebars.min.js"}
@ -51,7 +53,10 @@
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['bellsAndWhistles']['testLabel']" id="test-label">FOR TESTING PURPOSES ONLY</div>
<div class="notifications_global_wrap"></div>
<div class="dimmer"></div>
<div class="dimmer">
<div id='absolute_territory'></div>
</div>
<div class="upLeftErrors"></div>
<div class="articleView">
<a id="articleCloseButton" class="button" href="javascript:void(u('body').removeClass('article'));">{_close}</a>
@ -361,7 +366,7 @@
</div>
<div id="ajloader" class="loader">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px;">
<img src="/assets/packages/static/openvk/img/loading_mini.gif">
</div>
{include "components/cookies.xml"}
@ -424,6 +429,9 @@
window.openvk = {
"audio_genres": {\openvk\Web\Models\Entities\Audio::genres},
"at_search": {$atSearch ?? false},
"max_attachments": {\OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"] ?? 10},
"max_filesize_mb": 5,
"current_id": {$thisUser ? $thisUser->getId() : 0},
}
</script>

View file

@ -5,17 +5,17 @@
<div id="audioEmbed-{$id}" data-realid="{$audio->getId()}" {if $hideButtons}data-prettyid="{$audio->getPrettyId()}" data-name="{$audio->getName()}"{/if} data-genre="{$audio->getGenre()}" class="audioEmbed {if !$isAvailable}processed{/if} {if $isWithdrawn}withdrawn{/if}" data-length="{$audio->getLength()}" data-keys="{json_encode($audio->getKeys())}" data-url="{$audio->getURL()}">
<audio class="audio" />
<div id="miniplayer" class="audioEntry" style="min-height: 39px;">
<div style="display: flex;">
<div id="miniplayer" class="audioEntry">
<div class='audioEntryWrapper' style="display: flex;" draggable='true'>
<div class="playerButton">
<div class="playIcon"></div>
</div>
<div class="status" style="margin-top: 12px;">
<div class="status" draggable='false'>
<div class="mediaInfo noOverflow">
<div class="info">
<strong class="performer">
<a href="/search?query=&section=audios&order=listens&only_performers=on&q={$audio->getPerformer()}">{$audio->getPerformer()}</a>
<a href="/search?section=audios&order=listens&only_performers=on&q={$audio->getPerformer()}">{$audio->getPerformer()}</a>
</strong>
<span class="title {if !empty($audio->getLyrics())}withLyrics{/if}">{$audio->getTitle()}</span>
@ -27,8 +27,8 @@
</div>
</div>
<div class="volume" style="display: flex; flex-direction: column;width:14%;">
<span class="nobold {if !$hideButtons}hideOnHover{/if}" data-unformatted="{$audio->getLength()}" style="text-align: center;margin-top: 12px;">{$audio->getFormattedLength()}</span>
<div class="volume">
<span class="nobold {if !$hideButtons}hideOnHover{/if}" data-unformatted="{$audio->getLength()}">{$audio->getFormattedLength()}</span>
<div class="buttons">
{php $hasAudio = isset($thisUser) && $audio->isInLibraryOf($thisUser)}
@ -44,7 +44,7 @@
</div>
</div>
</div>
<div class="subTracks">
<div class="subTracks" draggable='false'>
<div class="lengthTrackWrapper">
<div class="track lengthTrack">
<div class="selectableTrack" n:attr="style => $isWithdrawn ? 'display: none;' : ''">

View file

@ -168,10 +168,10 @@
{/if}
{var $canReport = $thisUser->getId() != $club->getOwner()->getId()}
{if $canReport}
<a class="profile_link" style="display:block;" href="javascript:reportVideo()">{_report}</a>
<a class="profile_link" style="display:block;" href="javascript:reportClub()">{_report}</a>
<script>
function reportVideo() {
function reportClub() {
uReportMsgTxt = tr("going_to_report_club");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"

View file

@ -4,76 +4,72 @@
{block header}
{ifset $album}
<a href="{$album->getOwner()->getURL()}">
{$album->getOwner()->getCanonicalName()}
{var $album_owner = $album->getOwner()}
<a href="{$album_owner->getURL()}">
{$album_owner->getCanonicalName()}
</a>
{if ($album->getOwner() instanceof openvk\Web\Models\Entities\Club)}
» <a href="/albums{$album->getOwner()->getId() * -1}">{_albums}</a>
{if ($album_owner instanceof openvk\Web\Models\Entities\Club)}
» <a href="/albums{$album_owner->getId() * -1}">{_albums}</a>
{else}
» <a href="/albums{$album->getOwner()->getId()}">{_albums}</a>
» <a href="/albums{$album_owner->getId()}">{_albums}</a>
{/if}
» <a href="/album{$album->getPrettyId()}">{$album->getName()}</a>
{else}
<a href="{$photo->getOwner()->getURL()}">{$photo->getOwner()->getCanonicalName()}</a>
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
{/ifset}
» {_photo}
{/block}
{block content}
<center style="margin-bottom: 8pt;">
<img src="{$photo->getURLBySizeId('large')}" style="max-width: 80%; max-height: 60vh;" />
</center>
<hr/>
<div style="width: 100%; min-height: 100px;" class="ovk-photo-details">
<div style="float: left; min-height: 100px; width: 68%;margin-left: 3px;">
{include "../components/comments.xml", comments => $comments, count => $cCount, page => $cPage, model => "photos", parent => $photo, custom_id => 999}
<div class='media-page-wrapper photo-page-wrapper'>
<div class='photo-page-wrapper-photo'>
<img src="{$photo->getURLBySizeId('large')}" />
</div>
<div style="float:right;min-height: 100px;width: 30%;margin-left: 1px;">
<div>
<h4>{_information}</h4>
<span style="color: grey;">{_info_description}:</span>
{$photo->getDescription() ?? "(" . tr("none") . ")"}<br/>
<span style="color: grey;">{_info_uploaded_by}:</span>
<a href="{$photo->getOwner()->getURL()}">{$photo->getOwner()->getFullName()}</a><br/>
<span style="color: grey;">{_info_upload_date}:</span>
{$photo->getPublicationTime()}
</div>
<br/>
<h4>{_actions}</h4>
{if isset($thisUser) && $thisUser->getId() != $photo->getOwner()->getId()}
{var canReport = true}
{/if}
<div n:if="isset($thisUser) && $thisUser->getId() === $photo->getOwner()->getId()">
<a href="/photo{$photo->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;">{_edit}</a>
<a id="_photoDelete" href="/photo{$photo->getPrettyId()}/delete" class="profile_link" style="display:block;width:96%;">{_delete}</a>
</div>
<a href="{$photo->getURL()}" class="profile_link" target="_blank" style="display:block;width:96%;">{_"open_original"}</a>
<a n:if="$canReport ?? false" class="profile_link" style="display:block;width:96%;" href="javascript:reportPhoto()">{_report}</a>
<script n:if="$canReport ?? false">
function reportPhoto() {
uReportMsgTxt = tr("going_to_report_photo");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$photo->getId()} + "?reason=" + res + "&type=photo", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
<div class='ovk-photo-details'>
<div class='media-page-wrapper-description'>
<p n:if='!empty($photo->getDescription())'>{$photo->getDescription()}</p>
<div class='upload_time'>
{_info_upload_date}: {$photo->getPublicationTime()}
{if isset($thisUser)}
|
{var $liked = $photo->hasLikeFrom($thisUser)}
{var $likesCount = $photo->getLikesCount()}
<div class='like_wrap tidy'>
<a href="/photo{$photo->getPrettyId()}/like?hash={rawurlencode($csrfToken)}" class="post-like-button" data-liked="{(int) $liked}" data-likes="{$likesCount}">
<div class="heart" id="{if $liked}liked{/if}"></div>
<span class="likeCnt">{if $likesCount > 0}{$likesCount}{/if}</span>
</a>
</div>
{/if}
</div>
</div>
<hr/>
<div class="media-page-wrapper-details">
<div class='media-page-wrapper-comments'>
{include "../components/comments.xml", comments => $comments, count => $cCount, page => $cPage, model => "photos", parent => $photo, custom_id => 999}
</div>
<div class='media-page-wrapper-actions'>
<a href="{$owner->getURL()}" class='media-page-author-block'>
<img class='cCompactAvatars' src="{$owner->getAvatarURL('miniscule')}">
<div class='media-page-author-block-name'>
<b>{$owner->getCanonicalName()}</b>
</div>
</a>
<div n:if="isset($thisUser) && $thisUser->getId() === $photo->getOwner()->getId()">
<a href="/photo{$photo->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;">{_edit}</a>
<a id="_photoDelete" href="/photo{$photo->getPrettyId()}/delete" class="profile_link" style="display:block;width:96%;">{_delete}</a>
</div>
<a href="{$photo->getURL()}" class="profile_link" target="_blank" style="display:block;width:96%;">{_"open_original"}</a>
<a n:if="isset($thisUser) && $thisUser->getId() != $photo->getOwner()->getId()" class="profile_link" style="display:block;width:96%;" href="javascript:reportPhoto({$photo->getId()})">{_report}</a>
<a n:if="isset($thisUser)" onclick="javascript:repost({$photo->getPrettyId()}, 'photo')" class="profile_link" style="display:block;width:96%;">
{_share}
</a>
</div>
</div>
</div>
</div>
{/block}

View file

@ -325,13 +325,13 @@
</div>
<div style="padding: 5px;">
<div class="ovk-video" n:foreach="$videos as $video">
<a href="/video{$video->getPrettyId()}" class="preview" align="center" id="videoOpen" data-id="{$video->getId()}">
<a href="/video{$video->getPrettyId()}" class="preview" align="center" id="videoOpen" data-id="{$video->getPrettyId()}">
<img
src="{$video->getThumbnailURL()}"
style="max-width: 170px; max-height: 127px; margin: auto;" />
</a>
<div>
<b><a href="/video{$video->getPrettyId()}" id="videoOpen" data-id="{$video->getId()}">{ovk_proc_strtr($video->getName(), 30)}</a></b><br>
<b><a href="/video{$video->getPrettyId()}" id="videoOpen" data-id="{$video->getPrettyId()}">{ovk_proc_strtr($video->getName(), 30)}</a></b><br>
<span style="font-size: 10px;">{$video->getPublicationTime()} | {_comments} ({$video->getCommentsCount()})</span>
</div>
</div>

View file

@ -11,91 +11,88 @@
{/block}
{block content}
<center style="margin-bottom: 8pt;">
{if $video->getType() === 0}
<div class="bsdn" data-name="{$video->getName()}" data-author="{$user->getCanonicalName()}">
<video src="{$video->getURL()}"></video>
</div>
{else}
{var $driver = $video->getVideoDriver()}
{if !$driver}
{_unknown_video}
{else}
{$driver->getEmbed()|noescape}
{/if}
{/if}
</center>
<hr/>
<div style="width: 100%; min-height: 100px;">
<div style="float: left; min-height: 100px; width: 68%; margin-right: 2%;" id="comments">
{include "../components/comments.xml",
comments => $comments,
count => $cCount,
page => $cPage,
model => "videos",
parent => $video}
</div>
<div style="float: left; min-height: 100px; width: 30%; overflow: hidden; overflow-wrap: break-word;">
<div>
<h4>{_information}</h4>
<span style="color: grey;">{_info_name}:</span>
{$video->getName()}<br/>
<span style="color: grey;">{_info_description}:</span>
{$video->getDescription() ?? "(" . tr("none") . ")"}<br/>
<span style="color: grey;">{_info_uploaded_by}:</span>
<a href="{$user->getURL()}">{$user->getFullName()}</a><br/>
<span style="color: grey;">{_info_upload_date}:</span>
{$video->getPublicationTime()}
</div>
<br/>
<div>
<div n:if="isset($thisUser) && $thisUser->getId() === $user->getId()">
<h4>{_actions}</h4>
<a href="/video{$video->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;">
{_edit}
</a>
<a href="/video{$video->getPrettyId()}/remove" class="profile_link" style="display:block;width:96%;">
{_delete}
</a>
<div class='media-page-wrapper video-page-wrapper'>
<div class='video-page-wrapper-video'>
{if $video->getType() === 0}
<div class="bsdn" data-name="{$video->getName()}" data-author="{$user->getCanonicalName()}">
<video src="{$video->getURL()}"></video>
</div>
<a href="/video{$video->getPrettyId()}" class="profile_link" id="videoOpen" data-id="{$video->getId()}" style="display:block;width:96%;">
{_watch_in_window}
</a>
</div>
{if isset($thisUser)}
{if $thisUser->getId() != $video->getOwner()->getId()}
{var canReport = true}
{else}
{var $driver = $video->getVideoDriver()}
{if !$driver}
{_unknown_video}
{else}
{$driver->getEmbed()|noescape}
{/if}
{/if}
<a n:if="$canReport ?? false" class="profile_link" style="display:block;width:96%;" href="javascript:reportVideo()">{_report}</a>
<script n:if="$canReport ?? false">
function reportVideo() {
uReportMsgTxt = tr("going_to_report_video");
uReportMsgTxt += "<br/>"+tr("report_question_text");
uReportMsgTxt += "<br/><br/><b>"+tr("report_reason")+"</b>: <input type='text' id='uReportMsgInput' placeholder='" + tr("reason") + "' />"
MessageBox(tr("report_question"), uReportMsgTxt, [tr("confirm_m"), tr("cancel")], [
(function() {
res = document.querySelector("#uReportMsgInput").value;
xhr = new XMLHttpRequest();
xhr.open("GET", "/report/" + {$video->getId()} + "?reason=" + res + "&type=video", true);
xhr.onload = (function() {
if(xhr.responseText.indexOf("reason") === -1)
MessageBox(tr("error"), tr("error_sending_report"), ["OK"], [Function.noop]);
else
MessageBox(tr("action_successfully"), tr("will_be_watched"), ["OK"], [Function.noop]);
});
xhr.send(null);
}),
Function.noop
]);
}
</script>
</div>
<div class='ovk-vid-details'>
<div class='media-page-wrapper-description'>
<p><b>{$video->getName()}</b></p>
<p n:if='!empty($video->getDescription())'>{$video->getDescription()}</p>
<div class='upload_time'>
{_info_upload_date}: {$video->getPublicationTime()}
{if isset($thisUser)}
|
{var $liked = $video->hasLikeFrom($thisUser)}
{var $likesCount = $video->getLikesCount()}
<div class='like_wrap tidy'>
<a href="/video{$video->getPrettyId()}/like?hash={rawurlencode($csrfToken)}" class="post-like-button" data-liked="{(int) $liked}" data-likes="{$likesCount}">
<div class="heart" id="{if $liked}liked{/if}"></div>
<span class="likeCnt">{if $likesCount > 0}{$likesCount}{/if}</span>
</a>
</div>
{/if}
</div>
</div>
<hr/>
<div class="media-page-wrapper-details">
<div class='media-page-wrapper-comments' id="comments">
{include "../components/comments.xml",
comments => $comments,
count => $cCount,
page => $cPage,
model => "videos",
parent => $video}
</div>
<div class='media-page-wrapper-actions'>
<a href="{$user->getURL()}" class='media-page-author-block'>
<img class='cCompactAvatars' src="{$user->getAvatarURL('miniscule')}">
<div class='media-page-author-block-name'>
<b>{$user->getCanonicalName()}</b>
</div>
</a>
<div>
<div n:if="isset($thisUser) && $thisUser->getId() === $user->getId()">
<a href="/video{$video->getPrettyId()}/edit" class="profile_link" style="display:block;width:96%;">
{_edit}
</a>
<a id='_videoDelete' href="/video{$video->getPrettyId()}/remove" class="profile_link" style="display:block;width:96%;">
{_delete}
</a>
</div>
<a n:if="isset($thisUser)" onclick="javascript:repost({$video->getPrettyId()}, 'video')" class="profile_link" style="display:block;width:96%;">
{_share}
</a>
</div>
{if isset($thisUser)}
{if $thisUser->getId() != $video->getOwner()->getId()}
{var canReport = true}
{/if}
{/if}
<a n:if="$canReport ?? false" class="profile_link" style="display:block;width:96%;" href="javascript:reportVideo({$video->getId()})">{_report}</a>
<a n:if="$video->getType() == 0" href="{$video->getURL()}" download="" class="profile_link" style="display:block;width:96%;">{_download_video}</a>
</div>
</div>
</div>
</div>
{/block}

View file

@ -1,7 +1,7 @@
{if $attachment instanceof \openvk\Web\Models\Entities\Photo}
{if !$attachment->isDeleted()}
{var $link = "/photo" . ($attachment->isAnonymous() ? ("s/" . base_convert((string) $attachment->getId(), 10, 32)) : $attachment->getPrettyId())}
<a href="{$link}" onclick="OpenMiniature(event, {$attachment->getURLBySizeId('normal')}, {$parent->getPrettyId()}, {$attachment->getPrettyId()}, {$parentType})">
<a href="{$link}" onclick="OpenMiniature(event, {$attachment->getURLBySizeId('larger')}, {$parent->getPrettyId()}, {$attachment->getPrettyId()}, {$parentType})">
<img class="media media_makima" src="{$attachment->getURLBySizeId('normal')}" alt="{$attachment->getDescription()}" loading=lazy />
</a>
{else}
@ -10,39 +10,50 @@
</a>
{/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Video}
{if $attachment->getType() === 0}
<div class="bsdn media" data-name="{$attachment->getName()}" data-author="{$attachment->getOwner()->getCanonicalName()}">
<video class="media" src="{$attachment->getURL()}"></video>
{if $tilesCount <= 1}
{if $attachment->getType() === 0}
<div class="bsdn media" data-name="{$attachment->getName()}" data-author="{$attachment->getOwner()->getCanonicalName()}">
<video class="media" src="{$attachment->getURL()}"></video>
</div>
{else}
{var $driver = $attachment->getVideoDriver()}
{if !$driver}
<span style="color:red;">{_version_incompatibility}</span>
{else}
{$driver->getEmbed("100%")|noescape}
{/if}
{/if}
<div class="video-wowzer">
<div class="small-video-ico"></div>
<a href="/video{$attachment->getPrettyId()}" id="videoOpen" data-id="{$attachment->getPrettyId()}">{$attachment->getName()}</a>
<span class="video-wowzer-length" n:if="$attachment->getLength() != NULL">({$attachment->getFormattedLength()})</span>
</div>
{else}
{var $driver = $attachment->getVideoDriver()}
{if !$driver}
<span style="color:red;">{_version_incompatibility}</span>
{else}
{$driver->getEmbed("100%")|noescape}
{/if}
{/if}
<a class='compact_video' id='videoOpen' data-id='{$attachment->getPrettyId()}' href="/video{$attachment->getPrettyId()}">
<div class='play-button'>
<div class='play-button-ico'></div>
</div>
<div class='video-length' n:if="$attachment->getLength() != NULL">
{$attachment->getFormattedLength()}
</div>
<div class="video-wowzer">
<img src="/assets/packages/static/openvk/img/videoico.png" />
<a href="/video{$attachment->getPrettyId()}" id="videoOpen" data-id="{$attachment->getId()}">{$attachment->getName()}</a>
</div>
<img class="media media_makima" src="{$attachment->getThumbnailURL()}" loading=lazy />
</a>
{/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Poll}
{presenter "openvk!Poll->view", $attachment->getId()}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Note}
{if !$attachment->isDeleted()}
<div data-att_type='note' data-att_id="{$attachment->getPrettyId()}">
<div class="attachment_note">
<img class="attachment_note_icon" src="/assets/packages/static/openvk/img/note.svg">
<span class="attachment_note_text">{_note}</span>
<span class="attachment_note_name"><a href="javascript:void(showArticle({$attachment->getId()}));">{ovk_proc_strtr($attachment->getName(), 66)}</a></span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 10"><polygon points="0 0 0 10 8 10 8 4 4 4 4 0 0 0"/><polygon points="5 0 5 3 8 3 5 0"/></svg>
<div class='attachment_note_content'>
<span class="attachment_note_text">{_note}</span>
<span class="attachment_note_name"><a href="javascript:void(showArticle({$attachment->getId()}));">{ovk_proc_strtr($attachment->getName(), 66)}</a></span>
</div>
</div>
{else}
<div class="attachment_note">
<img class="attachment_note_icon" src="/assets/packages/static/openvk/img/note.svg">
<span class="attachment_note_text">{_note}</span>
<span class="attachment_note_name">{_deleted}</span>
</div>
{/if}
</div>
{elseif $attachment instanceof \openvk\Web\Models\Entities\Post}
{php $GLOBALS["_nesAttGloCou"] = (isset($GLOBALS["_nesAttGloCou"]) ? $GLOBALS["_nesAttGloCou"] : 0) + 1}
{if $GLOBALS["_nesAttGloCou"] > 2}
@ -51,7 +62,7 @@
{include "post.xml", post => $attachment, compact => true}
{/if}
{elseif $attachment instanceof \openvk\Web\Models\Entities\Audio}
<div style="width:100%;">
<div style="width:100%;" data-att_type='audio' data-att_id="{$attachment->getPrettyId()}">
{include "../Audio/player.xml", audio => $attachment}
</div>
{else}

View file

@ -5,7 +5,7 @@
{var $postId = $target instanceof \openvk\Web\Models\Entities\Post ? $target->getId() : NULL}
<a name="cid={$comment->getId()}"></a>
<table border="0" style="font-size: 11px;" class="post comment" id="_comment{$comment->getId()}" data-comment-id="{$comment->getId()}" data-owner-id="{$author->getId()}" data-from-group="{$author instanceof $Club}" n:attr="data-post-id => $postId">
<table data-id="1_{$comment->getId()}" border="0" style="font-size: 11px;" class="post comment" id="_comment{$comment->getId()}" data-comment-id="{$comment->getId()}" data-owner-id="{$author->getId()}" data-from-group="{$author instanceof $Club}" n:attr="data-post-id => $postId">
<tbody>
<tr>
<td width="30" valign="top">
@ -22,12 +22,12 @@
</div>
<div class="post-content" id="{$comment->getId()}">
<div class="text" id="text{$comment->getId()}">
<span data-text="{$comment->getText(false)}" class="really_text">{$comment->getText()|noescape}</span>
<span class="really_text">{$comment->getText()|noescape}</span>
{var $attachmentsLayout = $comment->getChildrenWithLayout(288)}
<div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};">
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}">
{include "attachment.xml", attachment => $attachment[2], parent => $comment, parentType => "comment"}
{include "attachment.xml", attachment => $attachment[2], parent => $comment, parentType => "comment", tilesCount => sizeof($attachmentsLayout->tiles)}
</div>
</div>
@ -77,6 +77,7 @@
</span>
</div>
</div>
<div class='post-edit'></div>
</td>
</tr>
</tbody>

View file

@ -18,7 +18,7 @@
{var $commentTextAreaId = $post === NULL ? rand(1,300) : $post->getId()}
<table border="0" style="font-size: 11px;" n:class="post, !$compact ? post-divider, $post->isExplicit() ? post-nsfw">
<table border="0" style="font-size: 11px;" data-id="{$post->getPrettyId()}" n:class="post, !$compact ? post-divider, $post->isExplicit() ? post-nsfw">
<tbody>
<tr>
<td width="54" valign="top">
@ -68,13 +68,10 @@
{/if}
{if $post->canBeEditedBy($thisUser) && !($forceNoEditLink ?? false) && $compact == false}
<a class="edit" id="editPost"
data-id="{$post->getId()}"
data-nsfw="{(int)$post->isExplicit()}"
{if $post->getTargetWall() < 0 && $wallOwner->canBeModifiedBy($thisUser)}data-fromgroup="{(int)$post->isPostedOnBehalfOfGroup()}"{/if}></a>
<a class="edit" id="editPost"></a>
{/if}
</div>
<div class="post-content" id="{$post->getPrettyId()}">
<div class="post-content" id="{$post->getPrettyId()}" data-localized-nsfw-text="{_nsfw_warning}">
<div class="text" id="text{$post->getPrettyId()}">
<span data-text="{$post->getText(false)}" class="really_text">{$post->getText()|noescape}</span>
@ -84,8 +81,8 @@
{/if}
{var $attachmentsLayout = $post->getChildrenWithLayout($width)}
<div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};">
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}">
{include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post"}
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};">
{include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post", tilesCount => sizeof($attachmentsLayout->tiles)}
</div>
</div>
@ -112,6 +109,7 @@
</span>
</div>
</div>
<div class='post-edit' n:if='!$compact'></div>
<div class="post-menu" n:if="$compact == false">
<a href="{if !$suggestion}/wall{$post->getPrettyId()}{else}javascript:void(0){/if}" class="date">{$post->getPublicationTime()}
<span n:if="$post->getEditTime()" class="edited editedMark">({_edited_short})</span>
@ -126,7 +124,7 @@
<a n:if="!($forceNoCommentsLink ?? false) && $commentsCount == 0" href="javascript:expand_comment_textarea({$commentTextAreaId})">{_comment}</a>
<div class="like_wrap">
<a n:if="!($forceNoShareLink ?? false)" id="reposts{$post->getPrettyId()}" class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')">
<a n:if="!($forceNoShareLink ?? false)" id="reposts{$post->getPrettyId()}" class="post-share-button" href="javascript:repost('{$post->getPrettyId()}', 'post')">
<div class="repost-icon" style="opacity: 0.4;"></div>
<span class="likeCnt" id="repostsCount{$post->getPrettyId()}">{if $repostsCount > 0}{$repostsCount}{/if}</span>
</a>

View file

@ -15,7 +15,7 @@
<table border="0" style="font-size: 11px;" n:class="post, $post->isExplicit() ? post-nsfw">
<table border="0" style="font-size: 11px;" data-id="{$post->getPrettyId()}" n:class="post, $post->isExplicit() ? post-nsfw">
<tbody>
<tr>
<td width="54" valign="top">
@ -67,7 +67,7 @@
</a>
</a>
</div>
<div class="post-content" id="{$post->getPrettyId()}">
<div class="post-content" id="{$post->getPrettyId()}" data-localized-nsfw-text="{_nsfw_warning}">
<div class="text" id="text{$post->getPrettyId()}">
{var $owner = $author->getId()}
@ -79,8 +79,8 @@
{/if}
{var $attachmentsLayout = $post->getChildrenWithLayout($width)}
<div n:ifcontent class="attachments attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};">
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}">
{include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post"}
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};">
{include "../attachment.xml", attachment => $attachment[2], parent => $post, parentType => "post", tilesCount => sizeof($attachmentsLayout->tiles)}
</div>
</div>
@ -111,6 +111,7 @@
</span>
</div>
</div>
<div class='post-edit' n:if='!$compact'></div>
<div n:if="!($compact ?? false)" class="post-menu">
{if is_null($thisUser)}
{var $forceNoDeleteLink = true}
@ -119,9 +120,7 @@
{if !($forceNoEditLink ?? false) && $post->canBeEditedBy($thisUser)}
<a id="editPost"
data-id="{$post->getId()}"
data-nsfw="{(int)$post->isExplicit()}"
{if $post->getTargetWall() < 0 && $wallOwner->canBeModifiedBy($thisUser)}data-fromgroup="{(int)$post->isPostedOnBehalfOfGroup()}"{/if}>{_edit}</a> &nbsp;|&nbsp;
data-id="{$post->getPrettyId()}">{_edit}</a> &nbsp;|&nbsp;
{/if}
{if !($forceNoDeleteLink ?? false) && $canBeDeleted}
@ -148,7 +147,7 @@
&nbsp;|&nbsp;
{/if}
<a n:if="!($forceNoShareLink ?? false)" id="reposts{$post->getPrettyId()}" class="post-share-button" {ifset $thisUser} href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')"{/ifset}>
<a n:if="!($forceNoShareLink ?? false)" id="reposts{$post->getPrettyId()}" class="post-share-button" {ifset $thisUser} href="javascript:repost('{$post->getPrettyId()}', 'post')"{/ifset}>
{_share}
{if $repostsCount > 0}
(<b id="repostsCount{$post->getPrettyId()}">{$repostsCount}</b>)

View file

@ -2,26 +2,18 @@
{var $textAreaId = ($post ?? NULL) === NULL ? (++$GLOBALS["textAreaCtr"]) : $post->getId()}
{var $textAreaId = ($custom_id ?? NULL) === NULL ? $textAreaId : $custom_id}
<div id="write" style="padding: 5px 0;" onfocusin="expand_wall_textarea({$textAreaId});">
<div id="write" class='model_content_textarea' style="padding: 5px 0;">
<form action="{$route}" method="post" enctype="multipart/form-data" style="margin:0;">
<textarea id="wall-post-input{$textAreaId}" placeholder="{_write}" name="text" style="width: 100%;resize: none;" class="small-textarea"></textarea>
<div>
<!-- padding to fix <br/> bug -->
</div>
<div id="post-buttons{$textAreaId}" style="display: none;">
<div class="upload">
</div>
<div class="post-upload">
<span style="color: inherit;"></span>
</div>
<div id="post-buttons{$textAreaId}" class='post-buttons' style="display: none;">
<div class="post-horizontal"></div>
<div class="post-vertical"></div>
<div class="post-has-poll">
{_poll}
</div>
<div class="post-has-note"></div>
<div class="post-has-videos"></div>
<div class="post-has-audios"></div>
<div class="post-source"></div>
<div n:if="$postOpts ?? true" class="post-opts">
@ -62,42 +54,44 @@
<input type="checkbox" name="as_group" /> {_comment_as_group}
</label>
</div>
<input type="hidden" name="photos" value="" />
<input type="hidden" name="videos" value="" />
<input type="hidden" name="audios" value="" />
<input type="hidden" name="horizontal_attachments" value="" />
<input type="hidden" name="vertical_attachments" value="" />
<input type="hidden" name="poll" value="none" />
<input type="hidden" id="note" name="note" value="none" />
<input type="hidden" id="source" name="source" value="none" />
<input type="hidden" name="type" value="1" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<br/>
<input type="submit" value="{_write}" class="button" />
<div style="float: right; display: flex; flex-direction: column;">
<a href="javascript:toggleMenu({$textAreaId});">
<a class='menu_toggler'>
{_attach}
</a>
<div id="wallAttachmentMenu" class="hidden">
<a class="header" href="javascript:toggleMenu({$textAreaId});">
<a class="header menu_toggler">
{_attach}
</a>
<a id="photosAttachments" {if !is_null($club ?? NULL) && $club->canBeModifiedBy($thisUser)}data-club="{$club->getId()}"{/if}>
<a id="__photoAttachment" {if !is_null($club ?? NULL) && $club->canBeModifiedBy($thisUser)}data-club="{$club->getId()}"{/if}>
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-x-egon.png" />
{_photo}
</a>
<a id="videoAttachment">
<a id="__videoAttachment">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-vnd.rn-realmedia.png" />
{_video}
</a>
<a id="_audioAttachment">
<a id="__audioAttachment">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/audio-ac3.png" />
{_audio}
</a>
<a n:if="$graffiti ?? false" href="javascript:initGraffiti({$textAreaId});">
<a n:if="$notes ?? false" id="__notesAttachment">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-x-srt.png" />
{_note}
</a>
<a n:if="$graffiti ?? false" onclick="initGraffiti(event);">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" />
{_graffiti}
</a>
<a n:if="$polls ?? false" href="javascript:initPoll({$textAreaId})">
<a n:if="$polls ?? false" onclick="initPoll(event);">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/office-chart-bar-stacked.png" />
{_poll}
</a>
@ -112,13 +106,8 @@
</div>
<script>
$(document).ready(() => {
setupWallPostInputHandlers({$textAreaId});
});
u("#post-buttons{$textAreaId} input[name='videos']")["nodes"].at(0).value = ""
u("#post-buttons{$textAreaId} input[name='photos']")["nodes"].at(0).value = ""
u("#post-buttons{$textAreaId} input[name='audios']")["nodes"].at(0).value = ""
u("#post-buttons{$textAreaId} input[name='horizontal_attachments']")["nodes"].at(0).value = ""
u("#post-buttons{$textAreaId} input[name='vertical_attachments']")["nodes"].at(0).value = ""
</script>
{if $graffiti}

View file

@ -3,7 +3,7 @@
<tbody>
<tr>
<td valign="top">
<a href="/video{$video->getPrettyId()}" data-id="{$video->getId()}">
<a href="/video{$video->getPrettyId()}" id='videoOpen' data-id="{$video->getPrettyId()}">
<div class="video-preview">
<img src="{$video->getThumbnailURL()}"
style="max-width: 170px; max-height: 127px; margin: auto;" >
@ -15,10 +15,13 @@
{include infotable, x => $dat}
{else}
<a href="/video{$video->getPrettyId()}">
<b class='video_name' {$videoModal ? "id='videoOpen'" : ''} data-id="{$video->getId()}">
<b class='video_name' id='videoOpen' data-id="{$video->getPrettyId()}">
{$video->getName()}
</b>
</a>
{if $video->getLength()}
({$video->getFormattedLength()})
{/if}
<br/>
<p>
<span class='video_description'>{$video->getDescription() ?? ""}</span>
@ -26,7 +29,7 @@
<span style="color: grey;">{_video_uploaded} {$video->getPublicationTime()}</span><br/>
<p>
<a href="/video{$video->getPrettyId()}" {$videoModal ? "id='videoOpen'" : ''} data-id="{$video->getId()}">{_view_video}</a>
<a href="/video{$video->getPrettyId()}" id='videoOpen' data-id="{$video->getPrettyId()}">{_view_video}</a>
{if $video->getCommentsCount() > 0}| <a href="/video{$video->getPrettyId()}#comments">{_comments} ({$video->getCommentsCount()})</a>{/if}
</p>
{/ifset}

View file

@ -18,7 +18,7 @@ class Makima
$this->photos = $photos;
}
private function getOrientation(Photo $photo, &$ratio): int
private function getOrientation($photo, &$ratio): int
{
[$width, $height] = $photo->getDimensions();
$ratio = $width / $height;

View file

@ -129,8 +129,6 @@ routes:
handler: "Wall->rss"
- url: "/wall{num}/makePost"
handler: "Wall->makePost"
- url: "/wall/edit"
handler: "Wall->edit"
- url: "/wall{num}_{num}"
handler: "Wall->post"
- url: "/wall{num}_{num}/like"
@ -171,6 +169,8 @@ routes:
handler: "Photos->uploadPhoto"
- url: "/photo{num}_{num}"
handler: "Photos->photo"
- url: "/photo{num}_{num}/like"
handler: "Photos->like"
- url: "/photos/thumbnails/{num}_{text}.jpeg"
handler: "Photos->thumbnail"
- url: "/photos/{text}"
@ -403,6 +403,8 @@ routes:
handler: "About->dev"
- url: "/iapi/getPhotosFromPost/{num}_{num}"
handler: "InternalAPI->getPhotosFromPost"
- url: "/iapi/getPostTemplate/{num}_{num}"
handler: "InternalAPI->getPostTemplate"
- url: "/tour"
handler: "About->tour"
- url: "/{?shortCode}"

View file

@ -267,6 +267,17 @@
color: white;
}
.audioEntry .volume {
display: flex;
flex-direction: column;
width: 14%;
}
.audioEntry .nobold {
text-align: center;
margin-top: 12px;
}
.audioEntry.nowPlaying .volume .nobold {
color: white !important;
}
@ -303,6 +314,7 @@
height: 100%;
position: relative;
width: 100%;
min-height: 39px;
}
.audioEntry .playerButton {
@ -341,6 +353,7 @@
grid-template-columns: 1fr;
width: 85%;
height: 23px;
margin-top: 12px;
}
.audioEntry .status .mediaInfo {
@ -588,7 +601,6 @@
}
.attachAudio {
float: left;
width: 28%;
height: 26px;
padding-top: 11px;
@ -771,6 +783,7 @@
.addToPlaylist {
width: 22%;
float: left;
}
#_addAudioAdditional {

View file

@ -24,6 +24,7 @@
gap: 6px;
z-index: 2;
opacity: 0;
height: 43px;
}
.bsdn-player.bsdn-dirty > .bsdn_controls {

View file

@ -7,6 +7,11 @@ body.dimmed > .dimmer {
opacity: .5;
}
body.dimmed > .dimmer #absolute_territory {
margin: 100px;
height: 100%;
}
.ovk-diag-cont {
z-index: 1024;
position: fixed;
@ -59,243 +64,147 @@ body.dimmed > .dimmer {
margin-left: 10px;
}
/* Modal player */
/* fullscreen player */
.ovk-fullscreen-player {
top: 9%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, 0%);
z-index: 6667;
position: absolute;
.ovk-modal-player-window {
box-shadow: 0px 0px 9px 2px rgba(0, 0, 0, 0.2);
width: 823px;
min-height: 400px;
box-shadow: 0px 0px 9px 2px rgba(0, 0, 0, 0.2);
margin: 9vh auto 0 auto;
}
.top-part span {
.ovk-modal-player-window #ovk-player-part {
height: 70vh;
background: black;
padding: 15px 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.ovk-modal-player-window #ovk-player-part .top-part {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.ovk-modal-player-window #ovk-player-part .top-part b {
color: #515151;
font-size: 13px;
transition: color 200ms ease-in-out;
font-size: 12px;
}
.top-part .clickable:hover {
color: #ffffff;
.ovk-modal-player-window #ovk-player-part .top-part .top-part-buttons, .ovk-modal-player-window #ovk-player-part .bottom-part {
color: #515151;
}
.ovk-fullscreen-player .bsdn_teaserTitleBox span {
color: unset;
font-size: unset;
.ovk-modal-player-window #ovk-player-part .top-part .top-part-buttons a, .ovk-modal-player-window #ovk-player-part .bottom-part a {
color: #515151;
}
.ovk-fullscreen-player .bsdn-player {
max-width: 80%;
max-height: 350px;
}
.inner-player {
background: #000000;
min-height: 438px;
max-height: 439px;
position: relative;
padding-top: 11px;
}
.top-part-name {
font-size: 15px;
font-weight: bolder;
margin-left: 20px;
margin-top: 5px;
}
.top-part-buttons {
float: right;
margin-right: 20px;
}
.top-part-buttons span {
cursor: pointer;
user-select: none;
}
.fplayer {
text-align: center;
margin-top: 20px;
}
.top-part-bottom-buttons {
position: absolute;
margin-left: 20px;
bottom: 0;
margin-bottom: 20px;
}
.top-part-bottom-buttons span {
user-select: none;
}
.top-part .clickable {
cursor: pointer;
}
.bottom-part {
.ovk-modal-player-window #ovk-player-info {
display: none;
}
.ovk-modal-player-window #ovk-player-info.shown {
display: block;
}
.ovk-modal-player-window #ovk-player-info {
background: white;
padding-bottom: 20px;
padding-top: 30px;
min-height: 200px;
padding: 5px 15px;
}
.left_block {
padding-left: 20px;
/*padding-top: 20px;*/
width: 75%;
float: left;
background: white;
padding-right: 6px;
max-height: 400px;
overflow-y: auto;
.ovk-modal-player-window .media-page-wrapper-comments {
width: 100%;
}
.right_block {
padding-left: 10px;
/*padding-top: 20px;*/
width: 20%;
border-left: 1px solid gray;
float: right;
.ovk-modal-player-window .center-part {
text-align: center;
height: 90%;
display: flex;
align-items: center;
justify-content: center;
}
.bottom-part span {
font-size: 13px;
}
.bottom-part .gray {
.ovk-modal-player-window .center-part .gray {
color: gray;
}
.ovk-fullscreen-dimmer {
/* спижжено у пулла с несколькими картинками там где просмотрщик фоток */
position: fixed;
left: 0px;
top: 0px;
right: 0px;
bottom: 0px;
overflow: auto;
padding-bottom: 20px;
z-index: 300;
}
.v_author {
margin-top: 20px;
.ovk-modal-player-window .center-part .bsdn, .ovk-modal-player-window .center-part .bsdn-player {
height: 100%;
width: 100%;
}
.miniplayer {
position: absolute;
top:0;
background: rgba(54, 54, 54, 0.9);
background: rgba(54, 54, 54, 0.95);
border-radius: 3px;
min-width: 299px;
min-height: 192px;
padding-top: 3px;
z-index: 9999;
z-index: 7777;
padding: 2px 7px;
display: flex;
flex-direction: column;
}
.miniplayer .bsdn-player {
max-height: 150px;
.miniplayer .miniplayer-head {
display: flex;
justify-content: space-between;
align-items: center;
}
.miniplayer .fplayer {
max-width: 286px;
margin-left: 6px;
margin-top: 10px;
}
.miniplayer-actions {
float: right;
margin-right: 8px;
margin-top: 4px;
}
.miniplayer-name {
color: #8a8a8a;
font-size: 14px;
margin-left: 7px;
margin-top: -6px;
font-weight: bolder;
.miniplayer .miniplayer-head b {
color: white;
user-select: none;
}
.ui-draggable {
position:fixed !important;
.miniplayer .miniplayer-head .miniplayer-head-buttons {
display: flex;
align-items: center;
gap: 4px;
}
.miniplayer-actions img {
max-width: 11px;
.miniplayer .miniplayer-head .miniplayer-head-buttons div {
width: 16px;
height: 16px;
background: url(/assets/packages/static/openvk/img/wall.png) no-repeat 1px 0;
background-repeat: no-repeat;
opacity: 0.5;
cursor: pointer;
transition: opacity 200ms ease-in-out;
opacity: 70%;
}
.miniplayer .fplayer iframe {
max-width: 260px;
max-height: 160px;
.miniplayer .miniplayer-head .miniplayer-head-buttons div:hover {
opacity: 1;
}
.miniplayer-actions img:hover {
opacity: 100%;
.miniplayer .miniplayer-head .miniplayer-head-buttons #__miniplayer_return {
background-position: -28px 0px;
}
#vidComments {
margin-top: 10px;
.miniplayer .miniplayer-head .miniplayer-head-buttons #__miniplayer_close {
background-position: -12px 0px;
}
.showMoreComments {
background: #eaeaea;
cursor: pointer;
text-align: center;
padding: 10px;
user-select: none;
margin-top: 10px;
.miniplayer .miniplayer-head-buttons {
height: 20px;
}
.loader {
display: none;
position: fixed;
top: -10%;
background: rgba(26, 26, 26, 0.9);;
padding-top: 12px;
width: 91px;
height: 25px;
text-align: center;
border-radius: 1px;
margin: auto;
left: 0;
right: 0;
bottom: 0;
z-index: 5555;
.miniplayer .miniplayer-body {
height: 100%;
}
.right-arrow, .left-arrow {
position: absolute;
cursor: pointer;
transition: all 200ms ease-in-out;
margin-left: -50px;
background: none;
height: 449px;
width: 57px;
user-select: none;
.miniplayer .miniplayer-body .bsdn {
height: 100%;
}
.right-arrow img, .left-arrow img {
user-select: none;
opacity: 5%;
transition: all 200ms ease-in-out;
.miniplayer .miniplayer-body .bsdn .bsdn-player {
height: 100%;
}
.right-arrow:hover, .left-arrow:hover {
background: rgba(0, 0, 0, 0.5);
}
.right-arrow img:hover, .left-arrow img:hover {
opacity: 50%;
.miniplayer .miniplayer-body iframe {
width: 100%;
height: 100%;
}

View file

@ -13,6 +13,10 @@ body {
word-wrap: break-word;
}
body, .ovk-fullscreen-dimmer, .ovk-photo-view-dimmer {
scrollbar-gutter: stable both-edges;
}
.nobold,
nobold {
font-weight: normal;
@ -41,6 +45,16 @@ h1 {
margin-top: 0;
}
.display_flex_column {
display: flex;
flex-direction: column;
}
.display_flex_row {
display: flex;
flex-direction: row;
}
.layout {
width: 791px;
margin: 0 auto;
@ -254,6 +268,28 @@ h1 {
width: calc(550px - 16px - 38px - 171px);
}
.upLeftErrors {
position: fixed;
display: flex;
flex-direction: column;
z-index: 1025;
}
.upLeftErrors .upLeftError {
padding: 2px 2px 2px 2px;
display: inline-block;
border-radius: 0px 0px 5px 0px;
font-size: 15px;
}
.upLeftErrors .upLeftError.upLeftErrorRed {
background: #ff7474;
}
.upLeftErrors .upLeftError.upLeftErrorGreen {
background: #74ff92;
}
.page_body {
width: 632px;
float: right;
@ -772,6 +808,14 @@ h4 {
font-size: 9px;
}
.editing .post-content, .post-edit {
display: none;
}
.editing .post-edit {
display: block;
}
.post-content .text {
padding: 4px;
font-size: 11px;
@ -1464,7 +1508,7 @@ body.scrolled .toTop:hover {
color: #3c3c3c;
}
.post-source #remove_source_button {
.post-source #remove_source_button, #small_remove_button {
display: inline-block;
background-repeat: no-repeat;
background: url('/assets/packages/static/openvk/img/arrows.png?v=2');
@ -1478,7 +1522,7 @@ body.scrolled .toTop:hover {
cursor: pointer;
}
.post-source #remove_source_button:hover {
.post-source #remove_source_button:hover, #small_remove_button:hover {
opacity: 0.8;
}
@ -1548,6 +1592,15 @@ body.scrolled .toTop:hover {
float: right;
}
.like_wrap.tidy {
float: none;
display: inline-block;
}
.like_wrap.tidy .post-like-button {
display: flex;
}
.heart {
background: url('/assets/packages/static/openvk/img/like.gif') no-repeat 1px 0;
height: 10px;
@ -1557,12 +1610,22 @@ body.scrolled .toTop:hover {
opacity: 0.4;
}
.like_wrap.tidy .heart {
float: none;
display: inline-block;
}
.likeCnt {
font-size: 10px;
margin-right: 3px;
float: left;
}
.like_wrap.tidy .likeCnt {
float: none;
display: inline;
}
.heart:hover {
opacity: 1 !important;
}
@ -1726,12 +1789,14 @@ body.scrolled .toTop:hover {
#wallAttachmentMenu {
position: absolute;
min-width: 94px;
border: 1px solid darkgrey;
background-color: white;
z-index: 32;
margin-top: -7px;
margin-left: -16px;
box-shadow: 0 1px 3px -1px;
user-select: none;
}
#wallAttachmentMenu>a {
@ -2389,26 +2454,66 @@ a.poll-retract-vote {
}
}
.upload {
margin-top: 8px;
.post-horizontal, .post-vertical {
margin-top: 0px;
}
.upload .upload-item {
width: 75px;
height: 60px;
.post-horizontal {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 3px;
}
.post-vertical {
display: flex;
flex-direction: column;
flex-wrap: wrap;
gap: 3px;
}
.post-horizontal .upload-item {
/*width: 75px;
height: 60px;*/
overflow: hidden;
display: inline-block;
margin-right: 3px;
display: inline-flex;
justify-content: center;
align-items: center;
position: relative;
min-height: 63px;
}
.upload-item .upload-delete {
.post-horizontal .upload-item .play-button, .compact_video .play-button {
position: absolute;
height: 30px;
width: 30px;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
}
.post-horizontal .upload-item .play-button .play-button-ico, .compact_video .play-button .play-button-ico {
background: url(/assets/packages/static/openvk/img/wall.png) no-repeat 1px 0;
display: inline-block;
height: 15px;
width: 12px;
}
.upload-item.dragged {
filter: contrast(0.5);
}
.post-horizontal .upload-item .upload-delete {
position: absolute;
background: rgba(0,0,0,0.5);
padding: 2px 5px;
text-decoration: none;
color: #fff;
font-size: 11px;
margin-left: 57px; /* мне лень переделывать :DDDD */
right: 0px;
top: 0px;
opacity: 0;
transition: 0.25s;
}
@ -2417,13 +2522,68 @@ a.poll-retract-vote {
opacity: 1;
}
.upload-item img {
.post-horizontal .upload-item img {
width: 100%;
max-height: 60px;
object-fit: cover;
border-radius: 3px;
}
.post-vertical .vertical-attachment {
display: grid;
grid-template-columns: 1fr 0fr;
}
.post-vertical .vertical-attachment:first-of-type {
margin-top: 7px;
}
.post-vertical .vertical-attachment .audioEntry {
max-height: 28px;
min-height: 18px;
}
.post-vertical .vertical-attachment .audioEntry:hover {
background: unset !important;
}
.post-vertical .vertical-attachment .audioEntry .playerButton {
padding: 0px 3px 0px 0px;
}
.post-vertical .vertical-attachment .audioEntry .buttons {
display: none;
}
.post-vertical .vertical-attachment .audioEntry .status {
margin-top: 1px;
margin-left: 2px;
height: 15px;
}
.post-vertical .vertical-attachment .audioEntry .nobold {
margin-top: 1px;
}
.post-vertical .vertical-attachment .audioEntry .subTracks {
padding-bottom: 2px;
padding-left: 3px;
padding-right: 0px;
}
.post-vertical .vertical-attachment .audioEntry .audioEntryWrapper {
height: 18px;
}
.post-vertical .vertical-attachment .vertical-attachment-content {
max-height: 27px;
}
.post-vertical .vertical-attachment .vertical-attachment-content .overflowedName {
position: initial;
width: 100% !important;
}
/* https://imgur.com/a/ihB3JZ4 */
.ovk-photo-view-dimmer {
@ -2441,21 +2601,33 @@ a.poll-retract-vote {
position: relative;
z-index: 999;
background: #fff;
width: 610px;
padding: 20px;
min-width: 600px;
width: fit-content;
padding: 25px;
padding-top: 15px;
padding-bottom: 10px;
box-shadow: 0px 0px 3px 1px #222;
margin: 15px auto 0 auto;
margin: 10px auto 0 auto;
}
.ovk-photo-details {
overflow: auto;
.ovk-photo-view .photo_viewer_wrapper {
position: relative;
height: 80vh;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.ovk-photo-view #ovk-photo-img {
max-width: 100%;
max-height: 80vh;
user-select: none;
}
.photo_com_title {
font-weight: bold;
padding-bottom: 20px;
padding-bottom: 16px;
}
.photo_com_title div {
@ -2467,7 +2639,6 @@ a.poll-retract-vote {
left: 0;
width: 35%;
height: 100%;
max-height: 60vh;
position: absolute;
cursor: pointer;
}
@ -2476,11 +2647,27 @@ a.poll-retract-vote {
right: 0;
width: 35%;
height: 100%;
max-height: 60vh;
position: absolute;
cursor: pointer;
}
.ovk-photo-view .media-page-wrapper-details {
display: flex;
gap: 10px;
}
.ovk-photo-view .media-page-wrapper-comments {
overflow-y: auto;
overflow-x: hidden;
min-height: 300px;
width: 100%;
padding: 0px 2px;
}
.ovk-photo-view .ovk-photo-details {
margin-top: 10px;
}
.client_app > img {
top: 3px;
position: relative;
@ -2538,12 +2725,13 @@ a.poll-retract-vote {
padding: 3px 0;
}
.video-wowzer a::before {
content: "b";
color: transparent;
width: 12px;
background-image: url(/assets/packages/static/openvk/img/videoico.png);
display: none;
.video-wowzer .small-video-ico {
vertical-align: bottom;
display: inline-block;
width: 11px;
height: 14px;
background: url(/assets/packages/static/openvk/img/wall.png?v=2);
background-position: -87px 0px;
}
/* Da search */
@ -2889,17 +3077,33 @@ a.poll-retract-vote {
width: 100%;
}
.attachment_note_icon {
.attachment_note .attachment_note_icon {
max-width: 9px;
}
.attachment_note_text {
.attachment_note .attachment_note_text {
color: #605F63;
margin-left: 2px;
}
.attachment_note {
user-select: none;
display: flex;
align-items: center;
gap: 1px;
}
.attachment_note svg {
width: 8px;
height: 10px;
fill: #605f63;
margin-top: 2px;
}
.attachment_note .attachment_note_content {
display: flex;
gap: 4px;
height: 12px;
}
#notesList
@ -3124,10 +3328,81 @@ body.article .floating_sidebar, body.article .page_content {
font-size: 11px;
}
.topGrayBlock {
.attachment_selector .topGrayBlock {
background: #F0F0F0;
height: 37px;
border-bottom: 1px solid #C7C7C7;
padding: 3px 10px;
align-items: center;
justify-content: center;
justify-content: space-between;
}
.attachment_selector .topGrayBlock #video_query {
width: 160px;
}
.attachment_selector .topGrayBlock #albumSelect {
width: 150px;
}
.attachment_selector #attachment_insert {
padding: 5px;
height: 281px;
overflow-y: scroll;
}
.attachment_selector #attachment_insert #attachment_insert_count {
position: fixed;
z-index: 1007;
width: 92%;
background: white;
margin-top: -5px;
padding-top: 6px;
}
#show_more {
width: 100%;
text-align: center;
background: #f0f0f0;
height: 22px;
padding-top: 9px;
cursor: pointer;
margin-top: 4px;
}
#show_more:hover {
background: #f2f2f2;
}
.attachment_selector #attachment_insert .photosList {
margin-top: 20px;
gap: 3px 6px;
}
.attachment_selector #attachment_insert .photosList .album-photo.selected img {
background-color: #646464;
}
.attachment_selector #attachment_insert .photosList .album-photo {
width: 15.8%;
margin: unset;
}
.attachment_selector #attachment_insert .videosInsert .video_list .video-preview {
height: 75px;
width: 133px;
overflow: hidden;
}
.attachment_selector #attachment_insert .videosInsert .video_list .video-preview img {
max-width: 133px;
height: 75px;
margin: auto;
}
.attachment_selector #attachment_insert .videosInsert .video_list .action_links {
width: 150px;
}
.edited {
@ -3145,16 +3420,20 @@ body.article .floating_sidebar, body.article .page_content {
user-select: none;
}
.editMenu.loading {
.edit_menu.loading {
filter: opacity(0.5);
cursor: progress;
user-select: none;
}
.editMenu.loading * {
.edit_menu.loading * {
pointer-events: none;
}
.edit_menu .edit_menu_buttons {
margin-top: 10px;
}
.lagged *, .lagged {
pointer-events: none;
}
@ -3316,6 +3595,129 @@ hr {
height: 7px;
}
.compact_video {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
position: relative;
margin-left: -2px;
margin-top: -2px;
}
.compact_video .video-length {
text-align: center;
position: absolute;
width: 34px;
height: 14px;
bottom: 5px;
right: 5px;
background: rgba(0,0,0,0.5);
color: white;
}
.media-page-wrapper .photo-page-wrapper-photo {
margin-bottom: 8pt;
text-align: center;
}
.media-page-wrapper .video-page-wrapper-video iframe {
width: 100%;
}
.media-page-wrapper .photo-page-wrapper-photo img {
max-width: 85%;
max-height: 60vh;
}
.media-page-wrapper-details {
width: 100%;
min-height: 100px;
display: flex;
justify-content: space-between;
gap: 10px;
}
.media-page-wrapper-comments {
min-height: 100px;
width: 68%;
margin-left: 3px;
}
.media-page-wrapper-actions {
min-height: 100px;
width: 30%;
margin-left: 1px;
}
.media-page-wrapper-description p {
margin: 0px;
}
.media-page-wrapper-description .upload_time {
color: gray;
display: inline-flex;
align-items: center;
height: 16px;
}
.media-page-author-block {
display: flex;
align-items: center;
gap: 7px;
margin-bottom: 5px;
}
.media-page-author-block img {
width: 30px;
}
.media-page-author-block .media-page-author-block-name {
display: flex;
flex-direction: column;
justify-content: center;
}
.media-page-author-block .media-page-author-block-name b {
font-weight: bold;
}
.media-page-author-block .media-page-author-block-name span {
text-transform: lowercase;
}
#ajloader {
display: none;
position: fixed;
top: -10%;
background: rgba(26, 26, 26, 0.9);;
padding-top: 12px;
width: 91px;
height: 25px;
text-align: center;
border-radius: 1px;
margin: auto;
left: 0;
right: 0;
bottom: 0;
z-index: 5555;
}
#ajloader.shown {
display: block;
}
.hoverable_color {
transition: all 0.17s ease-in-out;
}
.hoverable_color:hover {
color: white !important;
}
#__feed_settings_link {
float: right;
margin-right: 3px;

View file

@ -1,28 +1,28 @@
.post-nsfw .post-content .media {
filter: saturate(0.8) blur(15px);
}
.post-nsfw .post-content .attachment {
overflow: hidden;
.post-nsfw .post-content {
position: relative;
overflow: hidden;
cursor: pointer;
}
.post-nsfw .post-content .attachment:active .media {
filter: none;
.post-nsfw .post-content .text {
filter: saturate(0.8) blur(28px);
pointer-events: none;
}
.post-nsfw .post-content .attachment::after {
.post-nsfw .post-content::after {
position: absolute;
top: calc(50% - 16px);
top: 0;
left: 0;
width: 100%;
padding: 8px 0;
background-color: hsla(0, 0%, 0%, .5);
height: 100%;
z-index: 1;
background-color: hsla(0, 0%, 0%, .7);
color: #fff;
text-align: center;
content: attr(data-localized-nsfw-text);
}
.post-nsfw .post-content .attachment:active::after {
display: none;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 932 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 B

View file

@ -1 +0,0 @@
<svg id="note" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 40"><defs><style>.cls-1{fill:#a0a0a0;}</style></defs><title>note</title><polygon class="cls-1" points="0 0 0 40 35 40 35 20 17.5 20 17.5 0 0 0"/><polygon id="block" class="cls-1" points="20.26 1 35 18 20.26 18 20.26 1"/></svg>

Before

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

BIN
Web/static/img/wall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -35,3 +35,21 @@ window.API.Types = {};
window.API.Types.Message = (class Message {
});
window.OVKAPI = new class {
async call(method, params) {
if(!method) {
return
}
const url = `/method/${method}?auth_mechanism=roaming&${new URLSearchParams(params).toString()}&v=5.200`
const res = await fetch(url)
const json_response = await res.json()
if(json_response.response) {
return json_response.response
} else {
throw new Error(json_response.error_msg)
}
}
}

View file

@ -1,13 +1,3 @@
function fmtTime(time) {
const mins = String(Math.floor(time / 60)).padStart(2, '0');
const secs = String(Math.floor(time % 60)).padStart(2, '0');
return `${ mins}:${ secs}`;
}
function fastError(message) {
MessageBox(tr("error"), message, [tr("ok")], [Function.noop])
}
// elapsed это вроде прошедшие, а оставшееся это remaining но ладно уже
function getElapsedTime(fullTime, time) {
let timer = fullTime - time
@ -1325,8 +1315,8 @@ $(document).on("click", "#_deletePlaylist", (e) => {
}, Function.noop])
})
$(document).on("click", "#_audioAttachment", (e) => {
let form = e.currentTarget.closest("form")
$(document).on("click", "#__audioAttachment", (e) => {
const form = e.target.closest("form")
let body = `
<div class="searchBox">
<input name="query" type="text" maxlength="50" placeholder="${tr("header_search")}">
@ -1338,7 +1328,7 @@ $(document).on("click", "#_audioAttachment", (e) => {
<div class="audiosInsert"></div>
`
MessageBox(tr("select_audio"), body, [tr("ok")], [Function.noop])
MessageBox(tr("select_audio"), body, [tr("close")], [Function.noop])
document.querySelector(".ovk-diag-body").style.padding = "0"
document.querySelector(".ovk-diag-cont").style.width = "580px"
@ -1359,23 +1349,24 @@ $(document).on("click", "#_audioAttachment", (e) => {
result.querySelectorAll(".audioEmbed").forEach(el => {
let id = el.dataset.prettyid
let name = el.dataset.name
let isAttached = (form.querySelector("input[name='audios']").value.includes(`${id},`))
const is_attached = (u(form).find(`.post-vertical .vertical-attachment[data-id='${id}']`)).length > 0
document.querySelector(".audiosInsert").insertAdjacentHTML("beforeend", `
<div style="display: table;width: 100%;clear: both;">
<div style="width: 72%;float: left;">${el.outerHTML}</div>
<div class="attachAudio" data-attachmentdata="${id}" data-name="${name}">
<span>${isAttached ? tr("detach_audio") : tr("attach_audio")}</span>
<div class='audio_attachment_header' style="display: flex;width: 100%;">
<div class='player_part' style="width: 72%;">${el.outerHTML}</div>
<div class="attachAudio" data-attachmentdata="${id}">
<span>${is_attached ? tr("detach_audio") : tr("attach_audio")}</span>
</div>
</div>
`)
})
u("#loader").remove()
u('#show_more').remove()
if(thisc.page < pagesCount) {
document.querySelector(".audiosInsert").insertAdjacentHTML("beforeend", `
<div id="showMoreAudios" data-pagesCount="${pagesCount}" data-page="${thisc.page + 1}" class="showMore">
<div id="show_more" data-pagesCount="${pagesCount}" data-page="${thisc.page + 1}" class="showMore">
<span>${tr("show_more_audios")}</span>
</div>`)
}
@ -1395,14 +1386,12 @@ $(document).on("click", "#_audioAttachment", (e) => {
searcher.movePage(1)
$(".audiosInsert").on("click", "#showMoreAudios", (e) => {
u(e.currentTarget).remove()
u(".audiosInsert").on("click", "#show_more", async (e) => {
u(e.target).closest('#show_more').addClass('lagged')
searcher.movePage(Number(e.currentTarget.dataset.page))
})
$(".searchBox input").on("change", async (e) => {
await new Promise(r => setTimeout(r, 500));
if(e.currentTarget.value === document.querySelector(".searchBox input").value) {
searcher.clearContainer()
@ -1433,44 +1422,34 @@ $(document).on("click", "#_audioAttachment", (e) => {
return;
})
function insertAttachment(id) {
let audios = form.querySelector("input[name='audios']")
u(".audiosInsert").on("click", ".attachAudio", (ev) => {
const id = ev.currentTarget.dataset.attachmentdata
const is_attached = u(form).find(`.post-vertical .vertical-attachment[data-id='${id}']`).length > 0
if(!audios.value.includes(id + ",")) {
if(audios.value.split(",").length > 10) {
NewNotification(tr("error"), tr("max_attached_audios"))
return false
// 04.11.2024 19:03
if(is_attached) {
u(form).find(`.post-vertical .vertical-attachment[data-id='${id}']`).remove()
u(ev.currentTarget).find("span").html(tr("attach_audio"))
} else {
if(u(form).find(`.upload-item`).length > window.openvk.max_attachments) {
makeError(tr('too_many_attachments'), 'Red', 10000, 1)
return
}
form.querySelector("input[name='audios']").value += (id + ",")
u(ev.currentTarget).find("span").html(tr("detach_audio"))
return true
} else {
form.querySelector("input[name='audios']").value = form.querySelector("input[name='audios']").value.replace(id + ",", "")
return false
}
}
$(".audiosInsert").on("click", ".attachAudio", (ev) => {
if(!insertAttachment(ev.currentTarget.dataset.attachmentdata)) {
u(`.post-has-audios .post-has-audio[data-id='${ev.currentTarget.dataset.attachmentdata}']`).remove()
ev.currentTarget.querySelector("span").innerHTML = tr("attach_audio")
} else {
ev.currentTarget.querySelector("span").innerHTML = tr("detach_audio")
form.querySelector(".post-has-audios").insertAdjacentHTML("beforeend", `
<div class="post-has-audio" id="unattachAudio" data-id="${ev.currentTarget.dataset.attachmentdata}">
<span>${ovk_proc_strtr(escapeHtml(ev.currentTarget.dataset.name), 40)}</span>
const header = u(ev.currentTarget).closest('.audio_attachment_header')
const player = header.find('.player_part')
u(form).find(".post-vertical").append(`
<div class="vertical-attachment upload-item" data-type='audio' data-id="${ev.currentTarget.dataset.attachmentdata}">
<div class='vertical-attachment-content'>
${player.html()}
</div>
<div class='vertical-attachment-remove'>
<div id='small_remove_button'></div>
</div>
</div>
`)
u(`#unattachAudio[data-id='${ev.currentTarget.dataset.attachmentdata}']`).on("click", (e) => {
let id = ev.currentTarget.dataset.attachmentdata
form.querySelector("input[name='audios']").value = form.querySelector("input[name='audios']").value.replace(id + ",", "")
u(e.currentTarget).remove()
})
}
})
})
@ -1480,7 +1459,13 @@ $(document).on("click", ".audioEmbed.processed .playerButton", (e) => {
})
$(document).on("click", ".audioEmbed.withdrawn", (e) => {
MessageBox(tr("error"), tr("audio_embed_withdrawn"), [tr("ok")], [Function.noop])
const msg = new CMessageBox({
title: tr('error'),
body: tr('audio_embed_withdrawn'),
unique_name: 'withdrawn_notify',
buttons: [tr('ok')],
callbacks: [Function.noop]
})
})
$(document).on("click", ".musicIcon.report-icon", (e) => {

View file

@ -64,8 +64,9 @@ function pollRadioPressed(radio) {
form.submit();
}
function initPoll(id) {
let form = $(`#wall-post-input${id}`).parent();
function initPoll(event) {
let form = $(event.target.closest('.post-buttons')).parent();
const id = random_int(0, 100)
let mBody = `
<div id="poll_editor${id}">
@ -87,9 +88,9 @@ function initPoll(id) {
</div>
`;
MessageBox(tr("create_poll"), mBody, [tr("attach"), tr("cancel")], [
const msg = MessageBox(tr("create_poll"), mBody, [tr("attach"), tr("cancel")], [
function() {
let dialog = $(this.$dialog().nodes[0]);
let dialog = $(msg.getNode().nodes[0]);
$("input", dialog).unbind();
let title = $("input[name=title]", dialog).val();
@ -117,9 +118,9 @@ function initPoll(id) {
$(".post-has-poll", form).show();
},
function() {
$("input", $(this.$dialog().nodes[0])).unbind();
$("input", $(msg.getNode())).unbind();
}
]);
], true);
let editor = $(`#poll_editor${id}`);
$("input[name=newOption]", editor).bind("focus", function() {

File diff suppressed because it is too large Load diff

View file

@ -1,42 +1,177 @@
Function.noop = () => {};
function MessageBox(title, body, buttons, callbacks) {
if(u(".ovk-diag-cont").length > 0) return false;
class CMessageBox {
constructor(options = {}) {
const title = options.title ?? 'Untitled'
const body = options.body ?? '<hr>'
const buttons = options.buttons ?? []
const callbacks = options.callbacks ?? []
const close_on_buttons = options.close_on_buttons ?? true
const unique_name = options.unique_name ?? null
const warn_on_exit = options.warn_on_exit ?? false
const custom_template = options.custom_template ?? null
if(unique_name && window.messagebox_stack.find(item => item.unique_name == unique_name) != null) {
return
}
document.querySelector("html").style.overflowY = "hidden"
let dialog = u(
`<div class="ovk-diag-cont">
<div class="ovk-diag">
<div class="ovk-diag-head">${title}</div>
<div class="ovk-diag-body">${body}</div>
<div class="ovk-diag-action"></div>
</div>
</div>`);
u("body").addClass("dimmed").append(dialog);
this.title = title
this.body = body
this.id = random_int(0, 10000)
this.close_on_buttons = close_on_buttons
this.unique_name = unique_name
this.warn_on_exit = warn_on_exit
buttons.forEach((text, callback) => {
u(".ovk-diag-action").append(u(`<button class="button">${text}</button>`));
let button = u(u(".ovk-diag-action > button.button").last());
if(!custom_template) {
u('body').addClass('dimmed').append(this.__getTemplate())
} else {
custom_template.addClass('ovk-msg-all')
custom_template.attr('data-id', this.id)
u('body').addClass('dimmed').append(custom_template)
}
button.on("click", function(e) {
let __closeDialog = () => {
u('html').attr('style', 'overflow-y:hidden')
if(document.querySelector(".ovk-photo-view-dimmer") == null && document.querySelector(".ovk-fullscreen-player") == null) {
u("body").removeClass("dimmed");
document.querySelector("html").style.overflowY = "scroll"
buttons.forEach((text, callback) => {
this.getNode().find('.ovk-diag-action').append(u(`<button class="button">${text}</button>`))
let button = u(this.getNode().find('.ovk-diag-action > button.button').last())
button.on("click", (e) => {
callbacks[callback]()
if(close_on_buttons) {
this.close()
}
})
})
u(".ovk-diag-cont").remove();
};
window.messagebox_stack.push(this)
}
Reflect.apply(callbacks[callback], {
closeDialog: () => __closeDialog(),
$dialog: () => u(".ovk-diag-cont")
}, [e]);
__getTemplate() {
return u(
`<div class="ovk-diag-cont ovk-msg-all" data-id="${this.id}">
<div class="ovk-diag">
<div class="ovk-diag-head">${this.title}</div>
<div class="ovk-diag-body">${this.body}</div>
<div class="ovk-diag-action"></div>
</div>
</div>`)
}
__closeDialog();
});
});
getNode() {
return u(`.ovk-msg-all[data-id='${this.id}']`)
}
return u(".ovk-diag-cont");
async __showCloseConfirmationDialog() {
return new Promise((resolve, reject) => {
const msg = new CMessageBox({
title: tr('exit_noun'),
body: tr('exit_confirmation'),
warn_on_exit: false,
unique_name: 'close_confirmation',
buttons: [tr('no'), tr('yes')],
callbacks: [() => {
msg.close()
resolve(false)
}, () => {
this.__exitDialog()
resolve(true)
}]
})
})
}
__exitDialog() {
this.getNode().remove()
if(u('.ovk-msg-all').length < 1) {
u('body').removeClass('dimmed')
u('html').attr('style', 'overflow-y:scroll')
}
const current_item = window.messagebox_stack.find(item => item.id == this.id)
const index_of_item = window.messagebox_stack.indexOf(current_item)
window.messagebox_stack = array_splice(window.messagebox_stack, index_of_item)
delete this
}
close() {
this.__exitDialog()
}
hide() {
u('body').removeClass('dimmed')
u('html').attr('style', 'overflow-y:scroll')
this.getNode().attr('style', 'display: none;')
}
reveal() {
u('body').addClass('dimmed')
u('html').attr('style', 'overflow-y:hidden')
this.getNode().attr('style', 'display: block;')
}
static toggleLoader() {
u('#ajloader').toggleClass('shown')
}
}
window.messagebox_stack = []
function MessageBox(title, body, buttons, callbacks, return_msg = false) {
const msg = new CMessageBox({
title: title,
body: body,
buttons: buttons,
callbacks: callbacks,
})
if(return_msg) {
return msg
}
return msg.getNode()
}
// Close on 'Escape' key
u(document).on('keyup', async (e) => {
if(e.keyCode == 27 && window.messagebox_stack.length > 0) {
const msg = window.messagebox_stack[window.messagebox_stack.length - 1]
if(!msg) {
return
}
if(msg.close_on_buttons) {
msg.close()
return
}
if(msg.warn_on_exit) {
const res = await msg.__showCloseConfirmationDialog()
if(res === true) {
msg.close()
}
}
}
})
// Close when clicking on shadow
u(document).on('click', 'body.dimmed .dimmer', async (e) => {
if(u(e.target).hasClass('dimmer')) {
const msg = window.messagebox_stack[window.messagebox_stack.length - 1]
if(!msg) {
return
}
if(msg.close_on_buttons) {
msg.close()
return
}
if(msg.warn_on_exit) {
const res = await msg.__showCloseConfirmationDialog()
if(res === true) {
msg.close()
}
}
}
})

View file

@ -1,9 +1,5 @@

function expand_wall_textarea(id) {
var el = document.getElementById('post-buttons'+id);
var wi = document.getElementById('wall-post-input'+id);
el.style.display = "block";
wi.className = "expanded-textarea";
if(typeof u == 'undefined') {
console.error('!!! You forgot to install NPM packages !!!')
}
function expand_comment_textarea(id) {
@ -13,19 +9,6 @@ function expand_comment_textarea(id) {
wi.focus();
}
function edit_post(id, wid) {
var el = document.getElementById('text'+wid+'_'+id);
var ed = document.getElementById('text_edit'+wid+'_'+id);
if (el.style.display == "none") {
el.style.display = "block";
ed.style.display = "none";
} else {
el.style.display = "none";
ed.style.display = "block";
}
}
function hidePanel(panel, count = 0)
{
$(panel).toggleClass("content_title_expanded content_title_unexpanded");
@ -56,19 +39,9 @@ function parseAjaxResponse(responseString) {
}
}
function toggleMenu(id) {
if($(`#post-buttons${id} #wallAttachmentMenu`).is('.hidden')) {
$(`#post-buttons${id} #wallAttachmentMenu`).css({ opacity: 0 });
$(`#post-buttons${id} #wallAttachmentMenu`).toggleClass('hidden').fadeTo(250, 1);
} else {
$(`#post-buttons${id} #wallAttachmentMenu`).fadeTo(250, 0, function () {
$(this).toggleClass('hidden');
});
}
}
document.addEventListener("DOMContentLoaded", function() { //BEGIN
$(document).on("click", "#_photoDelete", function(e) {
$(document).on("click", "#_photoDelete, #_videoDelete", function(e) {
var formHtml = "<form id='tmpPhDelF' action='" + u(this).attr("href") + "' >";
formHtml += "<input type='hidden' name='hash' value='" + u("meta[name=csrf]").attr("value") + "' />";
formHtml += "</form>";
@ -186,74 +159,6 @@ document.addEventListener("DOMContentLoaded", function() { //BEGIN
})
}); //END ONREADY DECLS
async function repostPost(id, hash) {
uRepostMsgTxt = `
<b>${tr('auditory')}:</b> <br/>
<input type="radio" name="type" onchange="signs.setAttribute('hidden', 'hidden');document.getElementById('groupId').setAttribute('hidden', 'hidden')" value="wall" checked>${tr("in_wall")}<br/>
<input type="radio" name="type" onchange="signs.removeAttribute('hidden');document.getElementById('groupId').removeAttribute('hidden')" value="group" id="group">${tr("in_group")}<br/>
<select style="width:50%;" id="groupId" name="groupId" hidden>
</select><br/>
<b>${tr('your_comment')}:</b>
<textarea id='uRepostMsgInput_${id}'></textarea>
<div id="signs" hidden>
<label><input onchange="signed.checked ? signed.checked = false : null" type="checkbox" id="asgroup" name="asGroup">${tr('post_as_group')}</label><br>
<label><input onchange="asgroup.checked = true" type="checkbox" id="signed" name="signed">${tr('add_signature')}</label>
</div>
<br/><br/>`;
let clubs = [];
repostsCount = document.getElementById("repostsCount"+id)
prevVal = repostsCount != null ? Number(repostsCount.innerHTML) : 0;
MessageBox(tr('share'), uRepostMsgTxt, [tr('send'), tr('cancel')], [
(function() {
text = document.querySelector("#uRepostMsgInput_"+id).value;
type = "user";
radios = document.querySelectorAll('input[name="type"]')
for(const r of radios)
{
if(r.checked)
{
type = r.value;
break;
}
}
groupId = document.querySelector("#groupId").value;
asGroup = asgroup.checked == true ? 1 : 0;
signed = signed.checked == true ? 1 : 0;
hash = encodeURIComponent(hash);
xhr = new XMLHttpRequest();
xhr.open("POST", "/wall"+id+"/repost?hash="+hash, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = (function() {
if(xhr.responseText.indexOf("wall_owner") === -1)
MessageBox(tr('error'), tr('error_repost_fail'), [tr('ok')], [Function.noop]);
else {
let jsonR = JSON.parse(xhr.responseText);
NewNotification(tr('information_-1'), tr('shared_succ'), null, () => {window.location.href = "/wall" + jsonR.wall_owner});
repostsCount != null ?
repostsCount.innerHTML = prevVal+1 :
document.getElementById("reposts"+id).insertAdjacentHTML("beforeend", "(<b id='repostsCount"+id+"'>1</b>)") //для старого вида постов
}
});
xhr.send('text='+encodeURI(text) + '&type='+type + '&groupId='+groupId + "&asGroup="+asGroup + "&signed="+signed);
}),
Function.noop
]);
try
{
clubs = await API.Groups.getWriteableClubs();
for(const el of clubs) {
document.getElementById("groupId").insertAdjacentHTML("beforeend", `<option value="${el.id}">${escapeHtml(el.name)}</option>`)
}
} catch(rejection) {
console.error(rejection)
document.getElementById("group").setAttribute("disabled", "disabled")
}
}
function setClubAdminComment(clubId, adminId, hash) {
MessageBox("Изменить комментарий к администратору", `
<form action="/club${clubId}/setAdmin" method="post" id="uClubAdminCommentForm_${clubId}_${adminId}">
@ -319,17 +224,6 @@ function showCoinsTransferDialog(coinsCount, hash) {
]);
}
function chunkSubstr(string, size) {
const numChunks = Math.ceil(string.length / size);
const chunks = new Array(numChunks);
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = string.substr(o, size);
}
return chunks;
}
function autoTab(original, next, previous) {
if(original.getAttribute && original.value.length == original.getAttribute("maxlength") && next !== undefined)
next.focus();
@ -361,11 +255,6 @@ function supportFastAnswerDialogOnClick(answer) {
answerInput.focus();
}
function ovk_proc_strtr(string, length = 0) {
const newString = string.substring(0, length);
return newString + (string !== newString ? "…" : "");
}
function showProfileDeactivateDialog(hash) {
MessageBox(tr("profile_deactivate"), `
<div class="messagebox-content-header">
@ -488,57 +377,6 @@ function showIncreaseRatingDialog(coinsCount, userUrl, hash) {
};
}
function escapeHtml(text) {
var map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
function highlightText(searchText, container_selector, selectors = []) {
const container = u(container_selector)
const regexp = new RegExp(`(${searchText})`, 'gi')
function highlightNode(node) {
if(node.nodeType == 3) {
let newNode = escapeHtml(node.nodeValue)
newNode = newNode.replace(regexp, (match, ...args) => {
return `<span class='highlight'>${escapeHtml(match)}</span>`
})
const tempDiv = document.createElement('div')
tempDiv.innerHTML = newNode
while(tempDiv.firstChild) {
node.parentNode.insertBefore(tempDiv.firstChild, node)
}
node.parentNode.removeChild(node)
} else if(node.nodeType === 1 && node.tagName !== 'SCRIPT' && node.tagName !== 'BR' && node.tagName !== 'STYLE') {
Array.from(node.childNodes).forEach(highlightNode);
}
}
selectors.forEach(selector => {
elements = container.find(selector)
if(!elements || elements.length < 1) return;
elements.nodes.forEach(highlightNode)
})
}
String.prototype.escapeHtml = function() {
try {
return escapeHtml(this)
} catch(e) {
return ''
}
}
$(document).on("scroll", () => {
if($(document).scrollTop() > $(".sidebar").height() + 50) {
$(".floating_sidebar")[0].classList.add("show");

175
Web/static/js/utils.js Normal file
View file

@ -0,0 +1,175 @@
function escapeHtml(text) {
var map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
function highlightText(searchText, container_selector, selectors = []) {
const container = u(container_selector)
const regexp = new RegExp(`(${searchText})`, 'gi')
function highlightNode(node) {
if(node.nodeType == 3) {
let newNode = escapeHtml(node.nodeValue)
newNode = newNode.replace(regexp, (match, ...args) => {
return `<span class='highlight'>${escapeHtml(match)}</span>`
})
const tempDiv = document.createElement('div')
tempDiv.innerHTML = newNode
while(tempDiv.firstChild) {
node.parentNode.insertBefore(tempDiv.firstChild, node)
}
node.parentNode.removeChild(node)
} else if(node.nodeType === 1 && node.tagName !== 'SCRIPT' && node.tagName !== 'BR' && node.tagName !== 'STYLE' && !node.classList.contains('highlight')) {
Array.from(node.childNodes).forEach(highlightNode);
}
}
selectors.forEach(selector => {
elements = container.find(selector)
if(!elements || elements.length < 1) return;
elements.nodes.forEach(highlightNode)
})
}
String.prototype.escapeHtml = function() {
try {
return escapeHtml(this)
} catch(e) {
return ''
}
}
function fmtTime(time) {
const mins = String(Math.floor(time / 60)).padStart(2, '0');
const secs = String(Math.floor(time % 60)).padStart(2, '0');
return `${ mins}:${ secs}`;
}
function fastError(message) {
MessageBox(tr("error"), message, [tr("ok")], [Function.noop])
}
function humanFileSize(bytes, si) {
var thresh = si ? 1000 : 1024;
if(Math.abs(bytes) < thresh) {
return bytes + ' B';
}
var units = si
? ['kB','MB','GB','TB','PB','EB','ZB','YB']
: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
var u = -1;
do {
bytes /= thresh;
++u;
} while(Math.abs(bytes) >= thresh && u < units.length - 1);
return bytes.toFixed(1)+' '+units[u];
}
function trim(string) {
var newStr = string.substring(0, 10);
if(newStr.length !== string.length)
newStr += "…";
return newStr;
}
function trimNum(string, num) {
return ovk_proc_strtr(string, num);
}
function ovk_proc_strtr(string, length = 0) {
const newString = string.substring(0, length);
return newString + (string !== newString ? "…" : "");
}
function chunkSubstr(string, size) {
const numChunks = Math.ceil(string.length / size);
const chunks = new Array(numChunks);
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = string.substr(o, size);
}
return chunks;
}
function random_int(min, max) {
return Math.round(Math.random() * (max - min) + min)
}
function makeError(text, color = 'Red', timeout = 10000, uid = 0) {
const rand_id = uid != 0 ? uid : random_int(0, 10000)
if(uid != 0 && u(`.upLeftErrors .upLeftError[data-id='${uid}']`).length > 0) {
return
}
u('.upLeftErrors').append(`
<div class='upLeftError upLeftError${color}' data-id='${rand_id}'>${escapeHtml(text)}</div>
`)
setTimeout(() => {
u(`.upLeftError[data-id='${rand_id}']`).remove()
}, timeout)
}
function array_splice(array, key)
{
let resultArray = [];
for(let i = 0; i < array.length; i++){
if(i != key){
resultArray.push(array[i]);
}
}
return resultArray;
}
function strip_tags(text)
{
return text.replace(/(<([^>]+)>)/gi, "")
}
function find_author(id, profiles, groups)
{
if(id > 0) {
const profile = profiles.find(prof => prof.id == id)
if(profile) {
return profile
}
} else {
const group = groups.find(grou => grou.id == Math.abs(id))
if(group) {
return group
}
}
return null
}
function collect_attachments(target) {
const horizontal_array = []
const horizontal_attachments = target.find(`.post-horizontal > a`)
horizontal_attachments.nodes.forEach(_node => {
horizontal_array.push(`${_node.dataset.type}${_node.dataset.id}`)
})
const vertical_array = []
const vertical_attachments = target.find(`.post-vertical > .vertical-attachment`)
vertical_attachments.nodes.forEach(_node => {
vertical_array.push(`${_node.dataset.type}${_node.dataset.id}`)
})
return horizontal_array.concat(vertical_array)
}

View file

@ -232,49 +232,65 @@ function ovk_is_ssl(): bool
return $GLOBALS["requestIsSSL"];
}
function parseAttachments(string $attachments): array
function parseAttachments($attachments, array $allow_types = ['photo', 'video', 'note', 'audio']): array
{
$attachmentsArr = explode(",", $attachments);
$returnArr = [];
$exploded_attachments = is_array($attachments) ? $attachments : explode(",", $attachments);
$exploded_attachments = array_slice($exploded_attachments, 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"] ?? 10);
$exploded_attachments = array_unique($exploded_attachments);
$imploded_types = implode('|', $allow_types);
$output_attachments = [];
$repositories = [
'photo' => [
'repo' => 'openvk\Web\Models\Repositories\Photos',
'method' => 'getByOwnerAndVID',
],
'video' => [
'repo' => 'openvk\Web\Models\Repositories\Videos',
'method' => 'getByOwnerAndVID',
],
'audio' => [
'repo' => 'openvk\Web\Models\Repositories\Audios',
'method' => 'getByOwnerAndVID',
],
'note' => [
'repo' => 'openvk\Web\Models\Repositories\Notes',
'method' => 'getNoteById',
],
'poll' => [
'repo' => 'openvk\Web\Models\Repositories\Polls',
'method' => 'get',
'onlyId' => true,
],
];
foreach($attachmentsArr as $attachment) {
$attachmentType = NULL;
foreach($exploded_attachments as $attachment_string) {
if(preg_match("/$imploded_types/", $attachment_string, $matches) == 1) {
try {
$attachment_type = $matches[0];
if(!$repositories[$attachment_type])
continue;
if(str_contains($attachment, "photo"))
$attachmentType = "photo";
elseif(str_contains($attachment, "video"))
$attachmentType = "video";
elseif(str_contains($attachment, "note"))
$attachmentType = "note";
elseif(str_contains($attachment, "audio"))
$attachmentType = "audio";
$attachment_ids = str_replace($attachment_type, '', $attachment_string);
if($repositories[$attachment_type]['onlyId']) {
[$attachment_id] = array_map('intval', explode('_', $attachment_ids));
$attachmentIds = str_replace($attachmentType, "", $attachment);
$attachmentOwner = (int) explode("_", $attachmentIds)[0];
$gatoExplotano = explode("_", $attachmentIds);
$attachmentId = (int) end($gatoExplotano);
$repository_class = $repositories[$attachment_type]['repo'];
if(!$repository_class) continue;
$attachment_model = (new $repository_class)->{$repositories[$attachment_type]['method']}($attachment_id);
$output_attachments[] = $attachment_model;
} else {
[$attachment_owner, $attachment_id] = array_map('intval', explode('_', $attachment_ids));
switch($attachmentType) {
case "photo":
$attachmentObj = (new openvk\Web\Models\Repositories\Photos)->getByOwnerAndVID($attachmentOwner, $attachmentId);
$returnArr[] = $attachmentObj;
break;
case "video":
$attachmentObj = (new openvk\Web\Models\Repositories\Videos)->getByOwnerAndVID($attachmentOwner, $attachmentId);
$returnArr[] = $attachmentObj;
break;
case "note":
$attachmentObj = (new openvk\Web\Models\Repositories\Notes)->getNoteById($attachmentOwner, $attachmentId);
$returnArr[] = $attachmentObj;
break;
case "audio":
$attachmentObj = (new openvk\Web\Models\Repositories\Audios)->getByOwnerAndVID($attachmentOwner, $attachmentId);
$returnArr[] = $attachmentObj;
break;
$repository_class = $repositories[$attachment_type]['repo'];
if(!$repository_class) continue;
$attachment_model = (new $repository_class)->{$repositories[$attachment_type]['method']}($attachment_owner, $attachment_id);
$output_attachments[] = $attachment_model;
}
} catch(\Throwable) {continue;}
}
}
return $returnArr;
return $output_attachments;
}
function get_entity_by_id(int $id)

View file

@ -58,6 +58,7 @@ openvk:
enable: false
account: 100
postSizes:
maxAttachments: 10
maxSize: 60000
processingLimit: 3000
emojiProcessingLimit: 1000

View file

@ -0,0 +1,2 @@
ALTER TABLE `videos` ADD `length` SMALLINT(5) UNSIGNED NULL DEFAULT NULL AFTER `name`, ADD INDEX `length` (`length`);
ALTER TABLE `videos` ADD `height` SMALLINT(5) UNSIGNED NULL DEFAULT NULL AFTER `length`, ADD `width` SMALLINT(5) UNSIGNED NULL DEFAULT NULL AFTER `height`;

View file

@ -269,6 +269,7 @@
"others_posts" = "Others posts";
"show_more" = "Show more";
"has_repost" = "Has repost";
/* Friends */
@ -517,6 +518,7 @@
"click_to_go_to_album" = "Click here to go to album.";
"error_uploading_photo" = "Error when uploading photo";
"too_many_pictures" = "No more than 10 pictures";
"too_many_attachments" = "Too many attachments.";
"drag_files_here" = "Drag files here";
"only_images_accepted" = "File \"$1\" is not an image";
@ -848,6 +850,9 @@
"no_videos" = "You don't have uploaded videos.";
"no_videos_results" = "No results.";
"video_file_upload" = "Upload file";
"video_youtube_upload" = "Add from YouTube";
"change_video" = "Change video";
"unknown_video" = "This video is not supported in your version of OpenVK.";
@ -1777,6 +1782,8 @@
"question_confirm" = "This action can't be undone. Do you really wanna do it?";
"confirm_m" = "Confirm";
"action_successfully" = "Success";
"exit_noun" = "Exit";
"exit_confirmation" = "Are you sure want to exit?";
"apply" = "Apply";
/* User Alerts */

View file

@ -248,6 +248,7 @@
"clubs_posts" = "Записи сообщества";
"others_posts" = "Чужие записи";
"show_more" = "Показать больше";
"has_repost" = "Содержит репост";
/* Friends */
@ -500,9 +501,10 @@
"click_to_go_to_album" = "Нажмите, чтобы перейти к альбому.";
"error_uploading_photo" = "Не удалось загрузить фотографию";
"too_many_pictures" = "Не больше 10 фотографий";
"too_many_attachments" = "Слишком много вложений.";
"drag_files_here" = "Перетащите файлы сюда";
"only_images_accepted" = "Файл \"$1\" не является изображением";
"only_images_accepted" = "Файл \"$1\" не является изображением или видео.";
"max_filesize" = "Максимальный размер файла — $1 мегабайт";
"uploading_photos_from_computer" = "Загрузка фотографий с Вашего компьютера";
@ -807,6 +809,9 @@
"no_videos" = "У вас нет видео.";
"no_videos_results" = "Нет результатов.";
"video_file_upload" = "Загрузить файл";
"video_youtube_upload" = "Добавить с YouTube";
/* Audios */
"my" = "Моё";
@ -1481,7 +1486,7 @@
"error_adding_source_regex" = "Ошибка добавления источника: некорректная ссылка.";
"error_adding_source_long" = "Ошибка добавления источника: слишком длинная ссылка.";
"error_adding_source_sus" = "Ошибка добавления источника: подозрительная ссылка.";
"error_adding_source_sus" = "Ошибка добавления источника: гиперссылка заблокирована.";
/* Admin actions */
@ -1669,6 +1674,8 @@
"question_confirm" = "Это действие нельзя отменить. Вы действительно уверены в том что хотите сделать?";
"confirm_m" = "Подтвердить";
"action_successfully" = "Операция успешна";
"exit_noun" = "Выход";
"exit_confirmation" = "Уверены, что хотите выйти?";
"apply" = "Применить";
/* User alerts */
@ -2028,7 +2035,7 @@
/* Fullscreen player */
"hide_player" = "Скрыть";
"hide_player" = "Свернуть";
"close_player" = "Закрыть";
"show_comments" = "Показать информацию";
"close_comments" = "Скрыть информацию";

View file

@ -60,6 +60,7 @@ openvk:
enable: false
account: 100
postSizes:
maxAttachments: 10
maxSize: 60000
processingLimit: 3000
emojiProcessingLimit: 1000