Groups: Wall: add suggestions (#935)

* Wall: add early suggestions

* Fix br

* Fix empty posts

* fck

* Add offset for api

* Add notifications of new suggestion posts

* Fix mentions in suggested posts

* 🤮🤢

* Change regex

Теперь оно удаляет все теги а не только <br>

* Add da koroche pohuy

* Эдд апи метходс

Методы нестандартные немного

* Pon

* Add skloneniyia

* newlines

* int

* Update loaders and add avtopodgruzka postov

* Update JOERGK.strings

* Blin

* Remove repeated code, fix loaded buttons on chr...

...ome and fix getting suggested posts via API.Wall.getPost

* Fix polls

* Fihes

Теперь уведомление о принятии поста не приходит, если вы приняли свой же пост

Пофикшен баг перехода в предложку

Добавлен старый вид постов в предложке

Теперь счётчик постов в предложке у прикреплённой группы обновляется при принятии или отклонении поста

Убрано всплывающее уведомление об отклонении поста (оно раздражает)

Теперь если вы посмотрели все посты на одной странице (не на первой) и на ней не осталось постов, вас телепортирует на предыдущую страницу

* Remove ability to delete your accepted psto

* oi blin

* Improvements 2 api

* g

* openvk.uk

Возможно, приведение кода к кодстайлу (удаление скобочек то есть)

* aiaks

* al_wall.js -> al_suggestions.js

* 👨‍💻 Add 👨‍💻 fading 👨‍💻

* Add "owner's posts' and "other's posts"

Давайте рофлить👨‍💻👨‍💻👨‍💻

* planshet openvk

Add tabs for post view, add signer's object in wall get and add person icon in microblog

* Simplefai ze kod

* PHP 8 FIX WATAFAK

* Add indesk
This commit is contained in:
lalka2018 2023-11-16 19:44:12 +03:00 committed by GitHub
parent 3112372d01
commit 4699fcbeb9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1195 additions and 57 deletions

View file

@ -22,7 +22,10 @@ class Wall implements Handler
{ {
$post = $this->posts->get($id); $post = $this->posts->get($id);
if(!$post || $post->isDeleted()) if(!$post || $post->isDeleted())
$reject("No post with id=$id"); $reject(53, "No post with id=$id");
if($post->getSuggestionType() != 0)
$reject(25, "Can't get suggested post");
$res = (object) []; $res = (object) [];
$res->id = $post->getId(); $res->id = $post->getId();
@ -129,7 +132,7 @@ class Wall implements Handler
]; ];
foreach($videos as $video) { foreach($videos as $video) {
$res = json_decode(json_encode($video->toVkApiStruct()), true); $res = json_decode(json_encode($video->toVkApiStruct($this->user)), true);
$res["video"]["author_name"] = $video->getOwner()->getCanonicalName(); $res["video"]["author_name"] = $video->getOwner()->getCanonicalName();
$arr["items"][] = $res; $arr["items"][] = $res;

View file

@ -2,6 +2,7 @@
namespace openvk\VKAPI\Handlers; namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Clubs as ClubsRepo; use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
use openvk\Web\Models\Repositories\Users as UsersRepo; use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Repositories\Posts as PostsRepo;
use openvk\Web\Models\Entities\Club; use openvk\Web\Models\Entities\Club;
final class Groups extends VKAPIRequestHandler final class Groups extends VKAPIRequestHandler
@ -80,6 +81,19 @@ final class Groups extends VKAPIRequestHandler
break; break;
case "members_count": case "members_count":
$rClubs[$i]->members_count = $usr->getFollowersCount(); $rClubs[$i]->members_count = $usr->getFollowersCount();
break;
case "can_suggest":
$rClubs[$i]->can_suggest = !$usr->canBeModifiedBy($this->getUser()) && $usr->getWallType() == 2;
break;
# unstandard feild
case "suggested_count":
if($usr->getWallType() != 2) {
$rClubs[$i]->suggested_count = NULL;
break;
}
$rClubs[$i]->suggested_count = $usr->getSuggestedPostsCount($this->getUser());
break; break;
} }
} }
@ -188,6 +202,18 @@ final class Groups extends VKAPIRequestHandler
case "description": case "description":
$response[$i]->description = $clb->getDescription(); $response[$i]->description = $clb->getDescription();
break; break;
case "can_suggest":
$response[$i]->can_suggest = !$clb->canBeModifiedBy($this->getUser()) && $clb->getWallType() == 2;
break;
# unstandard feild
case "suggested_count":
if($clb->getWallType() != 2) {
$response[$i]->suggested_count = NULL;
break;
}
$response[$i]->suggested_count = $clb->getSuggestedPostsCount($this->getUser());
break;
case "contacts": case "contacts":
$contacts; $contacts;
$contactTmp = $clb->getManagers(1, true); $contactTmp = $clb->getManagers(1, true);
@ -288,7 +314,7 @@ final class Groups extends VKAPIRequestHandler
string $description = NULL, string $description = NULL,
string $screen_name = NULL, string $screen_name = NULL,
string $website = NULL, string $website = NULL,
int $wall = NULL, int $wall = -1,
int $topics = NULL, int $topics = NULL,
int $adminlist = NULL, int $adminlist = NULL,
int $topicsAboveWall = NULL, int $topicsAboveWall = NULL,
@ -308,17 +334,26 @@ final class Groups extends VKAPIRequestHandler
!empty($description) ? $club->setAbout($description) : NULL; !empty($description) ? $club->setAbout($description) : NULL;
!empty($screen_name) ? $club->setShortcode($screen_name) : NULL; !empty($screen_name) ? $club->setShortcode($screen_name) : NULL;
!empty($website) ? $club->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website) : NULL; !empty($website) ? $club->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website) : NULL;
!empty($wall) ? $club->setWall($wall) : NULL;
try {
$wall != -1 ? $club->setWall($wall) : NULL;
} catch(\Exception $e) {
$this->fail(50, "Invalid wall value");
}
!empty($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL; !empty($topics) ? $club->setEveryone_Can_Create_Topics($topics) : NULL;
!empty($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL; !empty($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL;
!empty($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL; !empty($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL;
!empty($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL; !empty($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL;
in_array($audio, [0, 1]) ? $club->setEveryone_can_upload_audios($audio) : NULL; in_array($audio, [0, 1]) ? $club->setEveryone_can_upload_audios($audio) : NULL;
try { try {
$club->save(); $club->save();
} catch(\TypeError $e) { } catch(\TypeError $e) {
$this->fail(8, "Nothing changed"); $this->fail(15, "Nothing changed");
} catch(\Exception $e) {
$this->fail(18, "An unknown error occurred: maybe you set an incorrect value?");
} }
return 1; return 1;
@ -472,7 +507,7 @@ final class Groups extends VKAPIRequestHandler
"title" => $club->getName(), "title" => $club->getName(),
"description" => $club->getDescription() != NULL ? $club->getDescription() : "", "description" => $club->getDescription() != NULL ? $club->getDescription() : "",
"address" => $club->getShortcode(), "address" => $club->getShortcode(),
"wall" => $club->canPost() == true ? 1 : 0, "wall" => $club->getWallType(), # отличается от вкшных но да ладно
"photos" => 1, "photos" => 1,
"video" => 0, "video" => 0,
"audio" => $club->isEveryoneCanUploadAudios() ? 1 : 0, "audio" => $club->isEveryoneCanUploadAudios() ? 1 : 0,

View file

@ -51,7 +51,7 @@ final class Newsfeed extends VKAPIRequestHandler
{ {
$this->requireUser(); $this->requireUser();
$queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND `posts`.`deleted` = 0"; $queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND `posts`.`deleted` = 0 AND `posts`.`suggested` = 0";
if($this->getUser()->getNsfwTolerance() === User::NSFW_INTOLERANT) if($this->getUser()->getNsfwTolerance() === User::NSFW_INTOLERANT)
$queryBase .= " AND `nsfw` = 0"; $queryBase .= " AND `nsfw` = 0";

View file

@ -1,7 +1,7 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers; namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Entities\User; use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Entities\Notifications\{WallPostNotification, RepostNotification, CommentNotification}; use openvk\Web\Models\Entities\Notifications\{PostAcceptedNotification, WallPostNotification, NewSuggestedPostsNotification, RepostNotification, CommentNotification};
use openvk\Web\Models\Repositories\Users as UsersRepo; use openvk\Web\Models\Repositories\Users as UsersRepo;
use openvk\Web\Models\Entities\Club; use openvk\Web\Models\Entities\Club;
use openvk\Web\Models\Repositories\Clubs as ClubsRepo; use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
@ -20,7 +20,7 @@ use openvk\Web\Models\Repositories\Audios as AudiosRepo;
final class Wall extends VKAPIRequestHandler final class Wall extends VKAPIRequestHandler
{ {
function get(int $owner_id, string $domain = "", int $offset = 0, int $count = 30, int $extended = 0): object function get(int $owner_id, string $domain = "", int $offset = 0, int $count = 30, int $extended = 0, string $filter = "all"): object
{ {
$this->requireUser(); $this->requireUser();
@ -29,7 +29,7 @@ final class Wall extends VKAPIRequestHandler
$items = []; $items = [];
$profiles = []; $profiles = [];
$groups = []; $groups = [];
$cnt = $posts->getPostCountOnUserWall($owner_id); $cnt = 0;
if ($owner_id > 0) if ($owner_id > 0)
$wallOnwer = (new UsersRepo)->get($owner_id); $wallOnwer = (new UsersRepo)->get($owner_id);
@ -43,7 +43,47 @@ final class Wall extends VKAPIRequestHandler
if(!$wallOnwer) if(!$wallOnwer)
$this->fail(15, "Access denied: wall is disabled"); // Don't search for logic here pls $this->fail(15, "Access denied: wall is disabled"); // Don't search for logic here pls
foreach($posts->getPostsFromUsersWall($owner_id, 1, $count, $offset) as $post) { $iteratorv;
switch($filter) {
case "all":
$iteratorv = $posts->getPostsFromUsersWall($owner_id, 1, $count, $offset);
$cnt = $posts->getPostCountOnUserWall($owner_id);
break;
case "owner":
$iteratorv = $posts->getOwnersPostsFromWall($owner_id, 1, $count, $offset);
$cnt = $posts->getOwnersCountOnUserWall($owner_id);
break;
case "others":
$iteratorv = $posts->getOthersPostsFromWall($owner_id, 1, $count, $offset);
$cnt = $posts->getOthersCountOnUserWall($owner_id);
break;
case "postponed":
$this->fail(42, "Postponed posts are not implemented.");
break;
case "suggests":
if($owner_id < 0) {
if($wallOnwer->getWallType() != 2)
$this->fail(125, "Group's wall type is open or closed");
if($wallOnwer->canBeModifiedBy($this->getUser())) {
$iteratorv = $posts->getSuggestedPosts($owner_id * -1, 1, $count, $offset);
$cnt = $posts->getSuggestedPostsCount($owner_id * -1);
} else {
$iteratorv = $posts->getSuggestedPostsByUser($owner_id * -1, $this->getUser()->getId(), 1, $count, $offset);
$cnt = $posts->getSuggestedPostsCountByUser($owner_id * -1, $this->getUser()->getId());
}
} else {
$this->fail(528, "Suggested posts avaiable only at groups");
}
break;
default:
$this->fail(254, "Invalid filter");
break;
}
foreach($iteratorv as $post) {
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId(); $from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
$attachments = []; $attachments = [];
@ -118,12 +158,23 @@ final class Wall extends VKAPIRequestHandler
]; ];
} }
$postType = "post";
$signerId = NULL;
if($post->getSuggestionType() != 0)
$postType = "suggest";
if($post->isSigned()) {
$actualAuthor = $post->getOwner(false);
$signerId = $actualAuthor->getId();
}
$items[] = (object)[ $items[] = (object)[
"id" => $post->getVirtualId(), "id" => $post->getVirtualId(),
"from_id" => $from_id, "from_id" => $from_id,
"owner_id" => $post->getTargetWall(), "owner_id" => $post->getTargetWall(),
"date" => $post->getPublicationTime()->timestamp(), "date" => $post->getPublicationTime()->timestamp(),
"post_type" => "post", "post_type" => $postType,
"text" => $post->getText(false), "text" => $post->getText(false),
"copy_history" => $repost, "copy_history" => $repost,
"can_edit" => $post->canBeEditedBy($this->getUser()), "can_edit" => $post->canBeEditedBy($this->getUser()),
@ -135,6 +186,7 @@ final class Wall extends VKAPIRequestHandler
"is_explicit" => $post->isExplicit(), "is_explicit" => $post->isExplicit(),
"attachments" => $attachments, "attachments" => $attachments,
"post_source" => $post_source, "post_source" => $post_source,
"signer_id" => $signerId,
"comments" => (object)[ "comments" => (object)[
"count" => $post->getCommentsCount(), "count" => $post->getCommentsCount(),
"can_post" => 1 "can_post" => 1
@ -156,6 +208,9 @@ final class Wall extends VKAPIRequestHandler
else else
$groups[] = $from_id * -1; $groups[] = $from_id * -1;
if($post->isSigned())
$profiles[] = $post->getOwner(false)->getId();
$attachments = NULL; # free attachments so it will not clone everythingg $attachments = NULL; # free attachments so it will not clone everythingg
} }
@ -298,12 +353,24 @@ final class Wall extends VKAPIRequestHandler
]; ];
} }
# TODO: $post->getVkApiType()
$postType = "post";
$signerId = NULL;
if($post->getSuggestionType() != 0)
$postType = "suggest";
if($post->isSigned()) {
$actualAuthor = $post->getOwner(false);
$signerId = $actualAuthor->getId();
}
$items[] = (object)[ $items[] = (object)[
"id" => $post->getVirtualId(), "id" => $post->getVirtualId(),
"from_id" => $from_id, "from_id" => $from_id,
"owner_id" => $post->getTargetWall(), "owner_id" => $post->getTargetWall(),
"date" => $post->getPublicationTime()->timestamp(), "date" => $post->getPublicationTime()->timestamp(),
"post_type" => "post", "post_type" => $postType,
"text" => $post->getText(false), "text" => $post->getText(false),
"copy_history" => $repost, "copy_history" => $repost,
"can_edit" => $post->canBeEditedBy($this->getUser()), "can_edit" => $post->canBeEditedBy($this->getUser()),
@ -314,6 +381,7 @@ final class Wall extends VKAPIRequestHandler
"is_pinned" => $post->isPinned(), "is_pinned" => $post->isPinned(),
"is_explicit" => $post->isExplicit(), "is_explicit" => $post->isExplicit(),
"post_source" => $post_source, "post_source" => $post_source,
"signer_id" => $signerId,
"attachments" => $attachments, "attachments" => $attachments,
"comments" => (object)[ "comments" => (object)[
"count" => $post->getCommentsCount(), "count" => $post->getCommentsCount(),
@ -336,6 +404,9 @@ final class Wall extends VKAPIRequestHandler
else else
$groups[] = $from_id * -1; $groups[] = $from_id * -1;
if($post->isSigned())
$profiles[] = $post->getOwner(false)->getId();
$attachments = NULL; # free attachments so it will not clone everything $attachments = NULL; # free attachments so it will not clone everything
$repost = NULL; # same $repost = NULL; # same
} }
@ -391,7 +462,7 @@ final class Wall extends VKAPIRequestHandler
]; ];
} }
function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0, string $attachments = ""): object function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0, string $attachments = "", int $post_id = 0): object
{ {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
@ -412,6 +483,46 @@ final class Wall extends VKAPIRequestHandler
if($canPost == false) $this->fail(15, "Access denied"); if($canPost == false) $this->fail(15, "Access denied");
if($post_id > 0) {
if($owner_id > 0)
$this->fail(62, "Suggested posts available only at groups");
$post = (new PostsRepo)->getPostById($owner_id, $post_id, true);
if(!$post || $post->isDeleted())
$this->fail(32, "Invald post");
if($post->getSuggestionType() == 0)
$this->fail(20, "Post is not suggested");
if($post->getSuggestionType() == 2)
$this->fail(16, "Post is declined");
if(!$post->canBePinnedBy($this->getUser()))
$this->fail(51, "Access denied");
$author = $post->getOwner();
$flags = 0;
$flags |= 0b10000000;
if($signed == 1)
$flags |= 0b01000000;
$post->setSuggested(0);
$post->setCreated(time());
$post->setFlags($flags);
if(!empty($message) && iconv_strlen($message) > 0)
$post->setContent($message);
$post->save();
if($author->getId() != $this->getUser()->getId())
(new PostAcceptedNotification($author, $post, $post->getWallOwner()))->emit();
return (object)["post_id" => $post->getVirtualId()];
}
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"]; $anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
if($wallOwner instanceof Club && $from_group == 1 && $signed != 1 && $anon) { if($wallOwner instanceof Club && $from_group == 1 && $signed != 1 && $anon) {
$manager = $wallOwner->getManager($this->getUser()); $manager = $wallOwner->getManager($this->getUser());
@ -440,6 +551,10 @@ final class Wall extends VKAPIRequestHandler
$post->setContent($message); $post->setContent($message);
$post->setFlags($flags); $post->setFlags($flags);
$post->setApi_Source_Name($this->getPlatform()); $post->setApi_Source_Name($this->getPlatform());
if($owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2)
$post->setSuggested(1);
$post->save(); $post->save();
} catch(\LogicException $ex) { } catch(\LogicException $ex) {
$this->fail(100, "One of the parameters specified was missing or invalid"); $this->fail(100, "One of the parameters specified was missing or invalid");
@ -528,6 +643,22 @@ final class Wall extends VKAPIRequestHandler
if($wall > 0 && $wall !== $this->user->identity->getId()) if($wall > 0 && $wall !== $this->user->identity->getId())
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit(); (new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
if($owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2) {
$suggsCount = (new PostsRepo)->getSuggestedPostsCount($wallOwner->getId());
if($suggsCount % 10 == 0) {
$managers = $wallOwner->getManagers();
$owner = $wallOwner->getOwner();
(new NewSuggestedPostsNotification($owner, $wallOwner))->emit();
foreach($managers as $manager) {
(new NewSuggestedPostsNotification($manager->getUser(), $wallOwner))->emit();
}
}
return (object)["post_id" => "on_view"];
}
return (object)["post_id" => $post->getVirtualId()]; return (object)["post_id" => $post->getVirtualId()];
} }
@ -831,6 +962,30 @@ final class Wall extends VKAPIRequestHandler
return 1; return 1;
} }
function delete(int $owner_id, int $post_id)
{
$this->requireUser();
$this->willExecuteWriteAction();
$post = (new PostsRepo)->getPostById($owner_id, $post_id, true);
if(!$post || $post->isDeleted())
$this->fail(583, "Invalid post");
$wallOwner = $post->getWallOwner();
if($post->getTargetWall() < 0 && !$post->getWallOwner()->canBeModifiedBy($this->getUser()) && $post->getWallOwner()->getWallType() != 1 && $post->getSuggestionType() == 0)
$this->fail(12, "Access denied: you can't delete your accepted post.");
if($post->getOwnerPost() == $this->getUser()->getId() || $post->getTargetWall() == $this->getUser()->getId() || $owner_id < 0 && $wallOwner->canBeModifiedBy($this->getUser())) {
$post->unwire();
$post->delete();
return 1;
} else {
$this->fail(15, "Access denied");
}
}
function edit(int $owner_id, int $post_id, string $message = "", string $attachments = "") { function edit(int $owner_id, int $post_id, string $message = "", string $attachments = "") {
$this->requireUser(); $this->requireUser();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();

View file

@ -3,7 +3,7 @@ namespace openvk\Web\Models\Entities;
use openvk\Web\Util\DateTime; use openvk\Web\Util\DateTime;
use openvk\Web\Models\RowModel; use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\{User, Manager}; use openvk\Web\Models\Entities\{User, Manager};
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Managers}; use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Managers, Posts};
use Nette\Database\Table\{ActiveRow, GroupedSelection}; use Nette\Database\Table\{ActiveRow, GroupedSelection};
use Chandler\Database\DatabaseConnection as DB; use Chandler\Database\DatabaseConnection as DB;
use Chandler\Security\User as ChandlerUser; use Chandler\Security\User as ChandlerUser;
@ -24,6 +24,10 @@ class Club extends RowModel
const SUBSCRIBED = 1; const SUBSCRIBED = 1;
const REQUEST_SENT = 2; const REQUEST_SENT = 2;
const WALL_CLOSED = 0;
const WALL_OPEN = 1;
const WALL_LIMITED = 2;
function getId(): int function getId(): int
{ {
return $this->getRecord()->id; return $this->getRecord()->id;
@ -46,6 +50,11 @@ class Club extends RowModel
return is_null($avPhoto) ? "$serverUrl/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURLBySizeId($size); return is_null($avPhoto) ? "$serverUrl/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURLBySizeId($size);
} }
function getWallType(): int
{
return $this->getRecord()->wall;
}
function getAvatarLink(): string function getAvatarLink(): string
{ {
$avPhoto = $this->getAvatarPhoto(); $avPhoto = $this->getAvatarPhoto();
@ -183,6 +192,14 @@ class Club extends RowModel
return true; return true;
} }
function setWall(int $type)
{
if($type > 2 || $type < 0)
throw new \LogicException("Invalid wall");
$this->stateChanges("wall", $type);
}
function isSubscriptionAccepted(User $user): bool function isSubscriptionAccepted(User $user): bool
{ {
return !is_null($this->getRecord()->related("subscriptions.follower")->where([ return !is_null($this->getRecord()->related("subscriptions.follower")->where([
@ -292,6 +309,21 @@ class Club extends RowModel
} }
} }
function getSuggestedPostsCount(User $user = NULL)
{
$count = 0;
if(is_null($user))
return NULL;
if($this->canBeModifiedBy($user))
$count = (new Posts)->getSuggestedPostsCount($this->getId());
else
$count = (new Posts)->getSuggestedPostsCountByUser($this->getId(), $user->getId());
return $count;
}
function getManagers(int $page = 1, bool $ignoreHidden = false): \Traversable function getManagers(int $page = 1, bool $ignoreHidden = false): \Traversable
{ {
$rels = $this->getRecord()->related("group_coadmins.club")->page($page, 6); $rels = $this->getRecord()->related("group_coadmins.club")->page($page, 6);

View file

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Notifications;
use openvk\Web\Models\Entities\{User, Club};
final class NewSuggestedPostsNotification extends Notification
{
protected $actionCode = 7;
function __construct(User $owner, Club $group)
{
parent::__construct($owner, $owner, $group, time(), "");
}
}

View file

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Notifications;
use openvk\Web\Models\Entities\{User, Club, Post};
final class PostAcceptedNotification extends Notification
{
protected $actionCode = 6;
function __construct(User $author, Post $post, Club $group)
{
parent::__construct($author, $post, $group, time(), "");
}
}

View file

@ -4,7 +4,7 @@ use openvk\Web\Models\Exceptions\TooMuchOptionsException;
use openvk\Web\Util\DateTime; use openvk\Web\Util\DateTime;
use \UnexpectedValueException; use \UnexpectedValueException;
use Nette\InvalidStateException; use Nette\InvalidStateException;
use openvk\Web\Models\Repositories\Users; use openvk\Web\Models\Repositories\{Users, Posts};
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Exceptions\PollLockedException; use openvk\Web\Models\Exceptions\PollLockedException;
use openvk\Web\Models\Exceptions\AlreadyVotedException; use openvk\Web\Models\Exceptions\AlreadyVotedException;
@ -165,7 +165,7 @@ class Poll extends Attachable
function canVote(User $user): bool function canVote(User $user): bool
{ {
return !$this->hasEnded() && !$this->hasVoted($user); return !$this->hasEnded() && !$this->hasVoted($user) && !is_null($this->getAttachedPost()) && $this->getAttachedPost()->getSuggestionType() == 0;
} }
function vote(User $user, array $optionIds): void function vote(User $user, array $optionIds): void
@ -292,4 +292,17 @@ class Poll extends Attachable
]); ]);
} }
} }
function getAttachedPost()
{
$post = DatabaseConnection::i()->getContext()->table("attachments")
->where(
["attachable_type" => static::class,
"attachable_id" => $this->getId()])->fetch();
if(!is_null($post->target_id))
return (new Posts)->get($post->target_id);
else
return NULL;
}
} }

View file

@ -207,6 +207,9 @@ class Post extends Postable
function canBeDeletedBy(User $user): bool function canBeDeletedBy(User $user): bool
{ {
if($this->getTargetWall() < 0 && !$this->getWallOwner()->canBeModifiedBy($user) && $this->getWallOwner()->getWallType() != 1 && $this->getSuggestionType() == 0)
return false;
return $this->getOwnerPost() === $user->getId() || $this->canBePinnedBy($user); return $this->getOwnerPost() === $user->getId() || $this->canBePinnedBy($user);
} }
@ -246,6 +249,11 @@ class Post extends Postable
$this->save(); $this->save();
} }
function getSuggestionType()
{
return $this->getRecord()->suggested;
}
function toNotifApiStruct() function toNotifApiStruct()
{ {
$res = (object)[]; $res = (object)[];
@ -273,6 +281,12 @@ class Post extends Postable
if($this->getTargetWall() > 0) if($this->getTargetWall() > 0)
return $this->getPublicationTime()->timestamp() + WEEK > time() && $user->getId() == $this->getOwner(false)->getId(); return $this->getPublicationTime()->timestamp() + WEEK > time() && $user->getId() == $this->getOwner(false)->getId();
else {
if($this->isPostedOnBehalfOfGroup())
return $this->getWallOwner()->canBeModifiedBy($user);
else
return $user->getId() == $this->getOwner(false)->getId();
}
return $user->getId() == $this->getOwner(false)->getId(); return $user->getId() == $this->getOwner(false)->getId();
} }

