Merge branch 'master' into feature/db-upgrade

This commit is contained in:
celestora 2025-05-19 23:28:30 +03:00 committed by GitHub
commit cd027813a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
85 changed files with 1195 additions and 714 deletions

36
.github/workflows/analyse.yaml vendored Normal file
View file

@ -0,0 +1,36 @@
name: Static analysis
on:
push:
pull_request:
jobs:
phpstan:
name: PHPStan
runs-on: ubuntu-latest
# 'push' runs on inner branches, 'pull_request' will run only on outer PRs
if: >
github.event_name == 'push'
|| (github.event_name == 'pull_request'
&& github.event.pull_request.head.repo.full_name != github.repository)
steps:
- name: Code Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and start Docker container
working-directory: install/automated/docker
run: |
docker build -t openvk ../../.. -f openvk.Dockerfile
- name: Run Docker container with PHPStan
working-directory: install/automated/docker
run: |
docker container run --rm \
-v ./chandler.example.yml:/opt/chandler/chandler.yml \
-v ./openvk.example.yml:/opt/chandler/extensions/available/openvk/openvk.yml \
openvk vendor/bin/phpstan analyse --memory-limit 1G

View file

@ -6,7 +6,7 @@ on:
jobs: jobs:
lint: lint:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
# 'push' runs on inner branches, 'pull_request' will run only on outer PRs # 'push' runs on inner branches, 'pull_request' will run only on outer PRs
if: > if: >

View file

@ -13,6 +13,7 @@ class Wall implements Handler
protected $user; protected $user;
protected $posts; protected $posts;
protected $notes; protected $notes;
protected $videos;
public function __construct(?User $user) public function __construct(?User $user)
{ {

View file

@ -18,7 +18,7 @@ final class Audio extends VKAPIRequestHandler
if (!$audio) { if (!$audio) {
$this->fail(0o404, "Audio not found"); $this->fail(0o404, "Audio not found");
} elseif (!$audio->canBeViewedBy($this->getUser())) { } elseif (!$audio->canBeViewedBy($this->getUser())) {
$this->fail(201, "Access denied to audio(" . $audio->getPrettyId() . ")"); $this->fail(201, "Access denied to audio(" . $audio->getId() . ")");
} }
# рофлан ебало # рофлан ебало
@ -201,7 +201,7 @@ final class Audio extends VKAPIRequestHandler
$this->fail(15, "Access denied"); $this->fail(15, "Access denied");
} }
if ($uploaded_only) { if ($uploaded_only && $owner_id == $this->getUser()->getRealId()) {
return DatabaseConnection::i()->getContext()->table("audios") return DatabaseConnection::i()->getContext()->table("audios")
->where([ ->where([
"deleted" => false, "deleted" => false,
@ -283,7 +283,7 @@ final class Audio extends VKAPIRequestHandler
} }
$dbCtx = DatabaseConnection::i()->getContext(); $dbCtx = DatabaseConnection::i()->getContext();
if ($uploaded_only == 1) { if ($uploaded_only == 1 && $owner_id == $this->getUser()->getRealId()) {
if ($owner_id <= 0) { if ($owner_id <= 0) {
$this->fail(8, "uploaded_only can only be used with owner_id > 0"); $this->fail(8, "uploaded_only can only be used with owner_id > 0");
} }

View file

@ -14,8 +14,7 @@ use openvk\Web\Models\Entities\{Topic, Comment, User, Photo, Video};
final class Board extends VKAPIRequestHandler final class Board extends VKAPIRequestHandler
{ {
# 13/13 public function addTopic(int $group_id, string $title, string $text = "", bool $from_group = true)
public function addTopic(int $group_id, string $title, string $text = "", bool $from_group = true, string $attachments = "")
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
@ -23,15 +22,14 @@ final class Board extends VKAPIRequestHandler
$club = (new ClubsRepo())->get($group_id); $club = (new ClubsRepo())->get($group_id);
if (!$club) { if (!$club) {
$this->fail(403, "Invalid club"); $this->fail(15, "Access denied");
} }
if (!$club->canBeModifiedBy($this->getUser()) && !$club->isEveryoneCanCreateTopics()) { if (!$club->canBeModifiedBy($this->getUser()) && !$club->isEveryoneCanCreateTopics()) {
$this->fail(403, "Access to club denied"); $this->fail(15, "Access denied");
} }
$flags = 0; $flags = 0;
if ($from_group == true && $club->canBeModifiedBy($this->getUser())) { if ($from_group == true && $club->canBeModifiedBy($this->getUser())) {
$flags |= 0b10000000; $flags |= 0b10000000;
} }
@ -53,59 +51,6 @@ final class Board extends VKAPIRequestHandler
$comment->setCreated(time()); $comment->setCreated(time());
$comment->setFlags($flags); $comment->setFlags($flags);
$comment->save(); $comment->save();
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";
} 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()->getId() != $this->getUser()->getId()) {
$this->fail(43, "You do not have access to this photo");
}
$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()->getId() != $this->getUser()->getId()) {
$this->fail(43, "You do not have access to this video");
}
$comment->attach($attacc);
}
}
}
} }
return $topic->getId(); return $topic->getId();
@ -118,7 +63,7 @@ final class Board extends VKAPIRequestHandler
$topic = (new TopicsRepo())->getTopicById($group_id, $topic_id); $topic = (new TopicsRepo())->getTopicById($group_id, $topic_id);
if (!$topic || !$topic->getClub() || !$topic->getClub()->canBeModifiedBy($this->getUser())) { if (!$topic || !$topic->getClub()->canBeModifiedBy($this->getUser())) {
return 0; return 0;
} }
@ -140,21 +85,15 @@ final class Board extends VKAPIRequestHandler
} }
$topic = (new TopicsRepo())->getTopicById($group_id, $topic_id); $topic = (new TopicsRepo())->getTopicById($group_id, $topic_id);
if (!$topic || $topic->isDeleted() || $topic->isClosed()) { if (!$topic || $topic->isDeleted() || $topic->isClosed()) {
$this->fail(100, "Topic is deleted, closed or invalid."); $this->fail(15, "Access denied");
} }
$flags = 0; $flags = 0;
if ($from_group != 0 && !is_null($topic->getClub()) && $topic->getClub()->canBeModifiedBy($this->user)) { if ($from_group != 0 && !is_null($topic->getClub()) && $topic->getClub()->canBeModifiedBy($this->user)) {
$flags |= 0b10000000; $flags |= 0b10000000;
} }
if (strlen($message) > 300) {
$this->fail(20, "Comment is too long.");
}
$comment = new Comment(); $comment = new Comment();
$comment->setOwner($this->getUser()->getId()); $comment->setOwner($this->getUser()->getId());
$comment->setModel(get_class($topic)); $comment->setModel(get_class($topic));
@ -164,74 +103,9 @@ final class Board extends VKAPIRequestHandler
$comment->setFlags($flags); $comment->setFlags($flags);
$comment->save(); $comment->save();
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";
} 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()->getId() != $this->getUser()->getId()) {
$this->fail(43, "You do not have access to this photo");
}
$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()->getId() != $this->getUser()->getId()) {
$this->fail(43, "You do not have access to this video");
}
$comment->attach($attacc);
}
}
}
return $comment->getId(); return $comment->getId();
} }
public function deleteComment(int $comment_id, int $group_id = 0, int $topic_id = 0)
{
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo())->get($comment_id);
if ($comment->isDeleted() || !$comment || !$comment->canBeDeletedBy($this->getUser())) {
$this->fail(403, "Access to comment denied");
}
$comment->delete();
return 1;
}
public function deleteTopic(int $group_id, int $topic_id) public function deleteTopic(int $group_id, int $topic_id)
{ {
$this->requireUser(); $this->requireUser();
@ -248,24 +122,6 @@ final class Board extends VKAPIRequestHandler
return 1; return 1;
} }
public function editComment(int $comment_id, int $group_id = 0, int $topic_id = 0, string $message, string $attachments)
{
/*
$this->requireUser();
$this->willExecuteWriteAction();
$comment = (new CommentsRepo)->get($comment_id);
if($comment->getOwner() != $this->getUser()->getId())
$this->fail(15, "Access to comment denied");
$comment->setContent($message);
$comment->setEdited(time());
$comment->save();
*/
return 1;
}
public function editTopic(int $group_id, int $topic_id, string $title) public function editTopic(int $group_id, int $topic_id, string $title)
{ {
$this->requireUser(); $this->requireUser();

View file

@ -45,7 +45,7 @@ final class Groups extends VKAPIRequestHandler
$clbsCount = $user->getClubCount(); $clbsCount = $user->getClubCount();
} }
$rClubs; $rClubs = [];
$ic = sizeof($clbs); $ic = sizeof($clbs);
if (sizeof($clbs) > $count) { if (sizeof($clbs) > $count) {

View file

@ -52,7 +52,7 @@ final class Newsfeed extends VKAPIRequestHandler
return $response; return $response;
} }
public 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) public 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, int $return_banned = 0)
{ {
$this->requireUser(); $this->requireUser();

View file

@ -185,12 +185,14 @@ final class Notes extends VKAPIRequestHandler
$this->fail(15, "Access denied"); $this->fail(15, "Access denied");
} }
$nodez = (object) [
"count" => 0,
"notes" => [],
];
if (empty($note_ids)) { if (empty($note_ids)) {
$nodez->count = (new NotesRepo())->getUserNotesCount($user);
$notes = array_slice(iterator_to_array((new NotesRepo())->getUserNotes($user, 1, $count + $offset, $sort == 0 ? "ASC" : "DESC")), $offset); $notes = array_slice(iterator_to_array((new NotesRepo())->getUserNotes($user, 1, $count + $offset, $sort == 0 ? "ASC" : "DESC")), $offset);
$nodez = (object) [
"count" => (new NotesRepo())->getUserNotesCount((new UsersRepo())->get($user_id)),
"notes" => [],
];
foreach ($notes as $note) { foreach ($notes as $note) {
if ($note->isDeleted()) { if ($note->isDeleted()) {
@ -210,6 +212,7 @@ final class Notes extends VKAPIRequestHandler
$note = (new NotesRepo())->getNoteById((int) $id[0], (int) $id[1]); $note = (new NotesRepo())->getNoteById((int) $id[0], (int) $id[1]);
if ($note && !$note->isDeleted()) { if ($note && !$note->isDeleted()) {
$nodez->notes[] = $note->toVkApiStruct(); $nodez->notes[] = $note->toVkApiStruct();
$nodez->count++;
} }
} }
} }

View file

@ -9,6 +9,7 @@ use Nette\Utils\ImageException;
use openvk\Web\Models\Entities\{Photo, Album, Comment}; use openvk\Web\Models\Entities\{Photo, Album, Comment};
use openvk\Web\Models\Repositories\Albums; use openvk\Web\Models\Repositories\Albums;
use openvk\Web\Models\Repositories\Photos as PhotosRepo; use openvk\Web\Models\Repositories\Photos as PhotosRepo;
use openvk\Web\Models\Repositories\Videos as VideosRepo;
use openvk\Web\Models\Repositories\Clubs; use openvk\Web\Models\Repositories\Clubs;
use openvk\Web\Models\Repositories\Users as UsersRepo; use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\Comments as CommentsRepo; use openvk\Web\Models\Repositories\Comments as CommentsRepo;
@ -247,7 +248,7 @@ final class Photos extends VKAPIRequestHandler
]; ];
} }
public function createAlbum(string $title, int $group_id = 0, string $description = "", int $privacy = 0) public function createAlbum(string $title, int $group_id = 0, string $description = "")
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
@ -256,7 +257,7 @@ final class Photos extends VKAPIRequestHandler
$club = (new Clubs())->get((int) $group_id); $club = (new Clubs())->get((int) $group_id);
if (!$club || !$club->canBeModifiedBy($this->getUser())) { if (!$club || !$club->canBeModifiedBy($this->getUser())) {
$this->fail(20, "Invalid club"); $this->fail(15, "Access denied");
} }
} }
@ -270,162 +271,133 @@ final class Photos extends VKAPIRequestHandler
return $album->toVkApiStruct($this->getUser()); return $album->toVkApiStruct($this->getUser());
} }
public function editAlbum(int $album_id, int $owner_id, string $title, string $description = "", int $privacy = 0) public function editAlbum(int $album_id, int $owner_id, string $title = null, string $description = null, int $privacy = 0)
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$album = (new Albums())->getAlbumByOwnerAndId($owner_id, $album_id); $album = (new Albums())->getAlbumByOwnerAndId($owner_id, $album_id);
if (!$album || $album->isDeleted()) { if (!$album || $album->isDeleted() || $album->isCreatedBySystem()) {
$this->fail(2, "Invalid album"); $this->fail(114, "Invalid album id");
} }
if (empty($title)) {
$this->fail(25, "Title is empty");
}
if ($album->isCreatedBySystem()) {
$this->fail(40, "You can't change system album");
}
if (!$album->canBeModifiedBy($this->getUser())) { if (!$album->canBeModifiedBy($this->getUser())) {
$this->fail(2, "Access to album denied"); $this->fail(15, "Access denied");
} }
$album->setName($title); if (!is_null($title) && !empty($title) && !ctype_space($title)) {
$album->setDescription($description); $album->setName($title);
}
if (!is_null($description)) {
$album->setDescription($description);
}
$album->save(); try {
$album->save();
} catch (\Throwable $e) {
return 1;
}
return $album->toVkApiStruct($this->getUser()); return 1;
} }
public function getAlbums(int $owner_id, string $album_ids = "", int $offset = 0, int $count = 100, bool $need_system = true, bool $need_covers = true, bool $photo_sizes = false) public function getAlbums(int $owner_id = null, string $album_ids = "", int $offset = 0, int $count = 100, bool $need_system = true, bool $need_covers = true, bool $photo_sizes = false)
{ {
$this->requireUser(); $this->requireUser();
$res = []; $res = [
"count" => 0,
"items" => [],
];
$albums_list = [];
if ($owner_id == null && empty($album_ids)) {
$owner_id = $this->getUser()->getId();
}
if (empty($album_ids)) { if (empty($album_ids)) {
$owner = get_entity_by_id($owner_id);
if (!$owner || !$owner->canBeViewedBy($this->getUser())) {
$this->fail(15, "Access denied");
}
if ($owner_id > 0 && !$owner->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(15, "Access denied");
}
$albums_list = null;
if ($owner_id > 0) { if ($owner_id > 0) {
$user = (new UsersRepo())->get($owner_id); # TODO rewrite to offset
$albums_list = array_slice(iterator_to_array((new Albums())->getUserAlbums($owner, 1, $count + $offset)), $offset);
$res = [ $res["count"] = (new Albums())->getUserAlbumsCount($owner);
"count" => (new Albums())->getUserAlbumsCount($user),
"items" => [],
];
if (!$user || $user->isDeleted()) {
$this->fail(2, "Invalid user");
}
if (!$user->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
}
$albums = array_slice(iterator_to_array((new Albums())->getUserAlbums($user, 1, $count + $offset)), $offset);
foreach ($albums as $album) {
if (!$need_system && $album->isCreatedBySystem()) {
continue;
}
$res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes);
}
} else { } else {
$club = (new Clubs())->get($owner_id * -1); $albums_list = array_slice(iterator_to_array((new Albums())->getClubAlbums($owner, 1, $count + $offset)), $offset);
$res["count"] = (new Albums())->getClubAlbumsCount($owner);
$res = [
"count" => (new Albums())->getClubAlbumsCount($club),
"items" => [],
];
if (!$club) {
$this->fail(2, "Invalid club");
}
$albums = array_slice(iterator_to_array((new Albums())->getClubAlbums($club, 1, $count + $offset)), $offset);
foreach ($albums as $album) {
if (!$need_system && $album->isCreatedBySystem()) {
continue;
}
$res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes);
}
} }
} else { } else {
$albums = explode(',', $album_ids); $album_ids = explode(',', $album_ids);
foreach ($album_ids as $album_id) {
$res = [ $album = (new Albums())->getAlbumByOwnerAndId((int) $owner_id, (int) $album_id);
"count" => sizeof($albums), if (!$album || $album->isDeleted() || !$album->canBeViewedBy($this->getUser())) {
"items" => [], continue;
];
foreach ($albums as $album) {
$id = explode("_", $album);
$album = (new Albums())->getAlbumByOwnerAndId((int) $id[0], (int) $id[1]);
if ($album && !$album->isDeleted()) {
if (!$need_system && $album->isCreatedBySystem()) {
continue;
}
$res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes);
} }
$albums_list[] = $album;
} }
} }
foreach ($albums_list as $album) {
if (!$need_system && $album->isCreatedBySystem()) { # TODO use queries
continue;
}
$res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes);
}
return $res; return $res;
} }
public function getAlbumsCount(int $user_id = 0, int $group_id = 0) public function getAlbumsCount(int $user_id = null, int $group_id = null)
{ {
$this->requireUser(); $this->requireUser();
if ($user_id == 0 && $group_id == 0 || $user_id > 0 && $group_id > 0) { if (is_null($user_id) && is_null($group_id)) {
$this->fail(21, "Select user_id or group_id"); $user_id = $this->getUser()->getId();
} }
if ($user_id > 0) { if (!is_null($user_id)) {
$us = (new UsersRepo())->get($user_id); $__user = (new UsersRepo())->get($user_id);
if (!$us || $us->isDeleted()) { if (!$__user || $__user->isDeleted() || !$__user->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "Invalid user"); $this->fail(15, "Access denied");
} }
if (!$us->getPrivacyPermission('photos.read', $this->getUser())) { return (new Albums())->getUserAlbumsCount($__user);
$this->fail(21, "This user chose to hide his albums."); }
if (!is_null($group_id)) {
$__club = (new Clubs())->get($group_id);
if (!$__club || !$__club->canBeViewedBy($this->getUser())) {
$this->fail(15, "Access denied");
} }
return (new Albums())->getUserAlbumsCount($us); return (new Albums())->getClubAlbumsCount($__club);
} }
if ($group_id > 0) { return 0;
$cl = (new Clubs())->get($group_id);
if (!$cl) {
$this->fail(21, "Invalid club");
}
return (new Albums())->getClubAlbumsCount($cl);
}
} }
public function getById(string $photos, bool $extended = false, bool $photo_sizes = false) public function getById(string $photos, bool $extended = false, bool $photo_sizes = false)
{ {
$this->requireUser(); $this->requireUser();
$phts = explode(",", $photos); $photos_splitted_list = explode(",", $photos);
$res = []; $res = [];
if (sizeof($photos_splitted_list) > 78) {
$this->fail(-78, "Photos count must not exceed limit");
}
foreach ($phts as $phota) { foreach ($photos_splitted_list as $photo_id) {
$ph = explode("_", $phota); $photo_s_id = explode("_", $photo_id);
$photo = (new PhotosRepo())->getByOwnerAndVID((int) $ph[0], (int) $ph[1]); $photo = (new PhotosRepo())->getByOwnerAndVID((int) $photo_s_id[0], (int) $photo_s_id[1]);
if (!$photo || $photo->isDeleted() || !$photo->canBeViewedBy($this->getUser())) {
if (!$photo || $photo->isDeleted()) { continue;
$this->fail(21, "Invalid photo");
}
if (!$photo->canBeViewedBy($this->getUser())) {
$this->fail(15, "Access denied");
} }
$res[] = $photo->toVkApiStruct($photo_sizes, $extended); $res[] = $photo->toVkApiStruct($photo_sizes, $extended);
@ -442,12 +414,7 @@ final class Photos extends VKAPIRequestHandler
if (empty($photo_ids)) { if (empty($photo_ids)) {
$album = (new Albums())->getAlbumByOwnerAndId($owner_id, $album_id); $album = (new Albums())->getAlbumByOwnerAndId($owner_id, $album_id);
if (!$album || $album->isDeleted() || !$album->canBeViewedBy($this->getUser())) {
if (!$album || $album->isDeleted()) {
$this->fail(21, "Invalid album");
}
if (!$album->canBeViewedBy($this->getUser())) {
$this->fail(15, "Access denied"); $this->fail(15, "Access denied");
} }
@ -458,11 +425,15 @@ final class Photos extends VKAPIRequestHandler
if (!$photo || $photo->isDeleted()) { if (!$photo || $photo->isDeleted()) {
continue; continue;
} }
$res["items"][] = $photo->toVkApiStruct($photo_sizes, $extended); $res["items"][] = $photo->toVkApiStruct($photo_sizes, $extended);
} }
} else { } else {
$photos = explode(',', $photo_ids); $photos = array_unique(explode(',', $photo_ids));
if (sizeof($photos) > 78) {
$this->fail(-78, "Photos count must not exceed limit");
}
$res = [ $res = [
"count" => sizeof($photos), "count" => sizeof($photos),
@ -472,10 +443,12 @@ final class Photos extends VKAPIRequestHandler
foreach ($photos as $photo) { foreach ($photos as $photo) {
$id = explode("_", $photo); $id = explode("_", $photo);
$phot = (new PhotosRepo())->getByOwnerAndVID((int) $id[0], (int) $id[1]); $photo_entity = (new PhotosRepo())->getByOwnerAndVID((int) $id[0], (int) $id[1]);
if ($phot && !$phot->isDeleted() && $phot->canBeViewedBy($this->getUser())) { if (!$photo_entity || $photo_entity->isDeleted() || !$photo_entity->canBeViewedBy($this->getUser())) {
$res["items"][] = $phot->toVkApiStruct($photo_sizes, $extended); continue;
} }
$res["items"][] = $photo_entity->toVkApiStruct($photo_sizes, $extended);
} }
} }
@ -489,12 +462,8 @@ final class Photos extends VKAPIRequestHandler
$album = (new Albums())->get($album_id); $album = (new Albums())->get($album_id);
if (!$album || $album->canBeModifiedBy($this->getUser())) { if (!$album || $album->isDeleted() || $album->isCreatedBySystem() || !$album->canBeModifiedBy($this->getUser())) {
$this->fail(21, "Invalid album"); $this->fail(15, "Access denied");
}
if ($album->isDeleted()) {
$this->fail(22, "Album already deleted");
} }
$album->delete(); $album->delete();
@ -509,12 +478,8 @@ final class Photos extends VKAPIRequestHandler
$photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id); $photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id);
if (!$photo) { if (!$photo || $photo->isDeleted() || !$photo->canBeModifiedBy($this->getUser())) {
$this->fail(21, "Invalid photo"); $this->fail(21, "Access denied");
}
if ($photo->isDeleted()) {
$this->fail(21, "Photo is deleted");
} }
if (!empty($caption)) { if (!empty($caption)) {
@ -525,60 +490,48 @@ final class Photos extends VKAPIRequestHandler
return 1; return 1;
} }
public function delete(int $owner_id, int $photo_id, string $photos = "") public function delete(int $owner_id = null, int $photo_id = null, string $photos = null)
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
if (empty($photos)) { if (!$owner_id) {
$owner_id = $this->getUser()->getId();
}
if (is_null($photos)) {
if (is_null($photo_id)) {
return 0;
}
$photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id); $photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id);
if (!$photo || $photo->isDeleted() || !$photo->canBeModifiedBy($this->getUser())) {
if ($this->getUser()->getId() !== $photo->getOwner()->getId()) { return 1;
$this->fail(21, "You can't delete another's photo");
}
if (!$photo) {
$this->fail(21, "Invalid photo");
}
if ($photo->isDeleted()) {
$this->fail(21, "Photo is already deleted");
} }
$photo->delete(); $photo->delete();
} else { } else {
$photozs = explode(',', $photos); $photos_list = array_unique(explode(',', $photos));
if (sizeof($photos_list) > 10) {
$this->fail(-78, "Photos count must not exceed limit");
}
foreach ($photozs as $photo) { foreach ($photos_list as $photo_id) {
$id = explode("_", $photo); $id = explode("_", $photo_id);
$photo = (new PhotosRepo())->getByOwnerAndVID((int) $id[0], (int) $id[1]);
$phot = (new PhotosRepo())->getByOwnerAndVID((int) $id[0], (int) $id[1]); if (!$photo || $photo->isDeleted() || !$photo->canBeModifiedBy($this->getUser())) {
continue;
if ($this->getUser()->getId() !== $phot->getOwner()->getId()) {
$this->fail(21, "You can't delete another's photo");
} }
if (!$phot) { $photo->delete();
$this->fail(21, "Invalid photo");
}
if ($phot->isDeleted()) {
$this->fail(21, "Photo already deleted");
}
$phot->delete();
} }
} }
return 1; return 1;
} }
public function getAllComments(int $owner_id, int $album_id, bool $need_likes = false, int $offset = 0, int $count = 100) # Поскольку комментарии едины, можно использовать метод "wall.deleteComment".
{ /*public function deleteComment(int $comment_id, int $owner_id = 0)
$this->fail(501, "Not implemented");
}
public function deleteComment(int $comment_id, int $owner_id = 0)
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
@ -595,9 +548,9 @@ final class Photos extends VKAPIRequestHandler
$comment->delete(); $comment->delete();
return 1; return 1;
} }*/
public function createComment(int $owner_id, int $photo_id, string $message = "", string $attachments = "", bool $from_group = false) public function createComment(int $owner_id, int $photo_id, string $message = "", bool $from_group = false)
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
@ -608,12 +561,8 @@ final class Photos extends VKAPIRequestHandler
$photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id); $photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id);
if (!$photo || $photo->isDeleted()) { if (!$photo || $photo->isDeleted() || !$photo->canBeViewedBy($this->getUser())) {
$this->fail(180, "Invalid photo"); $this->fail(15, "Access denied");
}
if (!$photo->canBeViewedBy($this->getUser())) {
$this->fail(15, "Access to photo denied");
} }
$comment = new Comment(); $comment = new Comment();
@ -624,55 +573,6 @@ final class Photos extends VKAPIRequestHandler
$comment->setCreated(time()); $comment->setCreated(time());
$comment->save(); $comment->save();
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";
} 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()->getId() != $this->getUser()->getId()) {
$this->fail(43, "You do not have access to this photo");
}
$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()->getId() != $this->getUser()->getId()) {
$this->fail(43, "You do not have access to this video");
}
$comment->attach($attacc);
}
}
}
return $comment->getId(); return $comment->getId();
} }
@ -681,16 +581,12 @@ final class Photos extends VKAPIRequestHandler
$this->requireUser(); $this->requireUser();
if ($owner_id < 0) { if ($owner_id < 0) {
$this->fail(4, "This method doesn't works with clubs"); $this->fail(-413, "Clubs are not supported");
} }
$user = (new UsersRepo())->get($owner_id); $user = (new UsersRepo())->get($owner_id);
if (!$user) { if (!$user || !$user->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(4, "Invalid user"); $this->fail(15, "Access denied");
}
if (!$user->getPrivacyPermission('photos.read', $this->getUser())) {
$this->fail(21, "This user chose to hide his albums.");
} }
$photos = (new PhotosRepo())->getEveryUserPhoto($user, $offset, $count); $photos = (new PhotosRepo())->getEveryUserPhoto($user, $offset, $count);
@ -716,12 +612,8 @@ final class Photos extends VKAPIRequestHandler
$photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id); $photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id);
$comms = array_slice(iterator_to_array($photo->getComments(1, $offset + $count)), $offset); $comms = array_slice(iterator_to_array($photo->getComments(1, $offset + $count)), $offset);
if (!$photo || $photo->isDeleted()) { if (!$photo || $photo->isDeleted() || !$photo->canBeViewedBy($this->getUser())) {
$this->fail(4, "Invalid photo"); $this->fail(15, "Access denied");
}
if (!$photo->canBeViewedBy($this->getUser())) {
$this->fail(21, "Access denied");
} }
$res = [ $res = [

View file

@ -292,14 +292,14 @@ final class Users extends VKAPIRequestHandler
break; break;
case 'blacklisted_by_me': case 'blacklisted_by_me':
if (!$authuser) { if (!$authuser) {
continue; break;
} }
$response[$i]->blacklisted_by_me = (int) $usr->isBlacklistedBy($this->getUser()); $response[$i]->blacklisted_by_me = (int) $usr->isBlacklistedBy($this->getUser());
break; break;
case 'blacklisted': case 'blacklisted':
if (!$authuser) { if (!$authuser) {
continue; break;
} }
$response[$i]->blacklisted = (int) $this->getUser()->isBlacklistedBy($usr); $response[$i]->blacklisted = (int) $this->getUser()->isBlacklistedBy($usr);
@ -383,7 +383,8 @@ final class Users extends VKAPIRequestHandler
string $fav_music = "", string $fav_music = "",
string $fav_films = "", string $fav_films = "",
string $fav_shows = "", string $fav_shows = "",
string $fav_books = "" string $fav_books = "",
string $interests = ""
) { ) {
if ($count > 100) { if ($count > 100) {
$this->fail(100, "One of the parameters specified was missing or invalid: count should be less or equal to 100"); $this->fail(100, "One of the parameters specified was missing or invalid: count should be less or equal to 100");

View file

@ -20,7 +20,7 @@ abstract class VKAPIRequestHandler
$this->platform = $platform; $this->platform = $platform;
} }
protected function fail(int $code, string $message): void protected function fail(int $code, string $message): never
{ {
throw new APIErrorException($message, $code); throw new APIErrorException($message, $code);
} }

View file

@ -53,7 +53,7 @@ final class Wall extends VKAPIRequestHandler
$this->fail(15, "Access denied: wall is disabled"); $this->fail(15, "Access denied: wall is disabled");
} // Don't search for logic here pls } // Don't search for logic here pls
$iteratorv; $iteratorv = null;
switch ($filter) { switch ($filter) {
case "all": case "all":
@ -722,7 +722,7 @@ final class Wall extends VKAPIRequestHandler
$post->attach($attachment); $post->attach($attachment);
} }
if ($wall > 0 && $wall !== $this->user->identity->getId()) { if ($owner_id > 0 && $owner_id !== $this->getUser()->getId()) {
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit(); (new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
} }
@ -734,7 +734,7 @@ final class Wall extends VKAPIRequestHandler
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$postArray; $postArray = [];
if (preg_match('/(wall|video|photo)((?:-?)[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"); $this->fail(100, "One of the parameters specified was missing or invalid: object is incorrect");
} }
@ -1176,8 +1176,9 @@ final class Wall extends VKAPIRequestHandler
if ($from_group == 1 && $wallOwner instanceof Club && $wallOwner->canBeModifiedBy($this->getUser())) { if ($from_group == 1 && $wallOwner instanceof Club && $wallOwner->canBeModifiedBy($this->getUser())) {
$flags |= 0b10000000; $flags |= 0b10000000;
} }
/*if($signed == 1) if ($post->isSigned() && $from_group == 1) {
$flags |= 0b01000000;*/ $flags |= 0b01000000;
}
$post->setFlags($flags); $post->setFlags($flags);
$post->save(true); $post->save(true);

View file

@ -26,7 +26,9 @@ class Album extends MediaCollection
{ {
$coverPhoto = $this->getCoverPhoto(); $coverPhoto = $this->getCoverPhoto();
if (!$coverPhoto) { if (!$coverPhoto) {
return "/assets/packages/static/openvk/img/camera_200.png"; $server_url = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
return $server_url . "/assets/packages/static/openvk/img/camera_200.png";
} }
return $coverPhoto->getURL(); return $coverPhoto->getURL();
@ -92,14 +94,13 @@ class Album extends MediaCollection
{ {
$res = (object) []; $res = (object) [];
$res->id = $this->getPrettyId(); $res->id = $this->getId();
$res->vid = $this->getId();
$res->thumb_id = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getPrettyId() : 0; $res->thumb_id = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getPrettyId() : 0;
$res->owner_id = $this->getOwner()->getId(); $res->owner_id = $this->getOwner()->getRealId();
$res->title = $this->getName(); $res->title = $this->getName();
$res->description = $this->getDescription(); $res->description = $this->getDescription();
$res->created = $this->getCreationTime()->timestamp(); $res->created = $this->getCreationTime()->timestamp();
$res->updated = $this->getEditTime() ? $this->getEditTime()->timestamp() : null; $res->updated = $this->getEditTime() ? $this->getEditTime()->timestamp() : $res->created;
$res->size = $this->size(); $res->size = $this->size();
$res->privacy_comment = 1; $res->privacy_comment = 1;
$res->upload_by_admins_only = 1; $res->upload_by_admins_only = 1;

View file

@ -435,6 +435,10 @@ class Audio extends Media
$obj->keys = $this->getKeys(); $obj->keys = $this->getKeys();
} }
if ($obj->editable) {
$obj->listens = $this->getListens();
}
return $obj; return $obj;
} }

View file

@ -323,7 +323,7 @@ class Club extends RowModel
return sizeof($this->getFollowersQuery()); return sizeof($this->getFollowersQuery());
} }
public function getFollowers(int $page = 1, int $perPage = 6, string $sort = "follower ASC"): \Traversable public function getFollowers(int $page = 1, int $perPage = 6, string $sort = "target DESC"): \Traversable
{ {
$rels = $this->getFollowersQuery($sort)->page($page, $perPage); $rels = $this->getFollowersQuery($sort)->page($page, $perPage);

View file

@ -351,7 +351,7 @@ class Document extends Media
return $this->getRecord()->owner; return $this->getRecord()->owner;
} }
public function toApiPreview(): object public function toApiPreview(): ?object
{ {
$preview = $this->getPreview(); $preview = $this->getPreview();
if ($preview instanceof Photo) { if ($preview instanceof Photo) {
@ -360,6 +360,8 @@ class Document extends Media
"sizes" => array_values($preview->getVkApiSizes()), "sizes" => array_values($preview->getVkApiSizes()),
], ],
]; ];
} else {
return null;
} }
} }

