From 10729d82949753a5f9de809b4b2f4540bd7c0bff Mon Sep 17 00:00:00 2001
From: lalka2016 <99399973+lalka2016@users.noreply.github.com>
Date: Sun, 30 Jul 2023 20:03:27 +0300
Subject: [PATCH] Wall: add early suggestions
---
ServiceAPI/Wall.php | 57 ++++++++++++
VKAPI/Handlers/Wall.php | 51 ++++++++++-
Web/Models/Entities/Club.php | 17 ++++
.../PostAcceptedNotification.php | 13 +++
Web/Models/Entities/Post.php | 5 ++
Web/Models/Repositories/Posts.php | 63 +++++++++++--
Web/Presenters/GroupPresenter.php | 78 +++++++++++++++-
Web/Presenters/WallPresenter.php | 16 +++-
Web/Presenters/templates/Group/Edit.xml | 7 +-
Web/Presenters/templates/Group/Suggested.xml | 26 ++++++
Web/Presenters/templates/Group/View.xml | 12 +++
.../components/notifications/6/_14_5_.xml | 7 ++
.../components/post/microblogpost.xml | 8 +-
Web/routes.yml | 4 +
Web/static/css/main.css | 18 ++++
Web/static/js/al_wall.js | 90 ++++++++++++++++++-
install/sqls/00039-suggest-posts.sql | 1 +
locales/en.strings | 41 ++++++++-
locales/ru.strings | 42 ++++++++-
19 files changed, 530 insertions(+), 26 deletions(-)
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 install/sqls/00039-suggest-posts.sql
diff --git a/ServiceAPI/Wall.php b/ServiceAPI/Wall.php
index 5677f7ba..73eeee36 100644
--- a/ServiceAPI/Wall.php
+++ b/ServiceAPI/Wall.php
@@ -2,6 +2,7 @@
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\Post;
use openvk\Web\Models\Entities\User;
+use openvk\Web\Models\Entities\Notifications\{PostAcceptedNotification};
use openvk\Web\Models\Repositories\{Posts, Notes};
class Wall implements Handler
@@ -95,4 +96,60 @@ class Wall implements Handler
$resolve($arr);
}
+
+ function declinePost(int $id, callable $resolve, callable $reject)
+ {
+ $post = $this->posts->get($id);
+ if(!$post || $post->isDeleted())
+ $reject(11, "No post with id=$id");
+
+ if($post->getSuggestionType() == 0)
+ $reject(19, "Post is not suggested");
+
+ if($post->getSuggestionType() == 2)
+ $reject(10, "Post is already declined");
+
+ if(!$post->canBePinnedBy($this->user))
+ $reject(22, "Access to post denied :)");
+
+ $post->setSuggested(2);
+ $post->save();
+
+ $resolve($this->posts->getSuggestedPostsCount($post->getWallOwner()->getId()));
+ }
+
+ function acceptPost(int $id, bool $sign, string $content, callable $resolve, callable $reject)
+ {
+ $post = $this->posts->get($id);
+ if(!$post || $post->isDeleted())
+ $reject(11, "No post with id=$id");
+
+ if($post->getSuggestionType() == 0)
+ $reject(19, "Post is not suggested");
+
+ if($post->getSuggestionType() == 2)
+ $reject(10, "Post is declined");
+
+ if(!$post->canBePinnedBy($this->user))
+ $reject(22, "Access to post denied :)");
+
+ $author = $post->getOwner();
+ $flags = 0;
+ $flags |= 0b10000000;
+
+ if($sign) {
+ $flags |= 0b01000000;
+ }
+
+ $post->setSuggested(0);
+ $post->setCreated(time());
+ $post->setFlags($flags);
+ $post->setContent($content);
+
+ $post->save();
+
+ (new PostAcceptedNotification($author, $post, $post->getWallOwner()))->emit();
+
+ $resolve(["id" => $post->getPrettyId(), "new_count" => $this->posts->getSuggestedPostsCount($post->getWallOwner()->getId())]);
+ }
}
diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php
index d52dfce1..dfe33c24 100644
--- a/VKAPI/Handlers/Wall.php
+++ b/VKAPI/Handlers/Wall.php
@@ -18,7 +18,7 @@ use openvk\Web\Models\Repositories\Notes as NotesRepo;
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();
@@ -27,7 +27,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);
@@ -41,7 +41,43 @@ 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":
+ $this->fail(66666, "Not implemented :(");
+ break;
+ case "others":
+ $this->fail(66666, "Not implemented :(");
+ break;
+ case "postponed":
+ $this->fail(66666, "Otlojka is not implemented :)");
+ break;
+ # В апи, походу, нету метода, который бы публиковал запись из предложки
+ case "suggests":
+ if($owner_id < 0) {
+ if($wallOnwer->canBeModifiedBy($this->getUser())) {
+ $iteratorv = $posts->getSuggestedPosts($owner_id * -1, 1, $count, $offset);
+ $cnt = $posts->getSuggestedPostsCount($owner_id * -1);
+ } else {
+ $iteratorv = $posts->getSuggestedPosts($owner_id * -1, 1, $count, $offset);
+ $cnt = $posts->getSuggestedPostsCount($owner_id * -1);
+ }
+ } 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 = [];
@@ -428,6 +464,11 @@ 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");
@@ -494,6 +535,10 @@ 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) {
+ return (object)["post_id" => "on_view"];
+ }
+
return (object)["post_id" => $post->getVirtualId()];
}
diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php
index 15c458a9..722233b7 100644
--- a/Web/Models/Entities/Club.php
+++ b/Web/Models/Entities/Club.php
@@ -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 > 3 || $type < 0)
+ throw new \LogicException("Invalid wall");
+
+ $this->stateChanges("wall", $type);
+ }
function isSubscriptionAccepted(User $user): bool
{
diff --git a/Web/Models/Entities/Notifications/PostAcceptedNotification.php b/Web/Models/Entities/Notifications/PostAcceptedNotification.php
new file mode 100644
index 00000000..bb99d34e
--- /dev/null
+++ b/Web/Models/Entities/Notifications/PostAcceptedNotification.php
@@ -0,0 +1,13 @@
+unwire();
$this->save();
}
+
+ function getSuggestionType()
+ {
+ return $this->getRecord()->suggested;
+ }
use Traits\TRichText;
}
diff --git a/Web/Models/Repositories/Posts.php b/Web/Models/Repositories/Posts.php
index c354f152..1dd2ca27 100644
--- a/Web/Models/Repositories/Posts.php
+++ b/Web/Models/Repositories/Posts.php
@@ -58,9 +58,10 @@ 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)
@@ -74,6 +75,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 +87,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 +122,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 +144,44 @@ 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 getSuggestedPosts(int $club, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable
+ {
+ $sel = $this->posts
+ ->where("deleted", 0)
+ ->where("wall", $club * -1)
+ ->order("created DESC")
+ ->where("suggested", 1)
+ ->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
+
+ 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): \Traversable
+ {
+ $sel = $this->posts
+ ->where("deleted", 0)
+ ->where("wall", $club * -1)
+ ->where("owner", $user)
+ ->order("created DESC")
+ ->where("suggested", 1)
+ ->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
+
+ 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 4f671df3..822582b7 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};
+use openvk\Web\Models\Repositories\{Posts, Clubs, Users, Albums, Managers, Topics};
use Chandler\Security\Authenticator;
final class GroupPresenter extends OpenVKPresenter
@@ -29,6 +29,14 @@ final class GroupPresenter extends OpenVKPresenter
$this->template->topics = (new Topics)->getLastTopics($club, 3);
$this->template->topicsCount = (new Topics)->getClubTopicsCount($club);
+ if(!is_null($this->user->identity) && !$club->canBeModifiedBy($this->user->identity) && $club->getWallType() == 2) {
+ $this->template->suggestedPostsCountByUser = (new Posts)->getSuggestedPostsCountByUser($club->getId(), $this->user->id);
+ }
+
+ if(!is_null($this->user->identity) && $club->canBeModifiedBy($this->user->identity) && $club->getWallType() == 2) {
+ $this->template->suggestedPostsCountByEveryone = (new Posts)->getSuggestedPostsCount($club->getId());
+ }
+
$this->template->club = $club;
}
}
@@ -192,7 +200,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->willExecuteWriteAction();
$club = $this->clubs->get($id);
- if(!$club || !$club->canBeModifiedBy($this->user->identity))
+ if(!$club || !$club->canBeModifiedBy($this->user->identity) || $club->isDeleted())
$this->notFound();
else
$this->template->club = $club;
@@ -396,4 +404,68 @@ final class GroupPresenter extends OpenVKPresenter
$this->flashFail("succ", tr("information_-1"), tr("group_owner_setted", $newOwner->getCanonicalName(), $club->getName()));
}
-}
+
+ function renderSuggestedThisUser(int $id)
+ {
+ $this->assertUserLoggedIn();
+
+ $club = $this->clubs->get($id);
+ if(!$club || method_exists($club, "isDeleted") && $club->isDeleted())
+ $this->notFound();
+ else
+ $this->template->club = $club;
+
+ if($club->getWallType() == 1) {
+ $this->flash("err", tr("error_suggestions"), tr("error_suggestions_closed"));
+ $this->redirect("/club".$club->getId());
+ }
+
+ if($club->getWallType() == 0) {
+ $this->flash("err", tr("error_suggestions"), tr("error_suggestions_open"));
+ $this->redirect("/club".$club->getId());
+ }
+
+ if($club->canBeModifiedBy($this->user->identity)) {
+ $this->flash("err", tr("error_suggestions"), "No sense");
+ $this->redirect("/club".$club->getId());
+ }
+
+ $this->template->posts = (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";
+ $this->template->page = (int) ($this->queryParam("p") ?? 1);
+ $this->template->_template = "Group/Suggested.xml";
+ }
+
+ function renderSuggestedAll(int $id)
+ {
+ $this->assertUserLoggedIn();
+
+ $club = $this->clubs->get($id);
+ if(!$club || method_exists($club, "isDeleted") && $club->isDeleted())
+ $this->notFound();
+ else
+ $this->template->club = $club;
+
+ if($club->getWallType() == 1) {
+ $this->flash("err", tr("error_suggestions"), tr("error_suggestions_closed"));
+ $this->redirect("/club".$club->getId());
+ }
+
+ if($club->getWallType() == 0) {
+ $this->flash("err", tr("error_suggestions"), tr("error_suggestions_open"));
+ $this->redirect("/club".$club->getId());
+ }
+
+ if(!$club->canBeModifiedBy($this->user->identity)) {
+ $this->flash("err", tr("error_suggestions"), tr("error_suggestions_access"));
+ $this->redirect("/club".$club->getId());
+ }
+
+ $this->template->posts = (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);
+ $this->template->_template = "Group/Suggested.xml";
+ }
+}
\ No newline at end of file
diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php
index 727101ff..c135c298 100644
--- a/Web/Presenters/WallPresenter.php
+++ b/Web/Presenters/WallPresenter.php
@@ -148,6 +148,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),
@@ -167,7 +168,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";
@@ -305,6 +306,11 @@ 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"));
@@ -334,7 +340,11 @@ final class WallPresenter extends OpenVKPresenter
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) {
+ $this->redirect("/club".$wallOwner->getId()."/suggested");
+ } else {
+ $this->redirect($wallOwner->getURL());
+ }
}
function renderPost(int $wall, int $post_id): void
@@ -436,7 +446,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;
diff --git a/Web/Presenters/templates/Group/Edit.xml b/Web/Presenters/templates/Group/Edit.xml
index 22f0b490..6d3b013d 100644
--- a/Web/Presenters/templates/Group/Edit.xml
+++ b/Web/Presenters/templates/Group/Edit.xml
@@ -77,7 +77,12 @@
{_wall}:
- {_group_allow_post_for_everyone}
+ |
diff --git a/Web/Presenters/templates/Group/Suggested.xml b/Web/Presenters/templates/Group/Suggested.xml
new file mode 100644
index 00000000..bd6649d9
--- /dev/null
+++ b/Web/Presenters/templates/Group/Suggested.xml
@@ -0,0 +1,26 @@
+{extends "../@layout.xml"}
+
+{block title}{_suggested} {$club->getCanonicalName()}{/block}
+
+{block header}
+ {$club->getName()}
+ » {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}
+ {if $type == "my"}{tr("x_suggested_posts_in_group_by_you", $count)}{else}{tr("x_suggested_posts_in_group", $count)}{/if}
+ {foreach $posts as $post}
+ {include "../components/post/microblogpost.xml", post => $post, commentSection => false, suggestion => true, forceNoCommentsLink => true, forceNoPinLink => true, forceNoLike => true, forceNoShareLink => true}
+ {/foreach}
+ {/if}
+ {include "../components/paginator.xml", conf => (object) [
+ "page" => $page,
+ "count" => $count,
+ "amount" => sizeof($posts),
+ "perPage" => OPENVK_DEFAULT_PER_PAGE,
+ "atBottom" => true,
+ ]}
+{/block}
diff --git a/Web/Presenters/templates/Group/View.xml b/Web/Presenters/templates/Group/View.xml
index bf392a9f..4a588c13 100644
--- a/Web/Presenters/templates/Group/View.xml
+++ b/Web/Presenters/templates/Group/View.xml
@@ -91,7 +91,19 @@
+
+
+
+
{presenter "openvk!Wall->wallEmbedded", -$club->getId()}
+
+
{var $avatarPhoto = $club->getAvatarPhoto()}
diff --git a/Web/Presenters/templates/components/notifications/6/_14_5_.xml b/Web/Presenters/templates/components/notifications/6/_14_5_.xml
new file mode 100644
index 00000000..ab77cb94
--- /dev/null
+++ b/Web/Presenters/templates/components/notifications/6/_14_5_.xml
@@ -0,0 +1,7 @@
+{var $club = $notification->getModel(1)}
+{var $post = $notification->getModel(0)}
+
+{_group}
+
{$club->getName()}
+{_nt_accepted_your_post}
+
{_nt_post_small}.
\ No newline at end of file
diff --git a/Web/Presenters/templates/components/post/microblogpost.xml b/Web/Presenters/templates/components/post/microblogpost.xml
index 98a41d72..b5060fcb 100644
--- a/Web/Presenters/templates/components/post/microblogpost.xml
+++ b/Web/Presenters/templates/components/post/microblogpost.xml
@@ -65,7 +65,7 @@
- {$post->getText()|noescape}
+
{$post->getText()|noescape}
+
+
+
+
diff --git a/Web/routes.yml b/Web/routes.yml
index d1a0e7ae..2dbc8a2b 100644
--- a/Web/routes.yml
+++ b/Web/routes.yml
@@ -201,6 +201,10 @@ routes:
handler: "Group->admin"
- url: "/club{num}/setAdmin"
handler: "Group->modifyAdmin"
+ - url: "/club{num}/suggested"
+ handler: "Group->suggestedThisUser"
+ - url: "/club{num}/suggested/all"
+ handler: "Group->suggestedAll"
- url: "/groups{num}"
handler: "User->groups"
- url: "/groups_pin"
diff --git a/Web/static/css/main.css b/Web/static/css/main.css
index bbbe4d28..22aa420d 100644
--- a/Web/static/css/main.css
+++ b/Web/static/css/main.css
@@ -2695,4 +2695,22 @@ body.article .floating_sidebar, body.article .page_content {
position: absolute;
right: 22px;
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;
}
\ No newline at end of file
diff --git a/Web/static/js/al_wall.js b/Web/static/js/al_wall.js
index bb349c14..f60a3061 100644
--- a/Web/static/js/al_wall.js
+++ b/Web/static/js/al_wall.js
@@ -192,7 +192,7 @@ tippy(".client_app", {
function addNote(textareaId, nid)
{
if(nid > 0) {
- note.value = nid
+ document.getElementById("note").value = nid
let noteObj = document.querySelector("#nd"+nid)
let nortd = document.querySelector("#post-buttons"+textareaId+" .post-has-note");
@@ -200,7 +200,7 @@ function addNote(textareaId, nid)
nortd.innerHTML = `${tr("note")} ${escapeHtml(noteObj.dataset.name)}`
} else {
- note.value = "none"
+ document.getElementById("note").value = "none"
let nortd = document.querySelector("#post-buttons"+textareaId+" .post-has-note");
nortd.style.display = "none"
@@ -227,7 +227,7 @@ async function attachNote(id)
${tr("select_or_create_new")}
`
- if(note.value != "none") {
+ if(document.getElementById("note").value != "none") {
body += `
${tr("do_not_attach_note")}
@@ -262,4 +262,86 @@ async function showArticle(note_id) {
u("#articleText").html(`
${note.title}
` + note.html);
u("body").removeClass("dimmed");
u("body").addClass("article");
-}
\ No newline at end of file
+}
+
+$(document).on("click", "#publish_post", async (e) => {
+ let id = Number(e.currentTarget.dataset.id)
+ let post;
+ let body = `
+
+
+ `
+
+ MessageBox(tr("publishing_suggested_post"), body, [tr("publish"), tr("cancel")], [(async () => {
+ let id = Number(e.currentTarget.dataset.id)
+ let post;
+
+ try {
+ post = await API.Wall.acceptPost(id, document.getElementById("signatr").checked, document.getElementById("pooblish").value)
+ } catch(ex) {
+ switch(ex.code) {
+ case 11:
+ MessageBox(tr("error"), tr("error_declining_invalid_post"), [tr("ok")], [Function.noop]);
+ break;
+ case 19:
+ MessageBox(tr("error"), tr("error_declining_not_suggested_post"), [tr("ok")], [Function.noop]);
+ break;
+ case 10:
+ MessageBox(tr("error"), tr("error_declining_declined_post"), [tr("ok")], [Function.noop]);
+ break;
+ case 22:
+ MessageBox(tr("error"), "Access denied", [tr("ok")], [Function.noop]);
+ break;
+ default:
+ MessageBox(tr("error"), "Unknown error "+ex.code+": "+ex.message, [tr("ok")], [Function.noop]);
+ break;
+ }
+
+ return 0;
+ }
+
+ NewNotification(tr("suggestion_succefully_published"), tr("suggestion_press_to_go"), null, () => {window.location.assign("/wall" + post.id)});
+ document.getElementById("cound").innerHTML = tr("x_suggested_posts_in_group", post.new_count)
+ e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode.outerHTML = ""
+ }), Function.noop]);
+
+ document.getElementById("pooblish").innerHTML = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode.querySelector(".really_text").innerHTML
+ document.querySelector(".ovk-diag-body").style.padding = "9px";
+})
+
+$(document).on("click", "#decline_post", async (e) => {
+ let id = Number(e.currentTarget.dataset.id)
+ let post;
+
+ try {
+ e.currentTarget.parentNode.parentNode.insertAdjacentHTML("afterbegin", `

`)
+ post = await API.Wall.declinePost(id)
+ } catch(ex) {
+ switch(ex.code) {
+ case 11:
+ MessageBox(tr("error"), tr("error_declining_invalid_post"), [tr("ok")], [Function.noop]);
+ break;
+ case 19:
+ MessageBox(tr("error"), tr("error_declining_not_suggested_post"), [tr("ok")], [Function.noop]);
+ break;
+ case 10:
+ MessageBox(tr("error"), tr("error_declining_declined_post"), [tr("ok")], [Function.noop]);
+ break;
+ case 22:
+ MessageBox(tr("error"), "Access denied", [tr("ok")], [Function.noop]);
+ break;
+ default:
+ MessageBox(tr("error"), "Unknown error "+ex.code+": "+ex.message, [tr("ok")], [Function.noop]);
+ break;
+ }
+
+ return 0;
+ } finally {
+ u("#deleteMe").remove()
+ }
+
+ // а хули
+ NewNotification(tr("suggestion_succefully_declined"), "", null);
+ e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode.outerHTML = ""
+ document.getElementById("cound").innerHTML = tr("x_suggested_posts_in_group", post)
+})
\ No newline at end of file
diff --git a/install/sqls/00039-suggest-posts.sql b/install/sqls/00039-suggest-posts.sql
new file mode 100644
index 00000000..321c2a6a
--- /dev/null
+++ b/install/sqls/00039-suggest-posts.sql
@@ -0,0 +1 @@
+ALTER TABLE `posts` ADD `suggested` TINYINT(2) UNSIGNED NOT NULL DEFAULT '0' AFTER `deleted`;
\ No newline at end of file
diff --git a/locales/en.strings b/locales/en.strings
index 4aaa4483..020076a9 100644
--- a/locales/en.strings
+++ b/locales/en.strings
@@ -260,6 +260,7 @@
/* Group */
+"group" = "Group";
"name_group" = "Name";
"subscribe" = "Subscribe";
"unsubscribe" = "Unsubscribe";
@@ -297,8 +298,38 @@
"set_comment" = "Set comment";
"hidden_yes" = "Hidden: Yes";
"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";
+"show_suggested_posts" = "$1 suggested posts by you";
+"show_suggested_posts_everyone" = "$1 suggested posts";
"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";
+
+"publishing_suggested_post" = "Publishing suggested post";
+"x_suggested_posts_in_group" = "This group has $1 suggested posts";
+"x_suggested_posts_in_group_by_you" = "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 him";
+
+"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";
"group_administrators_list" = "Admins list";
"group_display_only_creator" = "Display only group creator";
@@ -331,6 +362,11 @@
"search_by_groups" = "Search by groups";
"search_group_desc" = "Here you can browse through the existing groups and choose a group to suit 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.";
+
/* Albums */
"create" = "Create";
@@ -675,6 +711,7 @@
"nt_shared_yours" = "shared your";
"nt_commented_yours" = "commented";
"nt_written_on_your_wall" = "wrote on your wall";
+"nt_accepted_your_post" = "accepted your suggested";
"nt_made_you_admin" = "appointed you in the community";
"nt_from" = "from";
@@ -694,6 +731,8 @@
"nt_mention_in_note" = "in discussion of this note";
"nt_mention_in_topic" = "in the discussion";
+"nt_post_small" = "post";
+
/* Time */
"time_at_sp" = " at ";
diff --git a/locales/ru.strings b/locales/ru.strings
index 6faa5e2e..a4f06e47 100644
--- a/locales/ru.strings
+++ b/locales/ru.strings
@@ -245,6 +245,7 @@
/* Group */
+"group" = "Сообщество";
"name_group" = "Название";
"subscribe" = "Подписаться";
"unsubscribe" = "Отписаться";
@@ -281,8 +282,40 @@
"set_comment" = "Изменить комментарий";
"hidden_yes" = "Скрыт: Да";
"hidden_no" = "Скрыт: Нет";
-"group_allow_post_for_everyone" = "Разрешить публиковать записи всем";
+
+"group_allow_post_for_everyone" = "Открытая";
+"group_limited_post" = "Предложка";
+"group_closed_post" = "Закрытая";
+"suggest_new" = "Предложить новость";
+"show_suggested_posts" = "$1 предложенных вами записей";
+"show_suggested_posts_everyone" = "$1 предложенных записей";
"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" = "Отклонить";
+
+"publishing_suggested_post" = "Публикация предложенной записи";
+"x_suggested_posts_in_group" = "В эту группу предложили $1 записей";
+"x_suggested_posts_in_group_by_you" = "Вы предложили в эту группу $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" = "Статистика";
"group_administrators_list" = "Список админов";
"group_display_only_creator" = "Отображать только создателя группы";
@@ -315,6 +348,11 @@
"search_by_groups" = "Поиск по группам";
"search_group_desc" = "Здесь Вы можете просмотреть существующие группы и выбрать группу себе по вкусу...";
+"error_suggestions" = "Ошибка доступа к предложенным";
+"error_suggestions_closed" = "У этой группы закрытая стена.";
+"error_suggestions_open" = "У этой группы открытая стена.";
+"error_suggestions_access" = "Просматривать все предложенные записи могут только администраторы группы.";
+
/* Albums */
"create" = "Создать";
@@ -632,6 +670,7 @@
"nt_shared_yours" = "поделился(-ась) вашим";
"nt_commented_yours" = "оставил(а) комментарий под";
"nt_written_on_your_wall" = "написал(а) на вашей стене";
+"nt_accepted_your_post" = "опубликовало вашу предложенную";
"nt_made_you_admin" = "назначил(а) вас руководителем сообщества";
"nt_from" = "от";
"nt_yours_adjective" = "вашим";
@@ -649,6 +688,7 @@
"nt_mention_in_video" = "в обсуждении видеозаписи";
"nt_mention_in_note" = "в обсуждении заметки";
"nt_mention_in_topic" = "в обсуждении";
+"nt_post_small" = "запись";
/* Time */