View file

@ -33,7 +33,7 @@ abstract class Postable extends Attachable
{ {
$oid = (int) $this->getRecord()->owner; $oid = (int) $this->getRecord()->owner;
if(!$real && $this->isAnonymous()) if(!$real && $this->isAnonymous())
$oid = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"]; $oid = (int) OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"];
$oid = abs($oid); $oid = abs($oid);
if($oid > 0) if($oid > 0)

View file

@ -61,12 +61,57 @@ class Posts
"wall" => $user, "wall" => $user,
"pinned" => false, "pinned" => false,
"deleted" => false, "deleted" => false,
"suggested" => 0,
])->order("created DESC")->limit($perPage, $offset); ])->order("created DESC")->limit($perPage, $offset);
foreach($sel as $post) foreach($sel as $post)
yield new Post($post); yield new Post($post);
} }
function getOwnersPostsFromWall(int $user, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable
{
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
$offset ??= $perPage * ($page - 1);
$sel = $this->posts->where([
"wall" => $user,
"deleted" => false,
"suggested" => 0,
]);
if($user > 0)
$sel->where("owner", $user);
else
$sel->where("flags !=", 0);
$sel->order("created DESC")->limit($perPage, $offset);
foreach($sel as $post)
yield new Post($post);
}
function getOthersPostsFromWall(int $user, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable
{
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
$offset ??= $perPage * ($page - 1);
$sel = $this->posts->where([
"wall" => $user,
"deleted" => false,
"suggested" => 0,
]);
if($user > 0)
$sel->where("owner !=", $user);
else
$sel->where("flags", 0);
$sel->order("created DESC")->limit($perPage, $offset);
foreach($sel as $post)
yield new Post($post);
}
function getPostsByHashtag(string $hashtag, int $page = 1, ?int $perPage = NULL): \Traversable function getPostsByHashtag(string $hashtag, int $page = 1, ?int $perPage = NULL): \Traversable
{ {
$hashtag = "#$hashtag"; $hashtag = "#$hashtag";
@ -74,6 +119,7 @@ class Posts
->where("MATCH (content) AGAINST (? IN BOOLEAN MODE)", "+$hashtag") ->where("MATCH (content) AGAINST (? IN BOOLEAN MODE)", "+$hashtag")
->where("deleted", 0) ->where("deleted", 0)
->order("created DESC") ->order("created DESC")
->where("suggested", 0)
->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE); ->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
foreach($sel as $post) foreach($sel as $post)
@ -85,14 +131,22 @@ class Posts
$hashtag = "#$hashtag"; $hashtag = "#$hashtag";
$sel = $this->posts $sel = $this->posts
->where("content LIKE ?", "%$hashtag%") ->where("content LIKE ?", "%$hashtag%")
->where("deleted", 0); ->where("deleted", 0)
->where("suggested", 0);
return sizeof($sel); return sizeof($sel);
} }
function getPostById(int $wall, int $post): ?Post function getPostById(int $wall, int $post, bool $forceSuggestion = false): ?Post
{ {
$post = $this->posts->where(['wall' => $wall, 'virtual_id' => $post])->fetch(); $post = $this->posts->where(['wall' => $wall, 'virtual_id' => $post]);
if(!$forceSuggestion) {
$post->where("suggested", 0);
}
$post = $post->fetch();
if(!is_null($post)) if(!is_null($post))
return new Post($post); return new Post($post);
else else
@ -112,7 +166,7 @@ class Posts
else else
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL; $paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
$result = $this->posts->where("content LIKE ?", $query)->where("deleted", 0); $result = $this->posts->where("content LIKE ?", $query)->where("deleted", 0)->where("suggested", 0);
$nnparamsCount = sizeof($notNullParams); $nnparamsCount = sizeof($notNullParams);
if($nnparamsCount > 0) { if($nnparamsCount > 0) {
@ -134,7 +188,66 @@ class Posts
function getPostCountOnUserWall(int $user): int function getPostCountOnUserWall(int $user): int
{ {
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0])); return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "suggested" => 0]));
}
function getOwnersCountOnUserWall(int $user): int
{
if($user > 0)
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "owner" => $user]));
else
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "suggested" => 0])->where("flags !=", 0));
}
function getOthersCountOnUserWall(int $user): int
{
if($user > 0)
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0])->where("owner !=", $user));
else
return sizeof($this->posts->where(["wall" => $user, "deleted" => 0, "suggested" => 0])->where("flags", 0));
}
function getSuggestedPosts(int $club, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable
{
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
$offset ??= $perPage * ($page - 1);
$sel = $this->posts
->where("deleted", 0)
->where("wall", $club * -1)
->order("created DESC")
->where("suggested", 1)
->limit($perPage, $offset);
foreach($sel as $post)
yield new Post($post);
}
function getSuggestedPostsCount(int $club)
{
return sizeof($this->posts->where(["wall" => $club * -1, "deleted" => 0, "suggested" => 1]));
}
function getSuggestedPostsByUser(int $club, int $user, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable
{
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
$offset ??= $perPage * ($page - 1);
$sel = $this->posts
->where("deleted", 0)
->where("wall", $club * -1)
->where("owner", $user)
->order("created DESC")
->where("suggested", 1)
->limit($perPage, $offset);
foreach($sel as $post)
yield new Post($post);
}
function getSuggestedPostsCountByUser(int $club, int $user): int
{
return sizeof($this->posts->where(["wall" => $club * -1, "deleted" => 0, "suggested" => 1, "owner" => $user]));
} }
function getCount(): int function getCount(): int

View file

@ -3,7 +3,7 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Club, Photo, Post}; use openvk\Web\Models\Entities\{Club, Photo, Post};
use Nette\InvalidStateException; 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}; use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics, Audios, Posts};
use Chandler\Security\Authenticator; use Chandler\Security\Authenticator;
final class GroupPresenter extends OpenVKPresenter final class GroupPresenter extends OpenVKPresenter
@ -35,6 +35,13 @@ final class GroupPresenter extends OpenVKPresenter
$this->template->audiosCount = (new Audios)->getClubCollectionSize($club); $this->template->audiosCount = (new Audios)->getClubCollectionSize($club);
} }
if(!is_null($this->user->identity) && $club->getWallType() == 2) {
if(!$club->canBeModifiedBy($this->user->identity))
$this->template->suggestedPostsCountByUser = (new Posts)->getSuggestedPostsCountByUser($club->getId(), $this->user->id);
else
$this->template->suggestedPostsCountByEveryone = (new Posts)->getSuggestedPostsCount($club->getId());
}
$this->template->club = $club; $this->template->club = $club;
} }
} }
@ -216,7 +223,12 @@ final class GroupPresenter extends OpenVKPresenter
$club->setName((empty($this->postParam("name")) || mb_strlen(trim($this->postParam("name"))) === 0) ? $club->getName() : $this->postParam("name")); $club->setName((empty($this->postParam("name")) || mb_strlen(trim($this->postParam("name"))) === 0) ? $club->getName() : $this->postParam("name"));
$club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about")); $club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about"));
$club->setWall(empty($this->postParam("wall")) ? 0 : 1); try {
$club->setWall(empty($this->postParam("wall")) ? 0 : (int)$this->postParam("wall"));
} catch(\Exception $e) {
$this->flashFail("err", tr("error"), tr("error_invalid_wall_value"));
}
$club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display")); $club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display"));
$club->setEveryone_Can_Create_Topics(empty($this->postParam("everyone_can_create_topics")) ? 0 : 1); $club->setEveryone_Can_Create_Topics(empty($this->postParam("everyone_can_create_topics")) ? 0 : 1);
$club->setDisplay_Topics_Above_Wall(empty($this->postParam("display_topics_above_wall")) ? 0 : 1); $club->setDisplay_Topics_Above_Wall(empty($this->postParam("display_topics_above_wall")) ? 0 : 1);
@ -414,4 +426,37 @@ final class GroupPresenter extends OpenVKPresenter
$this->flashFail("succ", tr("information_-1"), tr("group_owner_setted", $newOwner->getCanonicalName(), $club->getName())); $this->flashFail("succ", tr("information_-1"), tr("group_owner_setted", $newOwner->getCanonicalName(), $club->getName()));
} }
function renderSuggested(int $id): void
{
$this->assertUserLoggedIn();
$club = $this->clubs->get($id);
if(!$club)
$this->notFound();
else
$this->template->club = $club;
if($club->getWallType() == 0) {
$this->flash("err", tr("error_suggestions"), tr("error_suggestions_closed"));
$this->redirect("/club".$club->getId());
}
if($club->getWallType() == 1) {
$this->flash("err", tr("error_suggestions"), tr("error_suggestions_open"));
$this->redirect("/club".$club->getId());
}
if(!$club->canBeModifiedBy($this->user->identity)) {
$this->template->posts = iterator_to_array((new Posts)->getSuggestedPostsByUser($club->getId(), $this->user->id, (int) ($this->queryParam("p") ?? 1)));
$this->template->count = (new Posts)->getSuggestedPostsCountByUser($club->getId(), $this->user->id);
$this->template->type = "my";
} else {
$this->template->posts = iterator_to_array((new Posts)->getSuggestedPosts($club->getId(), (int) ($this->queryParam("p") ?? 1)));
$this->template->count = (new Posts)->getSuggestedPostsCount($club->getId());
$this->template->type = "everyone";
}
$this->template->page = (int) ($this->queryParam("p") ?? 1);
}
} }