View file

@ -112,7 +112,6 @@ class Gift extends RowModel
public function setImage(string $file): bool public function setImage(string $file): bool
{ {
$imgBlob;
try { try {
$image = Image::fromFile($file); $image = Image::fromFile($file);
$image->resize(512, 512, Image::SHRINK_ONLY); $image->resize(512, 512, Image::SHRINK_ONLY);

View file

@ -33,6 +33,8 @@ class Message extends RowModel
return (new Users())->get($this->getRecord()->sender_id); return (new Users())->get($this->getRecord()->sender_id);
} elseif ($this->getRecord()->sender_type === 'openvk\Web\Models\Entities\Club') { } elseif ($this->getRecord()->sender_type === 'openvk\Web\Models\Entities\Club') {
return (new Clubs())->get($this->getRecord()->sender_id); return (new Clubs())->get($this->getRecord()->sender_id);
} else {
return null;
} }
} }
@ -49,6 +51,8 @@ class Message extends RowModel
return (new Users())->get($this->getRecord()->recipient_id); return (new Users())->get($this->getRecord()->recipient_id);
} elseif ($this->getRecord()->recipient_type === 'openvk\Web\Models\Entities\Club') { } elseif ($this->getRecord()->recipient_type === 'openvk\Web\Models\Entities\Club') {
return (new Clubs())->get($this->getRecord()->recipient_id); return (new Clubs())->get($this->getRecord()->recipient_id);
} else {
return null;
} }
} }
@ -147,7 +151,7 @@ class Message extends RowModel
"id" => $author->getId(), "id" => $author->getId(),
"link" => $_SERVER['REQUEST_SCHEME'] . "://" . $_SERVER['HTTP_HOST'] . $author->getURL(), "link" => $_SERVER['REQUEST_SCHEME'] . "://" . $_SERVER['HTTP_HOST'] . $author->getURL(),
"avatar" => $author->getAvatarUrl(), "avatar" => $author->getAvatarUrl(),
"name" => $author->getFirstName() . $unreadmsg, "name" => $author->getFirstName(),
], ],
"timing" => [ "timing" => [
"sent" => (string) $this->getSendTimeHumanized(), "sent" => (string) $this->getSendTimeHumanized(),

View file

@ -64,13 +64,15 @@ class Notification
return $this->recipient; return $this->recipient;
} }
public function getModel(int $index): RowModel public function getModel(int $index): ?RowModel
{ {
switch ($index) { switch ($index) {
case 0: case 0:
return $this->originModel; return $this->originModel;
case 1: case 1:
return $this->targetModel; return $this->targetModel;
default:
return null;
} }
} }
@ -166,14 +168,13 @@ class Notification
case 19: case 19:
$info["type"] = "comment_video"; $info["type"] = "comment_video";
$info["parent"] = $this->getModel(0)->toNotifApiStruct(); $info["parent"] = $this->getModel(0)->toNotifApiStruct();
$info["feedback"] = null; # айди коммента не сохраняется в бд( ну пиздец блять $info["feedback"] = null; # comment id is not saving at db
break; break;
case 13: case 13:
$info["type"] = "comment_photo"; $info["type"] = "comment_photo";
$info["parent"] = $this->getModel(0)->toNotifApiStruct(); $info["parent"] = $this->getModel(0)->toNotifApiStruct();
$info["feedback"] = null; $info["feedback"] = null;
break; break;
# unstandart (vk forgor about notes)
case 10: case 10:
$info["type"] = "comment_note"; $info["type"] = "comment_note";
$info["parent"] = $this->getModel(0)->toVkApiStruct(); $info["parent"] = $this->getModel(0)->toVkApiStruct();

