- + {$post->getPublicationTime()} ({_edited_short}){if $post->isPinned()}, {_pinned}{/if} +
From 4699fcbeb935deba0a52fe61ba5971597f1afe5f Mon Sep 17 00:00:00 2001
From: lalka2018 <99399973+lalka2016@users.noreply.github.com>
Date: Thu, 16 Nov 2023 19:44:12 +0300
Subject: [PATCH] Groups: Wall: add suggestions (#935)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 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
Теперь оно удаляет все теги а не только
* 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
---
ServiceAPI/Wall.php | 9 +-
VKAPI/Handlers/Groups.php | 45 +++-
VKAPI/Handlers/Newsfeed.php | 2 +-
VKAPI/Handlers/Wall.php | 169 ++++++++++++-
Web/Models/Entities/Club.php | 34 ++-
.../NewSuggestedPostsNotification.php | 13 +
.../PostAcceptedNotification.php | 13 +
Web/Models/Entities/Poll.php | 17 +-
Web/Models/Entities/Post.php | 14 ++
Web/Models/Entities/Postable.php | 2 +-
Web/Models/Repositories/Posts.php | 129 +++++++++-
Web/Presenters/GroupPresenter.php | 49 +++-
Web/Presenters/InternalAPIPresenter.php | 2 +-
Web/Presenters/WallPresenter.php | 158 +++++++++++-
Web/Presenters/templates/@layout.xml | 12 +-
Web/Presenters/templates/Group/Edit.xml | 7 +-
Web/Presenters/templates/Group/Suggested.xml | 34 +++
Web/Presenters/templates/Group/View.xml | 12 +
Web/Presenters/templates/Wall/Post.xml | 14 ++
Web/Presenters/templates/Wall/Wall.xml | 14 +-
.../components/notifications/6/_14_5_.xml | 7 +
.../components/notifications/7/_18_5_.xml | 5 +
.../components/post/microblogpost.xml | 13 +-
.../templates/components/post/oldpost.xml | 9 +-
Web/Presenters/templates/components/wall.xml | 3 +
Web/routes.yml | 6 +
Web/static/css/main.css | 54 ++++
Web/static/img/person.png | Bin 0 -> 265 bytes
Web/static/js/al_suggestions.js | 231 ++++++++++++++++++
Web/static/js/al_wall.js | 6 +-
install/sqls/00043-suggest-posts.sql | 1 +
locales/en.strings | 72 +++++-
locales/ru.strings | 74 +++++-
themepacks/midnight/stylesheet.css | 13 +
themepacks/openvk_modern/stylesheet.css | 9 +-
35 files changed, 1195 insertions(+), 57 deletions(-)
create mode 100644 Web/Models/Entities/Notifications/NewSuggestedPostsNotification.php
create mode 100644 Web/Models/Entities/Notifications/PostAcceptedNotification.php
create mode 100644 Web/Presenters/templates/Group/Suggested.xml
create mode 100644 Web/Presenters/templates/components/notifications/6/_14_5_.xml
create mode 100644 Web/Presenters/templates/components/notifications/7/_18_5_.xml
create mode 100644 Web/static/img/person.png
create mode 100644 Web/static/js/al_suggestions.js
create mode 100644 install/sqls/00043-suggest-posts.sql
diff --git a/ServiceAPI/Wall.php b/ServiceAPI/Wall.php
index f25d03d4..d01922b8 100644
--- a/ServiceAPI/Wall.php
+++ b/ServiceAPI/Wall.php
@@ -22,7 +22,10 @@ class Wall implements Handler
{
$post = $this->posts->get($id);
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->id = $post->getId();
@@ -96,7 +99,7 @@ class Wall implements Handler
$resolve($arr);
}
-
+
function getVideos(int $page = 1, callable $resolve, callable $reject)
{
$videos = $this->videos->getByUser($this->user, $page, 8);
@@ -129,7 +132,7 @@ class Wall implements Handler
];
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();
$arr["items"][] = $res;
diff --git a/VKAPI/Handlers/Groups.php b/VKAPI/Handlers/Groups.php
index 007b68ea..5dc47316 100644
--- a/VKAPI/Handlers/Groups.php
+++ b/VKAPI/Handlers/Groups.php
@@ -2,6 +2,7 @@
namespace openvk\VKAPI\Handlers;
use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
use openvk\Web\Models\Repositories\Users as UsersRepo;
+use openvk\Web\Models\Repositories\Posts as PostsRepo;
use openvk\Web\Models\Entities\Club;
final class Groups extends VKAPIRequestHandler
@@ -80,6 +81,19 @@ final class Groups extends VKAPIRequestHandler
break;
case "members_count":
$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;
}
}
@@ -188,7 +202,19 @@ final class Groups extends VKAPIRequestHandler
case "description":
$response[$i]->description = $clb->getDescription();
break;
- case "contacts":
+ 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":
$contacts;
$contactTmp = $clb->getManagers(1, true);
@@ -288,7 +314,7 @@ final class Groups extends VKAPIRequestHandler
string $description = NULL,
string $screen_name = NULL,
string $website = NULL,
- int $wall = NULL,
+ int $wall = -1,
int $topics = NULL,
int $adminlist = NULL,
int $topicsAboveWall = NULL,
@@ -308,17 +334,26 @@ final class Groups extends VKAPIRequestHandler
!empty($description) ? $club->setAbout($description) : NULL;
!empty($screen_name) ? $club->setShortcode($screen_name) : 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($adminlist) ? $club->setAdministrators_List_Display($adminlist) : NULL;
!empty($topicsAboveWall) ? $club->setDisplay_Topics_Above_Wall($topicsAboveWall) : NULL;
!empty($hideFromGlobalFeed) ? $club->setHide_From_Global_Feed($hideFromGlobalFeed) : NULL;
+
in_array($audio, [0, 1]) ? $club->setEveryone_can_upload_audios($audio) : NULL;
try {
$club->save();
} 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;
@@ -472,7 +507,7 @@ final class Groups extends VKAPIRequestHandler
"title" => $club->getName(),
"description" => $club->getDescription() != NULL ? $club->getDescription() : "",
"address" => $club->getShortcode(),
- "wall" => $club->canPost() == true ? 1 : 0,
+ "wall" => $club->getWallType(), # отличается от вкшных но да ладно
"photos" => 1,
"video" => 0,
"audio" => $club->isEveryoneCanUploadAudios() ? 1 : 0,
diff --git a/VKAPI/Handlers/Newsfeed.php b/VKAPI/Handlers/Newsfeed.php
index d9992430..4833e7be 100644
--- a/VKAPI/Handlers/Newsfeed.php
+++ b/VKAPI/Handlers/Newsfeed.php
@@ -51,7 +51,7 @@ final class Newsfeed extends VKAPIRequestHandler
{
$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)
$queryBase .= " AND `nsfw` = 0";
diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php
index fde87fcb..b6082c61 100644
--- a/VKAPI/Handlers/Wall.php
+++ b/VKAPI/Handlers/Wall.php
@@ -1,7 +1,7 @@
requireUser();
@@ -29,7 +29,7 @@ final class Wall extends VKAPIRequestHandler
$items = [];
$profiles = [];
$groups = [];
- $cnt = $posts->getPostCountOnUserWall($owner_id);
+ $cnt = 0;
if ($owner_id > 0)
$wallOnwer = (new UsersRepo)->get($owner_id);
@@ -43,7 +43,47 @@ final class Wall extends VKAPIRequestHandler
if(!$wallOnwer)
$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();
$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)[
"id" => $post->getVirtualId(),
"from_id" => $from_id,
"owner_id" => $post->getTargetWall(),
"date" => $post->getPublicationTime()->timestamp(),
- "post_type" => "post",
+ "post_type" => $postType,
"text" => $post->getText(false),
"copy_history" => $repost,
"can_edit" => $post->canBeEditedBy($this->getUser()),
@@ -135,6 +186,7 @@ final class Wall extends VKAPIRequestHandler
"is_explicit" => $post->isExplicit(),
"attachments" => $attachments,
"post_source" => $post_source,
+ "signer_id" => $signerId,
"comments" => (object)[
"count" => $post->getCommentsCount(),
"can_post" => 1
@@ -156,6 +208,9 @@ final class Wall extends VKAPIRequestHandler
else
$groups[] = $from_id * -1;
+ if($post->isSigned())
+ $profiles[] = $post->getOwner(false)->getId();
+
$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)[
"id" => $post->getVirtualId(),
"from_id" => $from_id,
"owner_id" => $post->getTargetWall(),
"date" => $post->getPublicationTime()->timestamp(),
- "post_type" => "post",
+ "post_type" => $postType,
"text" => $post->getText(false),
"copy_history" => $repost,
"can_edit" => $post->canBeEditedBy($this->getUser()),
@@ -314,6 +381,7 @@ final class Wall extends VKAPIRequestHandler
"is_pinned" => $post->isPinned(),
"is_explicit" => $post->isExplicit(),
"post_source" => $post_source,
+ "signer_id" => $signerId,
"attachments" => $attachments,
"comments" => (object)[
"count" => $post->getCommentsCount(),
@@ -336,6 +404,9 @@ final class Wall extends VKAPIRequestHandler
else
$groups[] = $from_id * -1;
+ if($post->isSigned())
+ $profiles[] = $post->getOwner(false)->getId();
+
$attachments = NULL; # free attachments so it will not clone everything
$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->willExecuteWriteAction();
@@ -412,6 +483,46 @@ final class Wall extends VKAPIRequestHandler
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"];
if($wallOwner instanceof Club && $from_group == 1 && $signed != 1 && $anon) {
$manager = $wallOwner->getManager($this->getUser());
@@ -440,6 +551,10 @@ final class Wall extends VKAPIRequestHandler
$post->setContent($message);
$post->setFlags($flags);
$post->setApi_Source_Name($this->getPlatform());
+
+ if($owner_id < 0 && !$wallOwner->canBeModifiedBy($this->getUser()) && $wallOwner->getWallType() == 2)
+ $post->setSuggested(1);
+
$post->save();
} catch(\LogicException $ex) {
$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())
(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()];
}
@@ -830,7 +961,31 @@ final class Wall extends VKAPIRequestHandler
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 = "") {
$this->requireUser();
$this->willExecuteWriteAction();
diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php
index 319678f3..5324efb6 100644
--- a/Web/Models/Entities/Club.php
+++ b/Web/Models/Entities/Club.php
@@ -3,7 +3,7 @@ namespace openvk\Web\Models\Entities;
use openvk\Web\Util\DateTime;
use openvk\Web\Models\RowModel;
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 Chandler\Database\DatabaseConnection as DB;
use Chandler\Security\User as ChandlerUser;
@@ -23,6 +23,10 @@ class Club extends RowModel
const NOT_RELATED = 0;
const SUBSCRIBED = 1;
const REQUEST_SENT = 2;
+
+ const WALL_CLOSED = 0;
+ const WALL_OPEN = 1;
+ const WALL_LIMITED = 2;
function getId(): int
{
@@ -45,6 +49,11 @@ class Club extends RowModel
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
{
@@ -182,6 +191,14 @@ class Club extends RowModel
$this->stateChanges("shortcode", $code);
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
{
@@ -291,6 +308,21 @@ class Club extends RowModel
yield $rel;
}
}
+
+ 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
{
diff --git a/Web/Models/Entities/Notifications/NewSuggestedPostsNotification.php b/Web/Models/Entities/Notifications/NewSuggestedPostsNotification.php
new file mode 100644
index 00000000..e1795b08
--- /dev/null
+++ b/Web/Models/Entities/Notifications/NewSuggestedPostsNotification.php
@@ -0,0 +1,13 @@
+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
@@ -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;
+ }
}
diff --git a/Web/Models/Entities/Post.php b/Web/Models/Entities/Post.php
index a8d444eb..48a2191f 100644
--- a/Web/Models/Entities/Post.php
+++ b/Web/Models/Entities/Post.php
@@ -207,6 +207,9 @@ class Post extends Postable
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);
}
@@ -245,6 +248,11 @@ class Post extends Postable
$this->unwire();
$this->save();
}
+
+ function getSuggestionType()
+ {
+ return $this->getRecord()->suggested;
+ }
function toNotifApiStruct()
{
@@ -273,6 +281,12 @@ class Post extends Postable
if($this->getTargetWall() > 0)
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();
}
diff --git a/Web/Models/Entities/Postable.php b/Web/Models/Entities/Postable.php
index a6478605..55e1adf7 100644
--- a/Web/Models/Entities/Postable.php
+++ b/Web/Models/Entities/Postable.php
@@ -33,7 +33,7 @@ abstract class Postable extends Attachable
{
$oid = (int) $this->getRecord()->owner;
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);
if($oid > 0)
diff --git a/Web/Models/Repositories/Posts.php b/Web/Models/Repositories/Posts.php
index c354f152..89ee58ea 100644
--- a/Web/Models/Repositories/Posts.php
+++ b/Web/Models/Repositories/Posts.php
@@ -58,14 +58,59 @@ class Posts
}
$sel = $this->posts->where([
- "wall" => $user,
- "pinned" => false,
- "deleted" => false,
+ "wall" => $user,
+ "pinned" => false,
+ "deleted" => false,
+ "suggested" => 0,
])->order("created DESC")->limit($perPage, $offset);
foreach($sel as $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
{
@@ -74,6 +119,7 @@ class Posts
->where("MATCH (content) AGAINST (? IN BOOLEAN MODE)", "+$hashtag")
->where("deleted", 0)
->order("created DESC")
+ ->where("suggested", 0)
->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
foreach($sel as $post)
@@ -85,14 +131,22 @@ class Posts
$hashtag = "#$hashtag";
$sel = $this->posts
->where("content LIKE ?", "%$hashtag%")
- ->where("deleted", 0);
+ ->where("deleted", 0)
+ ->where("suggested", 0);
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))
return new Post($post);
else
@@ -112,7 +166,7 @@ class Posts
else
$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);
if($nnparamsCount > 0) {
@@ -134,7 +188,66 @@ class Posts
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
diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php
index eb8f446c..beeede13 100644
--- a/Web/Presenters/GroupPresenter.php
+++ b/Web/Presenters/GroupPresenter.php
@@ -3,7 +3,7 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Club, Photo, Post};
use Nette\InvalidStateException;
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;
final class GroupPresenter extends OpenVKPresenter
@@ -35,6 +35,13 @@ final class GroupPresenter extends OpenVKPresenter
$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;
}
}
@@ -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->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->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);
@@ -414,4 +426,37 @@ final class GroupPresenter extends OpenVKPresenter
$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);
+ }
}
diff --git a/Web/Presenters/InternalAPIPresenter.php b/Web/Presenters/InternalAPIPresenter.php
index e2e6b50e..dca5db04 100644
--- a/Web/Presenters/InternalAPIPresenter.php
+++ b/Web/Presenters/InternalAPIPresenter.php
@@ -104,7 +104,7 @@ final class InternalAPIPresenter extends OpenVKPresenter
}
if($this->postParam("parentType", false) == "post") {
- $post = (new Posts)->getPostById($owner_id, $post_id);
+ $post = (new Posts)->getPostById($owner_id, $post_id, true);
} else {
$post = (new Comments)->get($post_id);
}
diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php
index a01c8262..d0f17d56 100644
--- a/Web/Presenters/WallPresenter.php
+++ b/Web/Presenters/WallPresenter.php
@@ -2,7 +2,7 @@
namespace openvk\Web\Presenters;
use openvk\Web\Models\Exceptions\TooMuchOptionsException;
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 Chandler\Database\DatabaseConnection;
use Nette\InvalidStateException as ISE;
@@ -66,11 +66,32 @@ final class WallPresenter extends OpenVKPresenter
$this->template->oObj = $owner;
if($user < 0)
$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->canPost = $canPost;
- $this->template->count = $this->posts->getPostCountOnUserWall($user);
- $this->template->posts = iterator_to_array($this->posts->getPostsFromUsersWall($user, (int) ($_GET["p"] ?? 1)));
+ $this->template->count = $count;
+ $this->template->type = $type;
+ $this->template->posts = iterator_to_array($iterator);
$this->template->paginatorConf = (object) [
"count" => $this->template->count,
"page" => (int) ($_GET["p"] ?? 1),
@@ -150,6 +171,7 @@ final class WallPresenter extends OpenVKPresenter
->select("id")
->where("wall IN (?)", $ids)
->where("deleted", 0)
+ ->where("suggested", 0)
->order("created DESC");
$this->template->paginatorConf = (object) [
"count" => sizeof($posts),
@@ -169,7 +191,7 @@ final class WallPresenter extends OpenVKPresenter
$page = (int) ($_GET["p"] ?? 1);
$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)
$queryBase .= " AND `nsfw` = 0";
@@ -343,6 +365,10 @@ final class WallPresenter extends OpenVKPresenter
$post->setAnonymous($anon);
$post->setFlags($flags);
$post->setNsfw($this->postParam("nsfw") === "on");
+
+ if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2)
+ $post->setSuggested(1);
+
$post->save();
} catch (\LengthException $ex) {
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big"));
@@ -371,12 +397,32 @@ final class WallPresenter extends OpenVKPresenter
if($wall > 0)
$excludeMentions[] = $wall;
- $mentions = iterator_to_array($post->resolveMentions($excludeMentions));
- foreach($mentions as $mentionee)
- if($mentionee instanceof User)
- (new MentionNotification($mentionee, $post, $post->getOwner(), strip_tags($post->getText())))->emit();
+ if($wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2) {
+ # Чтобы не было упоминаний из предложки
+ } else {
+ $mentions = iterator_to_array($post->resolveMentions($excludeMentions));
+
+ foreach($mentions as $mentionee)
+ if($mentionee instanceof User)
+ (new MentionNotification($mentionee, $post, $post->getOwner(), strip_tags($post->getText())))->emit();
+ }
- $this->redirect($wallOwner->getURL());
+ 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());
+ }
}
function renderPost(int $wall, int $post_id): void
@@ -487,7 +533,7 @@ final class WallPresenter extends OpenVKPresenter
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
- $post = $this->posts->getPostById($wall, $post_id);
+ $post = $this->posts->getPostById($wall, $post_id, true);
if(!$post)
$this->notFound();
$user = $this->user->id;
@@ -502,6 +548,9 @@ final class WallPresenter extends OpenVKPresenter
else $canBeDeletedByOtherUser = false;
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) {
$post->unwire();
$post->delete();
@@ -597,4 +646,93 @@ final class WallPresenter extends OpenVKPresenter
"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())
+ ]);
+ }
}
diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml
index 8f1a35f0..d636ad42 100644
--- a/Web/Presenters/templates/@layout.xml
+++ b/Web/Presenters/templates/@layout.xml
@@ -233,7 +233,16 @@