View file

@ -104,7 +104,7 @@ final class InternalAPIPresenter extends OpenVKPresenter
} }
if($this->postParam("parentType", false) == "post") { if($this->postParam("parentType", false) == "post") {
$post = (new Posts)->getPostById($owner_id, $post_id); $post = (new Posts)->getPostById($owner_id, $post_id, true);
} else { } else {
$post = (new Comments)->get($post_id); $post = (new Comments)->get($post_id);
} }

View file

@ -2,7 +2,7 @@
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Exceptions\TooMuchOptionsException; use openvk\Web\Models\Exceptions\TooMuchOptionsException;
use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User}; use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User};
use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification}; use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification, PostAcceptedNotification, NewSuggestedPostsNotification};
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes, Videos, Comments, Photos, Audios}; use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes, Videos, Comments, Photos, Audios};
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
use Nette\InvalidStateException as ISE; use Nette\InvalidStateException as ISE;
@ -67,10 +67,31 @@ final class WallPresenter extends OpenVKPresenter
if($user < 0) if($user < 0)
$this->template->club = $owner; $this->template->club = $owner;
$iterator = NULL;
$count = 0;
$type = $this->queryParam("type") ?? "all";
switch($type) {
default:
case "all":
$iterator = $this->posts->getPostsFromUsersWall($user, (int) ($_GET["p"] ?? 1));
$count = $this->posts->getPostCountOnUserWall($user);
break;
case "owners":
$iterator = $this->posts->getOwnersPostsFromWall($user, (int) ($_GET["p"] ?? 1));
$count = $this->posts->getOwnersCountOnUserWall($user);
break;
case "others":
$iterator = $this->posts->getOthersPostsFromWall($user, (int) ($_GET["p"] ?? 1));
$count = $this->posts->getOthersCountOnUserWall($user);
break;
}
$this->template->owner = $user; $this->template->owner = $user;
$this->template->canPost = $canPost; $this->template->canPost = $canPost;
$this->template->count = $this->posts->getPostCountOnUserWall($user); $this->template->count = $count;
$this->template->posts = iterator_to_array($this->posts->getPostsFromUsersWall($user, (int) ($_GET["p"] ?? 1))); $this->template->type = $type;
$this->template->posts = iterator_to_array($iterator);
$this->template->paginatorConf = (object) [ $this->template->paginatorConf = (object) [
"count" => $this->template->count, "count" => $this->template->count,
"page" => (int) ($_GET["p"] ?? 1), "page" => (int) ($_GET["p"] ?? 1),
@ -150,6 +171,7 @@ final class WallPresenter extends OpenVKPresenter
->select("id") ->select("id")
->where("wall IN (?)", $ids) ->where("wall IN (?)", $ids)
->where("deleted", 0) ->where("deleted", 0)
->where("suggested", 0)
->order("created DESC"); ->order("created DESC");
$this->template->paginatorConf = (object) [ $this->template->paginatorConf = (object) [
"count" => sizeof($posts), "count" => sizeof($posts),
@ -169,7 +191,7 @@ final class WallPresenter extends OpenVKPresenter
$page = (int) ($_GET["p"] ?? 1); $page = (int) ($_GET["p"] ?? 1);
$pPage = min((int) ($_GET["posts"] ?? OPENVK_DEFAULT_PER_PAGE), 50); $pPage = min((int) ($_GET["posts"] ?? OPENVK_DEFAULT_PER_PAGE), 50);
$queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND `posts`.`deleted` = 0"; $queryBase = "FROM `posts` LEFT JOIN `groups` ON GREATEST(`posts`.`wall`, 0) = 0 AND `groups`.`id` = ABS(`posts`.`wall`) WHERE (`groups`.`hide_from_global_feed` = 0 OR `groups`.`name` IS NULL) AND `posts`.`deleted` = 0 AND `posts`.`suggested` = 0";
if($this->user->identity->getNsfwTolerance() === User::NSFW_INTOLERANT) if($this->user->identity->getNsfwTolerance() === User::NSFW_INTOLERANT)
$queryBase .= " AND `nsfw` = 0"; $queryBase .= " AND `nsfw` = 0";
@ -343,6 +365,10 @@ final class WallPresenter extends OpenVKPresenter
$post->setAnonymous($anon); $post->setAnonymous($anon);
$post->setFlags($flags); $post->setFlags($flags);
$post->setNsfw($this->postParam("nsfw") === "on"); $post->setNsfw($this->postParam("nsfw") === "on");
if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2)
$post->setSuggested(1);
$post->save(); $post->save();
} catch (\LengthException $ex) { } catch (\LengthException $ex) {
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big")); $this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big"));
@ -371,13 +397,33 @@ final class WallPresenter extends OpenVKPresenter
if($wall > 0) if($wall > 0)
$excludeMentions[] = $wall; $excludeMentions[] = $wall;
if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2) {
# Чтобы не было упоминаний из предложки
} else {
$mentions = iterator_to_array($post->resolveMentions($excludeMentions)); $mentions = iterator_to_array($post->resolveMentions($excludeMentions));
foreach($mentions as $mentionee) foreach($mentions as $mentionee)
if($mentionee instanceof User) if($mentionee instanceof User)
(new MentionNotification($mentionee, $post, $post->getOwner(), strip_tags($post->getText())))->emit(); (new MentionNotification($mentionee, $post, $post->getOwner(), strip_tags($post->getText())))->emit();
}
if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2) {
$suggsCount = $this->posts->getSuggestedPostsCount($wallOwner->getId());
if($suggsCount % 10 == 0) {
$managers = $wallOwner->getManagers();
$owner = $wallOwner->getOwner();
(new NewSuggestedPostsNotification($owner, $wallOwner))->emit();
foreach($managers as $manager)
(new NewSuggestedPostsNotification($manager->getUser(), $wallOwner))->emit();
}
$this->redirect("/club".$wallOwner->getId()."/suggested");
} else {
$this->redirect($wallOwner->getURL()); $this->redirect($wallOwner->getURL());
} }
}
function renderPost(int $wall, int $post_id): void function renderPost(int $wall, int $post_id): void
{ {
@ -487,7 +533,7 @@ final class WallPresenter extends OpenVKPresenter
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
$post = $this->posts->getPostById($wall, $post_id); $post = $this->posts->getPostById($wall, $post_id, true);
if(!$post) if(!$post)
$this->notFound(); $this->notFound();
$user = $this->user->id; $user = $this->user->id;
@ -502,6 +548,9 @@ final class WallPresenter extends OpenVKPresenter
else $canBeDeletedByOtherUser = false; else $canBeDeletedByOtherUser = false;
if(!is_null($user)) { if(!is_null($user)) {
if($post->getTargetWall() < 0 && !$post->getWallOwner()->canBeModifiedBy($this->user->identity) && $post->getWallOwner()->getWallType() != 1 && $post->getSuggestionType() == 0)
$this->flashFail("err", tr("failed_to_delete_post"), tr("error_deleting_suggested"));
if($post->getOwnerPost() == $user || $post->getTargetWall() == $user || $canBeDeletedByOtherUser) { if($post->getOwnerPost() == $user || $post->getTargetWall() == $user || $canBeDeletedByOtherUser) {
$post->unwire(); $post->unwire();
$post->delete(); $post->delete();
@ -597,4 +646,93 @@ final class WallPresenter extends OpenVKPresenter
"avatar" => $post->getOwner()->getAvatarUrl() "avatar" => $post->getOwner()->getAvatarUrl()
]]); ]]);
} }
function renderAccept() {
$this->assertUserLoggedIn();
$this->willExecuteWriteAction(true);
if($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
exit("Ты дебил, это точка апи.");
}
$id = $this->postParam("id");
$sign = $this->postParam("sign") == 1;
$content = $this->postParam("new_content");
$post = (new Posts)->get((int)$id);
if(!$post || $post->isDeleted())
$this->flashFail("err", "Error", tr("error_accepting_invalid_post"), NULL, true);
if($post->getSuggestionType() == 0)
$this->flashFail("err", "Error", tr("error_accepting_not_suggested_post"), NULL, true);
if($post->getSuggestionType() == 2)
$this->flashFail("err", "Error", tr("error_accepting_declined_post"), NULL, true);
if(!$post->canBePinnedBy($this->user->identity))
$this->flashFail("err", "Error", "Can't accept this post.", NULL, true);
$author = $post->getOwner();
$flags = 0;
$flags |= 0b10000000;
if($sign)
$flags |= 0b01000000;
$post->setSuggested(0);
$post->setCreated(time());
$post->setApi_Source_Name(NULL);
$post->setFlags($flags);
if(mb_strlen($content) > 0)
$post->setContent($content);
$post->save();
if($author->getId() != $this->user->id)
(new PostAcceptedNotification($author, $post, $post->getWallOwner()))->emit();
$this->returnJson([
"success" => true,
"id" => $post->getPrettyId(),
"new_count" => (new Posts)->getSuggestedPostsCount($post->getWallOwner()->getId())
]);
}
function renderDecline() {
$this->assertUserLoggedIn();
$this->willExecuteWriteAction(true);
if($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
exit("Ты дебил, это метод апи.");
}
$id = $this->postParam("id");
$post = (new Posts)->get((int)$id);
if(!$post || $post->isDeleted())
$this->flashFail("err", "Error", tr("error_declining_invalid_post"), NULL, true);
if($post->getSuggestionType() == 0)
$this->flashFail("err", "Error", tr("error_declining_not_suggested_post"), NULL, true);
if($post->getSuggestionType() == 2)
$this->flashFail("err", "Error", tr("error_declining_declined_post"), NULL, true);
if(!$post->canBePinnedBy($this->user->identity))
$this->flashFail("err", "Error", "Can't decline this post.", NULL, true);
$post->setSuggested(2);
$post->setDeleted(1);
$post->save();
$this->returnJson([
"success" => true,
"new_count" => (new Posts)->getSuggestedPostsCount($post->getWallOwner()->getId())
]);
}
} }