View file

@ -349,7 +349,6 @@ class Photo extends Media
$res->width = $this->getDimensions()[0]; $res->width = $this->getDimensions()[0];
$res->height = $this->getDimensions()[1]; $res->height = $this->getDimensions()[1];
$res->date = $res->created = $this->getPublicationTime()->timestamp(); $res->date = $res->created = $this->getPublicationTime()->timestamp();
if ($photo_sizes) { if ($photo_sizes) {
$res->sizes = array_values($this->getVkApiSizes()); $res->sizes = array_values($this->getVkApiSizes());
$res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule"); $res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule");
@ -359,14 +358,19 @@ class Photo extends Media
$res->src_xxbig = $res->photo_1280 = $this->getURLBySizeId("larger"); $res->src_xxbig = $res->photo_1280 = $this->getURLBySizeId("larger");
$res->src_xxxbig = $res->photo_2560 = $this->getURLBySizeId("original"); $res->src_xxxbig = $res->photo_2560 = $this->getURLBySizeId("original");
$res->src_original = $res->url = $this->getURLBySizeId("UPLOADED_MAXRES"); $res->src_original = $res->url = $this->getURLBySizeId("UPLOADED_MAXRES");
$res->orig_photo = [
"height" => $res->height,
"width" => $res->width,
"type" => "base",
"url" => $this->getURL(),
];
} }
if ($extended) { if ($extended) {
$res->likes = $this->getLikesCount(); # их нету но пусть будут $res->likes = $this->getLikesCount();
$res->comments = $this->getCommentsCount(); $res->comments = $this->getCommentsCount();
$res->tags = 0;
$res->can_comment = 1; $res->can_comment = 1;
$res->can_repost = 0; $res->can_repost = 1;
} }
return $res; return $res;
@ -385,9 +389,9 @@ class Photo extends Media
} }
} }
public static function fastMake(int $owner, string $description = "", array $file, ?Album $album = null, bool $anon = false): Photo public static function fastMake(int $owner, string $description, array $file, ?Album $album = null, bool $anon = false): Photo
{ {
$photo = new static(); $photo = new Photo();
$photo->setOwner($owner); $photo->setOwner($owner);
$photo->setDescription(iconv_substr($description, 0, 36) . "..."); $photo->setDescription(iconv_substr($description, 0, 36) . "...");
$photo->setAnonymous($anon); $photo->setAnonymous($anon);

View file

@ -45,11 +45,7 @@ class Report extends RowModel
public function isDeleted(): bool public function isDeleted(): bool
{ {
if ($this->getRecord()->deleted === 0) { return $this->getRecord()->deleted === 1;
return false;
} elseif ($this->getRecord()->deleted === 1) {
return true;
}
} }
public function authorId(): int public function authorId(): int

View file

@ -41,11 +41,11 @@ trait TRichText
return preg_replace_callback( return preg_replace_callback(
"%(([A-z]++):\/\/(\S*?\.\S*?))([\s)\[\]{},\"\'<]|\.\s|$)%", "%(([A-z]++):\/\/(\S*?\.\S*?))([\s)\[\]{},\"\'<]|\.\s|$)%",
(function (array $matches): string { (function (array $matches): string {
$href = str_replace("#", "&num;", $matches[1]); $href = rawurlencode($matches[1]);
$href = rawurlencode(str_replace(";", "&#59;", $href)); $href = str_replace("%26amp%3B", "%26", $href);
$link = str_replace("#", "&num;", $matches[3]); $link = $matches[3];
# this string breaks ampersands # this string breaks ampersands
$link = str_replace(";", "&#59;", $link); # $link = str_replace(";", "&#59;", $link);
$rel = $this->isAd() ? "sponsored" : "ugc"; $rel = $this->isAd() ? "sponsored" : "ugc";
/*$server_domain = str_replace(':' . $_SERVER['SERVER_PORT'], '', $_SERVER['HTTP_HOST']); /*$server_domain = str_replace(':' . $_SERVER['SERVER_PORT'], '', $_SERVER['HTTP_HOST']);

View file

@ -306,10 +306,10 @@ class User extends RowModel
$content_type = $matches[1]; $content_type = $matches[1];
$content_id = (int) $matches[2]; $content_id = (int) $matches[2];
if (in_array($content_type, ["noSpamTemplate", "user"])) { if (in_array($content_type, ["noSpamTemplate", "user"])) {
$reason = "Подозрительная активность"; $reason = $this->getRawBanReason();
} else { } else {
if ($for !== "banned") { if ($for !== "banned") {
$reason = "Подозрительная активность"; $reason = $this->getRawBanReason();
} else { } else {
$reason = [$this->getTextForContentBan($content_type), $content_type]; $reason = [$this->getTextForContentBan($content_type), $content_type];
switch ($content_type) { switch ($content_type) {
@ -524,7 +524,10 @@ class User extends RowModel
public function getAge(): ?int public function getAge(): ?int
{ {
return (int) floor((time() - $this->getBirthday()->timestamp()) / YEAR); $birthday = new \DateTime();
$birthday->setTimestamp($this->getBirthday()->timestamp());
$today = new \DateTime();
return (int) $today->diff($birthday)->y;
} }
public function get2faSecret(): ?string public function get2faSecret(): ?string
@ -558,6 +561,7 @@ class User extends RowModel
"poster", "poster",
"apps", "apps",
"docs", "docs",
"fave",
], ],
])->get($id); ])->get($id);
} }
@ -932,6 +936,7 @@ class User extends RowModel
case 1: case 1:
return tr('female'); return tr('female');
case 2: case 2:
default:
return tr('neutral'); return tr('neutral');
} }
} }
@ -1195,6 +1200,7 @@ class User extends RowModel
"poster", "poster",
"apps", "apps",
"docs", "docs",
"fave",
], ],
])->set($id, (int) $status)->toInteger(); ])->set($id, (int) $status)->toInteger();
@ -1559,14 +1565,14 @@ class User extends RowModel
break; break;
case "blacklisted_by_me": case "blacklisted_by_me":
if (!$user) { if (!$user) {
continue; break;
} }
$res->blacklisted_by_me = (int) $this->isBlacklistedBy($user); $res->blacklisted_by_me = (int) $this->isBlacklistedBy($user);
break; break;
case "blacklisted": case "blacklisted":
if (!$user) { if (!$user) {
continue; break;
} }
$res->blacklisted = (int) $user->isBlacklistedBy($this); $res->blacklisted = (int) $user->isBlacklistedBy($this);

View file

@ -9,12 +9,13 @@ use openvk\Web\Util\Shell\Exceptions\{ShellUnavailableException, UnknownCommandE
use openvk\Web\Models\VideoDrivers\VideoDriver; use openvk\Web\Models\VideoDrivers\VideoDriver;
use Nette\InvalidStateException as ISE; use Nette\InvalidStateException as ISE;
define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<", false); define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<");
class Video extends Media class Video extends Media
{ {
public const TYPE_DIRECT = 0; public const TYPE_DIRECT = 0;
public const TYPE_EMBED = 1; public const TYPE_EMBED = 1;
public const TYPE_UNKNOWN = -1;
protected $tableName = "videos"; protected $tableName = "videos";
protected $fileExtension = "mp4"; protected $fileExtension = "mp4";
@ -108,6 +109,7 @@ class Video extends Media
} elseif (!is_null($this->getRecord()->link)) { } elseif (!is_null($this->getRecord()->link)) {
return Video::TYPE_EMBED; return Video::TYPE_EMBED;
} }
return Video::TYPE_UNKNOWN;
} }
public function getVideoDriver(): ?VideoDriver public function getVideoDriver(): ?VideoDriver
@ -238,7 +240,7 @@ class Video extends Media
$this->save(); $this->save();
} }
public static function fastMake(int $owner, string $name = "Unnamed Video.ogv", string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video public static function fastMake(int $owner, string $name, string $description, array $file, bool $unlisted = true, bool $anon = false): Video
{ {
if (OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) { if (OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) {
exit(VIDEOS_FRIENDLY_ERROR); exit(VIDEOS_FRIENDLY_ERROR);
@ -269,7 +271,7 @@ class Video extends Media
return false; return false;
} }
$streams = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute($error); $streams = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute();
$durations = []; $durations = [];
preg_match_all('%duration=([0-9\.]++)%', $streams, $durations); preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);

View file

@ -7,6 +7,7 @@ namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection as DB; use Chandler\Database\DatabaseConnection as DB;
use Nette\Database\Table\{ActiveRow, Selection}; use Nette\Database\Table\{ActiveRow, Selection};
use openvk\Web\Models\Entities\BannedLink; use openvk\Web\Models\Entities\BannedLink;
use function Symfony\Component\Translation\t; use function Symfony\Component\Translation\t;
class BannedLinks class BannedLinks
@ -62,10 +63,11 @@ class BannedLinks
public function genEntries($links, $uri): \Traversable public function genEntries($links, $uri): \Traversable
{ {
foreach ($links as $link) { foreach ($links as $link) {
if (preg_match($link->getRegexpRule(), $uri)) if (preg_match($link->getRegexpRule(), $uri)) {
yield $link->getId(); yield $link->getId();
else if ($this->isDomainBanned($link->getDomain())) } elseif ($this->isDomainBanned($link->getDomain())) {
yield $link->getId(); yield $link->getId();
}
} }
} }

View file

@ -13,6 +13,8 @@ class ChandlerGroups
{ {
private $context; private $context;
private $groups; private $groups;
private $members;
private $perms;
public function __construct() public function __construct()
{ {

View file

@ -91,7 +91,7 @@ class Clubs
return (clone $this->clubs)->count('*'); return (clone $this->clubs)->count('*');
} }
public function getPopularClubs(): \Traversable public function getPopularClubs(): ?\Traversable
{ {
// TODO rewrite // TODO rewrite
@ -106,6 +106,8 @@ class Clubs
"subscriptions" => $entry["subscriptions"], "subscriptions" => $entry["subscriptions"],
]; ];
*/ */
trigger_error("Clubs::getPopularClubs() is currently commented out and returns null", E_USER_WARNING);
return null;
} }
public function getWriteableClubs(int $id): \Traversable public function getWriteableClubs(int $id): \Traversable

View file

@ -1,53 +0,0 @@
<?php
declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\{Messages as M, User};
use Chandler\Database\DatabaseConnection as DB;
use Nette\Database\Table\ActiveRow;
class Conversations
{
private $context;
private $convos;
public function __construct()
{
$this->context = DB::i()->getContext();
$this->convos = $this->context->table("conversations");
}
private function toConversation(?ActiveRow $ar): ?M\AbstractConversation
{
if (is_null($ar)) {
return null;
} elseif ($ar->is_pm) {
return new M\PrivateConversation($ar);
} else {
return new M\Conversation($ar);
}
}
public function get(int $id): ?M\AbstractConversation
{
return $this->toConversation($this->convos->get($id));
}
public function getConversationsByUser(User $user, int $page = 1, ?int $perPage = null): \Traversable
{
$rels = $this->context->table("conversation_members")->where([
"deleted" => false,
"user" => $user->getId(),
])->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
foreach ($rels as $rel) {
yield $this->get($rel->conversation);
}
}
public function getPrivateConversation(User $user, int $peer): M\PrivateConversation
{
;
}
}

View file

@ -152,7 +152,7 @@ class Documents
switch ($paramName) { switch ($paramName) {
case "type": case "type":
if ($paramValue < 1 || $paramValue > 8) { if ($paramValue < 1 || $paramValue > 8) {
continue; break;
} }
$result->where("type", $paramValue); $result->where("type", $paramValue);
break; break;

View file

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\User;
use Nette\Database\Table\ActiveRow;
class Faves
{
private $context;
private $likes;
public function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->likes = $this->context->table("likes");
}
private function fetchLikes(User $user, string $class = 'Post')
{
$fetch = $this->likes->where([
"model" => "openvk\\Web\\Models\\Entities\\" . $class,
"origin" => $user->getRealId(),
]);
return $fetch;
}
public function fetchLikesSection(User $user, string $class = 'Post', int $page = 1, ?int $perPage = null): \Traversable
{
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
$fetch = $this->fetchLikes($user, $class)->page($page, $perPage)->order("index DESC");
foreach ($fetch as $like) {
$className = "openvk\\Web\\Models\\Repositories\\" . $class . "s";
$repo = new $className();
if (!$repo) {
continue;
}
$entity = $repo->get($like->target);
yield $entity;
}
}
public function fetchLikesSectionCount(User $user, string $class = 'Post')
{
return $this->fetchLikes($user, $class)->count();
}
}

View file

@ -51,6 +51,7 @@ class Photos
"deleted" => 0, "deleted" => 0,
"system" => 0, "system" => 0,
"private" => 0, "private" => 0,
"anonymous" => 0,
])->order("id DESC"); ])->order("id DESC");
foreach ($photos->limit($limit, $offset) as $photo) { foreach ($photos->limit($limit, $offset) as $photo) {
@ -65,6 +66,7 @@ class Photos
"deleted" => 0, "deleted" => 0,
"system" => 0, "system" => 0,
"private" => 0, "private" => 0,
"anonymous" => 0,
]); ]);
return sizeof($photos); return sizeof($photos);

View file

@ -11,7 +11,7 @@ use openvk\Web\Models\Entities\{User, SupportAgent};
class SupportAgents class SupportAgents
{ {
private $context; private $context;
private $tickets; private $agents;
public function __construct() public function __construct()
{ {

View file

@ -57,7 +57,7 @@ class Tickets
{ {
$requests = $this->tickets->where(["id" => $requestId])->fetch(); $requests = $this->tickets->where(["id" => $requestId])->fetch();
if (!is_null($requests)) { if (!is_null($requests)) {
return new Req($requests); return new Ticket($requests);
} else { } else {
return null; return null;
} }

View file

@ -157,7 +157,7 @@ class Users
{ {
return (object) [ return (object) [
"all" => (clone $this->users)->count('*'), "all" => (clone $this->users)->count('*'),
"active" => (clone $this->users)->where("online > 0")->count('*'), "active" => (clone $this->users)->where("online >= ?", time() - MONTH)->count('*'),
"online" => (clone $this->users)->where("online >= ?", time() - 900)->count('*'), "online" => (clone $this->users)->where("online >= ?", time() - 900)->count('*'),
]; ];
} }

View file

@ -255,7 +255,7 @@ final class AdminPresenter extends OpenVKPresenter
{ {
$this->warnIfNoCommerce(); $this->warnIfNoCommerce();
$cat; $cat = null;
$gen = false; $gen = false;
if ($id !== 0) { if ($id !== 0) {
$cat = $this->gifts->getCat($id); $cat = $this->gifts->getCat($id);

View file

@ -78,6 +78,10 @@ final class AudioPresenter extends OpenVKPresenter
} elseif ($mode === "new") { } elseif ($mode === "new") {
$audios = $this->audios->getNew(); $audios = $this->audios->getNew();
$audiosCount = $audios->size(); $audiosCount = $audios->size();
} elseif ($mode === "uploaded") {
$stream = $this->audios->getByUploader($this->user->identity);
$audios = $stream->page($page, 10);
$audiosCount = $stream->size();
} elseif ($mode === "playlists") { } elseif ($mode === "playlists") {
if ($owner < 0) { if ($owner < 0) {
$entity = (new Clubs())->get(abs($owner)); $entity = (new Clubs())->get(abs($owner));
@ -130,6 +134,11 @@ final class AudioPresenter extends OpenVKPresenter
} }
} }
public function renderUploaded()
{
$this->renderList(null, "uploaded");
}
public function renderEmbed(int $owner, int $id): void public function renderEmbed(int $owner, int $id): void
{ {
$audio = $this->audios->getByOwnerAndVID($owner, $id); $audio = $this->audios->getByOwnerAndVID($owner, $id);
@ -499,7 +508,7 @@ final class AudioPresenter extends OpenVKPresenter
$title = $this->postParam("title"); $title = $this->postParam("title");
$description = $this->postParam("description"); $description = $this->postParam("description");
$is_unlisted = (int) $this->postParam('is_unlisted'); $is_unlisted = (int) $this->postParam('is_unlisted');
$new_audios = !empty($this->postParam("audios")) ? explode(",", rtrim($this->postParam("audios"), ",")) : []; $new_audios = !empty($this->postParam("audios")) ? explode(",", rtrim($this->postParam("audios"), ",")) : null;
if (empty($title) || iconv_strlen($title) < 1) { if (empty($title) || iconv_strlen($title) < 1) {
$this->flashFail("err", tr("error"), tr("set_playlist_name")); $this->flashFail("err", tr("error"), tr("set_playlist_name"));
@ -529,13 +538,15 @@ final class AudioPresenter extends OpenVKPresenter
"collection" => $playlist->getId(), "collection" => $playlist->getId(),
])->delete(); ])->delete();
foreach ($new_audios as $new_audio) { if (!is_null($new_audios)) {
$audio = (new Audios())->get((int) $new_audio); foreach ($new_audios as $new_audio) {
if (!$audio || $audio->isDeleted()) { $audio = (new Audios())->get((int) $new_audio);
continue; if (!$audio || $audio->isDeleted()) {
} continue;
}
$playlist->add($audio); $playlist->add($audio);
}
} }
if ($is_ajax) { if ($is_ajax) {
@ -841,6 +852,10 @@ final class AudioPresenter extends OpenVKPresenter
$audios = [$found_audio]; $audios = [$found_audio];
$audiosCount = 1; $audiosCount = 1;
break; break;
case "uploaded":
$stream = $this->audios->getByUploader($this->user->identity);
$audios = $stream->page($page, $perPage);
$audiosCount = $stream->size();
} }
$pagesCount = ceil($audiosCount / $perPage); $pagesCount = ceil($audiosCount / $perPage);

View file

@ -11,14 +11,16 @@ final class AwayPresenter extends OpenVKPresenter
{ {
public function renderAway(): void public 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 (OPENVK_ROOT_CONF["openvk"]["preferences"]["susLinks"]["warnings"]) {
if (sizeof($checkBanEntries) > 0) if (sizeof($checkBanEntries) > 0) {
$this->pass("openvk!Away->view", $checkBanEntries[0]); $this->pass("openvk!Away->view", $checkBanEntries[0]);
}
}
header("HTTP/1.0 302 Found"); header("HTTP/1.0 302 Found");
header("X-Robots-Tag: noindex, nofollow, noarchive"); header("X-Robots-Tag: noindex, nofollow, noarchive");
header("Location: " . $this->queryParam("to")); header("Location: " . rawurldecode($this->queryParam("to")));
exit; exit;
} }

View file

@ -34,7 +34,8 @@ final class BlobPresenter extends OpenVKPresenter
} }
if (isset($_SERVER["HTTP_IF_NONE_MATCH"])) { if (isset($_SERVER["HTTP_IF_NONE_MATCH"])) {
exit(header("HTTP/1.1 304 Not Modified")); header("HTTP/1.1 304 Not Modified");
exit();
} }
header("Content-Type: " . mime_content_type($path)); header("Content-Type: " . mime_content_type($path));

View file

@ -7,6 +7,7 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post}; use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post};
use openvk\Web\Models\Entities\Notifications\CommentNotification; use openvk\Web\Models\Entities\Notifications\CommentNotification;
use openvk\Web\Models\Repositories\{Comments, Clubs, Videos, Photos, Audios}; use openvk\Web\Models\Repositories\{Comments, Clubs, Videos, Photos, Audios};
use Nette\InvalidStateException as ISE;
final class CommentPresenter extends OpenVKPresenter final class CommentPresenter extends OpenVKPresenter
{ {

View file

@ -8,17 +8,17 @@ use openvk\Web\Models\Repositories\ContentSearchRepository;
final class ContentSearchPresenter extends OpenVKPresenter final class ContentSearchPresenter extends OpenVKPresenter
{ {
private $repo; protected $repo;
public function __construct(ContentSearchRepository $repo) public function __construct(ContentSearchRepository $repository)
{ {
$this->repo = $repo; $this->repo = $repository;
} }
public function renderIndex(): void public function renderIndex(): void
{ {
if ($_SERVER["REQUEST_METHOD"] === "POST") { if ($_SERVER["REQUEST_METHOD"] === "POST") {
$this->template->results = $repo->find([ $this->template->results = $this->repo->find([
"query" => $this->postParam("query"), "query" => $this->postParam("query"),
]); ]);
} }

View file

@ -9,6 +9,7 @@ use Nette\InvalidStateException;
use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification; use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification;
use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics, Audios, Posts, Documents}; use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics, Audios, Posts, Documents};
use Chandler\Security\Authenticator; use Chandler\Security\Authenticator;
use Nette\InvalidStateException as ISE;
final class GroupPresenter extends OpenVKPresenter final class GroupPresenter extends OpenVKPresenter
{ {
@ -288,7 +289,6 @@ final class GroupPresenter extends OpenVKPresenter
(new Albums())->getClubAvatarAlbum($club)->addPhoto($photo); (new Albums())->getClubAvatarAlbum($club)->addPhoto($photo);
} catch (ISE $ex) { } catch (ISE $ex) {
$name = $album->getName();
$this->flashFail("err", tr("error"), tr("error_when_uploading_photo")); $this->flashFail("err", tr("error"), tr("error_when_uploading_photo"));
} }
} }
@ -373,6 +373,7 @@ final class GroupPresenter extends OpenVKPresenter
public function renderDeleteAvatar(int $id) public function renderDeleteAvatar(int $id)
{ {
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->assertNoCSRF();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$club = $this->clubs->get($id); $club = $this->clubs->get($id);

View file

@ -165,7 +165,7 @@ final class InternalAPIPresenter extends OpenVKPresenter
if ($type == 'post') { if ($type == 'post') {
$this->template->_template = 'components/post.xml'; $this->template->_template = 'components/post.xml';
$this->template->post = $post; $this->template->post = $post;
$this->template->commentSection = false; $this->template->commentSection = true;
} elseif ($type == 'comment') { } elseif ($type == 'comment') {
$this->template->_template = 'components/comment.xml'; $this->template->_template = 'components/comment.xml';
$this->template->comment = $post; $this->template->comment = $post;

View file

@ -93,9 +93,11 @@ final class MessengerPresenter extends OpenVKPresenter
header("Content-Type: application/json"); header("Content-Type: application/json");
if ($this->queryParam("act") !== "a_check") { if ($this->queryParam("act") !== "a_check") {
exit(header("HTTP/1.1 400 Bad Request")); header("HTTP/1.1 400 Bad Request");
exit();
} elseif (!$this->queryParam("key")) { } elseif (!$this->queryParam("key")) {
exit(header("HTTP/1.1 403 Forbidden")); header("HTTP/1.1 403 Forbidden");
exit();
} }
$key = $this->queryParam("key"); $key = $this->queryParam("key");
@ -158,7 +160,8 @@ final class MessengerPresenter extends OpenVKPresenter
$sel = $this->getCorrespondent($sel); $sel = $this->getCorrespondent($sel);
if ($sel->getId() !== $this->user->id && !$sel->getPrivacyPermission('messages.write', $this->user->identity)) { if ($sel->getId() !== $this->user->id && !$sel->getPrivacyPermission('messages.write', $this->user->identity)) {
exit(header("HTTP/1.1 403 Forbidden")); header("HTTP/1.1 403 Forbidden");
exit();
} }
$cor = new Correspondence($this->user->identity, $sel); $cor = new Correspondence($this->user->identity, $sel);

View file

@ -151,77 +151,6 @@ final class NoSpamPresenter extends OpenVKPresenter
$this->assertNoCSRF(); $this->assertNoCSRF();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
function searchByAdditionalParams(?string $table = null, ?string $where = null, ?string $ip = null, ?string $useragent = null, ?int $ts = null, ?int $te = null, $user = null)
{
$db = DatabaseConnection::i()->getContext();
if ($table && ($ip || $useragent || $ts || $te || $user)) {
$conditions = [];
if ($ip) {
$conditions[] = "`ip` REGEXP '$ip'";
}
if ($useragent) {
$conditions[] = "`useragent` REGEXP '$useragent'";
}
if ($ts) {
$conditions[] = "`ts` < $ts";
}
if ($te) {
$conditions[] = "`ts` > $te";
}
if ($user) {
$users = new Users();
$_user = $users->getByChandlerUser((new ChandlerUsers())->getById($user))
?? $users->get((int) $user)
?? $users->getByAddress($user)
?? null;
if ($_user) {
$conditions[] = "`user` = '" . $_user->getChandlerGUID() . "'";
}
}
$whereStart = "WHERE `object_table` = '$table'";
if ($table === "profiles") {
$whereStart .= "AND `type` = 0";
}
$conditions = count($conditions) > 0 ? "AND (" . implode(" AND ", $conditions) . ")" : "";
$response = [];
if ($conditions) {
$logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`");
foreach ($logs as $log) {
$log = (new Logs())->get($log->id);
$object = $log->getObject()->unwrap();
if (!$object) {
continue;
}
if ($where) {
if (str_starts_with($where, " AND")) {
$where = substr_replace($where, "", 0, strlen(" AND"));
}
$a = $db->query("SELECT * FROM `$table` WHERE $where")->fetchAll();
foreach ($a as $o) {
if ($object->id == $o["id"]) {
$response[] = $object;
}
}
} else {
$response[] = $object;
}
}
}
return $response;
}
}
try { try {
$response = []; $response = [];
$processed = 0; $processed = 0;
@ -290,7 +219,7 @@ final class NoSpamPresenter extends OpenVKPresenter
} }
if ($ip || $useragent || $ts || $te || $user) { if ($ip || $useragent || $ts || $te || $user) {
$rows = searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user); $rows = $this->searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user);
} else { } else {
if (!$where) { if (!$where) {
$rows = []; $rows = [];
@ -408,4 +337,75 @@ final class NoSpamPresenter extends OpenVKPresenter
$this->returnJson(["success" => false, "error" => $e->getMessage()]); $this->returnJson(["success" => false, "error" => $e->getMessage()]);
} }
} }
private function searchByAdditionalParams(?string $table = null, ?string $where = null, ?string $ip = null, ?string $useragent = null, ?int $ts = null, ?int $te = null, $user = null)
{
$db = DatabaseConnection::i()->getContext();
if ($table && ($ip || $useragent || $ts || $te || $user)) {
$conditions = [];
if ($ip) {
$conditions[] = "`ip` REGEXP '$ip'";
}
if ($useragent) {
$conditions[] = "`useragent` REGEXP '$useragent'";
}
if ($ts) {
$conditions[] = "`ts` < $ts";
}
if ($te) {
$conditions[] = "`ts` > $te";
}
if ($user) {
$users = new Users();
$_user = $users->getByChandlerUser((new ChandlerUsers())->getById($user))
?? $users->get((int) $user)
?? $users->getByAddress($user)
?? null;
if ($_user) {
$conditions[] = "`user` = '" . $_user->getChandlerGUID() . "'";
}
}
$whereStart = "WHERE `object_table` = '$table'";
if ($table === "profiles") {
$whereStart .= "AND `type` = 0";
}
$conditions = count($conditions) > 0 ? "AND (" . implode(" AND ", $conditions) . ")" : "";
$response = [];
if ($conditions) {
$logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`");
foreach ($logs as $log) {
$log = (new Logs())->get($log->id);
$object = $log->getObject()->unwrap();
if (!$object) {
continue;
}
if ($where) {
if (str_starts_with($where, " AND")) {
$where = substr_replace($where, "", 0, strlen(" AND"));
}
$a = $db->query("SELECT * FROM `$table` WHERE $where")->fetchAll();
foreach ($a as $o) {
if ($object->id == $o["id"]) {
$response[] = $object;
}
}
} else {
$response[] = $object;
}
}
}
return $response;
}
}
} }

View file

@ -9,6 +9,7 @@ use Chandler\MVC\SimplePresenter;
use Chandler\Session\Session; use Chandler\Session\Session;
use Chandler\Security\Authenticator; use Chandler\Security\Authenticator;
use Latte\Engine as TemplatingEngine; use Latte\Engine as TemplatingEngine;
use Nette\InvalidStateException as ISE;
use openvk\Web\Models\Entities\IP; use openvk\Web\Models\Entities\IP;
use openvk\Web\Themes\Themepacks; use openvk\Web\Themes\Themepacks;
use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets, Reports, CurrentUser, Posts}; use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets, Reports, CurrentUser, Posts};
@ -74,7 +75,6 @@ abstract class OpenVKPresenter extends SimplePresenter
protected function logInUserWithToken(): void protected function logInUserWithToken(): void
{ {
$header = $_SERVER["HTTP_AUTHORIZATION"] ?? ""; $header = $_SERVER["HTTP_AUTHORIZATION"] ?? "";
$token;
preg_match("%Bearer (.*)$%", $header, $matches); preg_match("%Bearer (.*)$%", $header, $matches);
$token = $matches[1] ?? ""; $token = $matches[1] ?? "";
@ -130,7 +130,7 @@ abstract class OpenVKPresenter extends SimplePresenter
} }
if ($throw) { if ($throw) {
throw new SecurityPolicyViolationException("Permission error"); throw new ISE("Permission error");
} else { } else {
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment")); $this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
} }
@ -371,7 +371,11 @@ abstract class OpenVKPresenter extends SimplePresenter
$whichbrowser->isEngine('NetFront') || // PSP and other japanese portable systems $whichbrowser->isEngine('NetFront') || // PSP and other japanese portable systems
$whichbrowser->isOs('Android') || $whichbrowser->isOs('Android') ||
$whichbrowser->isOs('iOS') || $whichbrowser->isOs('iOS') ||
$whichbrowser->isBrowser('Internet Explorer', '<=', '8')) { $whichbrowser->isBrowser('BlackBerry Browser') ||
$whichbrowser->isBrowser('Internet Explorer', '<=', '8') ||
$whichbrowser->isBrowser('Firefox', '<=', '47') ||
$whichbrowser->isBrowser('Safari', '<=', '7') ||
$whichbrowser->isBrowser('Google Chrome', '<=', '35')) {
// yeah, it's old, but ios and android are? // yeah, it's old, but ios and android are?
if ($whichbrowser->isOs('iOS') && $whichbrowser->isOs('iOS', '<=', '9')) { if ($whichbrowser->isOs('iOS') && $whichbrowser->isOs('iOS', '<=', '9')) {
return true; return true;

View file

@ -321,7 +321,7 @@ final class PhotosPresenter extends OpenVKPresenter
$photos = []; $photos = [];
if ((int) $this->postParam("count") > 10) { if ((int) $this->postParam("count") > 10) {
$this->flashFail("err", tr("no_photo"), "ты еблан", 500, true); $this->flashFail("err", tr("no_photo"), "Too many photos (max is 7-8)", 500, true);
} }
for ($i = 0; $i < $this->postParam("count"); $i++) { for ($i = 0; $i < $this->postParam("count"); $i++) {

View file

@ -42,7 +42,6 @@ final class SearchPresenter extends OpenVKPresenter
$page = (int) ($this->queryParam("p") ?? 1); $page = (int) ($this->queryParam("p") ?? 1);
# https://youtu.be/pSAWM5YuXx8 # https://youtu.be/pSAWM5YuXx8
# https://youtu.be/FfNZRhIn2Vk
$repos = [ $repos = [
"groups" => "clubs", "groups" => "clubs",
@ -70,7 +69,7 @@ final class SearchPresenter extends OpenVKPresenter
case 'marital_status': case 'marital_status':
case 'polit_views': case 'polit_views':
if ((int) $param_value == 0) { if ((int) $param_value == 0) {
continue; break;
} }
$parameters[$param_name] = $param_value; $parameters[$param_name] = $param_value;
@ -96,7 +95,7 @@ final class SearchPresenter extends OpenVKPresenter
# дай бог работал этот case # дай бог работал этот case
case 'from_me': case 'from_me':
if ((int) $param_value != 1) { if ((int) $param_value != 1) {
continue; break;
} }
$parameters['from_me'] = $this->user->id; $parameters['from_me'] = $this->user->id;

View file

@ -169,6 +169,13 @@ final class SupportPresenter extends OpenVKPresenter
$_redirect = "/support?act=list"; $_redirect = "/support?act=list";
} }
$helpdeskChat = OPENVK_ROOT_CONF["openvk"]["credentials"]["telegram"]["helpdeskChat"];
if ($helpdeskChat) {
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
$telegramText = "❌ <b>Тикет под названием</b> &quot;{$ticket->getName()}&quot; от <a href='$serverUrl{$ticket->getUser()->getURL()}'>{$ticket->getUser()->getCanonicalName()}</a> ({$ticket->getUser()->getRegistrationIP()}) <b>был удалён.</b>\n";
Telegram::send($helpdeskChat, $telegramText);
}
$ticket->delete(); $ticket->delete();
$this->redirect($_redirect); $this->redirect($_redirect);
} }
@ -200,6 +207,16 @@ final class SupportPresenter extends OpenVKPresenter
$comment->setCreated(time()); $comment->setCreated(time());
$comment->save(); $comment->save();
$helpdeskChat = OPENVK_ROOT_CONF["openvk"]["credentials"]["telegram"]["helpdeskChat"];
if ($helpdeskChat) {
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
$commentText = ovk_proc_strtr($this->postParam("text"), 1500);
$telegramText = "💬 <b>Новый комментарий от автора тикета</b> <a href='$serverUrl/support/reply/$id'>&quot;{$ticket->getName()}&quot;</a>\n";
$telegramText .= "$commentText\n\n";
$telegramText .= "Автор: <a href='$serverUrl{$ticket->getUser()->getURL()}'>{$ticket->getUser()->getCanonicalName()}</a> ({$ticket->getUser()->getRegistrationIP()})\n";
Telegram::send($helpdeskChat, $telegramText);
}
$this->redirect("/support/view/" . $id); $this->redirect("/support/view/" . $id);
} else { } else {
$this->flashFail("err", tr("error"), tr("you_have_not_entered_text")); $this->flashFail("err", tr("error"), tr("you_have_not_entered_text"));
@ -227,15 +244,33 @@ final class SupportPresenter extends OpenVKPresenter
{ {
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0); $this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$support_names = new SupportAgents();
$agent = $support_names->get($this->user->id);
$ticket = $this->tickets->get($id); $ticket = $this->tickets->get($id);
if ($_SERVER["REQUEST_METHOD"] === "POST") { if ($_SERVER["REQUEST_METHOD"] === "POST") {
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$helpdeskChat = OPENVK_ROOT_CONF["openvk"]["credentials"]["telegram"]["helpdeskChat"];
if (!empty($this->postParam("text")) && !empty($this->postParam("status"))) { if (!empty($this->postParam("text")) && !empty($this->postParam("status"))) {
$ticket->setType($this->postParam("status")); $status = $this->postParam("status");
$ticket->setType($status);
$ticket->save(); $ticket->save();
switch ($status) {
default:
# NOTICE falling through
case 0:
$state = "Вопрос на рассмотрении";
break;
case 1:
$state = "Есть ответ";
break;
case 2:
$state = "Закрыто";
}
$comment = new TicketComment(); $comment = new TicketComment();
$comment->setUser_id($this->user->id); $comment->setUser_id($this->user->id);
$comment->setUser_type(1); $comment->setUser_type(1);
@ -243,9 +278,42 @@ final class SupportPresenter extends OpenVKPresenter
$comment->setTicket_Id($id); $comment->setTicket_Id($id);
$comment->setCreated(time()); $comment->setCreated(time());
$comment->save(); $comment->save();
if ($helpdeskChat) {
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"] . "/support/agent" . $this->user->id;
$ticketUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"] . "/support/reply/" . $id;
$commentText = ovk_proc_strtr($this->postParam("text"), 1500);
$telegramText = "💬 <b>Новый комментарий от агента к тикету</b> <a href='$ticketUrl'>&quot;{$ticket->getName()}&quot;</a>\n";
$telegramText .= "Статус: {$state}\n\n";
$telegramText .= "$commentText\n\n";
$telegramText .= "Агент: <a href='$serverUrl'>{$this->user->identity->getFullName()}</a>\n";
Telegram::send($helpdeskChat, $telegramText);
}
} elseif (empty($this->postParam("text"))) { } elseif (empty($this->postParam("text"))) {
$ticket->setType($this->postParam("status")); $status = $this->postParam("status");
$ticket->setType($status);
$ticket->save(); $ticket->save();
switch ($status) {
default:
# NOTICE falling through
case 0:
$state = "Вопрос на рассмотрении";
break;
case 1:
$state = "Есть ответ";
break;
case 2:
$state = "Закрыто";
}
if ($helpdeskChat) {
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"] . "/support/agent" . $this->user->id;
$ticketUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"] . "/support/reply/" . $id;
$telegramText = "🔔 <b>Изменён статус тикета</b> <a href='$ticketUrl'>&quot;{$ticket->getName()}&quot;</a>: <b>{$state}</b>\n\n";
$telegramText .= "Агент: <a href='$serverUrl'>{$this->user->identity->getFullName()}</a>\n";
Telegram::send($helpdeskChat, $telegramText);
}
} }
$this->flashFail("succ", tr("ticket_changed"), tr("ticket_changed_comment")); $this->flashFail("succ", tr("ticket_changed"), tr("ticket_changed_comment"));
@ -314,17 +382,20 @@ final class SupportPresenter extends OpenVKPresenter
$comment = $this->comments->get($id); $comment = $this->comments->get($id);
if ($this->user->id !== $comment->getTicket()->getUser()->getId()) { if ($this->user->id !== $comment->getTicket()->getUser()->getId()) {
exit(header("HTTP/1.1 403 Forbidden")); header("HTTP/1.1 403 Forbidden");
exit();
} }
if ($mark !== 1 && $mark !== 2) { if ($mark !== 1 && $mark !== 2) {
exit(header("HTTP/1.1 400 Bad Request")); header("HTTP/1.1 400 Bad Request");
exit();
} }
$comment->setMark($mark); $comment->setMark($mark);
$comment->save(); $comment->save();
exit(header("HTTP/1.1 200 OK")); header("HTTP/1.1 200 OK");
exit();
} }
public function renderQuickBanInSupport(int $id): void public function renderQuickBanInSupport(int $id): void
@ -439,6 +510,13 @@ final class SupportPresenter extends OpenVKPresenter
$ticket->setType(2); $ticket->setType(2);
$ticket->save(); $ticket->save();
$helpdeskChat = OPENVK_ROOT_CONF["openvk"]["credentials"]["telegram"]["helpdeskChat"];
if ($helpdeskChat) {
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
$telegramText = "🔒 <b>Тикет под названием</b> <a href='$serverUrl/support/reply/{$ticket->getId()}'>&quot;{$ticket->getName()}&quot;</a> от <a href='$serverUrl{$ticket->getUser()->getURL()}'>{$ticket->getUser()->getCanonicalName()}</a> ({$ticket->getUser()->getRegistrationIP()}) <b>был закрыт автором.</b>\n";
Telegram::send($helpdeskChat, $telegramText);
}
$this->flashFail("succ", tr("ticket_changed"), tr("ticket_changed_comment")); $this->flashFail("succ", tr("ticket_changed"), tr("ticket_changed_comment"));
} }
} }

View file

@ -6,6 +6,7 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Topic, Club, Comment, Photo, Video}; use openvk\Web\Models\Entities\{Topic, Club, Comment, Photo, Video};
use openvk\Web\Models\Repositories\{Topics, Clubs}; use openvk\Web\Models\Repositories\{Topics, Clubs};
use Nette\InvalidStateException as ISE;
final class TopicsPresenter extends OpenVKPresenter final class TopicsPresenter extends OpenVKPresenter
{ {
@ -112,9 +113,6 @@ final class TopicsPresenter extends OpenVKPresenter
$video = null; $video = null;
if ($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) { if ($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
$album = null; $album = null;
if ($wall > 0 && $wall === $this->user->id) {
$album = (new Albums())->getUserWallAlbum($wallOwner);
}
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album); $photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album);
} }

View file

@ -9,7 +9,7 @@ use openvk\Web\Util\Sms;
use openvk\Web\Themes\Themepacks; use openvk\Web\Themes\Themepacks;
use openvk\Web\Models\Entities\{Photo, Post, EmailChangeVerification}; use openvk\Web\Models\Entities\{Photo, Post, EmailChangeVerification};
use openvk\Web\Models\Entities\Notifications\{CoinsTransferNotification, RatingUpNotification}; use openvk\Web\Models\Entities\Notifications\{CoinsTransferNotification, RatingUpNotification};
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Videos, Notes, Vouchers, EmailChangeVerifications, Audios}; use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Videos, Notes, Vouchers, EmailChangeVerifications, Audios, Faves};
use openvk\Web\Models\Exceptions\InvalidUserNameException; use openvk\Web\Models\Exceptions\InvalidUserNameException;
use openvk\Web\Util\Validator; use openvk\Web\Util\Validator;
use Chandler\Security\Authenticator; use Chandler\Security\Authenticator;
@ -474,6 +474,7 @@ final class UserPresenter extends OpenVKPresenter
public function renderDeleteAvatar() public function renderDeleteAvatar()
{ {
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->assertNoCSRF();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$avatar = $this->user->identity->getAvatarPhoto(); $avatar = $this->user->identity->getAvatarPhoto();
@ -666,6 +667,7 @@ final class UserPresenter extends OpenVKPresenter
"menu_standardo" => "poster", "menu_standardo" => "poster",
"menu_aplikoj" => "apps", "menu_aplikoj" => "apps",
"menu_doxc" => "docs", "menu_doxc" => "docs",
"menu_feva" => "fave",
]; ];
foreach ($settings as $checkbox => $setting) { foreach ($settings as $checkbox => $setting) {
$user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox)); $user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox));
@ -942,4 +944,65 @@ final class UserPresenter extends OpenVKPresenter
$this->redirect("/settings"); $this->redirect("/settings");
} }
} }
public function renderFave(): void
{
$this->assertUserLoggedIn();
$page = (int) ($this->queryParam("p") ?? 1);
$section = $this->queryParam("section") ?? "posts";
$display_section = "posts";
$data = null;
$count = 0;
switch ($section) {
default:
$this->notFound();
break;
case 'wall':
case 'post':
case 'posts':
$data = (new Faves())->fetchLikesSection($this->user->identity, 'Post', $page);
$count = (new Faves())->fetchLikesSectionCount($this->user->identity, 'Post');
$display_section = "posts";
break;
case 'comment':
case 'comments':
$data = (new Faves())->fetchLikesSection($this->user->identity, 'Comment', $page);
$count = (new Faves())->fetchLikesSectionCount($this->user->identity, 'Comment');
$display_section = "comments";
break;
case 'photo':
case 'photos':
$data = (new Faves())->fetchLikesSection($this->user->identity, 'Photo', $page);
$count = (new Faves())->fetchLikesSectionCount($this->user->identity, 'Photo');
$display_section = "photos";
break;
case 'video':
case 'videos':
$data = (new Faves())->fetchLikesSection($this->user->identity, 'Video', $page);
$count = (new Faves())->fetchLikesSectionCount($this->user->identity, 'Video');
$display_section = "videos";
break;
}
$this->template->data = iterator_to_array($data);
$this->template->count = $count;
$this->template->page = $page;
$this->template->perPage = OPENVK_DEFAULT_PER_PAGE;
$this->template->section = $display_section;
$this->template->paginatorConf = (object) [
"page" => $page,
"count" => $count,
"amount" => sizeof($this->template->data),
"perPage" => $this->template->perPage,
"atBottom" => false,
"tidy" => true,
'pageCount' => ceil($count / $this->template->perPage),
];
$this->template->extendedPaginatorConf = clone $this->template->paginatorConf;
$this->template->extendedPaginatorConf->space = 11;
$this->template->paginatorConf->atTop = true;
}
} }