View file

@ -233,7 +233,16 @@
<div id="_groupListPinnedGroups"> <div id="_groupListPinnedGroups">
<div n:if="$thisUser->getPinnedClubCount() > 0" class="menu_divider"></div> <div n:if="$thisUser->getPinnedClubCount() > 0" class="menu_divider"></div>
<a n:foreach="$thisUser->getPinnedClubs() as $club" href="{$club->getURL()}" class="link group_link">{$club->getName()}</a>
<a n:foreach="$thisUser->getPinnedClubs() as $club" href="{$club->getURL()}" class="link group_link">
{ovk_proc_strtr($club->getName(), 14)}
<object type="internal/link" style="white-space: normal;" id="sug{$club->getId()}" n:if="$club->getSuggestedPostsCount($thisUser) > 0 && $club->getWallType() == 2">
<a href="/club{$club->getId()}/suggested" class="linkunderline">
(<b>{$club->getSuggestedPostsCount($thisUser)}</b>)
</a>
</object>
</a>
</div> </div>
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && $thisUser->getCoins() != 0" id="votesBalance"> <div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && $thisUser->getCoins() != 0" id="votesBalance">
@ -388,6 +397,7 @@
{script "js/al_api.js"} {script "js/al_api.js"}
{script "js/al_mentions.js"} {script "js/al_mentions.js"}
{script "js/al_polls.js"} {script "js/al_polls.js"}
{script "js/al_suggestions.js"}
{ifset $thisUser} {ifset $thisUser}
{script "js/al_notifs.js"} {script "js/al_notifs.js"}

View file

@ -77,7 +77,12 @@
<span class="nobold">{_wall}: </span> <span class="nobold">{_wall}: </span>
</td> </td>
<td> <td>
<input type="checkbox" name="wall" value="1" n:attr="checked => $club->canPost()" /> {_group_allow_post_for_everyone}<br> <select name="wall">
<option value="1" n:attr="selected => $club->getWallType() == 1" /> {_group_allow_post_for_everyone}</option>
<option value="2" n:attr="selected => $club->getWallType() == 2" /> {_group_limited_post}</option>
<option value="0" n:attr="selected => $club->getWallType() == 0" /> {_group_closed_post}</option>
<select>
<input type="checkbox" name="hide_from_global_feed" value="1" n:attr="checked => $club->isHideFromGlobalFeedEnabled()" /> {_group_hide_from_global_feed} <input type="checkbox" name="hide_from_global_feed" value="1" n:attr="checked => $club->isHideFromGlobalFeedEnabled()" /> {_group_hide_from_global_feed}
</td> </td>
</tr> </tr>

View file

@ -0,0 +1,34 @@
{extends "../@layout.xml"}
{block title}{_suggested} {$club->getCanonicalName()}{/block}
{block header}
<a href="{$club->getURL()}">{$club->getName()}</a>
» {if $type == "my"}{_suggested_posts_by_you}{else}{_suggested_posts_by_everyone}{/if}
{/block}
{block content}
{if $count < 1}
{include "../components/error.xml", title => "", description => $type == "my" ? tr("no_suggested_posts_by_you") : tr("no_suggested_posts_by_people")}
{else}
<h4 id="cound">{if $type == "my"}{tr("suggested_posts_in_group_by_you", $count)}{else}{tr("suggested_posts_in_group", $count)}{/if}</h4>
<div id="postz" class="infContainer">
{var $microblog = $thisUser->hasMicroblogEnabled()}
<div class="infObj" n:foreach="$posts as $post">
{if $microblog}
{include "../components/post/microblogpost.xml", post => $post, commentSection => false, suggestion => true, forceNoCommentsLink => true, forceNoPinLink => true, forceNoLike => true, forceNoShareLink => true, forceNoDeleteLink => false}
{else}
{include "../components/post/oldpost.xml", post => $post, commentSection => false, suggestion => true, forceNoCommentsLink => true, forceNoPinLink => true, forceNoLike => true, forceNoShareLink => true, forceNoDeleteLink => false}
{/if}
</div>
{include "../components/paginator.xml", conf => (object) [
"page" => $page,
"count" => $count,
"amount" => sizeof($posts),
"perPage" => OPENVK_DEFAULT_PER_PAGE,
"atBottom" => true,
]}
</div>
{/if}
{/block}

View file

@ -110,7 +110,19 @@
</div> </div>
</div> </div>
<div n:if="!is_null($suggestedPostsCountByUser) && $suggestedPostsCountByUser > 0" class="sugglist">
<a href="/club{$club->getId()}/suggested" id="cound_r">{tr("suggested_by_you", $suggestedPostsCountByUser)}</a>
</div>
<div n:if="!is_null($suggestedPostsCountByEveryone) && $suggestedPostsCountByEveryone > 0" class="sugglist">
<a href="/club{$club->getId()}/suggested" id="cound_r">{tr("suggested_by_everyone", $suggestedPostsCountByEveryone)}</a>
</div>
{presenter "openvk!Wall->wallEmbedded", -$club->getId()} {presenter "openvk!Wall->wallEmbedded", -$club->getId()}
<script n:if="isset($thisUser) && $club->getWallType() == 2 && !$club->canBeModifiedBy($thisUser)">
document.querySelector("textarea").setAttribute("placeholder", tr("suggest_new"))
</script>
</div> </div>
<div class="right_small_block"> <div class="right_small_block">
{var $avatarPhoto = $club->getAvatarPhoto()} {var $avatarPhoto = $club->getAvatarPhoto()}

View file

@ -14,6 +14,20 @@
{/block} {/block}
{block content} {block content}
<div class="tabs">
<div class="tab">
<a href="/wall{$post->getTargetWall()}">{_all_posts}</a>
</div>
<div class="tab">
<a href="/wall{$post->getTargetWall()}?type=owners">{$post->getTargetWall() < 0 ? tr("clubs_posts") : tr("users_posts", ovk_proc_strtr($wallOwner->getFirstName(), 20))}</a>
</div>
<div class="tab">
<a href="/wall{$post->getTargetWall()}?type=others">{_others_posts}</a>
</div>
<div class="tab" id="activetabs">
<a href="" id="act_tab_a">{_post}</a>
</div>
</div>
{include "../components/post.xml", post => $post, forceNoCommentsLink => TRUE, forceNoDeleteLink => TRUE} {include "../components/post.xml", post => $post, forceNoCommentsLink => TRUE, forceNoDeleteLink => TRUE}
<hr/> <hr/>
<div style="float: left; min-height: 100px; width: 68%;"> <div style="float: left; min-height: 100px; width: 68%;">

View file

@ -15,7 +15,19 @@
{block content} {block content}
<div class="content_divider"> <div class="content_divider">
<div> <div>
<div n:if="$canPost" class="content_subtitle"> <div class="tabs">
<div n:attr="id => ($type != 'all' ? 'ki' : 'activetabs')" class="tab">
<a n:attr="id => ($type != 'all' ? 'ki' : 'act_tab_a')" href="/wall{$owner}">{_all_posts}</a>
</div>
<div n:attr="id => ($type != 'owners' ? 'ki' : 'activetabs')" class="tab">
<a n:attr="id => ($type != 'owners' ? 'ki' : 'act_tab_a')" href="/wall{$owner}?type=owners">{isset($club) ? tr("clubs_posts") : tr("users_posts", ovk_proc_strtr($oObj->getFirstName(), 20))}</a>
</div>
<div n:attr="id => ($type != 'others' ? 'ki' : 'activetabs')" class="tab">
<a n:attr="id => ($type != 'others' ? 'ki' : 'act_tab_a')" href="/wall{$owner}?type=others">{_others_posts}</a>
</div>
</div>
<div n:if="$canPost && $type == 'all'" class="content_subtitle">
{include "../components/textArea.xml", route => "/wall$owner/makePost"} {include "../components/textArea.xml", route => "/wall$owner/makePost"}
</div> </div>

View file

@ -0,0 +1,7 @@
{var $club = $notification->getModel(1)}
{var $post = $notification->getModel(0)}
{_group}
<a href="{$club->getURL()}"><b>{$club->getName()}</b></a>
{_nt_accepted_your_post}
<a href="/wall{$post->getPrettyId()}"><b>{_nt_post_small}</b></a>.

View file

@ -0,0 +1,5 @@
{var $club = $notification->getModel(1)}
{_nt_in_club}
<a href="/club{$club->getId()}/suggested"><b>{$club->getName()}</b></a>
{_nt_new_suggested_posts}

View file