View file

@ -273,7 +273,7 @@ final class VKAPIPresenter extends OpenVKPresenter
} }
} }
define("VKAPI_DECL_VER", $this->requestParam("v") ?? "4.100", false); define("VKAPI_DECL_VER", $this->requestParam("v") ?? "4.100");
try { try {
$res = $handler->{$method}(...$params); $res = $handler->{$method}(...$params);

View file

@ -265,8 +265,11 @@ final class WallPresenter extends OpenVKPresenter
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1)) $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1));
?? $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4"));
if ($wallOwner === null) {
$this->flashFail("err", tr("failed_to_publish_post"), tr("error_4"));
}
if ($wallOwner->isBanned()) { if ($wallOwner->isBanned()) {
$this->flashFail("err", tr("error"), tr("forbidden")); $this->flashFail("err", tr("error"), tr("forbidden"));
@ -406,7 +409,12 @@ final class WallPresenter extends OpenVKPresenter
} }
if ($wall > 0 && $wall !== $this->user->identity->getId()) { if ($wall > 0 && $wall !== $this->user->identity->getId()) {
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit(); $disturber = $this->user->identity;
if ($anon) {
$disturber = $post->getOwner();
}
(new WallPostNotification($wallOwner, $post, $disturber))->emit();
} }
$excludeMentions = [$this->user->identity->getId()]; $excludeMentions = [$this->user->identity->getId()];
@ -568,8 +576,11 @@ final class WallPresenter extends OpenVKPresenter
} }
$user = $this->user->id; $user = $this->user->id;
$wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1)) $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1));
?? $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4"));
if ($wallOwner === null) {
$this->flashFail("err", tr("failed_to_delete_post"), tr("error_4"));
}
if ($wallOwner->isBanned()) { if ($wallOwner->isBanned()) {
$this->flashFail("err", tr("error"), tr("forbidden")); $this->flashFail("err", tr("error"), tr("forbidden"));

View file

@ -202,6 +202,7 @@
<div class="menu_divider"></div> <div class="menu_divider"></div>
<a n:if="$thisUser->getLeftMenuItemStatus('apps')" href="/apps?act=installed" class="link">{_apps}</a> <a n:if="$thisUser->getLeftMenuItemStatus('apps')" href="/apps?act=installed" class="link">{_apps}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('docs')" href="/docs" class="link">{_my_documents}</a> <a n:if="$thisUser->getLeftMenuItemStatus('docs')" href="/docs" class="link">{_my_documents}</a>
{*<a n:if="$thisUser->getLeftMenuItemStatus('fave')" href="/fave" class="link">{_bookmarks_tab}</a>*}
{/if} {/if}
{var $canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)} {var $canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}

View file

@ -9,6 +9,8 @@
{/if} {/if}
{elseif $mode == 'new'} {elseif $mode == 'new'}
{_audio_new} {_audio_new}
{elseif $mode == 'uploaded'}
{_my_audios_small_uploaded}
{elseif $mode == 'popular'} {elseif $mode == 'popular'}
{_audio_popular} {_audio_popular}
{elseif $mode == 'alone_audio'} {elseif $mode == 'alone_audio'}
@ -32,6 +34,12 @@
</div> </div>
</div> </div>
<div n:if="$mode == 'uploaded'">
{_my_audios_small}
»
{_my_audios_small_uploaded}
</div>
<div n:if="$mode == 'new'"> <div n:if="$mode == 'new'">
{_audios} {_audios}
» »
@ -58,7 +66,7 @@
{block content} {block content}
{* ref: https://archive.li/P32em *} {* ref: https://archive.li/P32em *}
{include "bigplayer.xml"} {include "bigplayer.xml", buttonsShow_summary => $audiosCount > 10}
<script> <script>
window.__current_page_audio_context = null window.__current_page_audio_context = null
@ -68,6 +76,12 @@
entity_id: {$ownerId}, entity_id: {$ownerId},
page: {$page} page: {$page}
} }
{elseif $mode == 'uploaded'}
window.__current_page_audio_context = {
name: 'uploaded',
entity_id: 0,
page: {$page}
}
{elseif $mode == 'alone_audio'} {elseif $mode == 'alone_audio'}
window.__current_page_audio_context = { window.__current_page_audio_context = {
name: 'alone_audio', name: 'alone_audio',
@ -77,6 +91,22 @@
{/if} {/if}
</script> </script>
<div n:if="isset($audios)" class='summaryBarHideable summaryBar summaryBarFlex padding' style="margin: 0px -10px;width: 99.5%;display: none;">
<div class='summary'>
<b>{tr("is_x_audio", $audiosCount)}</b>
</div>
{include "../components/paginator.xml", conf => (object) [
"page" => $page,
"count" => $audiosCount,
"amount" => sizeof($audios),
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
"atTop" => true,
"space" => 6,
"tidy" => true,
]}
</div>
<div class="audiosDiv"> <div class="audiosDiv">
<div class="audiosContainer audiosSideContainer audiosPaddingContainer" n:if="$mode != 'playlists'"> <div class="audiosContainer audiosSideContainer audiosPaddingContainer" n:if="$mode != 'playlists'">
<div n:if="$audiosCount <= 0" style='height: 100%;'> <div n:if="$audiosCount <= 0" style='height: 100%;'>

View file

@ -52,5 +52,9 @@
<div class="shuffleButton musicIcon" data-tip='simple' data-title="{_shuffle_tip}"></div> <div class="shuffleButton musicIcon" data-tip='simple' data-title="{_shuffle_tip}"></div>
<div class="deviceButton musicIcon" data-tip='simple' data-title="{_mute_tip} [M]"></div> <div class="deviceButton musicIcon" data-tip='simple' data-title="{_mute_tip} [M]"></div>
</div> </div>
<div class="absoluteButtons">
<div n:if="$buttonsShow_summary" id="summarySwitchButton">-</div>
</div>
</div> </div>
</div> </div>

View file

@ -2,6 +2,7 @@
<div class="verticalGrayTabs"> <div class="verticalGrayTabs">
<div class='with_padding'> <div class='with_padding'>
<a n:if="isset($thisUser)" n:attr="id => $mode === 'list' && $isMy ? 'used' : 'ki'" href="/audios{$thisUser->getId()}">{_my_music}</a> <a n:if="isset($thisUser)" n:attr="id => $mode === 'list' && $isMy ? 'used' : 'ki'" href="/audios{$thisUser->getId()}">{_my_music}</a>
<a n:attr="id => $mode === 'uploaded' ? 'used' : 'ki'" href="/audios/uploaded">{_my_audios_small_uploaded}</a>
{* TODO: show upload link as and plusick (little plus) in button up*} {* TODO: show upload link as and plusick (little plus) in button up*}
<a n:if="isset($thisUser)" href="/player/upload{if $isMyClub}?gid={abs($ownerId)}{/if}">{_upload_audio}</a> <a n:if="isset($thisUser)" href="/player/upload{if $isMyClub}?gid={abs($ownerId)}{/if}">{_upload_audio}</a>
<a n:if="isset($thisUser)" n:attr="id => $mode === 'new' ? 'used' : 'ki'" href="/search?section=audios">{_audio_new}</a> <a n:if="isset($thisUser)" n:attr="id => $mode === 'new' ? 'used' : 'ki'" href="/search?section=audios">{_audio_new}</a>
@ -13,7 +14,7 @@
<a n:if="isset($thisUser)" href="/audios/newPlaylist">{_new_playlist}</a> <a n:if="isset($thisUser)" href="/audios/newPlaylist">{_new_playlist}</a>
{if !$isMy && $mode !== 'popular' && $mode !== 'new' && $mode != 'alone_audio'} {if !$isMy && $mode !== 'popular' && $mode !== 'new' && $mode != 'alone_audio' && $mode != 'uploaded'}
<hr> <hr>
<a n:if="!$isMy" n:attr="id => $mode === 'list' ? 'used' : 'ki'" href="/audios{$ownerId}">{if $ownerId > 0}{_music_user}{else}{_music_club}{/if}</a> <a n:if="!$isMy" n:attr="id => $mode === 'list' ? 'used' : 'ki'" href="/audios{$ownerId}">{if $ownerId > 0}{_music_user}{else}{_music_club}{/if}</a>

View file

@ -0,0 +1,86 @@
{extends "../@layout.xml"}
{block title}{_bookmarks_tab}{/block}
{block header}
{_bookmarks_tab}
{/block}
{block wrap}
<div class="wrap2">
<div class="wrap1">
<div class="page_wrap">
<div class='summaryBar summaryBarFlex padding'>
<div class='summary'>
<b>{tr("faves", $count)} {*tr("showing_x_y", $page, $count)*}</b>
</div>
{include "../components/paginator.xml", conf => $paginatorConf}
</div>
<div class='page_wrap_content' id='search_page'>
<div n:class='page_wrap_content_main, scroll_container, ($section == "photos" && $count > 0) ? album-flex'>
<style>
.scroll_node:first-of-type .comment {
border-top: unset;
padding: 0px;
}
.scroll_node:last-of-type .post {
border-bottom: unset;
}
.content_page_error {
min-height: 400px;
}
</style>
{if $count > 0}
{foreach $data as $dat}
{if $dat->isDeleted()}
<div n:class="deleted_mark, $section == 'photos' ? album-photo : deleted_mark_average">
<span>[deleted]</span>
</div>
{else}
{if $section == "posts"}
<div class="scroll_node">
{include "../components/post.xml", post => $dat, commentSection => true}
</div>
{elseif $section == "comments"}
<div class="scroll_node">
{include "../components/comment.xml", comment => $dat, correctLink => true, no_reply_button => true}
</div>
{elseif $section == "photos"}
<div class="album-photo scroll_node" onclick="OpenMiniature(event, {$dat->getURLBySizeId('larger')}, null, {$dat->getPrettyId()}, null)">
<a href="/photo{$dat->getPrettyId()}">
<img class="album-photo--image" src="{$dat->getURLBySizeId('tinier')}" alt="{$dat->getDescription()}" loading="lazy" />
</a>
</div>
{elseif $section == "videos"}
<div class="scroll_node">
{include "../components/video.xml", video => $dat}
</div>
{/if}
{/if}
{/foreach}
{else}
{include "../components/content_error.xml", description => tr("faves_".$section."_empty_tip")}
{/if}
</div>
<div class='page_wrap_content_options verticalGrayTabsWrapper'>
<div class="page_wrap_content_options_list verticalGrayTabs with_padding">
<a n:attr="id => $section === 'posts' ? 'used'" href="/fave?section=posts">{_s_posts}</a>
<a n:attr="id => $section === 'comments' ? 'used'" href="/fave?section=comments">{_s_comments}</a>
<a n:attr="id => $section === 'photos' ? 'used'" href="/fave?section=photos">{_s_photos}</a>
<a n:attr="id => $section === 'videos' ? 'used'" href="/fave?section=videos">{_s_videos}</a>
</div>
</div>
</div>
<div n:if='$paginatorConf->pageCount > 1' class='page_content_paginator_bottom'>
{include "../components/paginator.xml", conf => $extendedPaginatorConf}
</div>
</div>
</div>
</div>
</div>
{/block}

View file

@ -120,6 +120,10 @@
<br/> <br/>
<div class="settings_delete"> <div class="settings_delete">
<a href="/fave">{_bookmarks_tab}</a>
<a href="/search?section=posts&from_me=1">{_s_posts}</a>
<br><br>
{_you_can_also} <a onClick="showProfileDeactivateDialog({$csrfToken})">{_delete_your_page}</a>. {_you_can_also} <a onClick="showProfileDeactivateDialog({$csrfToken})">{_delete_your_page}</a>.
</div> </div>
@ -595,6 +599,17 @@
</tbody> </tbody>
</table> </table>
</form> </form>
<table cellspacing="7" cellpadding="0" width="60%" border="0" align="center" id="_js_settings">
<tbody>
<tr>
<td width="120" valign="top" align="right"></td>
<td>
<a href="javascript:openJsSettings()">{_ui_settings_window}</a>
</td>
</tr>
</tbody>
</table>
<h4>{_ui_settings_sidebar}</h4> <h4>{_ui_settings_sidebar}</h4>
<form action="/settings?act=lMenu" method="POST" enctype="multipart/form-data"> <form action="/settings?act=lMenu" method="POST" enctype="multipart/form-data">
@ -696,6 +711,17 @@
<span class="nobold">{_my_documents}</span> <span class="nobold">{_my_documents}</span>
</td> </td>
</tr> </tr>
<tr style="display:none;">
<td width="120" valign="top" align="right" align="right">
<input
n:attr="checked => $user->getLeftMenuItemStatus('fave')"
type="checkbox"
name="menu_feva" />
</td>
<td>
<span class="nobold">{_bookmarks_tab}</span>
</td>
</tr>
<tr n:if="sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0"> <tr n:if="sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0">
<td width="120" valign="top" align="right" align="right"> <td width="120" valign="top" align="right" align="right">
<input <input

View file

@ -2,7 +2,7 @@
{if !$attachment->isDeleted()} {if !$attachment->isDeleted()}
{var $link = "/photo" . ($attachment->isAnonymous() ? ("s/" . base_convert((string) $attachment->getId(), 10, 32)) : $attachment->getPrettyId())} {var $link = "/photo" . ($attachment->isAnonymous() ? ("s/" . base_convert((string) $attachment->getId(), 10, 32)) : $attachment->getPrettyId())}
<a href="{$link}" onclick="OpenMiniature(event, {$attachment->getURLBySizeId('larger')}, {$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 /> <img n:class="media, $tilesCount > 1 ? media_makima" src="{$attachment->getURLBySizeId('normal')}" alt="{$attachment->getDescription()}" loading=lazy />
</a> </a>
{else} {else}
<a href="javascript:alert('{_attach_no_longer_available}');"> <a href="javascript:alert('{_attach_no_longer_available}');">

View file

@ -60,8 +60,9 @@
<a href="javascript:reportComment({$comment->getId()})">{_report}</a> <a href="javascript:reportComment({$comment->getId()})">{_report}</a>
{/if} {/if}
<div style="float: right; font-size: .7rem;"> <div style="float: right; font-size: .7rem;">
{var $isLiked = $comment->hasLikeFrom($thisUser)}
<a class="post-like-button" href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}" data-likes='{$likesCount}' data-id="1_{$comment->getPrettyId()}" data-type='comment'> <a class="post-like-button" href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}" data-likes='{$likesCount}' data-id="1_{$comment->getPrettyId()}" data-type='comment'>
<div class="heart" style="{if $comment->hasLikeFrom($thisUser)}opacity: 1;{else}opacity: 0.4;{/if}"></div> <div class="heart" n:attr="id => $isLiked ? liked" style="{if $isLiked}opacity: 1;{else}opacity: 0.4;{/if}"></div>
<span class="likeCnt">{if $likesCount > 0}{$likesCount}{/if}</span> <span class="likeCnt">{if $likesCount > 0}{$likesCount}{/if}</span>
</a> </a>
</div> </div>

View file

@ -128,6 +128,6 @@ class Themepack
throw new Exceptions\IncompatibleThemeException("Theme is built for newer OVK (themeEngine" . $manifest->openvk_version . ")"); throw new Exceptions\IncompatibleThemeException("Theme is built for newer OVK (themeEngine" . $manifest->openvk_version . ")");
} }
return new static($manifest->id, $manifest->version, (bool) ($manifest->inherit_master ?? true), (bool) ($manifest->override_templates ?? false), (bool) ($manifest->enabled ?? true), (object) $manifest->metadata); return new Themepack($manifest->id, $manifest->version, (bool) ($manifest->inherit_master ?? true), (bool) ($manifest->override_templates ?? false), (bool) ($manifest->enabled ?? true), (object) $manifest->metadata);
} }
} }

View file

@ -88,25 +88,6 @@ class Themepacks implements \ArrayAccess
/* /ArrayAccess */ /* /ArrayAccess */
public function install(string $archivePath): bool
{
if (!file_exists($archivePath)) {
return false;
}
$tmpDir = mkdir(tempnam(OPENVK_ROOT . "/tmp/themepack_artifacts/", "themex_"));
try {
$archive = new \CabArchive($archivePath);
$archive->extract($tmpDir);
return $this->installUnpacked($tmpDir);
} catch (\Exception $e) {
return false;
} finally {
rmdir($tmpDir);
}
}
public function uninstall(string $id): bool public function uninstall(string $id): bool
{ {
if (!isset($loadedThemepacks[$id])) { if (!isset($loadedThemepacks[$id])) {

View file

@ -78,7 +78,7 @@ class Bitmask
} elseif (gettype($key) === "int") { } elseif (gettype($key) === "int") {
$this->setByOffset($key, $data); $this->setByOffset($key, $data);
} else { } else {
throw new TypeError("Key must be either offset (int) or a string index"); throw new \TypeError("Key must be either offset (int) or a string index");
} }
return $this; return $this;
@ -89,7 +89,7 @@ class Bitmask
if (gettype($key) === "string") { if (gettype($key) === "string") {
$key = $this->getOffsetByKey($key); $key = $this->getOffsetByKey($key);
} elseif (gettype($key) !== "int") { } elseif (gettype($key) !== "int") {
throw new TypeError("Key must be either offset (int) or a string index"); throw new \TypeError("Key must be either offset (int) or a string index");
} }
return $this->length === 1 ? $this->getBoolByOffset($key) : $this->getNumberByOffset($key); return $this->length === 1 ? $this->getBoolByOffset($key) : $this->getNumberByOffset($key);

View file

@ -66,6 +66,7 @@ class DateTime
case static::RELATIVE_FORMAT_LOWER: case static::RELATIVE_FORMAT_LOWER:
return $this->zmdate(); return $this->zmdate();
case static::RELATIVE_FORMAT_SHORT: case static::RELATIVE_FORMAT_SHORT:
default:
return ""; return "";
} }
} }