@ -71,7 +71,7 @@
{/if} {/if}
</div> </div>
<div class="post-content" id="{$post->getPrettyId()}"> <div class="post-content" id="{$post->getPrettyId()}">
<div class="text"> <div class="text" id="text{$post->getPrettyId()}">
<span data-text="{$post->getText(false)}" class="really_text">{$post->getText()|noescape}</span> <span data-text="{$post->getText(false)}" class="really_text">{$post->getText()|noescape}</span>
{var $width = ($GLOBALS["_bigWall"] ?? false) ? 550 : 320} {var $width = ($GLOBALS["_bigWall"] ?? false) ? 550 : 320}
@ -98,17 +98,18 @@
<div n:if="$post->isSigned()" class="post-signature"> <div n:if="$post->isSigned()" class="post-signature">
{var $actualAuthor = $post->getOwner(false)} {var $actualAuthor = $post->getOwner(false)}
<span> <span>
{_author}: <div class="authorIcon"></div>
<a href="{$actualAuthor->getURL()}" class="mention" data-mention-ref="{$actualAuthor->getId()}"> <a href="{$actualAuthor->getURL()}" class="mention authorName" data-mention-ref="{$actualAuthor->getId()}">
{$actualAuthor->getCanonicalName()} {$actualAuthor->getCanonicalName()}
</a> </a>
</span> </span>
</div> </div>
</div> </div>
<div class="post-menu" n:if="$compact == false"> <div class="post-menu" n:if="$compact == false">
<a href="/wall{$post->getPrettyId()}" class="date">{$post->getPublicationTime()} <a href="{if !$suggestion}/wall{$post->getPrettyId()}{else}javascript:void(0){/if}" class="date">{$post->getPublicationTime()}
<span n:if="$post->getEditTime()" class="edited editedMark">({_edited_short})</span> <span n:if="$post->getEditTime()" class="edited editedMark">({_edited_short})</span>
</a> </a>
<a n:if="!empty($platform)" class="client_app" data-app-tag="{$platform}" data-app-name="{$platformDetails['name']}" data-app-url="{$platformDetails['url']}" data-app-img="{$platformDetails['img']}"> <a n:if="!empty($platform)" class="client_app" data-app-tag="{$platform}" data-app-name="{$platformDetails['name']}" data-app-url="{$platformDetails['url']}" data-app-img="{$platformDetails['img']}">
<img src="/assets/packages/static/openvk/img/app_icons_mini/{$post->getPlatform(this)}.svg"> <img src="/assets/packages/static/openvk/img/app_icons_mini/{$post->getPlatform(this)}.svg">
</a> </a>
@ -144,6 +145,10 @@
{include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post, club => $club} {include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post, club => $club}
</div> </div>
</div> </div>
<div n:if="$suggestion && $post->canBePinnedBy($thisUser ?? NULL)" class="suggestionControls">
<input type="button" class="button" id="publish_post" data-id="{$post->getId()}" value="{_publish_suggested}">
<input type="button" class="button" id="decline_post" data-id="{$post->getId()}" value="{_decline_suggested}">
</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View file

@ -52,15 +52,16 @@
</a> </a>
{/if} {/if}
<br/> <br/>
<a href="/wall{$post->getPrettyId()}" class="date"> <a href="{if !$suggestion}/wall{$post->getPrettyId()}{else}javascript:void(0){/if}" class="date">
{$post->getPublicationTime()} <span n:if="$post->getEditTime()" class="editedMark">({_edited_short})</span>{if $post->isPinned()}, {_pinned}{/if} {$post->getPublicationTime()} <span n:if="$post->getEditTime()" class="editedMark">({_edited_short})</span>{if $post->isPinned()}, {_pinned}{/if}
<a n:if="!empty($platform)" class="client_app" data-app-tag="{$platform}" data-app-name="{$platformDetails['name']}" data-app-url="{$platformDetails['url']}" data-app-img="{$platformDetails['img']}"> <a n:if="!empty($platform)" class="client_app" data-app-tag="{$platform}" data-app-name="{$platformDetails['name']}" data-app-url="{$platformDetails['url']}" data-app-img="{$platformDetails['img']}">
<img src="/assets/packages/static/openvk/img/app_icons_mini/{$post->getPlatform(this)}.svg"> <img src="/assets/packages/static/openvk/img/app_icons_mini/{$post->getPlatform(this)}.svg">
</a> </a>
</a> </a>
</div> </div>
<div class="post-content" id="{$post->getPrettyId()}"> <div class="post-content" id="{$post->getPrettyId()}">
<div class="text"> <div class="text" id="text{$post->getPrettyId()}">
{var $owner = $author->getId()} {var $owner = $author->getId()}
<span data-text="{$post->getText(false)}" class="really_text">{$post->getText()|noescape}</span> <span data-text="{$post->getText(false)}" class="really_text">{$post->getText()|noescape}</span>
@ -82,6 +83,10 @@
</div> </div>
</div> </div>
</div> </div>
<div n:if="$suggestion && $post->canBePinnedBy($thisUser ?? NULL)" class="suggestionControls" style="margin-bottom: 7px;">
<input type="button" class="button" id="publish_post" data-id="{$post->getId()}" value="{_publish_suggested}">
<input type="button" class="button" id="decline_post" data-id="{$post->getId()}" value="{_decline_suggested}">
</div>
<div n:if="$post->isAd()" style="color:grey;"> <div n:if="$post->isAd()" style="color:grey;">
<br/> <br/>
&nbsp;! {_post_is_ad} &nbsp;! {_post_is_ad}

View file

@ -7,6 +7,8 @@
</nobold> </nobold>
</div> </div>
<div> <div>
<div class="insertThere" id="postz"></div>
<div id="underHeader">
<div n:if="$canPost" class="content_subtitle"> <div n:if="$canPost" class="content_subtitle">
{include "../components/textArea.xml", route => "/wall$owner/makePost", graffiti => true, polls => true, notes => true} {include "../components/textArea.xml", route => "/wall$owner/makePost", graffiti => true, polls => true, notes => true}
</div> </div>
@ -24,6 +26,7 @@
{/if} {/if}
</div> </div>
</div> </div>
</div>
</div> </div>
{if isset($thisUser) && $thisUser->hasMicroblogEnabled()} {if isset($thisUser) && $thisUser->hasMicroblogEnabled()}

View file

@ -141,6 +141,10 @@ routes:
handler: "Wall->delete" handler: "Wall->delete"
- url: "/wall{num}_{num}/pin" - url: "/wall{num}_{num}/pin"
handler: "Wall->pin" handler: "Wall->pin"
- url: "/wall/accept"
handler: "Wall->accept"
- url: "/wall/decline"
handler: "Wall->decline"
- url: "/blob_{text}/{?path}.{text}" - url: "/blob_{text}/{?path}.{text}"
handler: "Blob->file" handler: "Blob->file"
placeholders: placeholders:
@ -235,6 +239,8 @@ routes:
handler: "Group->admin" handler: "Group->admin"
- url: "/club{num}/setAdmin" - url: "/club{num}/setAdmin"
handler: "Group->modifyAdmin" handler: "Group->modifyAdmin"
- url: "/club{num}/suggested"
handler: "Group->suggested"
- url: "/groups{num}" - url: "/groups{num}"
handler: "User->groups" handler: "User->groups"
- url: "/groups_pin" - url: "/groups_pin"

View file