View file

@ -188,9 +188,9 @@ class Makima
$tries = []; $tries = [];
$firstLine; $firstLine = null;
$secondLine; $secondLine = null;
$thirdLine; $thirdLine = null;
# Try one line: # Try one line:
$tries[$firstLine = $count] = [$this->calculateMultiThumbsHeight($ratiosCropped, $maxWidth, $marginWidth)]; $tries[$firstLine = $count] = [$this->calculateMultiThumbsHeight($ratiosCropped, $maxWidth, $marginWidth)];
@ -234,7 +234,7 @@ class Makima
} }
} }
if (!$optimalConfiguration || $confDigff < $optimalDifference) { if (!$optimalConfiguration || $confDiff < $optimalDifference) {
$optimalConfiguration = $config; $optimalConfiguration = $config;
$optimalDifference = $confDiff; $optimalDifference = $confDiff;
} }

View file

@ -54,5 +54,6 @@ services:
- openvk\Web\Models\Repositories\BannedLinks - openvk\Web\Models\Repositories\BannedLinks
- openvk\Web\Models\Repositories\ChandlerGroups - openvk\Web\Models\Repositories\ChandlerGroups
- openvk\Web\Models\Repositories\Documents - openvk\Web\Models\Repositories\Documents
- openvk\Web\Models\Repositories\Faves
- openvk\Web\Presenters\MaintenancePresenter - openvk\Web\Presenters\MaintenancePresenter
- openvk\Web\Presenters\NoSpamPresenter - openvk\Web\Presenters\NoSpamPresenter

View file

@ -201,6 +201,8 @@ routes:
handler: "Audio->upload" handler: "Audio->upload"
- url: "/audios{num}" - url: "/audios{num}"
handler: "Audio->list" handler: "Audio->list"
- url: "/audios/uploaded"
handler: "Audio->uploaded"
- url: "/audio{num}/listen" - url: "/audio{num}/listen"
handler: "Audio->listen" handler: "Audio->listen"
- url: "/audio{num}_{num}" - url: "/audio{num}_{num}"
@ -413,6 +415,8 @@ routes:
handler: "InternalAPI->getPostTemplate" handler: "InternalAPI->getPostTemplate"
- url: "/tour" - url: "/tour"
handler: "About->tour" handler: "About->tour"
- url: "/fave"
handler: "User->fave"
- url: "/{?shortCode}" - url: "/{?shortCode}"
handler: "UnknownTextRouteStrategy->delegate" handler: "UnknownTextRouteStrategy->delegate"
placeholders: placeholders:

View file

@ -52,6 +52,34 @@
height: 46px; height: 46px;
} }
.bigPlayer .bigPlayerWrapper .absoluteButtons {
position: absolute;
bottom: 0;
right: 0;
}
.bigPlayer .bigPlayerWrapper .absoluteButtons > div {
width: 8px;
height: 8px;
font-size: 9px;
display: flex;
align-items: center;
justify-content: center;
background: #ebebeb;
border: 1px solid #c3c3c3;
border-bottom: unset;
border-right: unset;
color: #c3c3c3;
cursor: pointer;
user-select: none;
}
.bigPlayer .bigPlayerWrapper .absoluteButtons > div:active {
background: #c3c3c3;
color: #ebebeb;
}
/* Play button and arrows */ /* Play button and arrows */
.bigPlayer .playButtons { .bigPlayer .playButtons {
display: flex; display: flex;
@ -313,7 +341,7 @@
opacity: 0.8; opacity: 0.8;
} }
.audioEmbed.processed { .audioEmbed.processed .playerButton {
filter: opacity(0.6); filter: opacity(0.6);
} }

View file

@ -99,6 +99,7 @@ button.bsdn_playButton {
padding-left: 0; padding-left: 0;
font-size: 22px; font-size: 22px;
cursor: pointer; cursor: pointer;
width: 22px;
} }
.bsdn_fullScreenButton, .bsdn_repeatButton { .bsdn_fullScreenButton, .bsdn_repeatButton {

View file

@ -11,6 +11,7 @@ body {
font-size: 11px; font-size: 11px;
word-break: break-word; word-break: break-word;
word-wrap: break-word; word-wrap: break-word;
line-height: 1.19;
} }
body, .ovk-fullscreen-dimmer, .ovk-photo-view-dimmer { body, .ovk-fullscreen-dimmer, .ovk-photo-view-dimmer {
@ -2919,7 +2920,6 @@ a.poll-retract-vote {
position: relative; position: relative;
} }
/* не говновёрстка, а пиксель-пёрфект) */
.page_header.search_expanded.search_expanded_at_all #search_and_one_more_wrapper { .page_header.search_expanded.search_expanded_at_all #search_and_one_more_wrapper {
width: 547px; width: 547px;
} }
@ -3039,6 +3039,10 @@ a.poll-retract-vote {
gap: 1px; gap: 1px;
} }
.verticalGrayTabsPad {
padding: 0px 0px 0px 8px;
}
.searchList hr, .verticalGrayTabs hr { .searchList hr, .verticalGrayTabs hr {
width: 153px; width: 153px;
margin-left: 0px; margin-left: 0px;
@ -4278,3 +4282,7 @@ hr {
height: 30px; height: 30px;
background-position-y: 9px; background-position-y: 9px;
} }
.deleted_mark_average {
padding: 5px 61px;
}

View file

@ -68,7 +68,7 @@ u(document).on('click', '#__feed_settings_link', (e) => {
` `
MessageBox(tr("feed_settings"), body, [tr("close")], [Function.noop]) MessageBox(tr("feed_settings"), body, [tr("close")], [Function.noop])
u('.ovk-diag-body').attr('style', 'padding:0px;height: 255px;') u('.ovk-diag-body').attr('style', 'padding:0px;height: 255px;overflow: hidden;')
async function __switchTab(tab) async function __switchTab(tab)
{ {
@ -84,8 +84,6 @@ u(document).on('click', '#__feed_settings_link', (e) => {
const CURRENT_PERPAGE = Number(__temp_url.searchParams.get('posts') ?? 10) const CURRENT_PERPAGE = Number(__temp_url.searchParams.get('posts') ?? 10)
const CURRENT_PAGE = Number(__temp_url.searchParams.get('p') ?? 1) const CURRENT_PAGE = Number(__temp_url.searchParams.get('p') ?? 1)
const CURRENT_RETURN_BANNED = Number(__temp_url.searchParams.get('return_banned') ?? 0) const CURRENT_RETURN_BANNED = Number(__temp_url.searchParams.get('return_banned') ?? 0)
const CURRENT_AUTO_SCROLL = Number(localStorage.getItem('ux.auto_scroll') ?? 1)
const CURRENT_DISABLE_AJAX = Number(localStorage.getItem('ux.disable_ajax_routing') ?? 0)
const COUNT = [1, 5, 10, 20, 30, 40, 50] const COUNT = [1, 5, 10, 20, 30, 40, 50]
u('#_feed_settings_container #__content').html(` u('#_feed_settings_container #__content').html(`
<table cellspacing="7" cellpadding="0" border="0" align="center"> <table cellspacing="7" cellpadding="0" border="0" align="center">
@ -116,26 +114,6 @@ u(document).on('click', '#__feed_settings_link', (e) => {
<label for='showIgnored'>${tr('show_ignored_sources')}</label> <label for='showIgnored'>${tr('show_ignored_sources')}</label>
</td> </td>
</tr> </tr>
<tr>
<td width="120" valign="top">
<span class="nobold">
<input type='checkbox' data-act='localstorage_item' data-inverse="1" name='ux.disable_ajax_routing' id="ux.disable_ajax_routing" ${CURRENT_DISABLE_AJAX == 0 ? 'checked' : ''}>
</span>
</td>
<td>
<label for='ux.disable_ajax_routing'>${tr('ajax_routing')}</label>
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">
<input type='checkbox' data-act='localstorage_item' name='ux.auto_scroll' id="ux.auto_scroll" ${CURRENT_AUTO_SCROLL == 1 ? 'checked' : ''}>
</span>
</td>
<td>
<label for='ux.auto_scroll'>${tr('auto_scroll')}</label>
</td>
</tr>
<tr> <tr>
<td width="120" valign="top"> <td width="120" valign="top">
</td> </td>
@ -299,3 +277,32 @@ u(document).on('change', `input[data-act='localstorage_item']`, (e) => {
localStorage.setItem(e.target.name, Number(e.target.checked)) localStorage.setItem(e.target.name, Number(e.target.checked))
}) })
function openJsSettings() {
const CURRENT_AUTO_SCROLL = Number(localStorage.getItem('ux.auto_scroll') ?? 1)
const CURRENT_DISABLE_AJAX = Number(localStorage.getItem('ux.disable_ajax_routing') ?? 0)
u("#_js_settings td").remove()
u("#_js_settings").append(`
<tr>
<td width="120" valign="top">
<span class="nobold">
<input type='checkbox' data-act='localstorage_item' data-inverse="1" name='ux.disable_ajax_routing' id="ux.disable_ajax_routing" ${CURRENT_DISABLE_AJAX == 0 ? 'checked' : ''}>
</span>
</td>
<td>
<label for='ux.disable_ajax_routing'>${tr('ajax_routing')}</label>
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">
<input type='checkbox' data-act='localstorage_item' name='ux.auto_scroll' id="ux.auto_scroll" ${CURRENT_AUTO_SCROLL == 1 ? 'checked' : ''}>
</span>
</td>
<td>
<label for='ux.auto_scroll'>${tr('auto_scroll')}</label>
</td>
</tr>
`)
}

View file

@ -54,6 +54,9 @@ window.player = new class {
current_track_id = 0 current_track_id = 0
tracks = [] tracks = []
// time type:
// 0 - shows remaining time before end
// 1 - shows full track time
get timeType() { get timeType() {
return localStorage.getItem('audio.timeType') ?? 0 return localStorage.getItem('audio.timeType') ?? 0
} }
@ -62,6 +65,7 @@ window.player = new class {
localStorage.setItem('audio.timeType', value) localStorage.setItem('audio.timeType', value)
} }
// <audio> tag
get audioPlayer() { get audioPlayer() {
return this.__realAudioPlayer return this.__realAudioPlayer
} }
@ -231,6 +235,9 @@ window.player = new class {
'query': this.context.object.query, 'query': this.context.object.query,
})) }))
break break
case "uploaded":
form_data.append('context', this.context.object.name)
break
case 'alone_audio': case 'alone_audio':
form_data.append('context', this.context.object.name) form_data.append('context', this.context.object.name)
form_data.append('context_entity', this.context.object.entity_id) form_data.append('context_entity', this.context.object.entity_id)
@ -322,6 +329,10 @@ window.player = new class {
this.__updateFace() this.__updateFace()
u(this.audioPlayer).trigger('volumechange') u(this.audioPlayer).trigger('volumechange')
if(this.isAtAudiosPage()) {
document.title = ovk_proc_strtr(escapeHtml(`${window.player.currentTrack.performer}${window.player.currentTrack.name}`), 255)
}
} }
hasContext() { hasContext() {
@ -377,7 +388,7 @@ window.player = new class {
} }
await this.setTrack(this.previousTrack.id) await this.setTrack(this.previousTrack.id)
if(!this.currentTrack.available || this.currentTrack.withdrawn) { if(/*!this.currentTrack.available || */this.currentTrack.withdrawn) {
if(!this.previousTrack) { if(!this.previousTrack) {
return return
} }
@ -394,7 +405,7 @@ window.player = new class {
} }
await this.setTrack(this.nextTrack.id) await this.setTrack(this.nextTrack.id)
if(!this.currentTrack.available || this.currentTrack.withdrawn) { if(/*!this.currentTrack.available || */this.currentTrack.withdrawn) {
if(!this.nextTrack) { if(!this.nextTrack) {
return return
} }
@ -652,7 +663,7 @@ window.player = new class {
this.uiPlayer.find(".trackInfo .elapsedTime").html(getRemainingTime(this.currentTrack.length, new_time)) this.uiPlayer.find(".trackInfo .elapsedTime").html(getRemainingTime(this.currentTrack.length, new_time))
} }
} }
__updateMediaSession() { __updateMediaSession() {
const album = document.querySelector(".playlistBlock") const album = document.querySelector(".playlistBlock")
const cur = this.currentTrack const cur = this.currentTrack
@ -664,6 +675,8 @@ window.player = new class {
}) })
} }
// the listen counts if you reach half of song
// but it doesnt checks on server normally so you can "накрутить" listens
async __countListen() { async __countListen() {
let playlist = 0 let playlist = 0
if(!this.listen_coef) { if(!this.listen_coef) {
@ -787,6 +800,10 @@ window.player = new class {
} }
}) })
} }
toggleSummary() {
$(".summaryBarHideable").slideToggle(300, "linear")
}
} }
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
@ -1163,7 +1180,25 @@ u(document).on("drop", '.audiosContainer', function(e) {
} }
}) })
u(document).on("click", "#summarySwitchButton", (e) => {
if(u(".summaryBarHideable").nodes[0].style.overflow == "hidden") {
return
}
if(u(e.target).html() == "-") {
u(e.target).html("+")
} else {
u(e.target).html("-")
}
window.player.toggleSummary()
})
u(document).on('contextmenu', '.bigPlayer, .audioEmbed, #ajax_audio_player', (e) => { u(document).on('contextmenu', '.bigPlayer, .audioEmbed, #ajax_audio_player', (e) => {
if(e.shiftKey) {
return
}
e.preventDefault() e.preventDefault()
u('#ctx_menu').remove() u('#ctx_menu').remove()
@ -1179,6 +1214,10 @@ u(document).on('contextmenu', '.bigPlayer, .audioEmbed, #ajax_audio_player', (e)
x = e.pageX - rx x = e.pageX - rx
y = e.pageY - ry y = e.pageY - ry
if((rect.height + rect.top) + 100 > window.innerHeight) {
y = ((rect.height + 120) * -1)
}
const ctx_u = u(` const ctx_u = u(`
<div id='ctx_menu' style='top:${y}px;left:${x}px;' data-type='ctx_type'> <div id='ctx_menu' style='top:${y}px;left:${x}px;' data-type='ctx_type'>
<a id='audio_ctx_copy'>${tr('copy_link_to_audio')}</a> <a id='audio_ctx_copy'>${tr('copy_link_to_audio')}</a>
@ -1194,7 +1233,7 @@ u(document).on('contextmenu', '.bigPlayer, .audioEmbed, #ajax_audio_player', (e)
<a id='audio_ctx_add_to_playlist'>${tr('audio_ctx_add_to_playlist')}</a> <a id='audio_ctx_add_to_playlist'>${tr('audio_ctx_add_to_playlist')}</a>
${ctx_type == 'main_player' ? ` ${ctx_type == 'main_player' ? `
<a id='audio_ctx_clear_context'>${tr('audio_ctx_clear_context')}</a>` : ''} <a id='audio_ctx_clear_context'>${tr('audio_ctx_clear_context')}</a>` : ''}
${ctx_type == 'main_player' ? `<a href='https://github.com/mrilyew' target='_blank'>BigPlayer v1.1 by MrIlyew</a>` : ''} ${ctx_type == 'main_player' ? `<a href='https://github.com/mrilyew' target='_blank'>BigPlayer v1.2 by MrIlyew</a>` : ''}
</div> </div>
`) `)
u(parent).append(ctx_u) u(parent).append(ctx_u)
@ -1948,8 +1987,12 @@ $(document).on("click", ".audioEmbed.processed .playerButton", (e) => {
title: tr('error'), title: tr('error'),
body: tr('audio_embed_processing'), body: tr('audio_embed_processing'),
unique_name: 'processing_notify', unique_name: 'processing_notify',
buttons: [tr('ok')], buttons: [tr("audio_embed_processing_bait"), tr('ok')],
callbacks: [Function.noop] callbacks: [() => {
const pl = u(e.target).closest(".audioEmbed")
pl.removeClass("processed")
pl.find(".playIcon").trigger("click")
}, Function.noop]
}) })
}) })