@ -2849,6 +2849,59 @@ body.article .floating_sidebar, body.article .page_content {
font-size: 12px; font-size: 12px;
} }
.sugglist {
padding-bottom: 5px;
padding-top: 5px;
margin-bottom: -5px;
padding-left: 9px;
border-top: 1px solid gray;
background-color: #e6e6e6;
margin-top: 5px;
}
.sugglist a {
color: #626262;
}
.suggestionControls {
text-align: center;
}
.button.loaded {
background: #595959 url("/assets/packages/static/openvk/img/loading_mini.gif") no-repeat 50% 50%;
padding: 2px 9px 7px 39px;
margin-bottom: -6px;
}
@-moz-document url-prefix() {
.button.loaded {
padding: 16px 40px 6px 4px !important;
}
}
.sugglist a:hover {
text-decoration: underline;
}
.sugglist a[data-toogled="true"] {
text-decoration: underline;
color:#4a4a4a;
}
.authorIcon {
height: 11px;
margin-top: 1px;
width: 10px;
float: left;
background: url("/assets/packages/static/openvk/img/person.png") no-repeat 0 0;
}
.authorName {
margin-left: 4px;
font-weight: normal !important;
font-size: 11px;
}
.topGrayBlock { .topGrayBlock {
background: #F0F0F0; background: #F0F0F0;
height: 37px; height: 37px;
@ -3002,3 +3055,4 @@ hr {
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-position: 50% !important; background-position: 50% !important;
} }

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

View file

@ -0,0 +1,231 @@
function endSuggestAction(new_count, post_node) {
if(document.getElementById("cound") != null)
document.getElementById("cound").innerHTML = tr("suggested_posts_in_group", new_count)
else
document.getElementById("cound_r").innerHTML = tr("suggested_by_everyone", new_count)
if(document.querySelector("object a[href='"+location.pathname+"'] b") != null) {
document.querySelector("object a[href='"+location.pathname+"'] b").innerHTML = new_count
if(new_count < 1) {
u("object a[href='"+location.pathname+"']").remove()
}
}
if(new_count < 1 && document.querySelector(".sugglist") != null) {
$(".sugglist a").click()
$(".sugglist").remove()
}
post_node.style.transition = "opacity 300ms ease-in-out";
post_node.style.opacity = "0";
post_node.classList.remove("post")
setTimeout(() => {post_node.outerHTML = ""}, 300)
if(document.querySelectorAll("#postz .post").length < 1 && new_count > 0 && document.querySelector(".paginator") != null)
loadMoreSuggestedPosts()
}
// "Опубликовать запись"
$(document).on("click", "#publish_post", async (e) => {
let id = Number(e.currentTarget.dataset.id)
let post;
let body = `
<textarea id="pooblish" style="max-height:500px;resize:vertical;min-height:54px;"></textarea>
<label><input type="checkbox" id="signatr" checked>${tr("add_signature")}</label>
`
MessageBox(tr("publishing_suggested_post"), body, [tr("publish"), tr("cancel")], [(async () => {
let id = Number(e.currentTarget.dataset.id)
let post;
let formData = new FormData()
formData.append("id", id)
formData.append("sign", document.getElementById("signatr").checked ? 1 : 0)
formData.append("new_content", document.getElementById("pooblish").value)
formData.append("hash", u("meta[name=csrf]").attr("value"))
ky.post("/wall/accept", {
hooks: {
beforeRequest: [
(_request) => {
e.currentTarget.classList.add("loaded")
e.currentTarget.setAttribute("value", "")
e.currentTarget.setAttribute("id", "")
}
],
afterResponse: [
async (_request, _options, response) => {
json = await response.json()
if(json.success) {
NewNotification(tr("suggestion_succefully_published"), tr("suggestion_press_to_go"), null, () => {window.location.assign("/wall" + json.id)});
endSuggestAction(json.new_count, e.currentTarget.closest("table"))
} else {
MessageBox(tr("error"), json.flash.message, [tr("ok")], [Function.noop]);
}
e.currentTarget.setAttribute("value", tr("publish_suggested"))
e.currentTarget.classList.remove("loaded")
e.currentTarget.setAttribute("id", "publish_post")
}
]
},
body: formData
})
}), Function.noop]);
document.getElementById("pooblish").innerHTML = e.currentTarget.closest("table").querySelector(".really_text").dataset.text
document.querySelector(".ovk-diag-body").style.padding = "9px";
})
// "Отклонить"
$(document).on("click", "#decline_post", async (e) => {
let id = Number(e.currentTarget.dataset.id)
let formData = new FormData()
formData.append("id", id)
formData.append("hash", u("meta[name=csrf]").attr("value"))
ky.post("/wall/decline", {
hooks: {
beforeRequest: [
(_request) => {
e.currentTarget.classList.add("loaded")
e.currentTarget.setAttribute("value", "")
e.currentTarget.setAttribute("id", "")
}
],
afterResponse: [
async (_request, _options, response) => {
json = await response.json()
if(json.success) {
endSuggestAction(json.new_count, e.currentTarget.closest("table"))
} else {
MessageBox(tr("error"), json.flash.message, [tr("ok")], [Function.noop]);
}
e.currentTarget.setAttribute("value", tr("decline_suggested"))
e.currentTarget.setAttribute("id", "decline_post")
e.currentTarget.classList.remove("loaded")
}
]
},
body: formData
})
})
function loadMoreSuggestedPosts() {
let link = location.href
if(!link.includes("/suggested")) {
link += "/suggested"
}
ky.get(link, {
hooks: {
beforeRequest: [
(_request) => {
document.getElementById("postz").innerHTML = `<img src="/assets/packages/static/openvk/img/loading_mini.gif">`
}
],
afterResponse: [
async (_request, _options, response) => {
let text = await response.text()
let parser = new DOMParser()
let body = parser.parseFromString(text, "text/html")
if(body.querySelectorAll(".post").length < 1) {
let url = new URL(location.href)
url.searchParams.set("p", url.searchParams.get("p") - 1)
if(url.searchParams.get("p") < 1) {
return 0;
}
history.pushState({}, "", url)
loadMoreSuggestedPosts()
}
body.querySelectorAll(".bsdn").forEach(bsdnInitElement)
document.getElementById("postz").innerHTML = body.getElementById("postz").innerHTML
}
]
}
})
}
// нажатие на "x предложенных записей"
$(document).on("click", ".sugglist a", (e) => {
e.preventDefault()
if(e.currentTarget.getAttribute("data-toogled") == null || e.currentTarget.getAttribute("data-toogled") == "false") {
e.currentTarget.setAttribute("data-toogled", "true")
document.getElementById("underHeader").style.display = "none"
document.querySelector(".insertThere").style.display = "block"
document.querySelector(".insertThere").classList.add("infContainer")
history.pushState({}, "", e.currentTarget.href)
// если ещё ничего не подгружалось
if(document.querySelector(".insertThere").innerHTML == "") {
ky(e.currentTarget.href, {
hooks: {
beforeRequest: [
(_request) => {
document.querySelector(".insertThere").insertAdjacentHTML("afterbegin", `<img src="/assets/packages/static/openvk/img/loading_mini.gif">`)
}
],
afterResponse: [
async (_request, _options, response) => {
let parser = new DOMParser
let result = parser.parseFromString(await response.text(), 'text/html').querySelector(".infContainer")
result.querySelectorAll(".bsdn").forEach(bsdnInitElement)
document.querySelector(".insertThere").innerHTML = result.innerHTML
}
]
}
})
}
} else {
// переключение на нормальную стену
e.currentTarget.setAttribute("data-toogled", "false")
document.getElementById("underHeader").style.display = "block"
document.querySelector(".insertThere").style.display = "none"
document.querySelector(".insertThere").classList.remove("infContainer")
history.pushState({}, "", e.currentTarget.href.replace("/suggested", ""))
}
})
// нажатие на пагинатор у постов предложки
$(document).on("click", "#postz .paginator a", (e) => {
e.preventDefault()
ky(e.currentTarget.href, {
hooks: {
beforeRequest: [
(_request) => {
if(document.querySelector(".sugglist") != null) {
document.querySelector(".sugglist").scrollIntoView({behavior: "smooth"})
} else {
document.querySelector(".infContainer").scrollIntoView({behavior: "smooth"})
}
setTimeout(() => {document.getElementById("postz").innerHTML = `<img src="/assets/packages/static/openvk/img/loading_mini.gif">`}, 500)
}
],
afterResponse: [
async (_request, _options, response) => {
let result = (new DOMParser).parseFromString(await response.text(), "text/html").querySelector(".infContainer")
result.querySelectorAll(".bsdn").forEach(bsdnInitElement)
document.getElementById("postz").innerHTML = result.innerHTML
history.pushState({}, "", e.currentTarget.href)
}
]
}
})
})

View file

@ -445,7 +445,7 @@ tippy(".client_app", {
function addNote(textareaId, nid) function addNote(textareaId, nid)
{ {
if(nid > 0) { if(nid > 0) {
note.value = nid document.getElementById("note").value = nid
let noteObj = document.querySelector("#nd"+nid) let noteObj = document.querySelector("#nd"+nid)
let nortd = document.querySelector("#post-buttons"+textareaId+" .post-has-note"); let nortd = document.querySelector("#post-buttons"+textareaId+" .post-has-note");
@ -453,7 +453,7 @@ function addNote(textareaId, nid)
nortd.innerHTML = `${tr("note")} ${escapeHtml(noteObj.dataset.name)}` nortd.innerHTML = `${tr("note")} ${escapeHtml(noteObj.dataset.name)}`
} else { } else {
note.value = "none" document.getElementById("note").value = "none"
let nortd = document.querySelector("#post-buttons"+textareaId+" .post-has-note"); let nortd = document.querySelector("#post-buttons"+textareaId+" .post-has-note");
nortd.style.display = "none" nortd.style.display = "none"
@ -481,7 +481,7 @@ async function attachNote(id)
${tr("select_or_create_new")} ${tr("select_or_create_new")}
<div id="notesList">` <div id="notesList">`
if(note.value != "none") { if(document.getElementById("note").value != "none") {
body += ` body += `
<div class="ntSelect" onclick="addNote(${id}, 0)"> <div class="ntSelect" onclick="addNote(${id}, 0)">
<span>${tr("do_not_attach_note")}</span> <span>${tr("do_not_attach_note")}</span>

View file

@ -0,0 +1 @@
ALTER TABLE `posts` ADD `suggested` TINYINT(2) UNSIGNED NOT NULL DEFAULT '0' AFTER `deleted`, ADD INDEX (`suggested`);

View file

@ -230,6 +230,11 @@
"edited_short" = "edited"; "edited_short" = "edited";
"all_posts" = "All posts";
"users_posts" = "Posts by $1";
"clubs_posts" = "Group's posts";
"others_posts" = "Others posts";
/* Friends */ /* Friends */
"friends" = "Friends"; "friends" = "Friends";
@ -276,6 +281,7 @@
/* Group */ /* Group */
"group" = "Group";
"name_group" = "Name"; "name_group" = "Name";
"subscribe" = "Subscribe"; "subscribe" = "Subscribe";
"unsubscribe" = "Unsubscribe"; "unsubscribe" = "Unsubscribe";
@ -313,8 +319,60 @@
"set_comment" = "Set comment"; "set_comment" = "Set comment";
"hidden_yes" = "Hidden: Yes"; "hidden_yes" = "Hidden: Yes";
"hidden_no" = "Hidden: No"; "hidden_no" = "Hidden: No";
"group_allow_post_for_everyone" = "Allow posting for everyone"; "group_allow_post_for_everyone" = "Open";
"group_limited_post" = "Suggestions";
"group_closed_post" = "Closed";
"suggest_new" = "Suggest post";
"suggested_by_you_zero" = "$1 suggested posts by you";
"suggested_by_you_one" = "One suggested post by you";
"suggested_by_you_few" = "$1 suggested posts by you";
"suggested_by_you_many" = "$1 suggested posts by you";
"suggested_by_you_other" = "$1 suggested posts by you";
"suggested_by_everyone_zero" = "$1 suggested posts";
"suggested_by_everyone_one" = "One suggested post";
"suggested_by_everyone_few" = "$1 suggested posts";
"suggested_by_everyone_many" = "$1 suggested posts";
"suggested_by_everyone_other" = "$1 suggested posts";
"group_hide_from_global_feed" = "Don't display posts in the global feed"; "group_hide_from_global_feed" = "Don't display posts in the global feed";
"suggested_posts_by_you" = "Suggested posts by you";
"suggested_posts_by_everyone" = "Suggested posts";
"suggested" = "Suggested";
"suggested_posts_everyone" = "Suggested by users posts";
"no_suggested_posts_by_you" = "You haven't suggested posts to this group yet.";
"no_suggested_posts_by_people" = "No posts have been suggested to this group yet.";
"publish_suggested" = "Accept";
"decline_suggested" = "Decline";
"error_loading_suggest" = "Error when loading new posts";
"publishing_suggested_post" = "Publishing suggested post";
"suggested_posts_in_group_zero" = "You've looked at all the suggested posts, congratulations!";
"suggested_posts_in_group_one" = "This group has one suggested post";
"suggested_posts_in_group_few" = "This group has $1 suggested posts";
"suggested_posts_in_group_many" = "This group has $1 suggested posts";
"suggested_posts_in_group_other" = "This group has $1 suggested posts";
"suggested_posts_in_group_by_you_zero" = "You haven't suggested any posts to this group";
"suggested_posts_in_group_by_you_one" = "You suggested one post to this group";
"suggested_posts_in_group_by_you_few" = "You suggested $1 posts to this group";
"suggested_posts_in_group_by_you_many" = "You suggested $1 posts to this group";
"suggested_posts_in_group_by_you_other" = "You suggested $1 posts to this group";
"suggestion_succefully_published" = "Post successfully published";
"suggestion_succefully_declined" = "Post successfully declined";
"suggestion_press_to_go" = "Click to show it";
"error_declining_invalid_post" = "Error when declining post: post does not exists";
"error_declining_not_suggested_post" = "Error when declining post: post is not suggested";
"error_declining_declined_post" = "Error when declining post: post is already declined";
"error_accepting_invalid_post" = "Error when accepting post: post does not exists";
"error_accepting_not_suggested_post" = "Error when accepting post: post is not suggested";
"error_accepting_declined_post" = "Error when accepting post: cant accept declined post";
"statistics" = "Statistics"; "statistics" = "Statistics";
"group_administrators_list" = "Admins list"; "group_administrators_list" = "Admins list";
"group_display_only_creator" = "Display only group creator"; "group_display_only_creator" = "Display only group creator";
@ -347,6 +405,11 @@
"search_by_groups" = "Explore Groups"; "search_by_groups" = "Explore Groups";
"search_group_desc" = "Browse through existing groups and find the one that suits your needs..."; "search_group_desc" = "Browse through existing groups and find the one that suits your needs...";
"error_suggestions" = "Error accessing to suggestions";
"error_suggestions_closed" = "This group has closed wall.";
"error_suggestions_open" = "This group has open wall.";
"error_suggestions_access" = "Only group's administrators can view all suggested posts.";
"group_banned" = "Unfortunately, we had to block the <b>$1</b> group."; "group_banned" = "Unfortunately, we had to block the <b>$1</b> group.";
/* Albums */ /* Albums */
@ -870,6 +933,9 @@
"nt_shared_yours" = "shared your"; "nt_shared_yours" = "shared your";
"nt_commented_yours" = "commented"; "nt_commented_yours" = "commented";
"nt_written_on_your_wall" = "wrote on your wall"; "nt_written_on_your_wall" = "wrote on your wall";
"nt_accepted_your_post" = "accepted your suggested";
"nt_in_club" = "In group";
"nt_new_suggested_posts" = "new posts in suggestions";
"nt_made_you_admin" = "appointed you in the community"; "nt_made_you_admin" = "appointed you in the community";
"nt_from" = "from"; "nt_from" = "from";
@ -890,6 +956,8 @@
"nt_mention_in_topic" = "in the discussion"; "nt_mention_in_topic" = "in the discussion";
"nt_sent_gift" = "sent you a gift"; "nt_sent_gift" = "sent you a gift";
"nt_post_small" = "post";
/* Time */ /* Time */
"time_at_sp" = " at "; "time_at_sp" = " at ";
@ -1325,6 +1393,8 @@
"media_file_corrupted_or_too_large" = "The media content file is corrupted or too large."; "media_file_corrupted_or_too_large" = "The media content file is corrupted or too large.";
"post_is_empty_or_too_big" = "The post is empty or too big."; "post_is_empty_or_too_big" = "The post is empty or too big.";
"post_is_too_big" = "The post is too big."; "post_is_too_big" = "The post is too big.";
"error_deleting_suggested" = "You can't delete your accepted post";
"error_invalid_wall_value" = "Invalid wall value";
"error_sending_report" = "Failed to make a report..."; "error_sending_report" = "Failed to make a report...";

View file

@ -206,6 +206,11 @@
"post_is_ad" = "Этот пост был размещён за взятку."; "post_is_ad" = "Этот пост был размещён за взятку.";
"edited_short" = "ред."; "edited_short" = "ред.";
"all_posts" = "Все записи";
"users_posts" = "Записи $1";
"clubs_posts" = "Записи сообщества";
"others_posts" = "Чужие записи";
/* Friends */ /* Friends */
"friends" = "Друзья"; "friends" = "Друзья";
@ -259,6 +264,7 @@
/* Group */ /* Group */
"group" = "Сообщество";
"name_group" = "Название"; "name_group" = "Название";
"subscribe" = "Подписаться"; "subscribe" = "Подписаться";
"unsubscribe" = "Отписаться"; "unsubscribe" = "Отписаться";
@ -295,8 +301,61 @@
"set_comment" = "Изменить комментарий"; "set_comment" = "Изменить комментарий";
"hidden_yes" = "Скрыт: Да"; "hidden_yes" = "Скрыт: Да";
"hidden_no" = "Скрыт: Нет"; "hidden_no" = "Скрыт: Нет";
"group_allow_post_for_everyone" = "Разрешить публиковать записи всем";
"group_allow_post_for_everyone" = "Открытая";
"group_limited_post" = "Предложка";
"group_closed_post" = "Закрытая";
"suggest_new" = "Предложить новость";
"suggested_by_you_zero" = "$1 предложенных вами записей";
"suggested_by_you_one" = "Одна предложенная вами запись";
"suggested_by_you_few" = "$1 предложенные вами записи";
"suggested_by_you_many" = "$1 предложенных вами записей";
"suggested_by_you_other" = "$1 предложенных вами записей";
"suggested_by_everyone_zero" = "$1 предложенных записей";
"suggested_by_everyone_one" = "Одна предложенная запись";
"suggested_by_everyone_few" = "$1 предложенные записи";
"suggested_by_everyone_many" = "$1 предложенных записей";
"suggested_by_everyone_other" = "$1 предложенных записей";
"group_hide_from_global_feed" = "Не отображать публикации в глобальной ленте"; "group_hide_from_global_feed" = "Не отображать публикации в глобальной ленте";
"suggested_posts_by_you" = "Предложенные вами записи";
"suggested_posts_by_everyone" = "Предложенные записи";
"suggested" = "Предложено";
"suggested_posts_everyone" = "Предложенные пользователями записи";
"no_suggested_posts_by_you" = "Вы ещё не предлагали записей в эту группу.";
"no_suggested_posts_by_people" = "В эту группу ещё не предлагали записей.";
"publish_suggested" = "Опубликовать запись";
"decline_suggested" = "Отклонить";
"error_loading_suggest" = "Не удалось подгрузить новые посты";
"publishing_suggested_post" = "Публикация предложенной записи";
"suggested_posts_in_group_zero" = "Вы посмотрели всю предложку, поздравляю!";
"suggested_posts_in_group_one" = "В эту группу предложили одну запись";
"suggested_posts_in_group_few" = "В эту группу предложили $1 записи";
"suggested_posts_in_group_many" = "В эту группу предложили $1 записей";
"suggested_posts_in_group_other" = "В эту группу предложили $1 записей";
"suggested_posts_in_group_by_you_zero" = "Вы не предлагали в эту группу никаких записей";
"suggested_posts_in_group_by_you_one" = "Вы предложили в эту группу одну запись";
"suggested_posts_in_group_by_you_few" = "Вы предложили в эту группу $1 записи";
"suggested_posts_in_group_by_you_many" = "Вы предложили в эту группу $1 записей";
"suggested_posts_in_group_by_you_other" = "Вы предложили в эту группу $1 записей";
"suggestion_succefully_published" = "Запись успешно опубликована";
"suggestion_succefully_declined" = "Запись успешно отклонена";
"suggestion_press_to_go" = "Нажмите, чтобы перейти к ней";
"error_declining_invalid_post" = "Не удалость отклонить пост: поста не существует";
"error_declining_not_suggested_post" = "Не удалость отклонить пост: пост не из предложки";
"error_declining_declined_post" = "Не удалость отклонить пост: пост уже отклонён";
"error_accepting_invalid_post" = "Не удалость принять пост: поста не существует";
"error_accepting_not_suggested_post" = "Не удалость принять пост: пост не из предложки";
"error_accepting_declined_post" = "Не удалость принять пост: пост отклонён";
"statistics" = "Статистика"; "statistics" = "Статистика";
"group_administrators_list" = "Список админов"; "group_administrators_list" = "Список админов";
"group_display_only_creator" = "Отображать только создателя группы"; "group_display_only_creator" = "Отображать только создателя группы";
@ -330,6 +389,11 @@
"search_group_desc" = "Здесь Вы можете просмотреть существующие группы и выбрать группу себе по вкусу..."; "search_group_desc" = "Здесь Вы можете просмотреть существующие группы и выбрать группу себе по вкусу...";
"group_banned" = "К сожалению, нам пришлось заблокировать сообщество <b>$1</b>."; "group_banned" = "К сожалению, нам пришлось заблокировать сообщество <b>$1</b>.";
"error_suggestions" = "Ошибка доступа к предложенным";
"error_suggestions_closed" = "У этой группы закрытая стена.";
"error_suggestions_open" = "У этой группы открытая стена.";
"error_suggestions_access" = "Просматривать все предложенные записи могут только администраторы группы.";
/* Albums */ /* Albums */
"create" = "Создать"; "create" = "Создать";
@ -821,6 +885,9 @@
"nt_shared_yours" = "поделился(-ась) вашим"; "nt_shared_yours" = "поделился(-ась) вашим";
"nt_commented_yours" = "оставил(а) комментарий под"; "nt_commented_yours" = "оставил(а) комментарий под";
"nt_written_on_your_wall" = "написал(а) на вашей стене"; "nt_written_on_your_wall" = "написал(а) на вашей стене";
"nt_accepted_your_post" = "опубликовало вашу предложенную";
"nt_in_club" = "В сообществе";
"nt_new_suggested_posts" = "новые записи в предложке";
"nt_made_you_admin" = "назначил(а) вас руководителем сообщества"; "nt_made_you_admin" = "назначил(а) вас руководителем сообщества";
"nt_from" = "от"; "nt_from" = "от";
"nt_yours_adjective" = "вашим"; "nt_yours_adjective" = "вашим";
@ -838,6 +905,7 @@
"nt_mention_in_video" = "в обсуждении видеозаписи"; "nt_mention_in_video" = "в обсуждении видеозаписи";
"nt_mention_in_note" = "в обсуждении заметки"; "nt_mention_in_note" = "в обсуждении заметки";
"nt_mention_in_topic" = "в обсуждении"; "nt_mention_in_topic" = "в обсуждении";
"nt_post_small" = "запись";
"nt_sent_gift" = "отправил вам подарок"; "nt_sent_gift" = "отправил вам подарок";
/* Time */ /* Time */
@ -1226,6 +1294,10 @@
"media_file_corrupted_or_too_large" = "Файл медиаконтента повреждён или слишком велик."; "media_file_corrupted_or_too_large" = "Файл медиаконтента повреждён или слишком велик.";
"post_is_empty_or_too_big" = "Пост пустой или слишком большой."; "post_is_empty_or_too_big" = "Пост пустой или слишком большой.";
"post_is_too_big" = "Пост слишком большой."; "post_is_too_big" = "Пост слишком большой.";
"error_deleting_suggested" = "Вы не можете удалить ваш принятый пост";
"error_invalid_wall_value" = "Некорректное значение стены";
"error_sending_report" = "Не удалось подать жалобу..."; "error_sending_report" = "Не удалось подать жалобу...";
"error_when_saving_gift" = "Не удалось сохранить подарок"; "error_when_saving_gift" = "Не удалось сохранить подарок";
"error_when_saving_gift_bad_image" = "Изображение подарка кривое."; "error_when_saving_gift_bad_image" = "Изображение подарка кривое.";

View file

@ -243,6 +243,19 @@ input[type="radio"] {
border-color: #473e66 !important; border-color: #473e66 !important;
} }
.sugglist {
background-color: #231e33;
border-color: #2c2640 !important;
}
.sugglist a {
color: #7c94c5;
}
.button.loaded {
background: #383052 url("/assets/packages/static/openvk/img/loading_mini.gif") no-repeat 50% 50% !important;
}
.bigPlayer { .bigPlayer {
background-color: rgb(30, 26, 43) !important; background-color: rgb(30, 26, 43) !important;
} }

View file

@ -47,13 +47,13 @@ body {
.content_title_expanded { .content_title_expanded {
cursor: pointer; cursor: pointer;
background-image: unset; background-image: unset !important;
padding: 3px 10px; padding: 3px 10px;
border-top: #e6e6e6 solid 1px; border-top: #e6e6e6 solid 1px;
} }
.content_title_unexpanded { .content_title_unexpanded {
background-image: unset; background-image: unset !important;
padding: 3px 10px; padding: 3px 10px;
border-top: #eee solid 1px; border-top: #eee solid 1px;
} }
@ -329,6 +329,11 @@ input[type=checkbox] {
border-top: 1px solid #2f2f2f; border-top: 1px solid #2f2f2f;
} }
.sugglist {
border-top: unset;
border-bottom: 1px solid gray;
}
.musicIcon { .musicIcon {
filter: contrast(202%) !important; filter: contrast(202%) !important;
} }