View file

@ -2470,8 +2470,7 @@ u(document).on('click', '#__sourceAttacher', (e) => {
// Checking link // Checking link
const __checkCopyrightLinkRes = await fetch(`/method/wall.checkCopyrightLink?auth_mechanism=roaming&link=${encodeURIComponent(source_value)}`) const __checkCopyrightLinkRes = await fetch(`/method/wall.checkCopyrightLink?auth_mechanism=roaming&link=${encodeURIComponent(source_value)}`)
const checkCopyrightLink = await __checkCopyrightLinkRes.json() const checkCopyrightLink = await __checkCopyrightLinkRes.json()
// todo переписать блять мессенджбоксы чтоб они классами были
if(checkCopyrightLink.error_code) { if(checkCopyrightLink.error_code) {
__removeDialog() __removeDialog()
switch(checkCopyrightLink.error_code) { switch(checkCopyrightLink.error_code) {

View file

@ -481,8 +481,8 @@ return (function () {
define('YEAR', 365 * DAY); define('YEAR', 365 * DAY);
define("nullptr", null); define("nullptr", null);
define("OPENVK_DEFAULT_INSTANCE_NAME", "OpenVK", false); define("OPENVK_DEFAULT_INSTANCE_NAME", "OpenVK");
define("OPENVK_VERSION", "Altair Preview ($ver)", false); define("OPENVK_VERSION", "Altair Preview ($ver)");
define("OPENVK_DEFAULT_PER_PAGE", 10, false); define("OPENVK_DEFAULT_PER_PAGE", 10);
define("__OPENVK_ERROR_CLOCK_IN_FUTURE", "Server clock error: FK1200-DTF", false); define("__OPENVK_ERROR_CLOCK_IN_FUTURE", "Server clock error: FK1200-DTF");
}); });

10
chandler_loader.php Normal file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
namespace openvk;
$_SERVER["HTTP_ACCEPT_LANGUAGE"] = false;
$bootstrap = require(__DIR__ . "/../../../chandler/Bootstrap.php");
$bootstrap->ignite(true);

View file

@ -1,7 +1,8 @@
{ {
"scripts": { "scripts": {
"fix": "php-cs-fixer fix", "fix": "php-cs-fixer fix",
"lint": "php-cs-fixer fix --dry-run --diff --verbose" "lint": "php-cs-fixer fix --dry-run --diff --verbose",
"analyse": "phpstan analyse --memory-limit 1G"
}, },
"require": { "require": {
"php": "~7.3||~8.1", "php": "~7.3||~8.1",
@ -28,6 +29,7 @@
}, },
"minimum-stability": "beta", "minimum-stability": "beta",
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^3.68" "friendsofphp/php-cs-fixer": "^3.68",
"phpstan/phpstan": "^2.1"
} }
} }

60
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "b92d2ddd207f394a31c429c65d1785a7", "content-hash": "fe88a04383a75cc5c6591abac3128201",
"packages": [ "packages": [
{ {
"name": "al/emoji-detector", "name": "al/emoji-detector",
@ -3092,6 +3092,64 @@
], ],
"time": "2025-01-30T17:00:50+00:00" "time": "2025-01-30T17:00:50+00:00"
}, },
{
"name": "phpstan/phpstan",
"version": "2.1.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "7d08f569e582ade182a375c366cbd896eccadd3a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/7d08f569e582ade182a375c366cbd896eccadd3a",
"reference": "7d08f569e582ade182a375c366cbd896eccadd3a",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0"
},
"conflict": {
"phpstan/phpstan-shim": "*"
},
"bin": [
"phpstan",
"phpstan.phar"
],
"type": "library",
"autoload": {
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHPStan - PHP Static Analysis Tool",
"keywords": [
"dev",
"static analysis"
],
"support": {
"docs": "https://phpstan.org/user-guide/getting-started",
"forum": "https://github.com/phpstan/phpstan/discussions",
"issues": "https://github.com/phpstan/phpstan/issues",
"security": "https://github.com/phpstan/phpstan/security/policy",
"source": "https://github.com/phpstan/phpstan-src"
},
"funding": [
{
"url": "https://github.com/ondrejmirtes",
"type": "github"
},
{
"url": "https://github.com/phpstan",
"type": "github"
}
],
"time": "2025-01-21T14:54:06+00:00"
},
{ {
"name": "psr/event-dispatcher", "name": "psr/event-dispatcher",
"version": "1.0.0", "version": "1.0.0",

View file

@ -0,0 +1,3 @@
ALTER TABLE `support_names`
DROP PRIMARY KEY,
ADD PRIMARY KEY(`id`);

View file

@ -641,6 +641,8 @@
"my_feed" = "My Feed"; "my_feed" = "My Feed";
"my_feedback" = "My Feedback"; "my_feedback" = "My Feedback";
"my_settings" = "My Settings"; "my_settings" = "My Settings";
"bookmarks" = "Bookmarks";
"bookmarks_tab" = "Saved";
"bug_tracker" = "Bug Tracker"; "bug_tracker" = "Bug Tracker";
"menu_settings" = "Settings"; "menu_settings" = "Settings";
@ -701,6 +703,7 @@
"round_avatars" = "Round"; "round_avatars" = "Round";
"apply_style_for_this_device" = "Apply style only for this device"; "apply_style_for_this_device" = "Apply style only for this device";
"ui_settings_window" = "Advanced settings";
"search_for_groups" = "Search for groups"; "search_for_groups" = "Search for groups";
"search_for_users" = "Search for users"; "search_for_users" = "Search for users";
@ -926,7 +929,8 @@
"audio_embed_deleted" = "Audio has been deleted"; "audio_embed_deleted" = "Audio has been deleted";
"audio_embed_withdrawn" = "The audio has been withdrawn at the request of the copyright holder"; "audio_embed_withdrawn" = "The audio has been withdrawn at the request of the copyright holder";
"audio_embed_forbidden" = "The user's privacy settings do not allow this audio to be embedded"; "audio_embed_forbidden" = "The user's privacy settings do not allow this audio to be embedded";
"audio_embed_processing" = "Audio is still being processed or has not been processed correctly."; "audio_embed_processing" = "Audio is processing.";
"audio_embed_processing_bait" = "Play anyway";
"audios_count_zero" = "No audios"; "audios_count_zero" = "No audios";
"audios_count_one" = "One audio"; "audios_count_one" = "One audio";
@ -945,6 +949,7 @@
"audio_search" = "Search"; "audio_search" = "Search";
"my_audios_small" = "My audios"; "my_audios_small" = "My audios";
"my_audios_small_uploaded" = "Uploaded";
"my_playlists" = "My playlists"; "my_playlists" = "My playlists";
"playlists" = "Playlists"; "playlists" = "Playlists";
"audios_explicit" = "Contains obscene language"; "audios_explicit" = "Contains obscene language";
@ -1036,6 +1041,12 @@
"audio_ctx_play_next" = "Play next"; "audio_ctx_play_next" = "Play next";
"audio_ctx_clear_context" = "Clear tracks list"; "audio_ctx_clear_context" = "Clear tracks list";
"is_x_audio_zero" = "No audios";
"is_x_audio_one" = "Just one audio.";
"is_x_audio_few" = "Just $1 audios.";
"is_x_audio_many" = "Just $1 audios.";
"is_x_audio_other" = "Just $1 audios.";
/* Notifications */ /* Notifications */
"feedback" = "Feedback"; "feedback" = "Feedback";
@ -2113,6 +2124,7 @@
"s_apps" = "Applications"; "s_apps" = "Applications";
"s_posts" = "Posts"; "s_posts" = "Posts";
"s_comments" = "Comments"; "s_comments" = "Comments";
"s_photos" = "Photos";
"s_videos" = "Videos"; "s_videos" = "Videos";
"s_audios" = "Music"; "s_audios" = "Music";
"s_audios_playlists" = "Playlists"; "s_audios_playlists" = "Playlists";
@ -2223,6 +2235,7 @@
"mobile_like" = "Like"; "mobile_like" = "Like";
"mobile_user_info_hide" = "Hide"; "mobile_user_info_hide" = "Hide";
"mobile_user_info_show_details" = "Show details"; "mobile_user_info_show_details" = "Show details";
"mobile_attachment_only_for_pc" = "This attachments is not implemented for PDA version. Please, view it on PC.";
/* Fullscreen player */ /* Fullscreen player */
@ -2385,3 +2398,17 @@
"select_doc" = "Attach document"; "select_doc" = "Attach document";
"no_documents" = "No documents found"; "no_documents" = "No documents found";
"go_to_my_documents" = "Go to own documents"; "go_to_my_documents" = "Go to own documents";
/* Fave */
"faves" = "Bookmarks";
"faves_empty_tip" = "There will be your liked content.";
"faves_posts_empty_tip" = "There will be posts liked by you.";
"faves_comments_empty_tip" = "There will be comments liked by you.";
"faves_photos_empty_tip" = "There will be photos liked by you.";
"faves_videos_empty_tip" = "There will be videos liked by you.";
"faves_zero" = "No bookmarks";
"faves_one" = "One bookmark";
"faves_few" = "$1 bookmarks";
"faves_many" = "$1 bookmarks";
"faves_other" = "$1 bookmarks";

View file

@ -45,8 +45,8 @@
"register_meta_desc" = "$1 желісіне тіркеліңіз!"; "register_meta_desc" = "$1 желісіне тіркеліңіз!";
"register_referer_meta_title" = "$1 сізді $2 желіне шақырады!"; "register_referer_meta_title" = "$1 сізді $2 желіне шақырады!";
"register_referer_meta_desc" = "$1 және одан көп пайдаланушылармен $2 желісінде қосылыңыз!"; "register_referer_meta_desc" = "$1 және одан көп пайдаланушылармен $2 желісінде қосылыңыз!";
"registration_welcome_1" = "is a universal colleague search tool based on the VKontakte structure. дегеніміз — VK құрылымына негізделген әмбебап әріптестерді іздеу құралы"; "registration_welcome_1" = "дегеніміз — VK құрылымына негізделген әмбебап әріптестерді іздеу құралы";
"registration_welcome_2" = "Достар, сыныптастар, көршілер мен әріптестер үнемі байланыста болғанын қалаймыз."; "registration_welcome_2" = "Біз достар, сыныптастар, көршілер мен әріптестер үнемі байланыста болғанын қалаймыз.";
"users" = "Пайдаланушылар"; "users" = "Пайдаланушылар";
"other_fields" = "Қалғаны"; "other_fields" = "Қалғаны";

View file

@ -624,6 +624,8 @@
"my_feed" = "Мои Новости"; "my_feed" = "Мои Новости";
"my_feedback" = "Мои Ответы"; "my_feedback" = "Мои Ответы";
"my_settings" = "Мои Настройки"; "my_settings" = "Мои Настройки";
"bookmarks" = "Закладки";
"bookmarks_tab" = "Избранное";
"bug_tracker" = "Баг-трекер"; "bug_tracker" = "Баг-трекер";
"menu_settings" = "Настройки"; "menu_settings" = "Настройки";
@ -674,6 +676,7 @@
"cut" = "Квадратные"; "cut" = "Квадратные";
"round_avatars" = "Круглые"; "round_avatars" = "Круглые";
"apply_style_for_this_device" = "Применить стиль только для этого устройства"; "apply_style_for_this_device" = "Применить стиль только для этого устройства";
"ui_settings_window" = "Дополнительные настройки";
"search_for_groups" = "Поиск групп"; "search_for_groups" = "Поиск групп";
"search_for_users" = "Поиск людей"; "search_for_users" = "Поиск людей";
"search_for_posts" = "Поиск записей"; "search_for_posts" = "Поиск записей";
@ -881,7 +884,8 @@
"audio_embed_deleted" = "Аудиозапись была удалена"; "audio_embed_deleted" = "Аудиозапись была удалена";
"audio_embed_withdrawn" = "Аудиозапись была изъята по обращению правообладателя."; "audio_embed_withdrawn" = "Аудиозапись была изъята по обращению правообладателя.";
"audio_embed_forbidden" = "Настройки приватности пользователя не позволяют встраивать эту композицию"; "audio_embed_forbidden" = "Настройки приватности пользователя не позволяют встраивать эту композицию";
"audio_embed_processing" = "Аудио ещё обрабатывается, либо обработалось неправильно."; "audio_embed_processing" = "Аудио находится в обработке.";
"audio_embed_processing_bait" = "Всё равно хочу воспроизвести";
"audios_count_zero" = "Нет аудиозаписей"; "audios_count_zero" = "Нет аудиозаписей";
"audios_count_one" = "Одна аудиозапись"; /* сингл */ "audios_count_one" = "Одна аудиозапись"; /* сингл */
@ -900,6 +904,7 @@
"audio_search" = "Поиск"; "audio_search" = "Поиск";
"my_audios_small" = "Мои аудиозаписи"; "my_audios_small" = "Мои аудиозаписи";
"my_audios_small_uploaded" = "Загруженное";
"my_playlists" = "Мои плейлисты"; "my_playlists" = "Мои плейлисты";
"playlists" = "Плейлисты"; "playlists" = "Плейлисты";
"audios_explicit" = "Содержит нецензурную лексику"; "audios_explicit" = "Содержит нецензурную лексику";
@ -992,6 +997,12 @@
"audio_ctx_play_next" = "Воспроизвести следующим"; "audio_ctx_play_next" = "Воспроизвести следующим";
"audio_ctx_clear_context" = "Очистить список треков"; "audio_ctx_clear_context" = "Очистить список треков";
"is_x_audio_zero" = "Нету аудиозаписей";
"is_x_audio_one" = "Всего одна аудиозапись.";
"is_x_audio_few" = "Всего $1 аудиозаписи.";
"is_x_audio_many" = "Всего $1 аудиозаписей.";
"is_x_audio_other" = "Всего $1 аудиозаписей.";
/* Notifications */ /* Notifications */
"feedback" = "Ответы"; "feedback" = "Ответы";
@ -2008,6 +2019,7 @@
"s_apps" = "Приложения"; "s_apps" = "Приложения";
"s_posts" = "Записи"; "s_posts" = "Записи";
"s_comments" = "Комментарии"; "s_comments" = "Комментарии";
"s_photos" = "Фотографии";
"s_videos" = "Видео"; "s_videos" = "Видео";
"s_audios" = "Аудио"; "s_audios" = "Аудио";
"s_audios_playlists" = "Плейлисты"; "s_audios_playlists" = "Плейлисты";
@ -2118,6 +2130,7 @@
"mobile_like" = "Нравится"; "mobile_like" = "Нравится";
"mobile_user_info_hide" = "Скрыть"; "mobile_user_info_hide" = "Скрыть";
"mobile_user_info_show_details" = "Показать подробнее"; "mobile_user_info_show_details" = "Показать подробнее";
"mobile_attachment_only_for_pc" = "Вложение недоступно в PDA версии, его просмотр возможен только с другого устройства";
/* Fullscreen player */ /* Fullscreen player */
@ -2280,3 +2293,18 @@
"select_doc" = "Выбор документа"; "select_doc" = "Выбор документа";
"no_documents" = "Документов нет"; "no_documents" = "Документов нет";
"go_to_my_documents" = "Перейти к своим документам"; "go_to_my_documents" = "Перейти к своим документам";
/* Fave */
"faves" = "Закладки";
"faves_empty_tip" = "Здесь будет отображаться понравившийся Вам контент...";
"faves_posts_empty_tip" = "Здесь будут отображаться понравившиеся Вам записи.";
"faves_comments_empty_tip" = "Здесь будут отображаться понравившиеся Вам комментарии.";
"faves_photos_empty_tip" = "Здесь будут отображаться понравившиеся Вам фотографии.";
"faves_videos_empty_tip" = "Здесь будут отображаться понравившиеся Вам видео.";
"faves_zero" = "Ни одной закладки"; /* на украинском можно как ни одной вподобайки */
"faves_one" = "Одна закладка";
"faves_few" = "$1 закладки";
"faves_many" = "$1 закладок";
"faves_other" = "$1 закладок";

View file

@ -568,7 +568,7 @@
"notes_list_other" = "Знайдено $1 нотаток"; "notes_list_other" = "Знайдено $1 нотаток";
/* Notes: Article Viewer */ /* Notes: Article Viewer */
"aw_legacy_ui" = "Класичне дієвидло"; "aw_legacy_ui" = "Класичний інтерфейс";
"select_note" = "Вибір нотатки"; "select_note" = "Вибір нотатки";
"no_notes" = "Ви не маєте жодної нотатки"; "no_notes" = "Ви не маєте жодної нотатки";
@ -597,6 +597,8 @@
"my_feedback" = "Мої Відповіді"; "my_feedback" = "Мої Відповіді";
"my_apps" = "Застосунки"; "my_apps" = "Застосунки";
"my_settings" = "Мої Налаштування"; "my_settings" = "Мої Налаштування";
"bookmarks" = "Вподобані";
"bookmarks_tab" = "Вподобане";
"bug_tracker" = "Баг-трекер"; "bug_tracker" = "Баг-трекер";
"menu_settings" = "Налаштування"; "menu_settings" = "Налаштування";
@ -647,6 +649,7 @@
"cut" = "Квадратні"; "cut" = "Квадратні";
"round avatars" = "Круглі"; "round avatars" = "Круглі";
"apply_style_for_this_device" = "Застосувати стиль лише для цього пристрою"; "apply_style_for_this_device" = "Застосувати стиль лише для цього пристрою";
"ui_settings_window" = "Додаткові налаштування";
"search_for_groups" = "Пошук спільнот"; "search_for_groups" = "Пошук спільнот";
"search_for_users" = "Пошук користувачів"; "search_for_users" = "Пошук користувачів";
"search_for_posts" = "Пошук дописів"; "search_for_posts" = "Пошук дописів";
@ -656,6 +659,7 @@
"search_for_notes" = "Пошук нотаток"; "search_for_notes" = "Пошук нотаток";
"search_for_audios" = "Пошук музики"; "search_for_audios" = "Пошук музики";
"search_for_audios_playlists" = "Пошук списків відтворення"; "search_for_audios_playlists" = "Пошук списків відтворення";
"search_for_docs" = "Пошук документів";
"search_button" = "Знайти"; "search_button" = "Знайти";
"search_placeholder" = "Почніть вводити будь-яке ім'я, назву чи слово"; "search_placeholder" = "Почніть вводити будь-яке ім'я, назву чи слово";
"results_zero" = "Немає результатів"; "results_zero" = "Немає результатів";
@ -852,7 +856,8 @@
"audio_embed_deleted" = "Аудіозапис видалено"; "audio_embed_deleted" = "Аудіозапис видалено";
"audio_embed_withdrawn" = "Аудіозапис було вилучено на вимогу правовласника"; "audio_embed_withdrawn" = "Аудіозапис було вилучено на вимогу правовласника";
"audio_embed_forbidden" = "Налаштування конфіденційності користувача не дозволяють вбудовувати цей аудіозапис"; "audio_embed_forbidden" = "Налаштування конфіденційності користувача не дозволяють вбудовувати цей аудіозапис";
"audio_embed_processing" = "Аудіозапис ще обробляється або було оброблено неправильно."; "audio_embed_processing" = "Аудіозапис ще обробляється або був неправильно оброблений.";
"audio_embed_processing_bait" = "Все одно відтворити";
"audios_count_zero" = "Немає аудіозаписів"; "audios_count_zero" = "Немає аудіозаписів";
"audios_count_one" = "Один аудіозапис"; "audios_count_one" = "Один аудіозапис";
@ -871,6 +876,7 @@
"audio_search" = "Пошук"; "audio_search" = "Пошук";
"my_audios_small" = "Мої аудіозаписи"; "my_audios_small" = "Мої аудіозаписи";
"my_audios_small_uploaded" = "Завантажене";
"my_playlists" = "Мій список відтворення"; "my_playlists" = "Мій список відтворення";
"playlists" = "Списки відтворення"; "playlists" = "Списки відтворення";
"audios_explicit" = "Має нецензурну лексику"; "audios_explicit" = "Має нецензурну лексику";
@ -961,6 +967,12 @@
"audio_ctx_play_next" = "Відтворити наступним"; "audio_ctx_play_next" = "Відтворити наступним";
"audio_ctx_clear_context" = "Очистити список аудіозаписів"; "audio_ctx_clear_context" = "Очистити список аудіозаписів";
"is_x_audio_zero" = "Немає аудіозаписів";
"is_x_audio_one" = "Один аудіозапис.";
"is_x_audio_few" = "Всього $1 аудіозаписа.";
"is_x_audio_many" = "Всього $1 аудіозаписів.";
"is_x_audio_other" = "Всього $1 аудіозаписів.";
/* Notifications */ /* Notifications */
"feedback" = "Відповіді"; "feedback" = "Відповіді";
@ -1200,13 +1212,15 @@
"report_question_text" = "Що саме Ви вважаєте неприпустимим у цьому матеріалі?"; "report_question_text" = "Що саме Ви вважаєте неприпустимим у цьому матеріалі?";
"report_reason" = "Причина скарги"; "report_reason" = "Причина скарги";
"reason" = "Причина"; "reason" = "Причина";
"going_to_report_app" = "Ви збираєтеся поскаржитися на цей застосунок."; "going_to_report_app" = "Ви бажаєте поскаржитися на цей застосунок.";
"going_to_report_club" = "Ви збираєтеся поскаржитися на цю спільноту."; "going_to_report_club" = "Ви бажаєте поскаржитися на цю спільноту.";
"going_to_report_photo" = "Ви збираєтеся поскаржитися на цю фотографію."; "going_to_report_photo" = "Ви бажаєте поскаржитися на цю фотографію.";
"going_to_report_user" = "Ви збираєтеся поскаржитися на цього користувача."; "going_to_report_user" = "Ви бажаєте поскаржитися на цього користувача.";
"going_to_report_video" = "Ви збираєтеся поскаржитися на цей відеозапис."; "going_to_report_video" = "Ви бажаєте поскаржитися на цей відеозапис.";
"going_to_report_post" = "Ви збираєтеся поскаржитися на цей запис."; "going_to_report_audio" = "Ви бажаєте поскаржитися на цей аудіозапис.";
"going_to_report_comment" = "Ви збираєтеся поскаржитися на цей коментар."; "going_to_report_doc" = "Ви бажаєте поскаржитися на цей документ.";
"going_to_report_post" = "Ви бажаєте поскаржитися на цей запис.";
"going_to_report_comment" = "Ви бажаєте поскаржитися на цей коментар.";
"comment" = "Коментар"; "comment" = "Коментар";
"sender" = "Відправник"; "sender" = "Відправник";
@ -1931,7 +1945,7 @@
"tour_section_12_text_2_1" = "Ви можете задати варіант показу аватара користувача: стандартне, заокруглені та квадратні (1:1)"; "tour_section_12_text_2_1" = "Ви можете задати варіант показу аватара користувача: стандартне, заокруглені та квадратні (1:1)";
"tour_section_12_text_2_2" = "Ці налаштування буде видно тільки Вам"; "tour_section_12_text_2_2" = "Ці налаштування буде видно тільки Вам";
"tour_section_12_title_3" = "Редагування правого меню"; "tour_section_12_title_3" = "Редагування правого меню";
"tour_section_12_text_3_1" = "За потреби, ви можете приховати непотрібні розділи сайту"; "tour_section_12_text_3_1" = "Ви можете приховати непотрібні розділи сайту";
"tour_section_12_text_3_2" = "<b>Нагадування: </b>Розділи першої потреби (Моя Сторінка; Мої Друзі; Мої Відповіді; Мої Налаштування) приховати не можна"; "tour_section_12_text_3_2" = "<b>Нагадування: </b>Розділи першої потреби (Моя Сторінка; Мої Друзі; Мої Відповіді; Мої Налаштування) приховати не можна";
"tour_section_12_title_4" = "Вид дописів"; "tour_section_12_title_4" = "Вид дописів";
"tour_section_12_text_4_1" = "Якщо набрид старий дизайн стіни, який був у колись популярному оригінальному ВКонтакті, то ви завжди можете змінити вигляд дописів на Мікроблог"; "tour_section_12_text_4_1" = "Якщо набрид старий дизайн стіни, який був у колись популярному оригінальному ВКонтакті, то ви завжди можете змінити вигляд дописів на Мікроблог";
@ -1974,17 +1988,20 @@
"s_apps" = "Застосунки"; "s_apps" = "Застосунки";
"s_posts" = "Дописи"; "s_posts" = "Дописи";
"s_comments" = "Коментарі"; "s_comments" = "Коментарі";
"s_photos" = "Фотографії";
"s_videos" = "Відео"; "s_videos" = "Відео";
"s_audios" = "Музика"; "s_audios" = "Музика";
"s_audios_playlists" = "Списки відтворення"; "s_audios_playlists" = "Списки відтворення";
"s_by_people" = "за користувачами"; "s_documents" = "Документи";
"s_by_groups" = "за спільнотами"; "s_by_people" = "по користувачах";
"s_by_posts" = "за дописах"; "s_by_groups" = "по спільнотах";
"s_by_comments" = "за коментарями"; "s_by_posts" = "по дописах";
"s_by_videos" = "за відео"; "s_by_comments" = "по коментарях";
"s_by_apps" = "за застосунках"; "s_by_videos" = "по відео";
"s_by_audios" = "за аудіозаписами"; "s_by_apps" = "по застосунках";
"s_by_audios_playlists" = "за списках відтворення"; "s_by_audios" = "по аудіозаписах";
"s_by_audios_playlists" = "по списках відтворення";
"s_by_documents" = "по документах";
"s_order_by" = "Сортувати за..."; "s_order_by" = "Сортувати за...";
@ -2007,6 +2024,7 @@
"s_date_after" = "Після"; "s_date_after" = "Після";
"s_main" = "Основне"; "s_main" = "Основне";
"s_type" = "Тип";
"s_now_on_site" = "зараз на сайті"; "s_now_on_site" = "зараз на сайті";
"s_with_photo" = "з фото"; "s_with_photo" = "з фото";
@ -2081,7 +2099,8 @@
"mobile_menu" = "Меню"; "mobile_menu" = "Меню";
"mobile_like" = "Подобається"; "mobile_like" = "Подобається";
"mobile_user_info_hide" = "Приховувати"; "mobile_user_info_hide" = "Приховувати";
"mobile_user_info_show_details" = "Показати докладніше"; "mobile_user_info_show_details" = "Відобразити докладніше";
"mobile_attachment_only_for_pc" = "Вкладення недоступне в PDA версії, його перегляд можливий тільки з іншого пристрою";
"my" = "Мої"; "my" = "Мої";
"enter_a_name_or_artist" = "Введіть назву або виконавця..."; "enter_a_name_or_artist" = "Введіть назву або виконавця...";
@ -2161,3 +2180,88 @@
"upd_in_general" = "Оновлення фото сторінки"; "upd_in_general" = "Оновлення фото сторінки";
"on_wall" = "На стіні"; "on_wall" = "На стіні";
"sign_short" = "Підпис"; "sign_short" = "Підпис";
/* Documents */
"my_documents" = "Документи";
"my_documents_objectively" = "Мої Документи";
"documents_of_group" = "Документи спільноти";
"search_by_documents" = "Пошук по документах...";
"documents" = "Документи";
"document_uploading_in_general" = "Завантаження документа";
"document_editing_in_general" = "Редагування документа";
"file" = "Файл";
"tags" = "Теги";
"owner_is_hidden" = "Автор прихований";
"accessbility" = "Доступність";
"download_file" = "Завантажити файл";
"remove" = "Видалити";
"document" = "Документ";
"documents_all" = "Всі документи";
"document_type_0" = "Всі";
"document_type_1" = "Текстові";
"document_type_2" = "Архіви";
"document_type_3" = "GIF";
"document_type_4" = "Зображення";
"document_type_5" = "Аудіо";
"document_type_6" = "Відео";
"document_type_7" = "Книги";
"document_type_8" = "Інше";
"documents_one" = "$1 документ";
"documents_few" = "$1 документа";
"documents_many" = "$1 документів";
"documents_other" = "$1 документів";
"documents_zero" = "$1 документів";
"you_have_x_documents_one" = "У Вас $1 документ";
"you_have_x_documents_few" = "У Вас $1 документа";
"you_have_x_documents_many" = "У Вас $1 документів";
"you_have_x_documents_other" = "У Вас $1 документів";
"you_have_x_documents_zero" = "У Вас $1 документів";
"group_has_x_documents_one" = "Ця спільнота має $1 документ";
"group_has_x_documents_few" = "Ця спільнота має $1 документа";
"group_has_x_documents_many" = "Ця спільнота має $1 документів";
"group_has_x_documents_other" = "Ця спільнота має $1 документів";
"group_has_x_documents_zero" = "Ця спільнота має $1 документів";
"x_documents_in_tab_one" = "Ця вкладка має $1 документ";
"x_documents_in_tab_few" = "Ця вкладка має $1 документа";
"x_documents_in_tab_many" = "Ця вкладка має $1 документів";
"x_documents_in_tab_other" = "Ця вкладка має $1 документів";
"x_documents_in_tab_zero" = "Ця вкладка має $1 документів";
"there_is_no_documents_alright" = "Тут немає документів.";
"limitations_file_limit_size" = "Файл не може перевищувати $1 МБ";
"limitations_file_allowed_formats" = "Дозволені наступні типи файлів";
"limitations_file_author_rights" = "Файл не має порушувати авторські та суміжні права";
"select_file_fp" = "Обрати файл";
"error_file_too_big" = "Файл перевищує ліміт розміру.";
"error_file_invalid_format" = "Файл має заборонене розширення.";
"error_file_adding_copied" = "Не вдалося завантажити файл; його вже додано.";
"error_file_preview" = "Не вдалося завантажити файл: файл пошкоджено.";
"private_document" = "Приватний (за посиланням)";
"public_document" = "Публічний";
"documents_sort_add" = "За датою завантаження";
"documents_sort_alphabet" = "A-Z/А-Я";
"documents_sort_size" = "За розміром";
"select_doc" = "Вибір документа";
"no_documents" = "Документів немає";
"go_to_my_documents" = "Перейти до Моїх документів";
/* Fave */
"faves" = "Закладки";
"faves_empty_tip" = "Тут відображатиметься вподобаний Вами контент...";
"faves_posts_empty_tip" = "Тут відображатимуться вподобані Вами дописи.";
"faves_comments_empty_tip" = "Тут відображатимуться вподобані Вами коментарі.";
"faves_photos_empty_tip" = "Тут відображатимуться вподобані Вами фотографії.";
"faves_videos_empty_tip" = "Тут відображатимуться вподобані Вами відеозаписи.";
"faves_zero" = "Жодного вподобання";
"faves_one" = "Одна вподобання";
"faves_few" = "$1 вподобання";
"faves_many" = "$1 вподобань";
"faves_other" = "$1 вподобань";

View file

@ -7,9 +7,7 @@ namespace openvk;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
$_SERVER["HTTP_ACCEPT_LANGUAGE"] = false; require(__DIR__ . "/chandler_loader.php");
$bootstrap = require(__DIR__ . "/../../../chandler/Bootstrap.php");
$bootstrap->ignite(true);
$application = new Application(); $application = new Application();
$application->add(new CLI\UpgradeCommand()); $application->add(new CLI\UpgradeCommand());

14
phpstan.neon Normal file
View file

@ -0,0 +1,14 @@
parameters:
level: 0
paths:
- CLI
- ServiceAPI
- VKAPI
- Web
- bootstrap.php
- openvkctl
- chandler_loader.php
bootstrapFiles:
- chandler_loader.php
- bootstrap.php

View file

@ -660,3 +660,26 @@ ul {
.doc_icon.no_image span::before { .doc_icon.no_image span::before {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAKCAYAAABmBXS+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsAAAA7AAWrWiQkAAABXSURBVChTY2RAAoFe9ZuEBOR93314uHn9tkY/qDADE5QGA5ACZBoGUBThAkQpYkyOmvcfysYJiDMJROAzbe6yJEZ4EGBTCFIAolHCCVkhTAFWgGkiAwMAzxkZ3qVQ7YEAAAAASUVORK5CYII="); background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAKCAYAAABmBXS+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsAAAA7AAWrWiQkAAABXSURBVChTY2RAAoFe9ZuEBOR93314uHn9tkY/qDADE5QGA5ACZBoGUBThAkQpYkyOmvcfysYJiDMJROAzbe6yJEZ4EGBTCFIAolHCCVkhTAFWgGkiAwMAzxkZ3qVQ7YEAAAAASUVORK5CYII=");
} }
.bigPlayer .bigPlayerWrapper .absoluteButtons > div {
background: #1e1a2b;
border: 1px solid #2c2640;
}
.insertedPhoto {
background: #1e1a2b;
border: 1px solid #403a56;
}
.ovk-modal-player-window #ovk-player-info {
background: #0e0b1a;
}
.header_navigation #search_box #searchBoxFastTips {
background: #181826;
border-color: #2c2640;
}
.header_navigation #search_box #searchBoxFastTips a:hover, .header_navigation #search_box #searchBoxFastTips a:focus {
background: #111322;
}