mirror of
https://github.com/openvk/openvk
synced 2024-11-11 09:29:29 +03:00
Merge branch 'master' into feature-reports
This commit is contained in:
commit
47cfafc4c8
188 changed files with 18733 additions and 1158 deletions
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -1,3 +1,3 @@
|
|||
[submodule "locales"]
|
||||
path = locales
|
||||
url = https://github.com/openvk/locales
|
||||
[submodule "Web/static/img/oxygen-icons"]
|
||||
path = Web/static/img/oxygen-icons
|
||||
url = https://github.com/KDE/oxygen-icons5.git
|
||||
|
|
|
@ -11,7 +11,7 @@ RUN dnf -y module enable php:remi-7.4 && \
|
|||
dnf -y module enable nodejs:14
|
||||
|
||||
#And install dependencies:
|
||||
RUN dnf -y install php php-cli php-common unzip php-zip php-yaml php-gd php-pdo_mysql nodejs git
|
||||
RUN dnf -y --skip-broken install php php-cli php-common unzip php-zip php-yaml php-gd php-pdo_mysql nodejs git
|
||||
|
||||
#Don't forget about Yarn and Composer:
|
||||
RUN npm i -g yarn && \
|
||||
|
|
136
README.textile
136
README.textile
|
@ -1,64 +1,72 @@
|
|||
h1. <img align="right" src="https://github.com/openvk/openvk/raw/master/Web/static/img/logo.png" alt="openvk" title="openvk" width="15%">OpenVK
|
||||
|
||||
*OpenVK* is an attempt to create a simple CMS that -cosplays- imitates old VK. Code provided here is not stable yet.
|
||||
VKontakte belongs to Pavel Durov and mail.ru.
|
||||
To be honest, we don't even know whether it even works. However, this version is maintained and we will be happy to accept your bugreports "in our bug-tracker":https://github.com/openvk/openvk/projects/1. You should also be able to submit them using "ticketing system":https://openvk.su/support?act=new (you will need an OVK account for this).
|
||||
|
||||
h2. When's the release?
|
||||
|
||||
Please use the master branch, as it has the most changes.
|
||||
|
||||
Updating the source code is done with this command: @git pull@
|
||||
|
||||
h2. Instances
|
||||
|
||||
* *"openvk.su":https://openvk.su/*
|
||||
|
||||
h2. Can I create my own OpenVK instance?
|
||||
|
||||
Yes! And you're very welcome to.
|
||||
However, OVK makes use of Chandler Application Server. This software requires extensions, that may not be provided by your hosting provider (namely, sodium and yaml. this extensions are available on most of ISPManager hostings).
|
||||
Simply put, we would recommend you to use latest CentOS (running on your own VDS/Dedicated) with latest PHP from EPEL. We used to have this configuration on our "main instance":https://openvk.su/ and thus will be able to provide quicker support for OVK running in this environment.
|
||||
If you want, you can add your instance to the list above so that people can register there.
|
||||
|
||||
h3. Installation procedure
|
||||
|
||||
"samukhin":https://github.com/samukhin is working on a Docker container that contains a microblog version of OpenVK, see "#76":https://github.com/openvk/openvk/pull/76 for details. (Experimental)
|
||||
|
||||
# Install PHP 7, web-server, Composer, Node.js, Yarn and "Chandler":https://github.com/openvk/chandler
|
||||
PHP 8 has *not* yet been tested, so you should not expect it to work.
|
||||
# Install "commitcaptcha":https://github.com/openvk/commitcaptcha and OpenVK as Chandler extensions like this:
|
||||
@git clone --recursive https://github.com/openvk/openvk /path/to/chandler/extensions/available/openvk@
|
||||
@git clone https://github.com/openvk/commitcaptcha /path/to/chandler/extensions/available/commitcaptcha@
|
||||
# And enable them:
|
||||
@ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/extensions/enabled/@
|
||||
@ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/@
|
||||
# Import @install/init-static-db.sql@ to *same database* you installed Chandler to
|
||||
# Import @install/init-event-db.sql@ to *separate database*
|
||||
# Copy openvk-example.yml to openvk.yml and change options
|
||||
# Run @composer install@ in OpenVK directory
|
||||
# Move to @Web/static/js@ and execute @yarn install@
|
||||
# Set @openvk@ as your root app in @chandler.yml@
|
||||
|
||||
Once you are done, you can login as a system administrator on the network itself (no registration required):
|
||||
* *Login*: admin@localhost.localdomain6
|
||||
* *Password*: admin
|
||||
It is recommended to change the password before using the built-in account.
|
||||
|
||||
h3. If my website uses OpenVK, should I publish it's sources?
|
||||
|
||||
You are encouraged to do so. We don't enforce this though. You can keep your sources to yourself (unless you distribute your OpenVK distro to other people).
|
||||
You also not required to publish source texts of your themepacks and plugins.
|
||||
|
||||
h2. Where can I get assistance?
|
||||
|
||||
You may reach out to us via:
|
||||
* "Bug-tracker":https://github.com/openvk/openvk/projects/1
|
||||
* "Ticketing system":https://openvk.su/support?act=new
|
||||
* Telegram chat: Go to "our channel":https://t.me/openvkch and open discussion in our channel menu.
|
||||
* "Reddit":https://www.reddit.com/r/openvk/
|
||||
* "Discussions":https://github.com/openvk/openvk/discussions
|
||||
|
||||
*Attention*: bug tracker and telegram chat are public places. And ticketing system is being served by volunteers. If you need to report something, that shouldn't be immediately disclosed to general public (for instance, vulnerability report), *please use contact us directly*:
|
||||
* *Head of OpenVK Security Commitee*: stingray@jill.pl or "@id155":https://t.me/id155
|
||||
* *Backend developer*: "@saddyteirusu":https://t.me/saddyteirusu
|
||||
h1. <img align="right" src="https://github.com/openvk/openvk/raw/master/Web/static/img/logo_shadow.png" alt="openvk" title="openvk" width="15%">OpenVK
|
||||
|
||||
*OpenVK* is an attempt to create a simple CMS that -cosplays- imitates old VK. Code provided here is not stable yet.
|
||||
VKontakte belongs to Pavel Durov and VK Group.
|
||||
To be honest, we don't even know whether it even works. However, this version is maintained and we will be happy to accept your bugreports "in our bug-tracker":https://github.com/openvk/openvk/projects/1. You should also be able to submit them using "ticketing system":https://openvk.su/support?act=new (you will need an OVK account for this).
|
||||
|
||||
h2. When's the release?
|
||||
|
||||
Please use the master branch, as it has the most changes.
|
||||
|
||||
Updating the source code is done with this command: @git pull --recurse-submodules@
|
||||
|
||||
h2. Instances
|
||||
|
||||
* *"openvk.su":https://openvk.su/*
|
||||
* "social.fetbuk.ru":http://social.fetbuk.ru/
|
||||
|
||||
h2. Can I create my own OpenVK instance?
|
||||
|
||||
Yes! And you're very welcome to.
|
||||
However, OVK makes use of Chandler Application Server. This software requires extensions, that may not be provided by your hosting provider (namely, sodium and yaml. this extensions are available on most of ISPManager hostings).
|
||||
Simply put, we would recommend you to use latest CentOS (running on your own VDS/Dedicated) with latest PHP from EPEL. We used to have this configuration on our "main instance":https://openvk.su/ and thus will be able to provide quicker support for OVK running in this environment.
|
||||
If you want, you can add your instance to the list above so that people can register there.
|
||||
|
||||
h3. Installation procedure
|
||||
|
||||
"samukhin":https://github.com/samukhin is working on a Docker container that contains a microblog version of OpenVK, see "#76":https://github.com/openvk/openvk/pull/76 for details. (Experimental)
|
||||
|
||||
# Install PHP 7, web-server, Composer, Node.js, Yarn and "Chandler":https://github.com/openvk/chandler
|
||||
PHP 8 has *not* yet been tested, so you should not expect it to work.
|
||||
# Install "commitcaptcha":https://github.com/openvk/commitcaptcha and OpenVK as Chandler extensions like this:
|
||||
@git clone --recursive https://github.com/openvk/openvk /path/to/chandler/extensions/available/openvk@
|
||||
@git clone https://github.com/openvk/commitcaptcha /path/to/chandler/extensions/available/commitcaptcha@
|
||||
# And enable them:
|
||||
@ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/extensions/enabled/@
|
||||
@ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/@
|
||||
# Import @install/init-static-db.sql@ to *same database* you installed Chandler to
|
||||
# Import @install/init-event-db.sql@ to *separate database*
|
||||
# Copy @openvk-example.yml@ to @openvk.yml@ and change options
|
||||
# Run @composer install@ in OpenVK directory
|
||||
# Move to @Web/static/js@ and execute @yarn install@
|
||||
# Set @openvk@ as your root app in @chandler.yml@
|
||||
|
||||
*Note*: If OVK submodules were not downloaded beforehand (i.e. @--recursive@ was not used during cloning), this command *must be* executed in the @openvk@ folder: @git submodule update --init@
|
||||
|
||||
Once you are done, you can login as a system administrator on the network itself (no registration required):
|
||||
* *Login*: admin@localhost.localdomain6
|
||||
* *Password*: admin
|
||||
It is recommended to change the password before using the built-in account.
|
||||
|
||||
h3. If my website uses OpenVK, should I publish it's sources?
|
||||
|
||||
You are encouraged to do so. We don't enforce this though. You can keep your sources to yourself (unless you distribute your OpenVK distro to other people).
|
||||
You also not required to publish source texts of your themepacks and plugins.
|
||||
|
||||
h2. Where can I get assistance?
|
||||
|
||||
You may reach out to us via:
|
||||
* "Bug-tracker":https://github.com/openvk/openvk/projects/1
|
||||
* "Ticketing system":https://openvk.su/support?act=new
|
||||
* Telegram chat: Go to "our channel":https://t.me/openvkch and open discussion in our channel menu.
|
||||
* "Reddit":https://www.reddit.com/r/openvk/
|
||||
* "Discussions":https://github.com/openvk/openvk/discussions
|
||||
|
||||
*Attention*: bug tracker and telegram chat are public places. And ticketing system is being served by volunteers. If you need to report something, that shouldn't be immediately disclosed to general public (for instance, vulnerability report), *please use contact us directly*:
|
||||
* *Head of OpenVK Security Commitee*: stingray@jill.pl or "@id155":https://t.me/id155
|
||||
* *Backend developer*: "@saddyteirusu":https://t.me/saddyteirusu
|
||||
|
||||
Codeberg repository clone:
|
||||
<a href="https://codeberg.org/OpenVK/openvk">
|
||||
<img alt="Get it on Codeberg" src="https://codeberg.org/Codeberg/GetItOnCodeberg/media/branch/main/get-it-on-blue-on-white.png" height="60">
|
||||
</a>
|
||||
|
|
83
ServiceAPI/Notifications.php
Normal file
83
ServiceAPI/Notifications.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\ServiceAPI;
|
||||
use Latte\Engine as TemplatingEngine;
|
||||
use RdKafka\{Conf as RDKConf, KafkaConsumer};
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\{Notifications as N};
|
||||
|
||||
class Notifications implements Handler
|
||||
{
|
||||
protected $user;
|
||||
protected $notifs;
|
||||
|
||||
function __construct(?User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->notifs = new N;
|
||||
}
|
||||
|
||||
function ack(callable $resolve, callable $reject): void
|
||||
{
|
||||
$this->user->updateNotificationOffset();
|
||||
$this->user->save();
|
||||
$resolve("OK");
|
||||
}
|
||||
|
||||
function fetch(callable $resolve, callable $reject): void
|
||||
{
|
||||
$kafkaConf = OPENVK_ROOT_CONF["openvk"]["credentials"]["notificationsBroker"];
|
||||
if(!$kafkaConf["enable"]) {
|
||||
$reject(1999, "Disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
$kafkaConf = $kafkaConf["kafka"];
|
||||
$conf = new RDKConf();
|
||||
$conf->set("metadata.broker.list", $kafkaConf["addr"] . ":" . $kafkaConf["port"]);
|
||||
$conf->set("group.id", "UserFetch-" . $this->user->getId()); # Чтобы уведы приходили только на разные устройства одного чебупелика
|
||||
$conf->set("auto.offset.reset", "latest");
|
||||
|
||||
set_time_limit(30);
|
||||
$consumer = new KafkaConsumer($conf);
|
||||
$consumer->subscribe([ $kafkaConf["topic"] ]);
|
||||
|
||||
while(true) {
|
||||
$message = $consumer->consume(30*1000);
|
||||
switch ($message->err) {
|
||||
case RD_KAFKA_RESP_ERR_NO_ERROR:
|
||||
$descriptor = $message->payload;
|
||||
[,$user,] = explode(",", $descriptor);
|
||||
if(((int) $user) === $this->user->getId()) {
|
||||
$data = (object) [];
|
||||
$notification = $this->notifs->fromDescriptor($descriptor, $data);
|
||||
if(!$notification) {
|
||||
$reject(1982, "Server Error");
|
||||
return;
|
||||
}
|
||||
|
||||
$tplDir = __DIR__ . "/../Web/Presenters/templates/components/notifications/";
|
||||
$tplId = "$tplDir$data->actionCode/_$data->originModelType" . "_" . $data->targetModelType . "_.xml";
|
||||
$latte = new TemplatingEngine;
|
||||
$latte->setTempDirectory(CHANDLER_ROOT . "/tmp/cache/templates");
|
||||
$latte->addFilter("translate", fn($trId) => tr($trId));
|
||||
$resolve([
|
||||
"title" => tr("notif_" . $data->actionCode . "_" . $data->originModelType . "_" . $data->targetModelType),
|
||||
"body" => trim(preg_replace('%(\s){2,}%', "$1", $latte->renderToString($tplId, ["notification" => $notification]))),
|
||||
"ava" => $notification->getModel(1)->getAvatarUrl(),
|
||||
"priority" => 1,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case RD_KAFKA_RESP_ERR__TIMED_OUT:
|
||||
case RD_KAFKA_RESP_ERR__PARTITION_EOF:
|
||||
$reject(1983, "Nothing to report");
|
||||
break 2;
|
||||
default:
|
||||
$reject(1981, "Kafka Error: " . $message->errstr());
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ class Service implements Handler
|
|||
|
||||
function getTime(callable $resolve, callable $reject): void
|
||||
{
|
||||
$resolve((new DateTime)->format("%e %B %G" . tr("time_at_sp") . "%X"));
|
||||
$resolve(trim((new DateTime)->format("%e %B %G" . tr("time_at_sp") . "%X")));
|
||||
}
|
||||
|
||||
function getServerVersion(callable $resolve, callable $reject): void
|
||||
|
|
58
ServiceAPI/Wall.php
Normal file
58
ServiceAPI/Wall.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\ServiceAPI;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\Posts;
|
||||
|
||||
class Wall implements Handler
|
||||
{
|
||||
protected $user;
|
||||
protected $posts;
|
||||
|
||||
function __construct(?User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->posts = new Posts;
|
||||
}
|
||||
|
||||
function getPost(int $id, callable $resolve, callable $reject): void
|
||||
{
|
||||
$post = $this->posts->get($id);
|
||||
if(!$post || $post->isDeleted())
|
||||
$reject("No post with id=$id");
|
||||
|
||||
$res = (object) [];
|
||||
$res->id = $post->getId();
|
||||
$res->wall = $post->getTargetWall();
|
||||
$res->author = (($owner = $post->getOwner())) instanceof User
|
||||
? ($owner->getId())
|
||||
: ($owner->getId() * -1);
|
||||
|
||||
if($post->isSigned())
|
||||
$res->signedOffBy = $post->getOwnerPost();
|
||||
|
||||
$res->pinned = $post->isPinned();
|
||||
$res->sponsored = $post->isAd();
|
||||
$res->nsfw = $post->isExplicit();
|
||||
$res->text = $post->getText();
|
||||
|
||||
$res->likes = [
|
||||
"count" => $post->getLikesCount(),
|
||||
"hasLike" => $post->hasLikeFrom($this->user),
|
||||
"likedBy" => [],
|
||||
];
|
||||
foreach($post->getLikers() as $liker) {
|
||||
$res->likes["likedBy"][] = [
|
||||
"id" => $liker->getId(),
|
||||
"url" => $liker->getURL(),
|
||||
"name" => $liker->getCanonicalName(),
|
||||
"avatar" => $liker->getAvatarURL(),
|
||||
];
|
||||
}
|
||||
|
||||
$res->created = (string) $post->getPublicationTime();
|
||||
$res->canPin = $post->canBePinnedBy($this->user);
|
||||
$res->canEdit = $res->canDelete = $post->canBeDeletedBy($this->user);
|
||||
|
||||
$resolve((array) $res);
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ final class Account extends VKAPIRequestHandler
|
|||
];
|
||||
}
|
||||
|
||||
function setOnline(): object
|
||||
function setOnline(): int
|
||||
{
|
||||
$this->requireUser();
|
||||
|
||||
|
|
22
VKAPI/Handlers/Audio.php
Normal file
22
VKAPI/Handlers/Audio.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
|
||||
final class Audio extends VKAPIRequestHandler
|
||||
{
|
||||
function get(): object
|
||||
{
|
||||
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
|
||||
|
||||
return (object) [
|
||||
"count" => 1,
|
||||
"items" => [(object) [
|
||||
"id" => 1,
|
||||
"owner_id" => 1,
|
||||
"artist" => "В ОВК ПОКА НЕТ МУЗЫКИ",
|
||||
"title" => "ЖДИТЕ :)))",
|
||||
"duration" => 22,
|
||||
"url" => $serverUrl . "/assets/packages/static/openvk/audio/nomusic.mp3"
|
||||
]]
|
||||
];
|
||||
}
|
||||
}
|
|
@ -3,19 +3,35 @@ namespace openvk\VKAPI\Handlers;
|
|||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Entities\Clubs;
|
||||
use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
|
||||
use openvk\Web\Models\Repositories\Users as UsersRepo;
|
||||
use openvk\Web\Models\Entities\Post;
|
||||
use openvk\Web\Models\Entities\Postable;
|
||||
use openvk\Web\Models\Repositories\Posts as PostsRepo;
|
||||
|
||||
final class Groups extends VKAPIRequestHandler
|
||||
{
|
||||
function get(string $group_ids, string $fields = "", int $offset = 0, int $count = 100, bool $online = false): array
|
||||
function get(int $user_id = 0, string $fields = "", int $offset = 0, int $count = 6, bool $online = false): object
|
||||
{
|
||||
$this->requireUser();
|
||||
|
||||
$clubs = new ClubsRepo;
|
||||
$clbs = explode(',', $group_ids);
|
||||
$response;
|
||||
if ($user_id == 0) {
|
||||
foreach($this->getUser()->getClubs($offset+1) as $club) {
|
||||
$clbs[] = $club;
|
||||
}
|
||||
$clbsCount = $this->getUser()->getClubCount();
|
||||
} else {
|
||||
$users = new UsersRepo;
|
||||
$user = $users->get($user_id);
|
||||
if (is_null($user)) {
|
||||
$this->fail(15, "Access denied");
|
||||
}
|
||||
foreach($user->getClubs($offset+1) as $club) {
|
||||
$clbs[] = $club;
|
||||
}
|
||||
$clbsCount = $user->getClubCount();
|
||||
}
|
||||
|
||||
$rClubs;
|
||||
|
||||
$ic = sizeof($clbs);
|
||||
|
||||
|
@ -24,22 +40,21 @@ final class Groups extends VKAPIRequestHandler
|
|||
$clbs = array_slice($clbs, $offset * $count);
|
||||
|
||||
for ($i=0; $i < $ic; $i++) {
|
||||
$usr = $clubs->get((int) $clbs[$i]);
|
||||
$usr = $clbs[$i];
|
||||
if(is_null($usr))
|
||||
{
|
||||
$response[$i] = (object)[
|
||||
$rClubs[$i] = (object)[
|
||||
"id" => $clbs[$i],
|
||||
"first_name" => "DELETED",
|
||||
"last_name" => "",
|
||||
"name" => "DELETED",
|
||||
"deactivated" => "deleted"
|
||||
];
|
||||
}else if($clbs[$i] == null){
|
||||
|
||||
}else{
|
||||
$response[$i] = (object)[
|
||||
$rClubs[$i] = (object)[
|
||||
"id" => $usr->getId(),
|
||||
"first_name" => $usr->getFirstName(),
|
||||
"last_name" => $usr->getLastName(),
|
||||
"name" => $usr->getName(),
|
||||
"screen_name" => $usr->getShortCode(),
|
||||
"is_closed" => false,
|
||||
"can_access_closed" => true,
|
||||
];
|
||||
|
@ -49,34 +64,28 @@ final class Groups extends VKAPIRequestHandler
|
|||
foreach($flds as $field) {
|
||||
switch ($field) {
|
||||
case 'verified':
|
||||
$response[$i]->verified = intval($usr->isVerified());
|
||||
break;
|
||||
case 'sex':
|
||||
$response[$i]->sex = $this->getUser()->isFemale() ? 1 : 2;
|
||||
$rClubs[$i]->verified = intval($usr->isVerified());
|
||||
break;
|
||||
case 'has_photo':
|
||||
$response[$i]->has_photo = is_null($usr->getAvatarPhoto()) ? 0 : 1;
|
||||
$rClubs[$i]->has_photo = is_null($usr->getAvatarPhoto()) ? 0 : 1;
|
||||
break;
|
||||
case 'photo_max_orig':
|
||||
$response[$i]->photo_max_orig = $usr->getAvatarURL();
|
||||
$rClubs[$i]->photo_max_orig = $usr->getAvatarURL();
|
||||
break;
|
||||
case 'photo_max':
|
||||
$response[$i]->photo_max = $usr->getAvatarURL();
|
||||
$rClubs[$i]->photo_max = $usr->getAvatarURL();
|
||||
break;
|
||||
|
||||
case 'members_count':
|
||||
$rClubs[$i]->members_count = $usr->getFollowersCount();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// НУЖЕН фикс - либо из-за моего дебилизма, либо из-за сегментации котлеток некоторые пользовали отображаются как онлайн, хотя лол, если зайти на страницу, то оный уже офлайн
|
||||
if($online == true && $usr->getOnline()->timestamp() + 2505600 > time()) {
|
||||
$response[$i]->online = 1;
|
||||
}else{
|
||||
$response[$i]->online = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
return (object) [
|
||||
"count" => $clbsCount,
|
||||
"items" => $rClubs
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,14 @@ use openvk\Web\Models\Repositories\Users as UsersRepo;
|
|||
|
||||
final class Users extends VKAPIRequestHandler
|
||||
{
|
||||
function get(string $user_ids, string $fields = "", int $offset = 0, int $count = 100): array
|
||||
function get(string $user_ids = "0", string $fields = "", int $offset = 0, int $count = 100): array
|
||||
{
|
||||
$this->requireUser();
|
||||
|
||||
$users = new UsersRepo;
|
||||
if($user_ids == "0")
|
||||
$user_ids = (string) $this->getUser()->getId();
|
||||
|
||||
$usrs = explode(',', $user_ids);
|
||||
$response;
|
||||
|
||||
|
@ -44,54 +47,76 @@ final class Users extends VKAPIRequestHandler
|
|||
|
||||
foreach($flds as $field) {
|
||||
switch ($field) {
|
||||
case 'verified':
|
||||
$response[$i]->verified = intval($usr->isVerified());
|
||||
break;
|
||||
case 'sex':
|
||||
$response[$i]->sex = $this->getUser()->isFemale() ? 1 : 2;
|
||||
break;
|
||||
case 'has_photo':
|
||||
$response[$i]->has_photo = is_null($usr->getAvatarPhoto()) ? 0 : 1;
|
||||
break;
|
||||
case 'photo_max_orig':
|
||||
$response[$i]->photo_max_orig = $usr->getAvatarURL();
|
||||
break;
|
||||
case 'photo_max':
|
||||
$response[$i]->photo_max = $usr->getAvatarURL();
|
||||
break;
|
||||
case 'status':
|
||||
$response[$i]->status = $usr->getStatus();
|
||||
break;
|
||||
case 'screen_name':
|
||||
$response[$i]->screen_name = $usr->getShortCode();
|
||||
break;
|
||||
case 'music':
|
||||
$response[$i]->music = $usr->getFavoriteMusic();
|
||||
break;
|
||||
case 'movies':
|
||||
$response[$i]->movies = $usr->getFavoriteFilms();
|
||||
break;
|
||||
case 'tv':
|
||||
$response[$i]->tv = $usr->getFavoriteShows();
|
||||
break;
|
||||
case 'books':
|
||||
$response[$i]->books = $usr->getFavoriteBooks();
|
||||
break;
|
||||
case 'city':
|
||||
$response[$i]->city = $usr->getCity();
|
||||
break;
|
||||
case 'interests':
|
||||
$response[$i]->interests = $usr->getInterests();
|
||||
break;
|
||||
case 'verified':
|
||||
$response[$i]->verified = intval($usr->isVerified());
|
||||
break;
|
||||
case 'sex':
|
||||
$response[$i]->sex = $this->getUser()->isFemale() ? 1 : 2;
|
||||
break;
|
||||
case 'has_photo':
|
||||
$response[$i]->has_photo = is_null($usr->getAvatarPhoto()) ? 0 : 1;
|
||||
break;
|
||||
case 'photo_max_orig':
|
||||
$response[$i]->photo_max_orig = $usr->getAvatarURL();
|
||||
break;
|
||||
case 'photo_max':
|
||||
$response[$i]->photo_max = $usr->getAvatarURL();
|
||||
break;
|
||||
case 'status':
|
||||
if($usr->getStatus() != null)
|
||||
$response[$i]->status = $usr->getStatus();
|
||||
break;
|
||||
case 'screen_name':
|
||||
if($usr->getShortCode() != null)
|
||||
$response[$i]->screen_name = $usr->getShortCode();
|
||||
break;
|
||||
case 'friend_status':
|
||||
switch($usr->getSubscriptionStatus($this->getUser())) {
|
||||
case 3:
|
||||
case 0:
|
||||
$response[$i]->friend_status = $usr->getSubscriptionStatus($this->getUser());
|
||||
break;
|
||||
case 1:
|
||||
$response[$i]->friend_status = 2;
|
||||
break;
|
||||
case 2:
|
||||
$response[$i]->friend_status = 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'last_seen':
|
||||
if ($usr->onlineStatus() == 0) {
|
||||
$response[$i]->last_seen = (object) [
|
||||
"platform" => 1,
|
||||
"time" => $usr->getOnline()->timestamp()
|
||||
];
|
||||
}
|
||||
case 'music':
|
||||
$response[$i]->music = $usr->getFavoriteMusic();
|
||||
break;
|
||||
case 'movies':
|
||||
$response[$i]->movies = $usr->getFavoriteFilms();
|
||||
break;
|
||||
case 'tv':
|
||||
$response[$i]->tv = $usr->getFavoriteShows();
|
||||
break;
|
||||
case 'books':
|
||||
$response[$i]->books = $usr->getFavoriteBooks();
|
||||
break;
|
||||
case 'city':
|
||||
$response[$i]->city = $usr->getCity();
|
||||
break;
|
||||
case 'interests':
|
||||
$response[$i]->interests = $usr->getInterests();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// НУЖЕН фикс - либо из-за моего дебилизма, либо из-за сегментации котлеток некоторые пользовали отображаются как онлайн, хотя лол, если зайти на страницу, то оный уже офлайн
|
||||
if($usr->getOnline()->timestamp() + 2505600 > time()) {
|
||||
$response[$i]->online = 1;
|
||||
}else{
|
||||
$response[$i]->online = 0;
|
||||
}
|
||||
if($usr->getOnline()->timestamp() + 300 > time()) {
|
||||
$response[$i]->online = 1;
|
||||
}else{
|
||||
$response[$i]->online = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,20 +16,25 @@ final class Wall extends VKAPIRequestHandler
|
|||
$posts = new PostsRepo;
|
||||
|
||||
$items = [];
|
||||
$profiles = [];
|
||||
$groups = [];
|
||||
$count = $posts->getPostCountOnUserWall((int) $owner_id);
|
||||
|
||||
foreach ($posts->getPostsFromUsersWall((int)$owner_id) as $post) {
|
||||
foreach ($posts->getPostsFromUsersWall((int)$owner_id, 1, $count, $offset) as $post) {
|
||||
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
|
||||
$items[] = (object)[
|
||||
"id" => $post->getVirtualId(),
|
||||
"from_id" => $post->getOwner()->getId(),
|
||||
"from_id" => $from_id,
|
||||
"owner_id" => $post->getTargetWall(),
|
||||
"date" => $post->getPublicationTime()->timestamp(),
|
||||
"post_type" => "post",
|
||||
"text" => $post->getText(),
|
||||
"can_edit" => 0, // TODO
|
||||
"can_delete" => $post->canBeDeletedBy($this->getUser()),
|
||||
"can_pin" => 0, // TODO
|
||||
"can_pin" => $post->canBePinnedBy($this->getUser()),
|
||||
"can_archive" => false, // TODO MAYBE
|
||||
"is_archived" => false,
|
||||
"is_pinned" => $post->isPinned(),
|
||||
"post_source" => (object)["type" => "vk"],
|
||||
"comments" => (object)[
|
||||
"count" => $post->getCommentsCount(),
|
||||
|
@ -46,26 +51,66 @@ final class Wall extends VKAPIRequestHandler
|
|||
"user_reposted" => 0
|
||||
]
|
||||
];
|
||||
|
||||
if ($from_id > 0)
|
||||
$profiles[] = $from_id;
|
||||
else
|
||||
$groups[] = $from_id * -1;
|
||||
}
|
||||
|
||||
$profiles = [];
|
||||
$groups = [];
|
||||
if($extended == 1)
|
||||
{
|
||||
$profiles = array_unique($profiles);
|
||||
$groups = array_unique($groups);
|
||||
|
||||
$groups[0] = 'lol';
|
||||
$groups[2] = 'cec';
|
||||
$profilesFormatted = [];
|
||||
$groupsFormatted = [];
|
||||
|
||||
if($extended == 1)
|
||||
return (object)[
|
||||
"items" => (array)$items,
|
||||
"cock" => (array)$groups
|
||||
];
|
||||
foreach ($profiles as $prof) {
|
||||
$user = (new UsersRepo)->get($prof);
|
||||
$profilesFormatted[] = (object)[
|
||||
"first_name" => $user->getFirstName(),
|
||||
"id" => $user->getId(),
|
||||
"last_name" => $user->getLastName(),
|
||||
"can_access_closed" => false,
|
||||
"is_closed" => false,
|
||||
"sex" => $user->isFemale() ? 1 : 2,
|
||||
"screen_name" => $user->getShortCode(),
|
||||
"photo_50" => $user->getAvatarUrl(),
|
||||
"photo_100" => $user->getAvatarUrl(),
|
||||
"online" => $user->isOnline()
|
||||
];
|
||||
}
|
||||
|
||||
foreach($groups as $g) {
|
||||
$group = (new ClubsRepo)->get($g);
|
||||
$groupsFormatted[] = (object)[
|
||||
"id" => $group->getId(),
|
||||
"name" => $group->getName(),
|
||||
"screen_name" => $group->getShortCode(),
|
||||
"is_closed" => 0,
|
||||
"type" => "group",
|
||||
"photo_50" => $group->getAvatarUrl(),
|
||||
"photo_100" => $group->getAvatarUrl(),
|
||||
"photo_200" => $group->getAvatarUrl(),
|
||||
];
|
||||
}
|
||||
|
||||
return (object)[
|
||||
"count" => $count,
|
||||
"items" => (array)$items,
|
||||
"profiles" => (array)$profilesFormatted,
|
||||
"groups" => (array)$groupsFormatted
|
||||
];
|
||||
}
|
||||
else
|
||||
return (object)[
|
||||
"items" => (array)$items
|
||||
];
|
||||
return (object)[
|
||||
"count" => $count,
|
||||
"items" => (array)$items
|
||||
];
|
||||
}
|
||||
|
||||
function post(string $owner_id, string $message, int $from_group = 0): object
|
||||
function post(string $owner_id, string $message, int $from_group = 0, int $signed = 0): object
|
||||
{
|
||||
$this->requireUser();
|
||||
|
||||
|
@ -88,6 +133,8 @@ final class Wall extends VKAPIRequestHandler
|
|||
$flags = 0;
|
||||
if($from_group == 1)
|
||||
$flags |= 0b10000000;
|
||||
if($signed == 1)
|
||||
$flags |= 0b01000000;
|
||||
|
||||
try {
|
||||
$post = new Post;
|
||||
|
|
|
@ -89,7 +89,22 @@ class Club extends RowModel
|
|||
{
|
||||
return (new Users)->get($this->getRecord()->owner);
|
||||
}
|
||||
|
||||
function getOwnerComment(): string
|
||||
{
|
||||
return is_null($this->getRecord()->owner_comment) ? "" : $this->getRecord()->owner_comment;
|
||||
}
|
||||
|
||||
function isOwnerHidden(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->owner_hidden;
|
||||
}
|
||||
|
||||
function isOwnerClubPinned(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->owner_club_pinned;
|
||||
}
|
||||
|
||||
function getDescription(): ?string
|
||||
{
|
||||
return $this->getRecord()->about;
|
||||
|
@ -109,6 +124,11 @@ class Club extends RowModel
|
|||
{
|
||||
return $this->getRecord()->closed;
|
||||
}
|
||||
|
||||
function getAdministratorsListDisplay(): int
|
||||
{
|
||||
return $this->getRecord()->administrators_list_display;
|
||||
}
|
||||
|
||||
function getType(): int
|
||||
{
|
||||
|
@ -259,21 +279,11 @@ class Club extends RowModel
|
|||
}
|
||||
}
|
||||
|
||||
function getManagers(int $page = 1): \Traversable
|
||||
function getManagers(int $page = 1, bool $ignoreHidden = false): \Traversable
|
||||
{
|
||||
$rels = $this->getRecord()->related("group_coadmins.club")->page($page, 6);
|
||||
|
||||
foreach($rels as $rel) {
|
||||
$rel = (new Users)->get($rel->user);
|
||||
if(!$rel) continue;
|
||||
|
||||
yield $rel;
|
||||
}
|
||||
}
|
||||
|
||||
function getManagersWithComment(int $page = 1): \Traversable
|
||||
{
|
||||
$rels = $this->getRecord()->related("group_coadmins.club")->where("comment IS NOT NULL")->page($page, 10);
|
||||
if($ignoreHidden)
|
||||
$rels = $rels->where("hidden", false);
|
||||
|
||||
foreach($rels as $rel) {
|
||||
$rel = (new Managers)->get($rel->id);
|
||||
|
@ -282,15 +292,23 @@ class Club extends RowModel
|
|||
yield $rel;
|
||||
}
|
||||
}
|
||||
|
||||
function getManagersCount(): int
|
||||
{
|
||||
return sizeof($this->getRecord()->related("group_coadmins.club")) + 1;
|
||||
}
|
||||
|
||||
function getManagersCountWithComment(): int
|
||||
function getManager(User $user, bool $ignoreHidden = false): ?Manager
|
||||
{
|
||||
return sizeof($this->getRecord()->related("group_coadmins.club")->where("comment IS NOT NULL")) + 1;
|
||||
$manager = (new Managers)->getByUserAndClub($user->getId(), $this->getId());
|
||||
|
||||
if ($ignoreHidden && $manager !== null && $manager->isHidden())
|
||||
return null;
|
||||
|
||||
return $manager;
|
||||
}
|
||||
|
||||
function getManagersCount(bool $ignoreHidden = false): int
|
||||
{
|
||||
if($ignoreHidden)
|
||||
return sizeof($this->getRecord()->related("group_coadmins.club")->where("hidden", false)) + (int) !$this->isOwnerHidden();
|
||||
|
||||
return sizeof($this->getRecord()->related("group_coadmins.club")) + 1;
|
||||
}
|
||||
|
||||
function addManager(User $user, ?string $comment = NULL): void
|
||||
|
@ -318,6 +336,11 @@ class Club extends RowModel
|
|||
|
||||
return !is_null($this->getRecord()->related("group_coadmins.club")->where("user", $id)->fetch());
|
||||
}
|
||||
|
||||
function getWebsite(): ?string
|
||||
{
|
||||
return $this->getRecord()->website;
|
||||
}
|
||||
|
||||
use Traits\TSubscribable;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use openvk\Web\Models\Repositories\Clubs;
|
||||
use openvk\Web\Models\RowModel;
|
||||
|
||||
class Comment extends Post
|
||||
{
|
||||
|
@ -24,4 +26,19 @@ class Comment extends Post
|
|||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* May return fake owner (group), if flags are [1, (*)]
|
||||
*
|
||||
* @param bool $honourFlags - check flags
|
||||
*/
|
||||
function getOwner(bool $honourFlags = true, bool $real = false): RowModel
|
||||
{
|
||||
if($honourFlags && $this->isPostedOnBehalfOfGroup()) {
|
||||
if($this->getTarget() instanceof Post)
|
||||
return (new Clubs)->get(abs($this->getTarget()->getTargetWall()));
|
||||
}
|
||||
|
||||
return parent::getOwner($honourFlags, $real);
|
||||
}
|
||||
}
|
||||
|
|
164
Web/Models/Entities/Gift.php
Normal file
164
Web/Models/Entities/Gift.php
Normal file
|
@ -0,0 +1,164 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use openvk\Web\Util\DateTime;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use Nette\Utils\{Image, ImageException};
|
||||
|
||||
class Gift extends RowModel
|
||||
{
|
||||
const IMAGE_MAXSIZE = 131072;
|
||||
const IMAGE_BINARY = 0;
|
||||
const IMAGE_BASE64 = 1;
|
||||
const IMAGE_URL = 2;
|
||||
|
||||
const PERIOD_IGNORE = 0;
|
||||
const PERIOD_SET = 1;
|
||||
const PERIOD_SET_IF_NONE = 2;
|
||||
|
||||
protected $tableName = "gifts";
|
||||
|
||||
function getName(): string
|
||||
{
|
||||
return $this->getRecord()->internal_name;
|
||||
}
|
||||
|
||||
function getPrice(): int
|
||||
{
|
||||
return $this->getRecord()->price;
|
||||
}
|
||||
|
||||
function getUsages(): int
|
||||
{
|
||||
return $this->getRecord()->usages;
|
||||
}
|
||||
|
||||
function getUsagesBy(User $user, ?int $since = NULL): int
|
||||
{
|
||||
$sent = $this->getRecord()
|
||||
->related("gift_user_relations.gift")
|
||||
->where("sender", $user->getId())
|
||||
->where("sent >= ?", $since ?? $this->getRecord()->limit_period ?? 0);
|
||||
|
||||
return sizeof($sent);
|
||||
}
|
||||
|
||||
function getUsagesLeft(User $user): float
|
||||
{
|
||||
if($this->getLimit() === INF)
|
||||
return INF;
|
||||
|
||||
return max(0, $this->getLimit() - $this->getUsagesBy($user));
|
||||
}
|
||||
|
||||
function getImage(int $type = 0): /* ?binary */ string
|
||||
{
|
||||
switch($type) {
|
||||
default:
|
||||
case static::IMAGE_BINARY:
|
||||
return $this->getRecord()->image ?? "";
|
||||
break;
|
||||
case static::IMAGE_BASE64:
|
||||
return "data:image/png;base64," . base64_encode($this->getRecord()->image ?? "");
|
||||
break;
|
||||
case static::IMAGE_URL:
|
||||
return "/gift" . $this->getId() . "_" . $this->getUpdateDate()->timestamp() . ".png";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function getLimit(): float
|
||||
{
|
||||
$limit = $this->getRecord()->limit;
|
||||
|
||||
return !$limit ? INF : (float) $limit;
|
||||
}
|
||||
|
||||
function getLimitResetTime(): ?DateTime
|
||||
{
|
||||
return is_null($t = $this->getRecord()->limit_period) ? NULL : new DateTime($t);
|
||||
}
|
||||
|
||||
function getUpdateDate(): DateTime
|
||||
{
|
||||
return new DateTime($this->getRecord()->updated);
|
||||
}
|
||||
|
||||
function canUse(User $user): bool
|
||||
{
|
||||
return $this->getUsagesLeft($user) > 0;
|
||||
}
|
||||
|
||||
function isFree(): bool
|
||||
{
|
||||
return $this->getPrice() === 0;
|
||||
}
|
||||
|
||||
function used(): void
|
||||
{
|
||||
$this->stateChanges("usages", $this->getUsages() + 1);
|
||||
$this->save();
|
||||
}
|
||||
|
||||
function setName(string $name): void
|
||||
{
|
||||
$this->stateChanges("internal_name", $name);
|
||||
}
|
||||
|
||||
function setImage(string $file): bool
|
||||
{
|
||||
$imgBlob;
|
||||
try {
|
||||
$image = Image::fromFile($file);
|
||||
$image->resize(512, 512, Image::SHRINK_ONLY);
|
||||
|
||||
$imgBlob = $image->toString(Image::PNG);
|
||||
} catch(ImageException $ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(strlen($imgBlob) > (2**24 - 1)) {
|
||||
return false;
|
||||
} else {
|
||||
$this->stateChanges("updated", time());
|
||||
$this->stateChanges("image", $imgBlob);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function setLimit(?float $limit = NULL, int $periodBehaviour = 0): void
|
||||
{
|
||||
$limit ??= $this->getLimit();
|
||||
$limit = $limit === INF ? NULL : (int) $limit;
|
||||
$this->stateChanges("limit", $limit);
|
||||
|
||||
if(!$limit) {
|
||||
$this->stateChanges("limit_period", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
switch($periodBehaviour) {
|
||||
default:
|
||||
case static::PERIOD_IGNORE:
|
||||
break;
|
||||
|
||||
case static::PERIOD_SET:
|
||||
$this->stateChanges("limit_period", time());
|
||||
break;
|
||||
|
||||
case static::PERIOD_SET_IF_NONE:
|
||||
if(is_null($this->getRecord()) || is_null($this->getRecord()->limit_period))
|
||||
$this->stateChanges("limit_period", time());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function delete(bool $softly = true): void
|
||||
{
|
||||
$this->getRecord()->related("gift_relations.gift")->delete();
|
||||
|
||||
parent::delete($softly);
|
||||
}
|
||||
}
|
156
Web/Models/Entities/GiftCategory.php
Normal file
156
Web/Models/Entities/GiftCategory.php
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use Chandler\Database\DatabaseConnection as DB;
|
||||
use openvk\Web\Models\Repositories\Gifts;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use Transliterator;
|
||||
|
||||
class GiftCategory extends RowModel
|
||||
{
|
||||
protected $tableName = "gift_categories";
|
||||
|
||||
private function getLocalization(string $language): object
|
||||
{
|
||||
return $this->getRecord()
|
||||
->related("gift_categories_locales.category")
|
||||
->where("language", $language);
|
||||
}
|
||||
|
||||
private function createLocalizationIfNotExists(string $language): void
|
||||
{
|
||||
if(!is_null($this->getLocalization($language)->fetch()))
|
||||
return;
|
||||
|
||||
DB::i()->getContext()->table("gift_categories_locales")->insert([
|
||||
"category" => $this->getId(),
|
||||
"language" => $language,
|
||||
"name" => "Sample Text",
|
||||
"description" => "Sample Text",
|
||||
]);
|
||||
}
|
||||
|
||||
function getSlug(): string
|
||||
{
|
||||
return str_replace("ʹ", "-", Transliterator::createFromRules(
|
||||
":: Any-Latin;"
|
||||
. ":: NFD;"
|
||||
. ":: [:Nonspacing Mark:] Remove;"
|
||||
. ":: NFC;"
|
||||
. ":: [:Punctuation:] Remove;"
|
||||
. ":: Lower();"
|
||||
. "[:Separator:] > '-'"
|
||||
)->transliterate($this->getName()));
|
||||
}
|
||||
|
||||
function getThumbnailURL(): string
|
||||
{
|
||||
$primeGift = iterator_to_array($this->getGifts(1, 1))[0];
|
||||
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
|
||||
if(!$primeGift)
|
||||
return "$serverUrl/assets/packages/static/openvk/img/camera_200.png";
|
||||
|
||||
return $primeGift->getImage(Gift::IMAGE_URL);
|
||||
}
|
||||
|
||||
function getName(string $language = "_", bool $returnNull = false): ?string
|
||||
{
|
||||
$loc = $this->getLocalization($language)->fetch();
|
||||
if(!$loc) {
|
||||
if($returnNull)
|
||||
return NULL;
|
||||
|
||||
return $language === "_" ? "Unlocalized" : $this->getName();
|
||||
}
|
||||
|
||||
return $loc->name;
|
||||
}
|
||||
|
||||
function getDescription(string $language = "_", bool $returnNull = false): ?string
|
||||
{
|
||||
$loc = $this->getLocalization($language)->fetch();
|
||||
if(!$loc) {
|
||||
if($returnNull)
|
||||
return NULL;
|
||||
|
||||
return $language === "_" ? "Unlocalized" : $this->getDescription();
|
||||
}
|
||||
|
||||
return $loc->description;
|
||||
}
|
||||
|
||||
function getGifts(int $page = -1, ?int $perPage = NULL, &$count = nullptr): \Traversable
|
||||
{
|
||||
$gifts = $this->getRecord()->related("gift_relations.category");
|
||||
if($page !== -1) {
|
||||
$count = $gifts->count();
|
||||
$gifts = $gifts->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
|
||||
}
|
||||
|
||||
foreach($gifts as $rel)
|
||||
yield (new Gifts)->get($rel->gift);
|
||||
}
|
||||
|
||||
function isMagical(): bool
|
||||
{
|
||||
return !is_null($this->getRecord()->autoquery);
|
||||
}
|
||||
|
||||
function hasGift(Gift $gift): bool
|
||||
{
|
||||
$rels = $this->getRecord()->related("gift_relations.category");
|
||||
|
||||
return $rels->where("gift", $gift->getId())->count() > 0;
|
||||
}
|
||||
|
||||
function addGift(Gift $gift): void
|
||||
{
|
||||
if($this->hasGift($gift))
|
||||
return;
|
||||
|
||||
DB::i()->getContext()->table("gift_relations")->insert([
|
||||
"category" => $this->getId(),
|
||||
"gift" => $gift->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
function removeGift(Gift $gift): void
|
||||
{
|
||||
if(!$this->hasGift($gift))
|
||||
return;
|
||||
|
||||
DB::i()->getContext()->table("gift_relations")->where([
|
||||
"category" => $this->getId(),
|
||||
"gift" => $gift->getId(),
|
||||
])->delete();
|
||||
}
|
||||
|
||||
function setName(string $language, string $name): void
|
||||
{
|
||||
$this->createLocalizationIfNotExists($language);
|
||||
$this->getLocalization($language)->update([
|
||||
"name" => $name,
|
||||
]);
|
||||
}
|
||||
|
||||
function setDescription(string $language, string $description): void
|
||||
{
|
||||
$this->createLocalizationIfNotExists($language);
|
||||
$this->getLocalization($language)->update([
|
||||
"description" => $description,
|
||||
]);
|
||||
}
|
||||
|
||||
function setAutoQuery(?array $query = NULL): void
|
||||
{
|
||||
if(is_null($query)) {
|
||||
$this->stateChanges("autoquery", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
$allowedColumns = ["price", "usages"];
|
||||
if(array_diff_key($query, array_flip($allowedColumns)))
|
||||
throw new \LogicException("Invalid query");
|
||||
|
||||
$this->stateChanges("autoquery", serialize($query));
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ class Manager extends RowModel
|
|||
return $this->getRecord()->id;
|
||||
}
|
||||
|
||||
function getUserId(): string
|
||||
function getUserId(): int
|
||||
{
|
||||
return $this->getRecord()->user;
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class Manager extends RowModel
|
|||
return (new Users)->get($this->getRecord()->user);
|
||||
}
|
||||
|
||||
function getClubId(): string
|
||||
function getClubId(): int
|
||||
{
|
||||
return $this->getRecord()->club;
|
||||
}
|
||||
|
@ -41,6 +41,16 @@ class Manager extends RowModel
|
|||
{
|
||||
return is_null($this->getRecord()->comment) ? "" : $this->getRecord()->comment;
|
||||
}
|
||||
|
||||
function isHidden(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->hidden;
|
||||
}
|
||||
|
||||
function isClubPinned(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->club_pinned;
|
||||
}
|
||||
|
||||
use Traits\TSubscribable;
|
||||
}
|
||||
|
|
|
@ -89,4 +89,21 @@ abstract class Media extends Postable
|
|||
|
||||
$this->stateChanges("hash", $hash);
|
||||
}
|
||||
|
||||
function delete(bool $softly = true): void
|
||||
{
|
||||
$deleteQuirk = ovkGetQuirk("blobs.erase-upon-deletion");
|
||||
if($deleteQuirk === 2 || ($deleteQuirk === 1 && !$softly))
|
||||
@unlink($this->getFileName());
|
||||
|
||||
parent::delete($softly);
|
||||
}
|
||||
|
||||
function undelete(): void
|
||||
{
|
||||
if(ovkGetQuirk("blobs.erase-upon-deletion") === 2)
|
||||
throw new \LogicException("Can't undelete model which is tied to blob, because of config constraint (quriks.yml:blobs.erase-upon-deletion)");
|
||||
|
||||
parent::undelete();
|
||||
}
|
||||
}
|
||||
|
|
13
Web/Models/Entities/Notifications/GiftNotification.php
Normal file
13
Web/Models/Entities/Notifications/GiftNotification.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities\Notifications;
|
||||
use openvk\Web\Models\Entities\{User, Gift};
|
||||
|
||||
final class GiftNotification extends Notification
|
||||
{
|
||||
protected $actionCode = 9601;
|
||||
|
||||
function __construct(User $receiver, User $sender, Gift $gift, ?string $comment)
|
||||
{
|
||||
parent::__construct($receiver, $gift, $sender, time(), $comment ?? "");
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ namespace openvk\Web\Models\Entities\Notifications;
|
|||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Entities\{User};
|
||||
use openvk\Web\Util\DateTime;
|
||||
use RdKafka\{Conf, Producer};
|
||||
|
||||
class Notification
|
||||
{
|
||||
|
@ -80,14 +81,14 @@ class Notification
|
|||
return false;
|
||||
|
||||
$data = [
|
||||
$this->recipient->getId(),
|
||||
$this->encodeType($this->originModel),
|
||||
$this->originModel->getId(),
|
||||
$this->encodeType($this->targetModel),
|
||||
$this->targetModel->getId(),
|
||||
$this->actionCode,
|
||||
$this->data,
|
||||
$this->time,
|
||||
"recipient" => $this->recipient->getId(),
|
||||
"originModelType" => $this->encodeType($this->originModel),
|
||||
"originModelId" => $this->originModel->getId(),
|
||||
"targetModelType" => $this->encodeType($this->targetModel),
|
||||
"targetModelId" => $this->targetModel->getId(),
|
||||
"actionCode" => $this->actionCode,
|
||||
"additionalPayload" => $this->data,
|
||||
"timestamp" => $this->time,
|
||||
];
|
||||
|
||||
$edb = $e->getConnection();
|
||||
|
@ -96,12 +97,33 @@ class Notification
|
|||
$query = <<<'QUERY'
|
||||
SELECT * FROM `notifications` WHERE `recipientType`=0 AND `recipientId`=? AND `originModelType`=? AND `originModelId`=? AND `targetModelType`=? AND `targetModelId`=? AND `modelAction`=? AND `additionalData`=? AND `timestamp` > (? - ?)
|
||||
QUERY;
|
||||
$result = $edb->query($query, ...array_merge($data, [ $this->threshold ]));
|
||||
$result = $edb->query($query, ...array_merge(array_values($data), [ $this->threshold ]));
|
||||
if($result->getRowCount() > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
$edb->query("INSERT INTO notifications VALUES (0, ?, ?, ?, ?, ?, ?, ?, ?)", ...array_values($data));
|
||||
|
||||
$edb->query("INSERT INTO notifications VALUES (0, ?, ?, ?, ?, ?, ?, ?, ?)", ...$data);
|
||||
$kafkaConf = OPENVK_ROOT_CONF["openvk"]["credentials"]["notificationsBroker"];
|
||||
if($kafkaConf["enable"]) {
|
||||
$kafkaConf = $kafkaConf["kafka"];
|
||||
$brokerConf = new Conf();
|
||||
$brokerConf->set("log_level", (string) LOG_DEBUG);
|
||||
$brokerConf->set("debug", "all");
|
||||
|
||||
$producer = new Producer($brokerConf);
|
||||
$producer->addBrokers($kafkaConf["addr"] . ":" . $kafkaConf["port"]);
|
||||
|
||||
$descriptor = implode(",", [
|
||||
str_replace("\\", ".", get_class($this)),
|
||||
$this->recipient->getId(),
|
||||
base64_encode(serialize((object) $data)),
|
||||
]);
|
||||
|
||||
$notifTopic = $producer->newTopic($kafkaConf["topic"]);
|
||||
$notifTopic->produce(RD_KAFKA_PARTITION_UA, RD_KAFKA_MSG_F_BLOCK, $descriptor);
|
||||
$producer->flush(100);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -9,11 +9,13 @@ class Photo extends Media
|
|||
protected $tableName = "photos";
|
||||
protected $fileExtension = "jpeg";
|
||||
|
||||
const ALLOWED_SIDE_MULTIPLIER = 7;
|
||||
|
||||
protected function saveFile(string $filename, string $hash): bool
|
||||
{
|
||||
$image = Image::fromFile($filename);
|
||||
if(($image->height >= ($image->width * pi())) || ($image->width >= ($image->height * pi())))
|
||||
throw new ISE("Invalid layout: expected layout that matches (x, ?!>3x)");
|
||||
if(($image->height >= ($image->width * Photo::ALLOWED_SIDE_MULTIPLIER)) || ($image->width >= ($image->height * Photo::ALLOWED_SIDE_MULTIPLIER)))
|
||||
throw new ISE("Invalid layout: image is too wide/short");
|
||||
|
||||
$image->save($this->pathFromHash($hash), 92, Image::JPEG);
|
||||
|
||||
|
@ -41,4 +43,20 @@ class Photo extends Media
|
|||
|
||||
DB::i()->getContext()->table("album_relations")->where("media", $this->getRecord()->id)->delete();
|
||||
}
|
||||
|
||||
static function fastMake(int $owner, string $description = "", array $file, ?Album $album = NULL, bool $anon = false): Photo
|
||||
{
|
||||
$photo = new static;
|
||||
$photo->setOwner($owner);
|
||||
$photo->setDescription(iconv_substr($description, 0, 36) . "...");
|
||||
$photo->setAnonymous($anon);
|
||||
$photo->setCreated(time());
|
||||
$photo->setFile($file);
|
||||
$photo->save();
|
||||
|
||||
if(!is_null($album))
|
||||
$album->addPhoto($photo);
|
||||
|
||||
return $photo;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,25 +3,47 @@ namespace openvk\Web\Models\Entities;
|
|||
use Chandler\Database\DatabaseConnection as DB;
|
||||
use openvk\Web\Models\Repositories\Clubs;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Entities\Notifications\LikeNotification;
|
||||
|
||||
class Post extends Postable
|
||||
{
|
||||
protected $tableName = "posts";
|
||||
protected $upperNodeReferenceColumnName = "wall";
|
||||
|
||||
private function setLikeRecursively(bool $liked, User $user, int $depth): void
|
||||
{
|
||||
$searchData = [
|
||||
"origin" => $user->getId(),
|
||||
"model" => static::class,
|
||||
"target" => $this->getRecord()->id,
|
||||
];
|
||||
|
||||
if((sizeof(DB::i()->getContext()->table("likes")->where($searchData)) > 0) !== $liked) {
|
||||
if($this->getOwner(false)->getId() !== $user->getId() && !($this->getOwner() instanceof Club) && !$this instanceof Comment)
|
||||
(new LikeNotification($this->getOwner(false), $this, $user))->emit();
|
||||
|
||||
parent::setLike($liked, $user);
|
||||
}
|
||||
|
||||
if($depth < ovkGetQuirk("wall.repost-liking-recursion-limit"))
|
||||
foreach($this->getChildren() as $attachment)
|
||||
if($attachment instanceof Post)
|
||||
$attachment->setLikeRecursively($liked, $user, $depth + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* May return fake owner (group), if flags are [1, (*)]
|
||||
*
|
||||
* @param bool $honourFlags - check flags
|
||||
*/
|
||||
function getOwner(bool $honourFlags = true): RowModel
|
||||
function getOwner(bool $honourFlags = true, bool $real = false): RowModel
|
||||
{
|
||||
if($honourFlags && ( ($this->getRecord()->flags & 0b10000000) > 0 )) {
|
||||
if($honourFlags && $this->isPostedOnBehalfOfGroup()) {
|
||||
if($this->getRecord()->wall < 0)
|
||||
return (new Clubs)->get(abs($this->getRecord()->wall));
|
||||
}
|
||||
|
||||
return parent::getOwner();
|
||||
return parent::getOwner($real);
|
||||
}
|
||||
|
||||
function getPrettyId(): string
|
||||
|
@ -75,7 +97,7 @@ class Post extends Postable
|
|||
|
||||
function getOwnerPost(): int
|
||||
{
|
||||
return $this->getRecord()->owner;
|
||||
return $this->getOwner(false)->getId();
|
||||
}
|
||||
|
||||
function pin(): void
|
||||
|
@ -121,6 +143,25 @@ class Post extends Postable
|
|||
|
||||
$this->stateChanges("content", $content);
|
||||
}
|
||||
|
||||
function toggleLike(User $user): bool
|
||||
{
|
||||
$liked = parent::toggleLike($user);
|
||||
|
||||
if($this->getOwner(false)->getId() !== $user->getId() && !($this->getOwner() instanceof Club) && !$this instanceof Comment)
|
||||
(new LikeNotification($this->getOwner(false), $this, $user))->emit();
|
||||
|
||||
foreach($this->getChildren() as $attachment)
|
||||
if($attachment instanceof Post)
|
||||
$attachment->setLikeRecursively($liked, $user, 2);
|
||||
|
||||
return $liked;
|
||||
}
|
||||
|
||||
function setLike(bool $liked, User $user): void
|
||||
{
|
||||
$this->setLikeRecursively($liked, $user, 1);
|
||||
}
|
||||
|
||||
function deletePost(): void
|
||||
{
|
||||
|
|
|
@ -29,9 +29,12 @@ abstract class Postable extends Attachable
|
|||
return DB::i()->getContext()->table($this->tableName);
|
||||
}
|
||||
|
||||
function getOwner(): RowModel
|
||||
function getOwner(bool $real = false): RowModel
|
||||
{
|
||||
$oid = (int) $this->getRecord()->owner;
|
||||
if(!$real && $this->isAnonymous())
|
||||
$oid = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["account"];
|
||||
|
||||
if($oid > 0)
|
||||
return (new Users)->get($oid);
|
||||
else
|
||||
|
@ -70,10 +73,10 @@ abstract class Postable extends Attachable
|
|||
{
|
||||
return (new Comments)->getCommentsCountByTarget($this);
|
||||
}
|
||||
|
||||
function getLastComments()
|
||||
|
||||
function getLastComments(int $count): \Traversable
|
||||
{
|
||||
return (new Comments)->getLastCommentsByTarget($this);
|
||||
return (new Comments)->getLastCommentsByTarget($this, $count);
|
||||
}
|
||||
|
||||
function getLikesCount(): int
|
||||
|
@ -84,17 +87,52 @@ abstract class Postable extends Attachable
|
|||
]));
|
||||
}
|
||||
|
||||
function toggleLike(User $user): void
|
||||
// TODO add pagination
|
||||
function getLikers(): \Traversable
|
||||
{
|
||||
$sel = DB::i()->getContext()->table("likes")->where([
|
||||
"model" => static::class,
|
||||
"target" => $this->getRecord()->id,
|
||||
]);
|
||||
|
||||
foreach($sel as $like)
|
||||
yield (new Users)->get($like->origin);
|
||||
}
|
||||
|
||||
function isAnonymous(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->anonymous;
|
||||
}
|
||||
|
||||
function toggleLike(User $user): bool
|
||||
{
|
||||
$searchData = [
|
||||
"origin" => $user->getId(),
|
||||
"model" => static::class,
|
||||
"target" => $this->getRecord()->id,
|
||||
];
|
||||
if(sizeof(DB::i()->getContext()->table("likes")->where($searchData)) > 0)
|
||||
|
||||
if(sizeof(DB::i()->getContext()->table("likes")->where($searchData)) > 0) {
|
||||
DB::i()->getContext()->table("likes")->where($searchData)->delete();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
DB::i()->getContext()->table("likes")->insert($searchData);
|
||||
return true;
|
||||
}
|
||||
|
||||
function setLike(bool $liked, User $user): void
|
||||
{
|
||||
$searchData = [
|
||||
"origin" => $user->getId(),
|
||||
"model" => static::class,
|
||||
"target" => $this->getRecord()->id,
|
||||
];
|
||||
|
||||
if($liked)
|
||||
DB::i()->getContext()->table("likes")->insert($searchData);
|
||||
else
|
||||
DB::i()->getContext()->table("likes")->where($searchData)->delete();
|
||||
}
|
||||
|
||||
function hasLikeFrom(User $user): bool
|
||||
|
|
39
Web/Models/Entities/SupportAlias.php
Normal file
39
Web/Models/Entities/SupportAlias.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Repositories\Users;
|
||||
|
||||
class SupportAlias extends RowModel
|
||||
{
|
||||
protected $tableName = "support_names";
|
||||
|
||||
function getUser(): User
|
||||
{
|
||||
return (new Users)->get($this->getRecord()->agent);
|
||||
}
|
||||
|
||||
function getName(): string
|
||||
{
|
||||
return $this->getRecord()->name;
|
||||
}
|
||||
|
||||
function getIcon(): ?string
|
||||
{
|
||||
return $this->getRecord()->icon;
|
||||
}
|
||||
|
||||
function shouldAppendNumber(): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->numerate;
|
||||
}
|
||||
|
||||
function setAgent(User $agent): void
|
||||
{
|
||||
$this->stateChanges("agent", $agent->getId());
|
||||
}
|
||||
|
||||
function setNumeration(bool $numerate): void
|
||||
{
|
||||
$this->stateChanges("numerate", $numerate);
|
||||
}
|
||||
}
|
|
@ -11,8 +11,9 @@ use Nette\Database\Table\Selection;
|
|||
|
||||
class Ticket extends RowModel
|
||||
{
|
||||
|
||||
protected $tableName = "tickets";
|
||||
|
||||
private $overrideContentColumn = "text";
|
||||
|
||||
function getId(): int
|
||||
{
|
||||
|
@ -23,11 +24,11 @@ class Ticket extends RowModel
|
|||
{
|
||||
if ($this->getRecord()->type === 0)
|
||||
{
|
||||
return 'Вопрос находится на рассмотрении.';
|
||||
return tr("support_status_0");
|
||||
} elseif ($this->getRecord()->type === 1) {
|
||||
return 'Есть ответ.';
|
||||
return tr("support_status_1");
|
||||
} elseif ($this->getRecord()->type === 2) {
|
||||
return 'Закрыто.';
|
||||
return tr("support_status_2");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +44,11 @@ class Ticket extends RowModel
|
|||
|
||||
function getContext(): string
|
||||
{
|
||||
return $this->getRecord()->text;
|
||||
$text = $this->getRecord()->text;
|
||||
$text = $this->formatLinks($text);
|
||||
$text = $this->removeZalgo($text);
|
||||
$text = nl2br($text);
|
||||
return $text;
|
||||
}
|
||||
|
||||
function getTime(): DateTime
|
||||
|
@ -70,4 +75,11 @@ class Ticket extends RowModel
|
|||
{
|
||||
return (new Users)->get($this->getRecord()->user_id);
|
||||
}
|
||||
|
||||
function isAd(): bool /* Эх, костыли... */
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
use Traits\TRichText;
|
||||
}
|
||||
|
|
|
@ -4,20 +4,27 @@ use openvk\Web\Util\DateTime;
|
|||
use Nette\Database\Table\ActiveRow;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use openvk\Web\Models\Repositories\Users;
|
||||
use openvk\Web\Models\Repositories\{Users, SupportAliases};
|
||||
use Chandler\Database\DatabaseConnection as DB;
|
||||
use Nette\InvalidStateException as ISE;
|
||||
use Nette\Database\Table\Selection;
|
||||
|
||||
class TicketComment extends RowModel
|
||||
{
|
||||
|
||||
protected $tableName = "tickets_comments";
|
||||
|
||||
|
||||
private $overrideContentColumn = "text";
|
||||
|
||||
private function getSupportAlias(): ?SupportAlias
|
||||
{
|
||||
return (new SupportAliases)->get($this->getUser()->getId());
|
||||
}
|
||||
|
||||
function getId(): int
|
||||
{
|
||||
return $this->getRecord()->id;
|
||||
}
|
||||
|
||||
function getUType(): int
|
||||
{
|
||||
return $this->getRecord()->user_type;
|
||||
|
@ -26,7 +33,34 @@ class TicketComment extends RowModel
|
|||
function getUser(): User
|
||||
{
|
||||
return (new Users)->get($this->getRecord()->user_id);
|
||||
}
|
||||
}
|
||||
|
||||
function getAuthorName(): string
|
||||
{
|
||||
if($this->getUType() === 0)
|
||||
return $this->getUser()->getCanonicalName();
|
||||
|
||||
$alias = $this->getSupportAlias();
|
||||
if(!$alias)
|
||||
return OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["supportName"] . " №" . $this->getAgentNumber();
|
||||
|
||||
$name = $alias->getName();
|
||||
if($alias->shouldAppendNumber())
|
||||
$name .= " №" . $this->getAgentNumber();
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
function getAvatar(): string
|
||||
{
|
||||
if($this->getUType() === 0)
|
||||
return $this->getUser()->getAvatarUrl();
|
||||
|
||||
$default = "/assets/packages/static/openvk/img/support.jpeg";
|
||||
$alias = $this->getSupportAlias();
|
||||
|
||||
return is_null($alias) ? $default : ($alias->getIcon() ?? $default);
|
||||
}
|
||||
|
||||
function getAgentNumber(): ?string
|
||||
{
|
||||
|
@ -46,6 +80,9 @@ class TicketComment extends RowModel
|
|||
if(is_null($agent = $this->getAgentNumber()))
|
||||
return NULL;
|
||||
|
||||
if(!is_null($this->getSupportAlias()))
|
||||
return 0;
|
||||
|
||||
$agent = (int) $agent;
|
||||
$rotation = $agent > 500 ? ( ($agent * 360) / 999 ) : $agent; # cap at 360deg
|
||||
$values = [0, 45, 160, 220, 310, 345]; # good looking colors
|
||||
|
@ -59,12 +96,22 @@ class TicketComment extends RowModel
|
|||
|
||||
function getContext(): string
|
||||
{
|
||||
return $this->getRecord()->text;
|
||||
$text = $this->getRecord()->text;
|
||||
$text = $this->formatLinks($text);
|
||||
$text = $this->removeZalgo($text);
|
||||
$text = nl2br($text);
|
||||
return $text;
|
||||
}
|
||||
|
||||
function getTime(): DateTime
|
||||
{
|
||||
return new DateTime($this->getRecord()->created);
|
||||
}
|
||||
|
||||
|
||||
function isAd(): bool
|
||||
{
|
||||
return false; # Кооостыыыль!!!
|
||||
}
|
||||
|
||||
use Traits\TRichText;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ trait TRichText
|
|||
{
|
||||
private function formatEmojis(string $text): string
|
||||
{
|
||||
if(iconv_strlen($this->getRecord()->content) > OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["emojiProcessingLimit"])
|
||||
$contentColumn = property_exists($this, "overrideContentColumn") ? $this->overrideContentColumn : "content";
|
||||
if(iconv_strlen($this->getRecord()->{$contentColumn}) > OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["emojiProcessingLimit"])
|
||||
return $text;
|
||||
|
||||
$emojis = \Emoji\detect_emoji($text);
|
||||
|
@ -26,21 +27,21 @@ trait TRichText
|
|||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private function formatLinks(string &$text): string
|
||||
{
|
||||
return preg_replace_callback(
|
||||
"%(([A-z]++):\/\/(\S*?\.\S*?))([\s)\[\]{},;\"\'<]|\.\s|$)%",
|
||||
(function (array $matches): string {
|
||||
$href = str_replace("#", "#", $matches[1]);
|
||||
$link = str_replace("#", "#", $matches[3]);
|
||||
$rel = $this->isAd() ? "sponsored" : "ugc";
|
||||
|
||||
return "<a href='$href' rel='$rel' target='_blank'>$link</a>" . htmlentities($matches[4]);
|
||||
}),
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
private function formatLinks(string &$text): string
|
||||
{
|
||||
return preg_replace_callback(
|
||||
"%(([A-z]++):\/\/(\S*?\.\S*?))([\s)\[\]{},;\"\'<]|\.\s|$)%",
|
||||
(function (array $matches): string {
|
||||
$href = str_replace("#", "#", $matches[1]);
|
||||
$link = str_replace("#", "#", $matches[3]);
|
||||
$rel = $this->isAd() ? "sponsored" : "ugc";
|
||||
|
||||
return "<a href='$href' rel='$rel' target='_blank'>$link</a>" . htmlentities($matches[4]);
|
||||
}),
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
private function removeZalgo(string $text): string
|
||||
{
|
||||
|
@ -49,8 +50,10 @@ trait TRichText
|
|||
|
||||
function getText(bool $html = true): string
|
||||
{
|
||||
$text = htmlentities($this->getRecord()->content, ENT_DISALLOWED | ENT_XHTML);
|
||||
$proc = iconv_strlen($this->getRecord()->content) <= OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["processingLimit"];
|
||||
$contentColumn = property_exists($this, "overrideContentColumn") ? $this->overrideContentColumn : "content";
|
||||
|
||||
$text = htmlentities($this->getRecord()->{$contentColumn}, ENT_DISALLOWED | ENT_XHTML);
|
||||
$proc = iconv_strlen($this->getRecord()->{$contentColumn}) <= OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["processingLimit"];
|
||||
if($html) {
|
||||
if($proc) {
|
||||
$rel = $this->isAd() ? "sponsored" : "ugc";
|
||||
|
|
|
@ -3,8 +3,8 @@ namespace openvk\Web\Models\Entities;
|
|||
use openvk\Web\Themes\{Themepack, Themepacks};
|
||||
use openvk\Web\Util\DateTime;
|
||||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Entities\{Photo, Message, Correspondence};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Notifications};
|
||||
use openvk\Web\Models\Entities\{Photo, Message, Correspondence, Gift};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Gifts, Notifications};
|
||||
use Nette\Database\Table\ActiveRow;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use Chandler\Security\User as ChandlerUser;
|
||||
|
@ -204,6 +204,11 @@ class User extends RowModel
|
|||
return $this->getRecord()->shortcode;
|
||||
}
|
||||
|
||||
function getAlert(): ?string
|
||||
{
|
||||
return $this->getRecord()->alert;
|
||||
}
|
||||
|
||||
function getBanReason(): ?string
|
||||
{
|
||||
return $this->getRecord()->block_reason;
|
||||
|
@ -214,11 +219,19 @@ class User extends RowModel
|
|||
return $this->getRecord()->type;
|
||||
}
|
||||
|
||||
function getCoins(): int
|
||||
function getCoins(): float
|
||||
{
|
||||
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
|
||||
return 0.0;
|
||||
|
||||
return $this->getRecord()->coins;
|
||||
}
|
||||
|
||||
function getRating(): int
|
||||
{
|
||||
return OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"] ? $this->getRecord()->rating : 0;
|
||||
}
|
||||
|
||||
function getReputation(): int
|
||||
{
|
||||
return $this->getRecord()->reputation;
|
||||
|
@ -308,7 +321,22 @@ class User extends RowModel
|
|||
{
|
||||
return $this->getRecord()->birthday;
|
||||
}
|
||||
|
||||
function getAge(): ?int
|
||||
{
|
||||
return (int)floor((time() - $this->getBirthday()) / mktime(0, 0, 0, 1, 1, 1971));
|
||||
}
|
||||
|
||||
function get2faSecret(): ?string
|
||||
{
|
||||
return $this->getRecord()["2fa_secret"];
|
||||
}
|
||||
|
||||
function is2faEnabled(): bool
|
||||
{
|
||||
return !is_null($this->get2faSecret());
|
||||
}
|
||||
|
||||
function updateNotificationOffset(): void
|
||||
{
|
||||
$this->stateChanges("notification_offset", time());
|
||||
|
@ -392,8 +420,16 @@ class User extends RowModel
|
|||
$incompleteness += 20;
|
||||
}
|
||||
|
||||
$total = max(100 - $incompleteness + $this->getRating(), 0);
|
||||
if(ovkGetQuirk("profile.rating-bar-behaviour") === 0)
|
||||
if ($total >= 100)
|
||||
$percent = round(($total / 10**strlen(strval($total))) * 100, 0);
|
||||
else
|
||||
$percent = min($total, 100);
|
||||
|
||||
return (object) [
|
||||
"total" => 100 - $incompleteness,
|
||||
"total" => $total,
|
||||
"percent" => $percent,
|
||||
"unfilled" => $unfilled,
|
||||
];
|
||||
}
|
||||
|
@ -433,25 +469,78 @@ class User extends RowModel
|
|||
return sizeof(DatabaseConnection::i()->getContext()->table("messages")->where(["recipient_id" => $this->getId(), "unread" => 1]));
|
||||
}
|
||||
|
||||
function getClubs(int $page = 1): \Traversable
|
||||
function getClubs(int $page = 1, bool $admin = false): \Traversable
|
||||
{
|
||||
$sel = $this->getRecord()->related("subscriptions.follower")->page($page, OPENVK_DEFAULT_PER_PAGE);
|
||||
foreach($sel->where("model", "openvk\\Web\\Models\\Entities\\Club") as $target) {
|
||||
$target = (new Clubs)->get($target->target);
|
||||
if(!$target) continue;
|
||||
|
||||
yield $target;
|
||||
if($admin) {
|
||||
$id = $this->getId();
|
||||
$query = "SELECT `id` FROM `groups` WHERE `owner` = ? UNION SELECT `club` as `id` FROM `group_coadmins` WHERE `user` = ?";
|
||||
$query .= " LIMIT " . OPENVK_DEFAULT_PER_PAGE . " OFFSET " . ($page - 1) * OPENVK_DEFAULT_PER_PAGE;
|
||||
|
||||
$sel = DatabaseConnection::i()->getConnection()->query($query, $id, $id);
|
||||
foreach($sel as $target) {
|
||||
$target = (new Clubs)->get($target->id);
|
||||
if(!$target) continue;
|
||||
|
||||
yield $target;
|
||||
}
|
||||
} else {
|
||||
$sel = $this->getRecord()->related("subscriptions.follower")->page($page, OPENVK_DEFAULT_PER_PAGE);
|
||||
foreach($sel->where("model", "openvk\\Web\\Models\\Entities\\Club") as $target) {
|
||||
$target = (new Clubs)->get($target->target);
|
||||
if(!$target) continue;
|
||||
|
||||
yield $target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getClubCount(): int
|
||||
function getClubCount(bool $admin = false): int
|
||||
{
|
||||
$sel = $this->getRecord()->related("subscriptions.follower");
|
||||
$sel = $sel->where("model", "openvk\\Web\\Models\\Entities\\Club");
|
||||
|
||||
return sizeof($sel);
|
||||
if($admin) {
|
||||
$id = $this->getId();
|
||||
$query = "SELECT COUNT(*) AS `cnt` FROM (SELECT `id` FROM `groups` WHERE `owner` = ? UNION SELECT `club` as `id` FROM `group_coadmins` WHERE `user` = ?) u0;";
|
||||
|
||||
return (int) DatabaseConnection::i()->getConnection()->query($query, $id, $id)->fetch()->cnt;
|
||||
} else {
|
||||
$sel = $this->getRecord()->related("subscriptions.follower");
|
||||
$sel = $sel->where("model", "openvk\\Web\\Models\\Entities\\Club");
|
||||
|
||||
return sizeof($sel);
|
||||
}
|
||||
}
|
||||
|
||||
function getPinnedClubs(): \Traversable
|
||||
{
|
||||
foreach($this->getRecord()->related("groups.owner")->where("owner_club_pinned", true) as $target) {
|
||||
$target = (new Clubs)->get($target->id);
|
||||
if(!$target) continue;
|
||||
|
||||
yield $target;
|
||||
}
|
||||
|
||||
foreach($this->getRecord()->related("group_coadmins.user")->where("club_pinned", true) as $target) {
|
||||
$target = (new Clubs)->get($target->club);
|
||||
if(!$target) continue;
|
||||
|
||||
yield $target;
|
||||
}
|
||||
}
|
||||
|
||||
function getPinnedClubCount(): int
|
||||
{
|
||||
return sizeof($this->getRecord()->related("groups.owner")->where("owner_club_pinned", true)) + sizeof($this->getRecord()->related("group_coadmins.user")->where("club_pinned", true));
|
||||
}
|
||||
|
||||
function isClubPinned(Club $club): bool
|
||||
{
|
||||
if($club->getOwner()->getId() === $this->getId())
|
||||
return $club->isOwnerClubPinned();
|
||||
|
||||
$manager = $club->getManager($this);
|
||||
if(!is_null($manager))
|
||||
return $manager->isClubPinned();
|
||||
}
|
||||
|
||||
function getMeetings(int $page = 1): \Traversable
|
||||
{
|
||||
$sel = $this->getRecord()->related("event_turnouts.user")->page($page, OPENVK_DEFAULT_PER_PAGE);
|
||||
|
@ -468,6 +557,57 @@ class User extends RowModel
|
|||
return sizeof($this->getRecord()->related("event_turnouts.user"));
|
||||
}
|
||||
|
||||
function getGifts(int $page = 1, ?int $perPage = NULL): \Traversable
|
||||
{
|
||||
$gifts = $this->getRecord()->related("gift_user_relations.receiver")->order("sent DESC")->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
|
||||
foreach($gifts as $rel) {
|
||||
yield (object) [
|
||||
"sender" => (new Users)->get($rel->sender),
|
||||
"gift" => (new Gifts)->get($rel->gift),
|
||||
"caption" => $rel->comment,
|
||||
"anon" => $rel->anonymous,
|
||||
"sent" => new DateTime($rel->sent),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function getGiftCount(): int
|
||||
{
|
||||
return sizeof($this->getRecord()->related("gift_user_relations.receiver"));
|
||||
}
|
||||
|
||||
function get2faBackupCodes(): \Traversable
|
||||
{
|
||||
$sel = $this->getRecord()->related("2fa_backup_codes.owner");
|
||||
foreach($sel as $target)
|
||||
yield $target->code;
|
||||
}
|
||||
|
||||
function get2faBackupCodeCount(): int
|
||||
{
|
||||
return sizeof($this->getRecord()->related("2fa_backup_codes.owner"));
|
||||
}
|
||||
|
||||
function generate2faBackupCodes(): void
|
||||
{
|
||||
$codes = [];
|
||||
|
||||
for($i = 0; $i < 10 - $this->get2faBackupCodeCount(); $i++) {
|
||||
$codes[] = [
|
||||
owner => $this->getId(),
|
||||
code => random_int(10000000, 99999999)
|
||||
];
|
||||
}
|
||||
|
||||
if(sizeof($codes) > 0)
|
||||
DatabaseConnection::i()->getContext()->table("2fa_backup_codes")->insert($codes);
|
||||
}
|
||||
|
||||
function use2faBackupCode(int $code): bool
|
||||
{
|
||||
return (bool) $this->getRecord()->related("2fa_backup_codes.owner")->where("code", $code)->delete();
|
||||
}
|
||||
|
||||
function getSubscriptionStatus(User $user): int
|
||||
{
|
||||
$subbed = !is_null($this->getRecord()->related("subscriptions.follower")->where([
|
||||
|
@ -528,6 +668,11 @@ class User extends RowModel
|
|||
return !is_null($this->getBanReason());
|
||||
}
|
||||
|
||||
function isOnline(): bool
|
||||
{
|
||||
return time() - $this->getRecord()->online <= 300;
|
||||
}
|
||||
|
||||
function prefersNotToSeeRating(): bool
|
||||
{
|
||||
return !((bool) $this->getRecord()->show_rating);
|
||||
|
@ -538,6 +683,18 @@ class User extends RowModel
|
|||
return !is_null($this->getPendingPhoneVerification());
|
||||
}
|
||||
|
||||
function gift(User $sender, Gift $gift, ?string $comment = NULL, bool $anonymous = false): void
|
||||
{
|
||||
DatabaseConnection::i()->getContext()->table("gift_user_relations")->insert([
|
||||
"sender" => $sender->getId(),
|
||||
"receiver" => $this->getId(),
|
||||
"gift" => $gift->getId(),
|
||||
"comment" => $comment,
|
||||
"anonymous" => $anonymous,
|
||||
"sent" => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
function ban(string $reason): void
|
||||
{
|
||||
$subs = DatabaseConnection::i()->getContext()->table("subscriptions");
|
||||
|
@ -615,9 +772,11 @@ class User extends RowModel
|
|||
$this->stateChanges("left_menu", $mask);
|
||||
}
|
||||
|
||||
function setShortCode(?string $code = NULL): ?bool
|
||||
function setShortCode(?string $code = NULL, bool $force = false): ?bool
|
||||
{
|
||||
if(!is_null($code)) {
|
||||
if(strlen($code) < OPENVK_ROOT_CONF["openvk"]["preferences"]["shortcodes"]["minLength"] && !$force)
|
||||
return false;
|
||||
if(!preg_match("%^[a-z][a-z0-9\\.\\_]{0,30}[a-z0-9]$%", $code))
|
||||
return false;
|
||||
if(in_array($code, OPENVK_ROOT_CONF["openvk"]["preferences"]["shortcodes"]["forbiddenNames"]))
|
||||
|
@ -625,9 +784,9 @@ class User extends RowModel
|
|||
if(\Chandler\MVC\Routing\Router::i()->getMatchingRoute("/$code")[0]->presenter !== "UnknownTextRouteStrategy")
|
||||
return false;
|
||||
|
||||
$pClub = DatabaseConnection::i()->getContext()->table("groups")->where("shortcode", $code)->fetch();
|
||||
if(!is_null($pClub))
|
||||
return false;
|
||||
$pClub = DatabaseConnection::i()->getContext()->table("groups")->where("shortcode", $code)->fetch();
|
||||
if(!is_null($pClub))
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->stateChanges("shortcode", $code);
|
||||
|
@ -709,6 +868,10 @@ class User extends RowModel
|
|||
}
|
||||
}
|
||||
|
||||
function getWebsite(): ?string
|
||||
{
|
||||
return $this->getRecord()->website;
|
||||
}
|
||||
|
||||
use Traits\TSubscribable;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use openvk\Web\Util\Shell\Shell;
|
||||
use openvk\Web\Util\Shell\Shell\Exceptions\ShellUnavailableException;
|
||||
use openvk\Web\Util\Shell\Shell\Exceptions\UnknownCommandException;
|
||||
use openvk\Web\Util\Shell\Shell\Exceptions\{ShellUnavailableException, UnknownCommandException};
|
||||
use openvk\Web\Models\VideoDrivers\VideoDriver;
|
||||
use Nette\InvalidStateException as ISE;
|
||||
|
||||
|
@ -18,7 +17,24 @@ class Video extends Media
|
|||
|
||||
protected function saveFile(string $filename, string $hash): bool
|
||||
{
|
||||
if(!Shell::commandAvailable("ffmpeg")) exit(VIDEOS_FRIENDLY_ERROR);
|
||||
if(!Shell::commandAvailable("ffmpeg") || !Shell::commandAvailable("ffprobe"))
|
||||
exit(VIDEOS_FRIENDLY_ERROR);
|
||||
|
||||
$error = NULL;
|
||||
$streams = Shell::ffprobe("-i", $filename, "-show_streams", "-select_streams v", "-loglevel error")->execute($error);
|
||||
if($error !== 0)
|
||||
throw new \DomainException("$filename is not a valid video file");
|
||||
else if(empty($streams) || ctype_space($streams))
|
||||
throw new \DomainException("$filename does not contain any video streams");
|
||||
|
||||
$durations = [];
|
||||
preg_match('%duration=([0-9\.]++)%', $streams, $durations);
|
||||
if(sizeof($durations[1]) === 0)
|
||||
throw new \DomainException("$filename does not contain any meaningful video streams");
|
||||
|
||||
foreach($durations[1] as $duration)
|
||||
if(floatval($duration) < 1.0)
|
||||
throw new \DomainException("$filename does not contain any meaningful video streams");
|
||||
|
||||
try {
|
||||
if(!is_dir($dirId = $this->pathFromHash($hash)))
|
||||
|
@ -93,10 +109,10 @@ class Video extends Media
|
|||
|
||||
function isDeleted(): bool
|
||||
{
|
||||
if ($this->getRecord()->deleted == 1)
|
||||
return TRUE;
|
||||
else
|
||||
return FALSE;
|
||||
if ($this->getRecord()->deleted == 1)
|
||||
return TRUE;
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
function deleteVideo(): void
|
||||
|
@ -105,4 +121,19 @@ class Video extends Media
|
|||
$this->unwire();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
static function fastMake(int $owner, string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video
|
||||
{
|
||||
$video = new Video;
|
||||
$video->setOwner($owner);
|
||||
$video->setName("Unnamed Video.ogv");
|
||||
$video->setDescription(ovk_proc_strtr($description, 300));
|
||||
$video->setAnonymous($anon);
|
||||
$video->setCreated(time());
|
||||
$video->setFile($file);
|
||||
$video->setUnlisted($unlisted);
|
||||
$video->save();
|
||||
|
||||
return $video;
|
||||
}
|
||||
}
|
||||
|
|
85
Web/Models/Entities/Voucher.php
Normal file
85
Web/Models/Entities/Voucher.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities;
|
||||
use Chandler\Database\DatabaseConnection as DB;
|
||||
use openvk\Web\Models\Repositories\Users;
|
||||
use openvk\Web\Models\RowModel;
|
||||
|
||||
class Voucher extends RowModel
|
||||
{
|
||||
protected $tableName = "coin_vouchers";
|
||||
|
||||
function getCoins(): int
|
||||
{
|
||||
return $this->getRecord()->coins;
|
||||
}
|
||||
|
||||
function getRating(): int
|
||||
{
|
||||
return $this->getRecord()->rating;
|
||||
}
|
||||
|
||||
function getToken(): string
|
||||
{
|
||||
return $this->getRecord()->token;
|
||||
}
|
||||
|
||||
function getFormattedToken(): string
|
||||
{
|
||||
$fmtTok = "";
|
||||
$token = $this->getRecord()->token;
|
||||
foreach(array_chunk(str_split($token), 6) as $chunk)
|
||||
$fmtTok .= implode("", $chunk) . "-";
|
||||
|
||||
return substr($fmtTok, 0, -1);
|
||||
}
|
||||
|
||||
function getRemainingUsages(): float
|
||||
{
|
||||
return (float) ($this->getRecord()->usages_left ?? INF);
|
||||
}
|
||||
|
||||
function getUsers(int $page = -1, ?int $perPage = NULL): \Traversable
|
||||
{
|
||||
$relations = $this->getRecord()->related("voucher_users.voucher");
|
||||
if($page !== -1)
|
||||
$relations = $relations->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
|
||||
|
||||
foreach($relations as $relation)
|
||||
yield (new Users)->get($relation->user);
|
||||
}
|
||||
|
||||
function isExpired(): bool
|
||||
{
|
||||
return $this->getRemainingUsages() < 1;
|
||||
}
|
||||
|
||||
function wasUsedBy(User $user): bool
|
||||
{
|
||||
$record = $this->getRecord()->related("voucher_users.voucher")->where("user", $user->getId());
|
||||
|
||||
return sizeof($record) > 0;
|
||||
}
|
||||
|
||||
function willUse(User $user): bool
|
||||
{
|
||||
if($this->wasUsedBy($user))
|
||||
return false;
|
||||
|
||||
if($this->isExpired())
|
||||
return false;
|
||||
|
||||
$this->setRemainingUsages($this->getRemainingUsages() - 1);
|
||||
DB::i()->getContext()->table("voucher_users")->insert([
|
||||
"voucher" => $this->getId(),
|
||||
"user" => $user->getId(),
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function setRemainingUsages(float $usages): void
|
||||
{
|
||||
$this->stateChanges("usages_left", $usages === INF ? NULL : ((int) $usages));
|
||||
$this->save();
|
||||
}
|
||||
}
|
|
@ -37,6 +37,19 @@ class Comments
|
|||
foreach($comments as $comment)
|
||||
yield $this->toComment($comment);
|
||||
}
|
||||
|
||||
function getLastCommentsByTarget(Postable $target, ?int $count = NULL): \Traversable
|
||||
{
|
||||
$comments = $this->comments->where([
|
||||
"model" => get_class($target),
|
||||
"target" => $target->getId(),
|
||||
"deleted" => false,
|
||||
])->page(1, $count ?? OPENVK_DEFAULT_PER_PAGE)->order("created DESC");
|
||||
|
||||
$comments = array_reverse(iterator_to_array($comments));
|
||||
foreach($comments as $comment)
|
||||
yield $this->toComment($comment);
|
||||
}
|
||||
|
||||
function getCommentsCountByTarget(Postable $target): int
|
||||
{
|
||||
|
|
45
Web/Models/Repositories/Gifts.php
Normal file
45
Web/Models/Repositories/Gifts.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Repositories;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use openvk\Web\Models\Entities\{Gift, GiftCategory};
|
||||
|
||||
class Gifts
|
||||
{
|
||||
private $context;
|
||||
private $gifts;
|
||||
private $cats;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->context = DatabaseConnection::i()->getContext();
|
||||
$this->gifts = $this->context->table("gifts");
|
||||
$this->cats = $this->context->table("gift_categories");
|
||||
}
|
||||
|
||||
function get(int $id): ?Gift
|
||||
{
|
||||
$gift = $this->gifts->get($id);
|
||||
if(!$gift)
|
||||
return NULL;
|
||||
|
||||
return new Gift($gift);
|
||||
}
|
||||
|
||||
function getCat(int $id): ?GiftCategory
|
||||
{
|
||||
$cat = $this->cats->get($id);
|
||||
if(!$cat)
|
||||
return NULL;
|
||||
|
||||
return new GiftCategory($cat);
|
||||
}
|
||||
|
||||
function getCategories(int $page, ?int $perPage = NULL, &$count = nullptr): \Traversable
|
||||
{
|
||||
$cats = $this->cats->where("deleted", false);
|
||||
$count = $cats->count();
|
||||
$cats = $cats->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
|
||||
foreach($cats as $cat)
|
||||
yield new GiftCategory($cat);
|
||||
}
|
||||
}
|
|
@ -32,6 +32,15 @@ class Notes
|
|||
foreach($this->notes->where("owner", $user->getId())->where("deleted", 0)->page($page, $perPage) as $album)
|
||||
yield new Note($album);
|
||||
}
|
||||
|
||||
function getNoteById(int $owner, int $note): ?Note
|
||||
{
|
||||
$note = $this->notes->where(['owner' => $owner, 'virtual_id' => $note])->fetch();
|
||||
if(!is_null($note))
|
||||
return new Note($note);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
function getUserNotesCount(User $user): int
|
||||
{
|
||||
|
|
|
@ -43,6 +43,19 @@ class Notifications
|
|||
return $query;
|
||||
}
|
||||
|
||||
private function assemble(int $act, int $originModelType, int $originModelId, int $targetModelType, int $targetModelId, int $recipientId, int $timestamp, $data, ?string $class = NULL): Notification
|
||||
{
|
||||
$class ??= 'openvk\Web\Models\Entities\Notifications\Notification';
|
||||
|
||||
$originModel = $this->getModel($originModelType, $originModelId);
|
||||
$targetModel = $this->getModel($targetModelType, $targetModelId);
|
||||
$recipient = (new Users)->get($recipientId);
|
||||
|
||||
$notification = new $class($recipient, $originModel, $targetModel, $timestamp, $data);
|
||||
$notification->setActionCode($act);
|
||||
return $notification;
|
||||
}
|
||||
|
||||
function getNotificationCountByUser(User $user, int $offset, bool $archived = false): int
|
||||
{
|
||||
$db = $this->getEDB(false);
|
||||
|
@ -64,13 +77,38 @@ class Notifications
|
|||
|
||||
$results = $this->getEDB()->query($this->getQuery($user, false, $offset, $archived, $page, $perPage));
|
||||
foreach($results->fetchAll() as $notif) {
|
||||
$originModel = $this->getModel($notif->originModelType, $notif->originModelId);
|
||||
$targetModel = $this->getModel($notif->targetModelType, $notif->targetModelId);
|
||||
$recipient = (new Users)->get($notif->recipientId);
|
||||
|
||||
$notification = new Notification($recipient, $originModel, $targetModel, $notif->timestamp, $notif->additionalData);
|
||||
$notification->setActionCode($notif->modelAction);
|
||||
yield $notification;
|
||||
yield $this->assemble(
|
||||
$notif->modelAction,
|
||||
$notif->originModelType,
|
||||
$notif->originModelId,
|
||||
|
||||
$notif->targetModelType,
|
||||
$notif->targetModelId,
|
||||
|
||||
$notif->recipientId,
|
||||
$notif->timestamp,
|
||||
$notif->additionalData
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function fromDescriptor(string $descriptor, ?object &$parsedData = nullptr)
|
||||
{
|
||||
[$class, $recv, $data] = explode(",", $descriptor);
|
||||
$class = str_replace(".", "\\", $class);
|
||||
|
||||
$parsedData = unserialize(base64_decode($data));
|
||||
return $this->assemble(
|
||||
$parsedData->actionCode,
|
||||
$parsedData->originModelType,
|
||||
$parsedData->originModelId,
|
||||
|
||||
$parsedData->targetModelType,
|
||||
$parsedData->targetModelId,
|
||||
|
||||
$parsedData->recipient,
|
||||
$parsedData->timestamp,
|
||||
$parsedData->additionalPayload,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,20 +37,24 @@ class Posts
|
|||
return $this->toPost($post);
|
||||
}
|
||||
|
||||
function getPostsFromUsersWall(int $user, int $page = 1, ?int $perPage = NULL): \Traversable
|
||||
function getPostsFromUsersWall(int $user, int $page = 1, ?int $perPage = NULL, ?int $offset = NULL): \Traversable
|
||||
{
|
||||
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
|
||||
$offset = $perPage * ($page - 1);
|
||||
$offset ??= $perPage * ($page - 1);
|
||||
|
||||
$pinPost = $this->getPinnedPost($user);
|
||||
if(!is_null($pinPost)) {
|
||||
if($page === 1) {
|
||||
$perPage--;
|
||||
|
||||
yield $pinPost;
|
||||
} else {
|
||||
$offset--;
|
||||
if(is_null($offset) || $offset == 0) {
|
||||
if(!is_null($pinPost)) {
|
||||
if($page === 1) {
|
||||
$perPage--;
|
||||
|
||||
yield $pinPost;
|
||||
} else {
|
||||
$offset--;
|
||||
}
|
||||
}
|
||||
} else if(!is_null($offset)) {
|
||||
$offset--;
|
||||
}
|
||||
|
||||
$sel = $this->posts->where([
|
||||
|
|
|
@ -6,8 +6,8 @@ use Nette\Database\Table\ActiveRow;
|
|||
|
||||
abstract class Repository
|
||||
{
|
||||
private $context;
|
||||
private $table;
|
||||
protected $context;
|
||||
protected $table;
|
||||
|
||||
protected $tableName;
|
||||
protected $modelName;
|
||||
|
@ -29,5 +29,18 @@ abstract class Repository
|
|||
return $this->toEntity($this->table->get($id));
|
||||
}
|
||||
|
||||
function size(bool $withDeleted = false): int
|
||||
{
|
||||
return sizeof($this->table->where("deleted", $withDeleted));
|
||||
}
|
||||
|
||||
function enumerate(int $page, ?int $perPage = NULL, bool $withDeleted = false): \Traversable
|
||||
{
|
||||
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
|
||||
|
||||
foreach($this->table->where("deleted", $withDeleted)->page($page, $perPage) as $entity)
|
||||
yield $this->toEntity($entity);
|
||||
}
|
||||
|
||||
use \Nette\SmartObject;
|
||||
}
|
||||
|
|
13
Web/Models/Repositories/SupportAliases.php
Normal file
13
Web/Models/Repositories/SupportAliases.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Repositories;
|
||||
|
||||
class SupportAliases extends Repository
|
||||
{
|
||||
protected $tableName = "support_names";
|
||||
protected $modelName = "SupportAlias";
|
||||
|
||||
function get(int $agent)
|
||||
{
|
||||
return $this->toEntity($this->table->where("agent", $agent)->fetch());
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ class Tickets
|
|||
|
||||
function getTickets(int $state = 0, int $page = 1): \Traversable
|
||||
{
|
||||
foreach($this->tickets->where(["deleted" => 0, "type" => $state])->page($page, OPENVK_DEFAULT_PER_PAGE) as $t)
|
||||
foreach($this->tickets->where(["deleted" => 0, "type" => $state])->order("created DESC")->page($page, OPENVK_DEFAULT_PER_PAGE) as $t)
|
||||
yield new Ticket($t);
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,12 @@ class Tickets
|
|||
|
||||
function getTicketsByuId(int $user_id): \Traversable
|
||||
{
|
||||
foreach($this->tickets->where(['user_id' => $user_id, 'deleted' => 0]) as $ticket) yield new Ticket($ticket);
|
||||
foreach($this->tickets->where(['user_id' => $user_id, 'deleted' => 0])->order("created DESC") as $ticket) yield new Ticket($ticket);
|
||||
}
|
||||
|
||||
function getTicketsCountByuId(int $user_id, int $type = 0): int
|
||||
{
|
||||
return sizeof($this->tickets->where(['user_id' => $user_id, 'deleted' => 0, 'type' => $type]));
|
||||
}
|
||||
|
||||
function getRequestById(int $req_id): ?Ticket
|
||||
|
|
|
@ -37,12 +37,12 @@ class Videos
|
|||
function getByUser(User $user, int $page = 1, ?int $perPage = NULL): \Traversable
|
||||
{
|
||||
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
|
||||
foreach($this->videos->where("owner", $user->getId())->where("deleted", 0)->page($page, $perPage) as $video)
|
||||
foreach($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->page($page, $perPage)->order("created DESC") as $video)
|
||||
yield new Video($video);
|
||||
}
|
||||
|
||||
function getUserVideosCount(User $user): int
|
||||
{
|
||||
return sizeof($this->videos->where("owner", $user->getId())->where("deleted", 0));
|
||||
return sizeof($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0]));
|
||||
}
|
||||
}
|
||||
|
|
19
Web/Models/Repositories/Vouchers.php
Normal file
19
Web/Models/Repositories/Vouchers.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Repositories;
|
||||
use openvk\Web\Models\Entities\Voucher;
|
||||
|
||||
class Vouchers extends Repository
|
||||
{
|
||||
protected $tableName = "coin_vouchers";
|
||||
protected $modelName = "Voucher";
|
||||
|
||||
function getByToken(string $token, bool $withDeleted = false)
|
||||
{
|
||||
$voucher = $this->table->where([
|
||||
"token" => $token,
|
||||
"deleted" => $withDeleted,
|
||||
])->fetch();
|
||||
|
||||
return $this->toEntity($voucher);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Themes\Themepacks;
|
||||
use openvk\Web\Models\Repositories\{Users, Managers};
|
||||
use openvk\Web\Util\Localizator;
|
||||
use Chandler\Session\Session;
|
||||
|
||||
final class AboutPresenter extends OpenVKPresenter
|
||||
|
@ -62,10 +63,22 @@ final class AboutPresenter extends OpenVKPresenter
|
|||
$this->template->languages = getLanguages();
|
||||
|
||||
if(!is_null($_GET['lg'])){
|
||||
$this->assertNoCSRF();
|
||||
setLanguage($_GET['lg']);
|
||||
}
|
||||
}
|
||||
|
||||
function renderExportJSLanguage($lg = NULL): void
|
||||
{
|
||||
$localizer = Localizator::i();
|
||||
$lang = $lg;
|
||||
if(is_null($lg))
|
||||
$this->throwError(404, "Not found", "Language is not found");
|
||||
header("Content-Type: application/javascript");
|
||||
echo "window.lang = " . json_encode($localizer->export($lang)) . ";"; // привет хардкод :DDD
|
||||
exit;
|
||||
}
|
||||
|
||||
function renderSandbox(): void
|
||||
{
|
||||
$this->template->languages = getLanguages();
|
||||
|
|
|
@ -1,21 +1,31 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs};
|
||||
use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Vouchers, Gifts};
|
||||
|
||||
final class AdminPresenter extends OpenVKPresenter
|
||||
{
|
||||
private $users;
|
||||
private $clubs;
|
||||
private $vouchers;
|
||||
private $gifts;
|
||||
|
||||
function __construct(Users $users, Clubs $clubs)
|
||||
function __construct(Users $users, Clubs $clubs, Vouchers $vouchers, Gifts $gifts)
|
||||
{
|
||||
$this->users = $users;
|
||||
$this->clubs = $clubs;
|
||||
$this->users = $users;
|
||||
$this->clubs = $clubs;
|
||||
$this->vouchers = $vouchers;
|
||||
$this->gifts = $gifts;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
private function warnIfNoCommerce(): void
|
||||
{
|
||||
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
|
||||
$this->flash("warn", "Коммерция отключена системным администратором", "Настройки ваучеров и подарков будут сохранены, но не будут оказывать никакого влияния.");
|
||||
}
|
||||
|
||||
private function searchResults(object $repo, &$count)
|
||||
{
|
||||
$query = $this->queryParam("q") ?? "";
|
||||
|
@ -106,6 +116,219 @@ final class AdminPresenter extends OpenVKPresenter
|
|||
}
|
||||
}
|
||||
|
||||
function renderVouchers(): void
|
||||
{
|
||||
$this->warnIfNoCommerce();
|
||||
|
||||
$this->template->count = $this->vouchers->size();
|
||||
$this->template->vouchers = iterator_to_array($this->vouchers->enumerate((int) ($this->queryParam("p") ?? 1)));
|
||||
}
|
||||
|
||||
function renderVoucher(int $id): void
|
||||
{
|
||||
$this->warnIfNoCommerce();
|
||||
|
||||
$voucher = NULL;
|
||||
$this->template->form = (object) [];
|
||||
if($id === 0) {
|
||||
$this->template->form->id = 0;
|
||||
$this->template->form->token = NULL;
|
||||
$this->template->form->coins = 0;
|
||||
$this->template->form->rating = 0;
|
||||
$this->template->form->usages = -1;
|
||||
$this->template->form->users = [];
|
||||
} else {
|
||||
$voucher = $this->vouchers->get($id);
|
||||
if(!$voucher)
|
||||
$this->notFound();
|
||||
|
||||
$this->template->form->id = $voucher->getId();
|
||||
$this->template->form->token = $voucher->getToken();
|
||||
$this->template->form->coins = $voucher->getCoins();
|
||||
$this->template->form->rating = $voucher->getRating();
|
||||
$this->template->form->usages = $voucher->getRemainingUsages();
|
||||
$this->template->form->users = iterator_to_array($voucher->getUsers());
|
||||
|
||||
if($this->template->form->usages === INF)
|
||||
$this->template->form->usages = -1;
|
||||
else
|
||||
$this->template->form->usages = (int) $this->template->form->usages;
|
||||
}
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] !== "POST")
|
||||
return;
|
||||
|
||||
$voucher ??= new Voucher;
|
||||
$voucher->setCoins((int) $this->postParam("coins"));
|
||||
$voucher->setRating((int) $this->postParam("rating"));
|
||||
$voucher->setRemainingUsages($this->postParam("usages") === '-1' ? INF : ((int) $this->postParam("usages")));
|
||||
if(!empty($tok = $this->postParam("token")) && strlen($tok) === 24)
|
||||
$voucher->setToken($tok);
|
||||
|
||||
$voucher->save();
|
||||
|
||||
$this->redirect("/admin/vouchers/id" . $voucher->getId(), static::REDIRECT_TEMPORARY);
|
||||
exit;
|
||||
}
|
||||
|
||||
function renderGiftCategories(): void
|
||||
{
|
||||
$this->warnIfNoCommerce();
|
||||
|
||||
$this->template->act = $this->queryParam("act") ?? "list";
|
||||
$this->template->categories = iterator_to_array($this->gifts->getCategories((int) ($this->queryParam("p") ?? 1), NULL, $this->template->count));
|
||||
}
|
||||
|
||||
function renderGiftCategory(string $slug, int $id): void
|
||||
{
|
||||
$this->warnIfNoCommerce();
|
||||
|
||||
$cat;
|
||||
$gen = false;
|
||||
if($id !== 0) {
|
||||
$cat = $this->gifts->getCat($id);
|
||||
if(!$cat)
|
||||
$this->notFound();
|
||||
else if($cat->getSlug() !== $slug)
|
||||
$this->redirect("/admin/gifts/" . $cat->getSlug() . "." . $id . ".meta", static::REDIRECT_TEMPORARY);
|
||||
} else {
|
||||
$gen = true;
|
||||
$cat = new GiftCategory;
|
||||
}
|
||||
|
||||
$this->template->form = (object) [];
|
||||
$this->template->form->id = $id;
|
||||
$this->template->form->languages = [];
|
||||
foreach(getLanguages() as $language) {
|
||||
$language = (object) $language;
|
||||
$this->template->form->languages[$language->code] = (object) [];
|
||||
|
||||
$this->template->form->languages[$language->code]->name = $gen ? "" : ($cat->getName($language->code, true) ?? "");
|
||||
$this->template->form->languages[$language->code]->description = $gen ? "" : ($cat->getDescription($language->code, true) ?? "");
|
||||
}
|
||||
|
||||
$this->template->form->languages["master"] = (object) [
|
||||
"name" => $gen ? "Unknown Name" : $cat->getName(),
|
||||
"description" => $gen ? "" : $cat->getDescription(),
|
||||
];
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] !== "POST")
|
||||
return;
|
||||
|
||||
if($gen) {
|
||||
$cat->setAutoQuery(NULL);
|
||||
$cat->save();
|
||||
}
|
||||
|
||||
$cat->setName("_", $this->postParam("name_master"));
|
||||
$cat->setDescription("_", $this->postParam("description_master"));
|
||||
foreach(getLanguages() as $language) {
|
||||
$code = $language["code"];
|
||||
if(!empty($this->postParam("name_$code") ?? NULL))
|
||||
$cat->setName($code, $this->postParam("name_$code"));
|
||||
|
||||
if(!empty($this->postParam("description_$code") ?? NULL))
|
||||
$cat->setDescription($code, $this->postParam("description_$code"));
|
||||
}
|
||||
|
||||
$this->redirect("/admin/gifts/" . $cat->getSlug() . "." . $cat->getId() . ".meta", static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
|
||||
function renderGifts(string $catSlug, int $catId): void
|
||||
{
|
||||
$this->warnIfNoCommerce();
|
||||
|
||||
$cat = $this->gifts->getCat($catId);
|
||||
if(!$cat)
|
||||
$this->notFound();
|
||||
else if($cat->getSlug() !== $catSlug)
|
||||
$this->redirect("/admin/gifts/" . $cat->getSlug() . "." . $catId . "/", static::REDIRECT_TEMPORARY);
|
||||
|
||||
$this->template->cat = $cat;
|
||||
$this->template->gifts = iterator_to_array($cat->getGifts((int) ($this->queryParam("p") ?? 1), NULL, $this->template->count));
|
||||
}
|
||||
|
||||
function renderGift(int $id): void
|
||||
{
|
||||
$this->warnIfNoCommerce();
|
||||
|
||||
$gift = $this->gifts->get($id);
|
||||
$act = $this->queryParam("act") ?? "edit";
|
||||
switch($act) {
|
||||
case "delete":
|
||||
$this->assertNoCSRF();
|
||||
if(!$gift)
|
||||
$this->notFound();
|
||||
|
||||
$gift->delete();
|
||||
$this->flashFail("succ", "Gift moved successfully", "This gift will now be in <b>Recycle Bin</b>.");
|
||||
break;
|
||||
case "copy":
|
||||
case "move":
|
||||
$this->assertNoCSRF();
|
||||
if(!$gift)
|
||||
$this->notFound();
|
||||
|
||||
$catFrom = $this->gifts->getCat((int) ($this->queryParam("from") ?? 0));
|
||||
$catTo = $this->gifts->getCat((int) ($this->queryParam("to") ?? 0));
|
||||
if(!$catFrom || !$catTo || !$catFrom->hasGift($gift))
|
||||
$this->badRequest();
|
||||
|
||||
if($act === "move")
|
||||
$catFrom->removeGift($gift);
|
||||
|
||||
$catTo->addGift($gift);
|
||||
|
||||
$name = $catTo->getName();
|
||||
$this->flash("succ", "Gift moved successfully", "This gift will now be in <b>$name</b>.");
|
||||
$this->redirect("/admin/gifts/" . $catTo->getSlug() . "." . $catTo->getId() . "/", static::REDIRECT_TEMPORARY);
|
||||
break;
|
||||
default:
|
||||
case "edit":
|
||||
$gen = false;
|
||||
if(!$gift) {
|
||||
$gen = true;
|
||||
$gift = new Gift;
|
||||
}
|
||||
|
||||
$this->template->form = (object) [];
|
||||
$this->template->form->id = $id;
|
||||
$this->template->form->name = $gen ? "New Gift (1)" : $gift->getName();
|
||||
$this->template->form->price = $gen ? 0 : $gift->getPrice();
|
||||
$this->template->form->usages = $gen ? 0 : $gift->getUsages();
|
||||
$this->template->form->limit = $gen ? -1 : ($gift->getLimit() === INF ? -1 : $gift->getLimit());
|
||||
$this->template->form->pic = $gen ? NULL : $gift->getImage(Gift::IMAGE_URL);
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] !== "POST")
|
||||
return;
|
||||
|
||||
$limit = $this->postParam("limit") ?? $this->template->form->limit;
|
||||
$limit = $limit == "-1" ? INF : (float) $limit;
|
||||
$gift->setLimit($limit, is_null($this->postParam("reset_limit")) ? Gift::PERIOD_SET_IF_NONE : Gift::PERIOD_SET);
|
||||
|
||||
$gift->setName($this->postParam("name"));
|
||||
$gift->setPrice((int) $this->postParam("price"));
|
||||
$gift->setUsages((int) $this->postParam("usages"));
|
||||
if(isset($_FILES["pic"]) && $_FILES["pic"]["error"] === UPLOAD_ERR_OK) {
|
||||
if(!$gift->setImage($_FILES["pic"]["tmp_name"]))
|
||||
$this->flashFail("err", "Не удалось сохранить подарок", "Изображение подарка кривое.");
|
||||
} else if($gen) {
|
||||
# If there's no gift pic but it's newly created
|
||||
$this->flashFail("err", "Не удалось сохранить подарок", "Пожалуйста, загрузите изображение подарка.");
|
||||
}
|
||||
|
||||
$gift->save();
|
||||
|
||||
if($gen && !is_null($cat = $this->postParam("_cat"))) {
|
||||
$cat = $this->gifts->getCat((int) $cat);
|
||||
if(!is_null($cat))
|
||||
$cat->addGift($gift);
|
||||
}
|
||||
|
||||
$this->redirect("/admin/gifts/id" . $gift->getId(), static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
}
|
||||
|
||||
function renderFiles(): void
|
||||
{
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use Chandler\Session\Session;
|
|||
use Chandler\Security\User as ChandlerUser;
|
||||
use Chandler\Security\Authenticator;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use lfkeitel\phptotp\{Base32, Totp};
|
||||
|
||||
final class AuthPresenter extends OpenVKPresenter
|
||||
{
|
||||
|
@ -89,6 +90,9 @@ final class AuthPresenter extends OpenVKPresenter
|
|||
if(!$this->emailValid($this->postParam("email")))
|
||||
$this->flashFail("err", "Неверный email адрес", "Email, который вы ввели, не является корректным.");
|
||||
|
||||
if (strtotime($this->postParam("birthday")) > time())
|
||||
$this->flashFail("err", "Неверная дата рождения", "Дату рождения, которую вы ввели, не является корректным.");
|
||||
|
||||
$chUser = ChandlerUser::create($this->postParam("email"), $this->postParam("password"));
|
||||
if(!$chUser)
|
||||
$this->flashFail("err", "Не удалось зарегистрироваться", "Пользователь с таким email уже существует.");
|
||||
|
@ -101,6 +105,7 @@ final class AuthPresenter extends OpenVKPresenter
|
|||
$user->setEmail($this->postParam("email"));
|
||||
$user->setSince(date("Y-m-d H:i:s"));
|
||||
$user->setRegistering_Ip(CONNECTING_IP);
|
||||
$user->setBirthday(strtotime($this->postParam("birthday")));
|
||||
$user->save();
|
||||
|
||||
if(!is_null($referer)) {
|
||||
|
@ -128,9 +133,27 @@ final class AuthPresenter extends OpenVKPresenter
|
|||
if(!$user)
|
||||
$this->flashFail("err", "Не удалось войти", "Неверное имя пользователя или пароль. <a href='/restore.pl'>Забыли пароль?</a>");
|
||||
|
||||
if(!$this->authenticator->login($user->id, $this->postParam("password")))
|
||||
if(!$this->authenticator->verifyCredentials($user->id, $this->postParam("password")))
|
||||
$this->flashFail("err", "Не удалось войти", "Неверное имя пользователя или пароль. <a href='/restore.pl'>Забыли пароль?</a>");
|
||||
|
||||
$secret = $user->related("profiles.user")->fetch()["2fa_secret"];
|
||||
$code = $this->postParam("code");
|
||||
if(!is_null($secret)) {
|
||||
$this->template->_template = "Auth/LoginSecondFactor.xml";
|
||||
$this->template->login = $this->postParam("login");
|
||||
$this->template->password = $this->postParam("password");
|
||||
|
||||
if(is_null($code))
|
||||
return;
|
||||
|
||||
$ovkUser = new User($user->related("profiles.user")->fetch());
|
||||
if(!($code === (new Totp)->GenerateToken(Base32::decode($secret)) || $ovkUser->use2faBackupCode((int) $code))) {
|
||||
$this->flash("err", "Не удалось войти", tr("incorrect_2fa_code"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->authenticator->authenticate($user->id);
|
||||
$this->redirect($redirUrl ?? "/id" . $user->related("profiles.user")->fetch()->id, static::REDIRECT_TEMPORARY);
|
||||
exit;
|
||||
}
|
||||
|
@ -159,6 +182,7 @@ final class AuthPresenter extends OpenVKPresenter
|
|||
function renderLogout(): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
$this->assertNoCSRF();
|
||||
$this->authenticator->logout();
|
||||
Session::i()->set("_su", NULL);
|
||||
|
||||
|
@ -172,8 +196,20 @@ final class AuthPresenter extends OpenVKPresenter
|
|||
$this->flash("err", "Ошибка манипулирования токеном", "Токен недействителен или истёк");
|
||||
$this->redirect("/");
|
||||
}
|
||||
|
||||
$this->template->is2faEnabled = $request->getUser()->is2faEnabled();
|
||||
|
||||
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
if($request->getUser()->is2faEnabled()) {
|
||||
$user = $request->getUser();
|
||||
$code = $this->postParam("code");
|
||||
$secret = $user->get2faSecret();
|
||||
if(!($code === (new Totp)->GenerateToken(Base32::decode($secret)) || $user->use2faBackupCode((int) $code))) {
|
||||
$this->flash("err", tr("error"), tr("incorrect_2fa_code"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$user = $request->getUser()->getChandlerUser();
|
||||
$this->db->table("ChandlerTokens")->where("user", $user->getId())->delete(); #Logout from everywhere
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Entities\{Comment, User};
|
||||
use openvk\Web\Models\Entities\{Comment, Photo, Video, User};
|
||||
use openvk\Web\Models\Entities\Notifications\CommentNotification;
|
||||
use openvk\Web\Models\Repositories\Comments;
|
||||
|
||||
|
@ -38,6 +38,41 @@ final class CommentPresenter extends OpenVKPresenter
|
|||
$entity = $repo->get($eId);
|
||||
if(!$entity) $this->notFound();
|
||||
|
||||
$flags = 0;
|
||||
if($this->postParam("as_group") === "on")
|
||||
$flags |= 0b10000000;
|
||||
|
||||
$photo = NULL;
|
||||
if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
|
||||
try {
|
||||
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"]);
|
||||
} catch(ISE $ex) {
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Файл изображения повреждён, слишком велик или одна сторона изображения в разы больше другой.");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO move to trait
|
||||
try {
|
||||
$photo = NULL;
|
||||
$video = NULL;
|
||||
if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
|
||||
$album = NULL;
|
||||
if($wall > 0 && $wall === $this->user->id)
|
||||
$album = (new Albums)->getUserWallAlbum($wallOwner);
|
||||
|
||||
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album);
|
||||
}
|
||||
|
||||
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) {
|
||||
$video = Video::fastMake($this->user->id, $this->postParam("text"), $_FILES["_vid_attachment"]);
|
||||
}
|
||||
} catch(ISE $ex) {
|
||||
$this->flashFail("err", "Не удалось опубликовать комментарий", "Файл медиаконтента повреждён или слишком велик.");
|
||||
}
|
||||
|
||||
if(empty($this->postParam("text")) && !$photo && !$video)
|
||||
$this->flashFail("err", "Не удалось опубликовать комментарий", "Комментарий пустой или слишком большой.");
|
||||
|
||||
try {
|
||||
$comment = new Comment;
|
||||
$comment->setOwner($this->user->id);
|
||||
|
@ -45,11 +80,18 @@ final class CommentPresenter extends OpenVKPresenter
|
|||
$comment->setTarget($entity->getId());
|
||||
$comment->setContent($this->postParam("text"));
|
||||
$comment->setCreated(time());
|
||||
$comment->setFlags($flags);
|
||||
$comment->save();
|
||||
} catch(\LogicException $ex) {
|
||||
$this->flashFail("err", "Не удалось опубликовать комментарий", "Нельзя опубликовать пустой комментарий.");
|
||||
} catch (\LengthException $ex) {
|
||||
$this->flashFail("err", "Не удалось опубликовать комментарий", "Комментарий слишком большой.");
|
||||
}
|
||||
|
||||
if(!is_null($photo))
|
||||
$comment->attach($photo);
|
||||
|
||||
if(!is_null($video))
|
||||
$comment->attach($video);
|
||||
|
||||
if($entity->getOwner()->getId() !== $this->user->identity->getId())
|
||||
if(($owner = $entity->getOwner()) instanceof User)
|
||||
(new CommentNotification($owner, $comment, $entity, $this->user->identity))->emit();
|
||||
|
|
140
Web/Presenters/GiftsPresenter.php
Normal file
140
Web/Presenters/GiftsPresenter.php
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Repositories\{Gifts, Users};
|
||||
use openvk\Web\Models\Entities\Notifications\GiftNotification;
|
||||
|
||||
final class GiftsPresenter extends OpenVKPresenter
|
||||
{
|
||||
private $gifts;
|
||||
private $users;
|
||||
|
||||
function __construct(Gifts $gifts, Users $users)
|
||||
{
|
||||
$this->gifts = $gifts;
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
function renderUserGifts(int $user): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$user = $this->users->get($user);
|
||||
if(!$user)
|
||||
$this->notFound();
|
||||
|
||||
$this->template->user = $user;
|
||||
$this->template->page = $page = (int) ($this->queryParam("p") ?? 1);
|
||||
$this->template->count = $user->getGiftCount();
|
||||
$this->template->iterator = $user->getGifts($page);
|
||||
$this->template->hideInfo = $this->user->id !== $user->getId();
|
||||
}
|
||||
|
||||
function renderGiftMenu(): void
|
||||
{
|
||||
$user = $this->users->get((int) ($this->queryParam("user") ?? 0));
|
||||
if(!$user)
|
||||
$this->notFound();
|
||||
|
||||
$this->template->page = $page = (int) ($this->queryParam("p") ?? 1);
|
||||
$cats = $this->gifts->getCategories($page, NULL, $this->template->count);
|
||||
|
||||
$this->template->user = $user;
|
||||
$this->template->iterator = $cats;
|
||||
$this->template->_template = "Gifts/Menu.xml";
|
||||
}
|
||||
|
||||
function renderGiftList(): void
|
||||
{
|
||||
$user = $this->users->get((int) ($this->queryParam("user") ?? 0));
|
||||
$cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0));
|
||||
if(!$user || !$cat)
|
||||
$this->flashFail("err", "Не удалось подарить", "Пользователь или набор не существуют.");
|
||||
|
||||
$this->template->page = $page = (int) ($this->queryParam("p") ?? 1);
|
||||
$gifts = $cat->getGifts($page, null, $this->template->count);
|
||||
|
||||
$this->template->user = $user;
|
||||
$this->template->cat = $cat;
|
||||
$this->template->gifts = iterator_to_array($gifts);
|
||||
$this->template->_template = "Gifts/Pick.xml";
|
||||
}
|
||||
|
||||
function renderConfirmGift(): void
|
||||
{
|
||||
$user = $this->users->get((int) ($this->queryParam("user") ?? 0));
|
||||
$gift = $this->gifts->get((int) ($this->queryParam("elid") ?? 0));
|
||||
$cat = $this->gifts->getCat((int) ($this->queryParam("pack") ?? 0));
|
||||
if(!$user || !$cat || !$gift || !$cat->hasGift($gift))
|
||||
$this->flashFail("err", "Не удалось подарить", "Не удалось подтвердить права на подарок.");
|
||||
|
||||
if(!$gift->canUse($this->user->identity))
|
||||
$this->flashFail("err", "Не удалось подарить", "У вас больше не осталось таких подарков.");
|
||||
|
||||
$coinsLeft = $this->user->identity->getCoins() - $gift->getPrice();
|
||||
if($coinsLeft < 0)
|
||||
$this->flashFail("err", "Не удалось подарить", "Ору нищ не пук.");
|
||||
|
||||
$this->template->_template = "Gifts/Confirm.xml";
|
||||
if($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||
$this->template->user = $user;
|
||||
$this->template->cat = $cat;
|
||||
$this->template->gift = $gift;
|
||||
return;
|
||||
}
|
||||
|
||||
$comment = empty($c = $this->postParam("comment")) ? NULL : $c;
|
||||
$notification = new GiftNotification($user, $this->user->identity, $gift, $comment);
|
||||
$notification->emit();
|
||||
$this->user->identity->setCoins($coinsLeft);
|
||||
$this->user->identity->save();
|
||||
$user->gift($this->user->identity, $gift, $comment, !is_null($this->postParam("anonymous")));
|
||||
$gift->used();
|
||||
|
||||
$this->flash("succ", "Подарок отправлен", "Вы отправили подарок <b>" . $user->getFirstName() . "</b> за " . $gift->getPrice() . " голосов.");
|
||||
$this->redirect($user->getURL(), static::REDIRECT_TEMPORARY);
|
||||
}
|
||||
|
||||
function renderStub(): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$act = $this->queryParam("act");
|
||||
switch($act) {
|
||||
case "pick":
|
||||
$this->renderGiftMenu();
|
||||
break;
|
||||
|
||||
case "menu":
|
||||
$this->renderGiftList();
|
||||
break;
|
||||
|
||||
case "confirm":
|
||||
$this->renderConfirmGift();
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->notFound();
|
||||
}
|
||||
}
|
||||
|
||||
function renderGiftImage(int $id, int $timestamp): void
|
||||
{
|
||||
$gift = $this->gifts->get($id);
|
||||
if(!$gift)
|
||||
$this->notFound();
|
||||
|
||||
$image = $gift->getImage();
|
||||
header("Cache-Control: no-transform, immutable");
|
||||
header("Content-Length: " . strlen($image));
|
||||
header("Content-Type: image/png");
|
||||
exit($image);
|
||||
}
|
||||
|
||||
function onStartup(): void
|
||||
{
|
||||
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
|
||||
$this->flashFail("err", tr("error"), tr("feature_disabled"));
|
||||
|
||||
parent::onStartup();
|
||||
}
|
||||
}
|
|
@ -82,10 +82,24 @@ final class GroupPresenter extends OpenVKPresenter
|
|||
function renderFollowers(int $id): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$this->template->club = $this->clubs->get($id);
|
||||
$this->template->followers = $this->template->club->getFollowers((int) ($this->queryParam("p") ?? 1));
|
||||
$this->template->count = $this->template->club->getFollowersCount();
|
||||
|
||||
$this->template->club = $this->clubs->get($id);
|
||||
$this->template->onlyShowManagers = $this->queryParam("onlyAdmins") == "1";
|
||||
if($this->template->onlyShowManagers) {
|
||||
$this->template->followers = null;
|
||||
|
||||
$this->template->managers = $this->template->club->getManagers((int) ($this->queryParam("p") ?? 1), !$this->template->club->canBeModifiedBy($this->user->identity));
|
||||
if($this->template->club->canBeModifiedBy($this->user->identity) || !$this->template->club->isOwnerHidden()) {
|
||||
$this->template->managers = array_merge([$this->template->club->getOwner()], iterator_to_array($this->template->managers));
|
||||
}
|
||||
|
||||
$this->template->count = $this->template->club->getManagersCount();
|
||||
} else {
|
||||
$this->template->followers = $this->template->club->getFollowers((int) ($this->queryParam("p") ?? 1));
|
||||
$this->template->managers = null;
|
||||
$this->template->count = $this->template->club->getFollowersCount();
|
||||
}
|
||||
|
||||
$this->template->paginatorConf = (object) [
|
||||
"count" => $this->template->count,
|
||||
"page" => $this->queryParam("p") ?? 1,
|
||||
|
@ -98,6 +112,8 @@ final class GroupPresenter extends OpenVKPresenter
|
|||
{
|
||||
$user = is_null($this->queryParam("user")) ? $this->postParam("user") : $this->queryParam("user");
|
||||
$comment = $this->postParam("comment");
|
||||
$removeComment = $this->postParam("removeComment") === "1";
|
||||
$hidden = ["0" => false, "1" => true][$this->queryParam("hidden")] ?? null;
|
||||
//$index = $this->queryParam("index");
|
||||
if(!$user)
|
||||
$this->badRequest();
|
||||
|
@ -107,19 +123,56 @@ final class GroupPresenter extends OpenVKPresenter
|
|||
if(!$user || !$club)
|
||||
$this->notFound();
|
||||
|
||||
if(!$club->canBeModifiedBy($this->user->identity ?? NULL) && $club->getOwner()->getId() !== $user->getId())
|
||||
if(!$club->canBeModifiedBy($this->user->identity ?? NULL))
|
||||
$this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс.");
|
||||
|
||||
/* if(!empty($index)){
|
||||
$manager = (new Managers)->get($index);
|
||||
$manager->setComment($comment);
|
||||
if(!is_null($hidden)) {
|
||||
if($club->getOwner()->getId() == $user->getId()) {
|
||||
$club->setOwner_Hidden($hidden);
|
||||
$club->save();
|
||||
} else {
|
||||
$manager = (new Managers)->getByUserAndClub($user->getId(), $club->getId());
|
||||
$manager->setHidden($hidden);
|
||||
$manager->save();
|
||||
}
|
||||
|
||||
if($club->getManagersCount(true) == 0) {
|
||||
$club->setAdministrators_List_Display(2);
|
||||
$club->save();
|
||||
}
|
||||
|
||||
if($hidden) {
|
||||
$this->flashFail("succ", "Операция успешна", "Теперь " . $user->getCanonicalName() . " будет показываться как обычный подписчик всем кроме других администраторов");
|
||||
} else {
|
||||
$this->flashFail("succ", "Операция успешна", "Теперь все будут знать про то что " . $user->getCanonicalName() . " - администратор");
|
||||
}
|
||||
} elseif($removeComment) {
|
||||
if($club->getOwner()->getId() == $user->getId()) {
|
||||
$club->setOwner_Comment(null);
|
||||
$club->save();
|
||||
} else {
|
||||
$manager = (new Managers)->getByUserAndClub($user->getId(), $club->getId());
|
||||
$manager->setComment(null);
|
||||
$manager->save();
|
||||
}
|
||||
|
||||
$this->flashFail("succ", "Операция успешна", "Комментарий к администратору удален");
|
||||
} elseif($comment) {
|
||||
if(mb_strlen($comment) > 36) {
|
||||
$commentLength = (string) mb_strlen($comment);
|
||||
$this->flashFail("err", "Ошибка", "Комментарий слишком длинный ($commentLength символов вместо 36 символов)");
|
||||
}
|
||||
|
||||
if($club->getOwner()->getId() == $user->getId()) {
|
||||
$club->setOwner_Comment($comment);
|
||||
$club->save();
|
||||
} else {
|
||||
$manager = (new Managers)->getByUserAndClub($user->getId(), $club->getId());
|
||||
$manager->setComment($comment);
|
||||
$manager->save();
|
||||
}
|
||||
|
||||
$this->flashFail("succ", "Операция успешна", "Комментарий к администратору изменён");
|
||||
}else{ */
|
||||
if($comment) {
|
||||
$manager = (new Managers)->getByUserAndClub($user->getId(), $club->getId());
|
||||
$manager->setComment($comment);
|
||||
$manager->save();
|
||||
$this->flashFail("succ", "Операция успешна", ".");
|
||||
}else{
|
||||
if($club->canBeModifiedBy($user)) {
|
||||
$club->removeManager($user);
|
||||
|
@ -150,6 +203,13 @@ final class GroupPresenter extends OpenVKPresenter
|
|||
$club->setAbout(empty($this->postParam("about")) ? NULL : $this->postParam("about"));
|
||||
$club->setShortcode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode"));
|
||||
$club->setWall(empty($this->postParam("wall")) ? 0 : 1);
|
||||
$club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display"));
|
||||
|
||||
$website = $this->postParam("website") ?? "";
|
||||
if(empty($website))
|
||||
$club->setWebsite(NULL);
|
||||
else
|
||||
$club->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website);
|
||||
|
||||
if($_FILES["ava"]["error"] === UPLOAD_ERR_OK) {
|
||||
$photo = new Photo;
|
||||
|
|
|
@ -58,8 +58,8 @@ final class InternalAPIPresenter extends OpenVKPresenter
|
|||
try {
|
||||
$params = array_merge($input->params ?? [], [function($data) {
|
||||
$this->succ($data);
|
||||
}, function($data) {
|
||||
$this->fail($data);
|
||||
}, function(int $errno, string $errstr) {
|
||||
$this->fail($errno, $errstr);
|
||||
}]);
|
||||
$handler->{$method}(...$params);
|
||||
} catch(\TypeError $te) {
|
||||
|
@ -68,4 +68,4 @@ final class InternalAPIPresenter extends OpenVKPresenter
|
|||
$this->fail(-32603, "Uncaught " . get_class($ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,10 +31,10 @@ final class NotesPresenter extends OpenVKPresenter
|
|||
];
|
||||
}
|
||||
|
||||
function renderView(int $owner, int $id): void
|
||||
function renderView(int $owner, int $note_id): void
|
||||
{
|
||||
$note = $this->notes->get($id);
|
||||
if(!$note || $note->getOwner()->getId() !== $owner)
|
||||
$note = $this->notes->getNoteById($owner, $note_id);
|
||||
if(!$note || $note->getOwner()->getId() !== $owner || $note->isDeleted())
|
||||
$this->notFound();
|
||||
|
||||
$this->template->cCount = $note->getCommentsCount();
|
||||
|
@ -65,7 +65,7 @@ final class NotesPresenter extends OpenVKPresenter
|
|||
$note->setSource($this->postParam("html"));
|
||||
$note->save();
|
||||
|
||||
$this->redirect("/note" . $this->user->id . "_" . $note->getId());
|
||||
$this->redirect("/note" . $this->user->id . "_" . $note->getVirtualId());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
24
Web/Presenters/OpenVKPresenter.php
Normal file → Executable file
24
Web/Presenters/OpenVKPresenter.php
Normal file → Executable file
|
@ -6,7 +6,8 @@ use Chandler\Session\Session;
|
|||
use Chandler\Security\Authenticator;
|
||||
use Latte\Engine as TemplatingEngine;
|
||||
use openvk\Web\Models\Entities\IP;
|
||||
use openvk\Web\Models\Repositories\{IPs, Users, APITokens};
|
||||
use openvk\Web\Themes\Themepacks;
|
||||
use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets};
|
||||
|
||||
abstract class OpenVKPresenter extends SimplePresenter
|
||||
{
|
||||
|
@ -32,6 +33,11 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
"code" => $code,
|
||||
]));
|
||||
}
|
||||
|
||||
protected function setTempTheme(string $theme): void
|
||||
{
|
||||
Session::i()->set("_tempTheme", $theme);
|
||||
}
|
||||
|
||||
protected function flashFail(string $type, string $title, ?string $message = NULL, ?int $code = NULL): void
|
||||
{
|
||||
|
@ -178,7 +184,7 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
{
|
||||
$user = Authenticator::i()->getUser();
|
||||
|
||||
$this->template->isXmas = intval(date('d')) >= 15 && date('m') == 12 || intval(date('d')) <= 15 && date('m') == 1 ? true : false;
|
||||
$this->template->isXmas = intval(date('d')) >= 1 && date('m') == 12 || intval(date('d')) <= 15 && date('m') == 1 ? true : false;
|
||||
|
||||
if(!is_null($user)) {
|
||||
$this->user = (object) [];
|
||||
|
@ -201,6 +207,9 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
$this->user->identity->save();
|
||||
}
|
||||
|
||||
$this->template->ticketAnsweredCount = (new Tickets)->getTicketsCountByuId($this->user->id, 1);
|
||||
if($user->can("write")->model("openvk\Web\Models\Entities\TicketReply")->whichBelongsTo(0))
|
||||
$this->template->helpdeskTicketNotAnsweredCount = (new Tickets)->getTicketCount(0);
|
||||
}
|
||||
|
||||
setlocale(LC_TIME, ...(explode(";", tr("__locale"))));
|
||||
|
@ -216,12 +225,21 @@ abstract class OpenVKPresenter extends SimplePresenter
|
|||
$theme = $this->user->identity->getTheme();
|
||||
if(!is_null($theme) && $theme->overridesTemplates()) {
|
||||
$this->template->_templatePath = $theme->getBaseDir() . "/tpl";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!is_null(Session::i()->get("_error"))) {
|
||||
$this->template->flashMessage = json_decode(Session::i()->get("_error"));
|
||||
Session::i()->set("_error", NULL);
|
||||
}
|
||||
|
||||
if(Session::i()->get("_tempTheme"))
|
||||
$this->template->theme = Themepacks::i()[Session::i()->get("_tempTheme", "ovk")];
|
||||
else if($this->requestParam("themePreview"))
|
||||
$this->template->theme = Themepacks::i()[$this->requestParam("themePreview")];
|
||||
else if($this->user->identity !== null && $this->user->identity->getTheme())
|
||||
$this->template->theme = $this->user->identity->getTheme();
|
||||
|
||||
// Знаю, каша ебаная, целестора рефактор всё равно сделает :)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,6 +159,17 @@ final class PhotosPresenter extends OpenVKPresenter
|
|||
$this->template->comments = iterator_to_array($photo->getComments($this->template->cPage));
|
||||
}
|
||||
|
||||
function renderAbsolutePhoto($id): void
|
||||
{
|
||||
$id = (int) base_convert((string) $id, 32, 10);
|
||||
$photo = $this->photos->get($id);
|
||||
if(!$photo || $photo->isDeleted())
|
||||
$this->notFound();
|
||||
|
||||
$this->template->_template = "Photos/Photo.xml";
|
||||
$this->renderPhoto($photo->getOwner(true)->getId(), $photo->getVirtualId());
|
||||
}
|
||||
|
||||
function renderEditPhoto(int $ownerId, int $photoId): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
|
|
@ -181,7 +181,7 @@ final class SupportPresenter extends OpenVKPresenter
|
|||
$comment = new TicketComment;
|
||||
$comment->setUser_id($this->user->id);
|
||||
$comment->setUser_type(1);
|
||||
$comment->setText('Здравствуйте, '.$ticket->getUser()->getFirstName().'!<br></br>'.$this->postParam("text").'<br></br>С уважением,<br/> Команда поддержки OpenVK.');
|
||||
$comment->setText($this->postParam("text"));
|
||||
$comment->setTicket_id($id);
|
||||
$comment->setCreated(time());
|
||||
$comment->save();
|
||||
|
|
|
@ -4,9 +4,14 @@ use openvk\Web\Util\Sms;
|
|||
use openvk\Web\Themes\Themepacks;
|
||||
use openvk\Web\Models\Entities\Photo;
|
||||
use openvk\Web\Models\Repositories\Users;
|
||||
use openvk\Web\Models\Repositories\Clubs;
|
||||
use openvk\Web\Models\Repositories\Albums;
|
||||
use openvk\Web\Models\Repositories\Videos;
|
||||
use openvk\Web\Models\Repositories\Notes;
|
||||
use openvk\Web\Models\Repositories\Vouchers;
|
||||
use Chandler\Security\Authenticator;
|
||||
use lfkeitel\phptotp\{Base32, Totp};
|
||||
use chillerlan\QRCode\{QRCode, QROptions};
|
||||
|
||||
final class UserPresenter extends OpenVKPresenter
|
||||
{
|
||||
|
@ -29,18 +34,14 @@ final class UserPresenter extends OpenVKPresenter
|
|||
if(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH) !== "/" . $user->getShortCode())
|
||||
$this->redirect("/" . $user->getShortCode(), static::REDIRECT_TEMPORARY_PRESISTENT);
|
||||
|
||||
$then = date_create("@" . $user->getOnline()->timestamp());
|
||||
$now = date_create();
|
||||
$diff = date_diff($now, $then);
|
||||
|
||||
$this->template->albums = (new Albums)->getUserAlbums($user);
|
||||
$this->template->albumsCount = (new Albums)->getUserAlbumsCount($user);
|
||||
$this->template->videos = (new Videos)->getByUser($user, 1, 2);
|
||||
$this->template->videosCount = (new Videos)->getUserVideosCount($user);
|
||||
$this->template->notes = (new Notes)->getUserNotes($user, 1, 4);
|
||||
$this->template->notesCount = (new Notes)->getUserNotesCount($user);
|
||||
|
||||
$this->template->user = $user;
|
||||
$this->template->diff = $diff;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,8 +82,41 @@ final class UserPresenter extends OpenVKPresenter
|
|||
} else {
|
||||
$this->template->user = $user;
|
||||
$this->template->page = $this->queryParam("p") ?? 1;
|
||||
$this->template->admin = $this->queryParam("act") == "managed";
|
||||
}
|
||||
}
|
||||
|
||||
function renderPinClub(): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$club = (new Clubs)->get((int) $this->queryParam("club"));
|
||||
if(!$club)
|
||||
$this->notFound();
|
||||
|
||||
if(!$club->canBeModifiedBy($this->user->identity ?? NULL))
|
||||
$this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс.");
|
||||
|
||||
$isClubPinned = $this->user->identity->isClubPinned($club);
|
||||
if(!$isClubPinned && $this->user->identity->getPinnedClubCount() > 10)
|
||||
$this->flashFail("err", "Ошибка", "Находится в левом меню могут максимум 10 групп");
|
||||
|
||||
if($club->getOwner()->getId() === $this->user->identity->getId()) {
|
||||
$club->setOwner_Club_Pinned(!$isClubPinned);
|
||||
$club->save();
|
||||
} else {
|
||||
$manager = $club->getManager($this->user->identity);
|
||||
if(!is_null($manager)) {
|
||||
$manager->setClub_Pinned(!$isClubPinned);
|
||||
$manager->save();
|
||||
}
|
||||
}
|
||||
|
||||
if($isClubPinned)
|
||||
$this->flashFail("succ", "Операция успешна", "Группа " . $club->getName() . " была успешно удалена из левого меню");
|
||||
else
|
||||
$this->flashFail("succ", "Операция успешна", "Группа " . $club->getName() . " была успешно добавлена в левое меню");
|
||||
}
|
||||
|
||||
function renderEdit(): void
|
||||
{
|
||||
|
@ -108,7 +142,7 @@ final class UserPresenter extends OpenVKPresenter
|
|||
if ($this->postParam("marialstatus") <= 8 && $this->postParam("marialstatus") >= 0)
|
||||
$user->setMarital_Status($this->postParam("marialstatus"));
|
||||
|
||||
if ($this->postParam("politViews") <= 8 && $this->postParam("politViews") >= 0)
|
||||
if ($this->postParam("politViews") <= 9 && $this->postParam("politViews") >= 0)
|
||||
$user->setPolit_Views($this->postParam("politViews"));
|
||||
|
||||
if ($this->postParam("gender") <= 1 && $this->postParam("gender") >= 0)
|
||||
|
@ -125,9 +159,15 @@ final class UserPresenter extends OpenVKPresenter
|
|||
}
|
||||
} elseif($_GET['act'] === "contacts") {
|
||||
$user->setEmail_Contact(empty($this->postParam("email_contact")) ? NULL : $this->postParam("email_contact"));
|
||||
$user->setTelegram(empty($this->postParam("telegram")) ? NULL : $this->postParam("telegram"));
|
||||
$user->setTelegram(empty($this->postParam("telegram")) ? NULL : ltrim($this->postParam("telegram"), "@"));
|
||||
$user->setCity(empty($this->postParam("city")) ? NULL : $this->postParam("city"));
|
||||
$user->setAddress(empty($this->postParam("address")) ? NULL : $this->postParam("address"));
|
||||
|
||||
$website = $this->postParam("website") ?? "";
|
||||
if(empty($website))
|
||||
$user->setWebsite(NULL);
|
||||
else
|
||||
$user->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website);
|
||||
} elseif($_GET['act'] === "interests") {
|
||||
$user->setInterests(empty($this->postParam("interests")) ? NULL : ovk_proc_strtr($this->postParam("interests"), 300));
|
||||
$user->setFav_Music(empty($this->postParam("fav_music")) ? NULL : ovk_proc_strtr($this->postParam("fav_music"), 300));
|
||||
|
@ -136,6 +176,18 @@ final class UserPresenter extends OpenVKPresenter
|
|||
$user->setFav_Books(empty($this->postParam("fav_books")) ? NULL : ovk_proc_strtr($this->postParam("fav_books"), 300));
|
||||
$user->setFav_Quote(empty($this->postParam("fav_quote")) ? NULL : ovk_proc_strtr($this->postParam("fav_quote"), 300));
|
||||
$user->setAbout(empty($this->postParam("about")) ? NULL : ovk_proc_strtr($this->postParam("about"), 300));
|
||||
} elseif($_GET['act'] === "status") {
|
||||
if(mb_strlen($this->postParam("status")) > 255) {
|
||||
$statusLength = (string) mb_strlen($this->postParam("status"));
|
||||
$this->flashFail("err", "Ошибка", "Статус слишком длинный ($statusLength символов вместо 255 символов)");
|
||||
}
|
||||
|
||||
$user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status"));
|
||||
$user->save();
|
||||
|
||||
header("HTTP/1.1 302 Found");
|
||||
header("Location: /id" . $user->getId());
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -224,6 +276,9 @@ final class UserPresenter extends OpenVKPresenter
|
|||
if(!$id)
|
||||
$this->notFound();
|
||||
|
||||
if(in_array($this->queryParam("act"), ["finance", "finance.top-up"]) && !OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
|
||||
$this->flashFail("err", tr("error"), tr("feature_disabled"));
|
||||
|
||||
$user = $this->users->get($id);
|
||||
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$this->willExecuteWriteAction();
|
||||
|
@ -231,6 +286,12 @@ final class UserPresenter extends OpenVKPresenter
|
|||
if($_GET['act'] === "main" || $_GET['act'] == NULL) {
|
||||
if($this->postParam("old_pass") && $this->postParam("new_pass") && $this->postParam("repeat_pass")) {
|
||||
if($this->postParam("new_pass") === $this->postParam("repeat_pass")) {
|
||||
if($this->user->identity->is2faEnabled()) {
|
||||
$code = $this->postParam("code");
|
||||
if(!($code === (new Totp)->GenerateToken(Base32::decode($this->user->identity->get2faSecret())) || $this->user->identity->use2faBackupCode((int) $code)))
|
||||
$this->flashFail("err", tr("error"), tr("incorrect_2fa_code"));
|
||||
}
|
||||
|
||||
if(!$this->user->identity->getChandlerUser()->updatePassword($this->postParam("new_pass"), $this->postParam("old_pass")))
|
||||
$this->flashFail("err", tr("error"), tr("error_old_password"));
|
||||
} else {
|
||||
|
@ -240,7 +301,7 @@ final class UserPresenter extends OpenVKPresenter
|
|||
|
||||
if(!$user->setShortCode(empty($this->postParam("sc")) ? NULL : $this->postParam("sc")))
|
||||
$this->flashFail("err", tr("error"), tr("error_shorturl_incorrect"));
|
||||
}elseif($_GET['act'] === "privacy") {
|
||||
} else if($_GET['act'] === "privacy") {
|
||||
$settings = [
|
||||
"page.read",
|
||||
"page.info.read",
|
||||
|
@ -256,9 +317,27 @@ final class UserPresenter extends OpenVKPresenter
|
|||
$input = $this->postParam(str_replace(".", "_", $setting));
|
||||
$user->setPrivacySetting($setting, min(3, abs($input ?? $user->getPrivacySetting($setting))));
|
||||
}
|
||||
}elseif($_GET['act'] === "interface") {
|
||||
} else if($_GET['act'] === "finance.top-up") {
|
||||
$token = $this->postParam("key0") . $this->postParam("key1") . $this->postParam("key2") . $this->postParam("key3");
|
||||
$voucher = (new Vouchers)->getByToken($token);
|
||||
if(!$voucher)
|
||||
$this->flashFail("err", tr("invalid_voucher"), tr("voucher_bad"));
|
||||
|
||||
$perm = $voucher->willUse($user);
|
||||
if(!$perm)
|
||||
$this->flashFail("err", tr("invalid_voucher"), tr("voucher_bad"));
|
||||
|
||||
$user->setCoins($user->getCoins() + $voucher->getCoins());
|
||||
$user->setRating($user->getRating() + $voucher->getRating());
|
||||
$user->save();
|
||||
|
||||
$this->flashFail("succ", tr("voucher_good"), tr("voucher_redeemed"));
|
||||
} else if($_GET['act'] === "interface") {
|
||||
if (isset(Themepacks::i()[$this->postParam("style")]) || $this->postParam("style") === Themepacks::DEFAULT_THEME_ID)
|
||||
$user->setStyle($this->postParam("style"));
|
||||
{
|
||||
$user->setStyle($this->postParam("style"));
|
||||
$this->setTempTheme($this->postParam("style"));
|
||||
}
|
||||
|
||||
if ($this->postParam("style_avatar") <= 2 && $this->postParam("style_avatar") >= 0)
|
||||
$user->setStyle_Avatar((int)$this->postParam("style_avatar"));
|
||||
|
@ -271,7 +350,7 @@ final class UserPresenter extends OpenVKPresenter
|
|||
|
||||
if(in_array($this->postParam("nsfw"), [0, 1, 2]))
|
||||
$user->setNsfwTolerance((int) $this->postParam("nsfw"));
|
||||
}elseif($_GET['act'] === "lMenu") {
|
||||
} else if($_GET['act'] === "lMenu") {
|
||||
$settings = [
|
||||
"menu_bildoj" => "photos",
|
||||
"menu_filmetoj" => "videos",
|
||||
|
@ -293,17 +372,79 @@ final class UserPresenter extends OpenVKPresenter
|
|||
throw $ex;
|
||||
}
|
||||
|
||||
$this->flash(
|
||||
$this->flash(
|
||||
"succ",
|
||||
"Изменения сохранены",
|
||||
"Новые данные появятся на вашей странице.<br/>Если вы изменили стиль, перезагрузите страницу."
|
||||
"Новые данные появятся на вашей странице."
|
||||
);
|
||||
}
|
||||
$this->template->mode = in_array($this->queryParam("act"), [
|
||||
"main", "privacy", "finance", "interface"
|
||||
"main", "privacy", "finance", "finance.top-up", "interface"
|
||||
]) ? $this->queryParam("act")
|
||||
: "main";
|
||||
$this->template->user = $user;
|
||||
$this->template->themes = Themepacks::i()->getThemeList();
|
||||
}
|
||||
|
||||
function renderTwoFactorAuthSettings(): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
if($this->user->identity->is2faEnabled()) {
|
||||
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
if(!Authenticator::verifyHash($this->postParam("password"), $this->user->identity->getChandlerUser()->getRaw()->passwordHash))
|
||||
$this->flashFail("err", tr("error"), tr("incorrect_password"));
|
||||
|
||||
$this->user->identity->generate2faBackupCodes();
|
||||
$this->template->_template = "User/TwoFactorAuthCodes.xml";
|
||||
$this->template->codes = $this->user->identity->get2faBackupCodes();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->redirect("/settings");
|
||||
}
|
||||
|
||||
$secret = Base32::encode(Totp::GenerateSecret(16));
|
||||
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
if(!Authenticator::verifyHash($this->postParam("password"), $this->user->identity->getChandlerUser()->getRaw()->passwordHash))
|
||||
$this->flashFail("err", tr("error"), tr("incorrect_password"));
|
||||
|
||||
$secret = $this->postParam("secret");
|
||||
$code = $this->postParam("code");
|
||||
|
||||
if($code === (new Totp)->GenerateToken(Base32::decode($secret))) {
|
||||
$this->user->identity->set2fa_secret($secret);
|
||||
$this->user->identity->save();
|
||||
|
||||
$this->flash("succ", tr("two_factor_authentication_enabled_message"), tr("two_factor_authentication_enabled_message_description"));
|
||||
$this->redirect("/settings");
|
||||
}
|
||||
|
||||
$this->template->secret = $secret;
|
||||
$this->flash("err", tr("error"), tr("incorrect_code"));
|
||||
} else {
|
||||
$this->template->secret = $secret;
|
||||
}
|
||||
|
||||
$issuer = OPENVK_ROOT_CONF["openvk"]["appearance"]["name"];
|
||||
$email = $this->user->identity->getEmail();
|
||||
$this->template->qrCode = substr((new QRCode(new QROptions([
|
||||
"imageTransparent" => false
|
||||
])))->render("otpauth://totp/$issuer:$email?secret=$secret&issuer=$issuer"), 22);
|
||||
}
|
||||
|
||||
function renderDisableTwoFactorAuth(): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
if(!Authenticator::verifyHash($this->postParam("password"), $this->user->identity->getChandlerUser()->getRaw()->passwordHash))
|
||||
$this->flashFail("err", tr("error"), tr("incorrect_password"));
|
||||
|
||||
$this->user->identity->set2fa_secret(NULL);
|
||||
$this->user->identity->save();
|
||||
$this->flashFail("succ", tr("information_-1"), tr("two_factor_authentication_disabled_message"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use Chandler\Database\DatabaseConnection as DB;
|
|||
use openvk\VKAPI\Exceptions\APIErrorException;
|
||||
use openvk\Web\Models\Entities\{User, APIToken};
|
||||
use openvk\Web\Models\Repositories\{Users, APITokens};
|
||||
use lfkeitel\phptotp\{Base32, Totp};
|
||||
|
||||
final class VKAPIPresenter extends OpenVKPresenter
|
||||
{
|
||||
|
@ -160,6 +161,10 @@ final class VKAPIPresenter extends OpenVKPresenter
|
|||
|
||||
$uId = $chUser->related("profiles.user")->fetch()->id;
|
||||
$user = (new Users)->get($uId);
|
||||
|
||||
$code = $this->requestParam("code");
|
||||
if($user->is2faEnabled() && !($code === (new Totp)->GenerateToken(Base32::decode($user->get2faSecret())) || $user->use2faBackupCode((int) $code)))
|
||||
$this->fail(28, "Invalid 2FA code", "internal", "acquireToken");
|
||||
|
||||
$token = new APIToken;
|
||||
$token->setUser($user);
|
||||
|
|
|
@ -68,6 +68,8 @@ final class VideosPresenter extends OpenVKPresenter
|
|||
$video->setLink($this->postParam("link"));
|
||||
else
|
||||
$this->flashFail("err", "Нету видеозаписи", "Выберите файл или укажите ссылку.");
|
||||
} catch(\DomainException $ex) {
|
||||
$this->flashFail("err", "Произошла ошибка", "Файл повреждён или не содержит видео." );
|
||||
} catch(ISE $ex) {
|
||||
$this->flashFail("err", "Произошла ошибка", "Возможно, ссылка некорректна.");
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Entities\{Post, Photo, Club, User};
|
||||
use openvk\Web\Models\Entities\Notifications\{LikeNotification, RepostNotification, WallPostNotification};
|
||||
use openvk\Web\Models\Entities\{Post, Photo, Video, Club, User};
|
||||
use openvk\Web\Models\Entities\Notifications\{RepostNotification, WallPostNotification};
|
||||
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums};
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
use Nette\InvalidStateException as ISE;
|
||||
|
@ -45,17 +45,21 @@ final class WallPresenter extends OpenVKPresenter
|
|||
exit("Ошибка доступа: " . (string) random_int(0, 255));
|
||||
|
||||
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
|
||||
if(is_null($this->user))
|
||||
if(is_null($this->user)) {
|
||||
$canPost = false;
|
||||
else if($user > 0)
|
||||
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
|
||||
else if($user < 0)
|
||||
} else if($user > 0) {
|
||||
if(!$owner->isBanned())
|
||||
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
|
||||
else
|
||||
$this->flashFail("err", tr("error"), "Ошибка доступа");
|
||||
} else if($user < 0) {
|
||||
if($owner->canBeModifiedBy($this->user->identity))
|
||||
$canPost = true;
|
||||
else
|
||||
$canPost = $owner->canPost();
|
||||
else
|
||||
$canPost = false;
|
||||
} else {
|
||||
$canPost = false;
|
||||
}
|
||||
|
||||
if ($embedded == true) $this->template->_template = "components/wall.xml";
|
||||
$this->template->oObj = $owner;
|
||||
|
@ -164,21 +168,33 @@ final class WallPresenter extends OpenVKPresenter
|
|||
|
||||
$wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1))
|
||||
?? $this->flashFail("err", "Не удалось опубликовать пост", "Такого пользователя не существует.");
|
||||
if($wall > 0)
|
||||
$canPost = $wallOwner->getPrivacyPermission("wall.write", $this->user->identity);
|
||||
else if($wall < 0)
|
||||
if($wall > 0) {
|
||||
if(!$wallOwner->isBanned())
|
||||
$canPost = $wallOwner->getPrivacyPermission("wall.write", $this->user->identity);
|
||||
else
|
||||
$this->flashFail("err", "Ошибка доступа", "Вам нельзя писать на эту стену.");
|
||||
} else if($wall < 0) {
|
||||
if($wallOwner->canBeModifiedBy($this->user->identity))
|
||||
$canPost = true;
|
||||
else
|
||||
$canPost = $wallOwner->canPost();
|
||||
else
|
||||
} else {
|
||||
$canPost = false;
|
||||
|
||||
}
|
||||
|
||||
if(!$canPost)
|
||||
$this->flashFail("err", "Ошибка доступа", "Вам нельзя писать на эту стену.");
|
||||
|
||||
if(false)
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Пост слишком большой.");
|
||||
|
||||
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
|
||||
if($wallOwner instanceof Club && $this->postParam("as_group") === "on" && $this->postParam("force_sign") !== "on" && $anon) {
|
||||
$manager = $wallOwner->getManager($this->user->identity);
|
||||
if($manager)
|
||||
$anon = $manager->isHidden();
|
||||
elseif($this->user->identity->getId() === $wallOwner->getOwner()->getId())
|
||||
$anon = $wallOwner->isOwnerHidden();
|
||||
} else {
|
||||
$anon = $anon && $this->postParam("anon") === "on";
|
||||
}
|
||||
|
||||
$flags = 0;
|
||||
if($this->postParam("as_group") === "on")
|
||||
|
@ -186,49 +202,49 @@ final class WallPresenter extends OpenVKPresenter
|
|||
if($this->postParam("force_sign") === "on")
|
||||
$flags |= 0b01000000;
|
||||
|
||||
|
||||
if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
|
||||
try {
|
||||
$photo = new Photo;
|
||||
$photo->setOwner($this->user->id);
|
||||
$photo->setDescription(iconv_substr($this->postParam("text"), 0, 36) . "...");
|
||||
$photo->setCreated(time());
|
||||
$photo->setFile($_FILES["_pic_attachment"]);
|
||||
$photo->save();
|
||||
try {
|
||||
$photo = NULL;
|
||||
$video = NULL;
|
||||
if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
|
||||
$album = NULL;
|
||||
if(!$anon && $wall > 0 && $wall === $this->user->id)
|
||||
$album = (new Albums)->getUserWallAlbum($wallOwner);
|
||||
|
||||
if($wall > 0 && $wall === $this->user->id) {
|
||||
(new Albums)->getUserWallAlbum($wallOwner)->addPhoto($photo);
|
||||
}
|
||||
} catch(ISE $ex) {
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Файл изображения повреждён, слишком велик или одна сторона изображения в разы больше другой.");
|
||||
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album, $anon);
|
||||
}
|
||||
|
||||
if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK) {
|
||||
$video = Video::fastMake($this->user->id, $this->postParam("text"), $_FILES["_vid_attachment"], $anon);
|
||||
}
|
||||
} catch(\DomainException $ex) {
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Файл медиаконтента повреждён.");
|
||||
} catch(ISE $ex) {
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Файл медиаконтента повреждён или слишком велик.");
|
||||
}
|
||||
|
||||
if(empty($this->postParam("text")) && !$photo && !$video)
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Пост пустой или слишком большой.");
|
||||
|
||||
try {
|
||||
$post = new Post;
|
||||
$post->setOwner($this->user->id);
|
||||
$post->setWall($wall);
|
||||
$post->setCreated(time());
|
||||
$post->setContent($this->postParam("text"));
|
||||
$post->setAnonymous($anon);
|
||||
$post->setFlags($flags);
|
||||
$post->setNsfw($this->postParam("nsfw") === "on");
|
||||
$post->save();
|
||||
$post->attach($photo);
|
||||
} elseif($this->postParam("text")) {
|
||||
try {
|
||||
$post = new Post;
|
||||
$post->setOwner($this->user->id);
|
||||
$post->setWall($wall);
|
||||
$post->setCreated(time());
|
||||
$post->setContent($this->postParam("text"));
|
||||
$post->setFlags($flags);
|
||||
$post->setNsfw($this->postParam("nsfw") === "on");
|
||||
$post->save();
|
||||
} catch(\LogicException $ex) {
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Пост пустой или слишком большой.");
|
||||
}
|
||||
} else {
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Пост пустой или слишком большой.");
|
||||
} catch (\LengthException $ex) {
|
||||
$this->flashFail("err", "Не удалось опубликовать пост", "Пост слишком большой.");
|
||||
}
|
||||
|
||||
if(!is_null($photo))
|
||||
$post->attach($photo);
|
||||
|
||||
if(!is_null($video))
|
||||
$post->attach($video);
|
||||
|
||||
if($wall > 0 && $wall !== $this->user->identity->getId())
|
||||
(new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
|
||||
|
||||
|
@ -250,10 +266,11 @@ final class WallPresenter extends OpenVKPresenter
|
|||
$this->logPostView($post, $wall);
|
||||
|
||||
$this->template->post = $post;
|
||||
if ($post->getTargetWall() > 0)
|
||||
{
|
||||
if ($post->getTargetWall() > 0) {
|
||||
$this->template->wallOwner = (new Users)->get($post->getTargetWall());
|
||||
$this->template->isWallOfGroup = false;
|
||||
if($this->template->wallOwner->isBanned())
|
||||
$this->flashFail("err", tr("error"), "Ошибка доступа");
|
||||
} else {
|
||||
$this->template->wallOwner = (new Clubs)->get(abs($post->getTargetWall()));
|
||||
$this->template->isWallOfGroup = true;
|
||||
|
@ -274,9 +291,6 @@ final class WallPresenter extends OpenVKPresenter
|
|||
|
||||
if(!is_null($this->user)) {
|
||||
$post->toggleLike($this->user->identity);
|
||||
|
||||
if($post->getOwner(false)->getId() !== $this->user->identity->getId() && !($post->getOwner() instanceof Club))
|
||||
(new LikeNotification($post->getOwner(false), $post, $this->user->identity))->emit();
|
||||
}
|
||||
|
||||
$this->redirect(
|
||||
|
@ -298,7 +312,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
$nPost = new Post;
|
||||
$nPost->setOwner($this->user->id);
|
||||
$nPost->setWall($this->user->id);
|
||||
$nPost->setContent("");
|
||||
$nPost->setContent($this->postParam("text"));
|
||||
$nPost->save();
|
||||
$nPost->attach($post);
|
||||
|
||||
|
@ -306,8 +320,7 @@ final class WallPresenter extends OpenVKPresenter
|
|||
(new RepostNotification($post->getOwner(false), $post, $this->user->identity))->emit();
|
||||
};
|
||||
|
||||
$this->flash("succ", "Успешно", "Запись появится на вашей стене. <a href='/wall" . $wall . "_" . $post_id . "'>Вернуться к записи.</a>");
|
||||
$this->redirect($this->user->identity->getURL());
|
||||
exit(json_encode(["wall_owner" => $this->user->identity->getId()]));
|
||||
}
|
||||
|
||||
function renderDelete(int $wall, int $post_id): void
|
||||
|
|
|
@ -1,57 +1,61 @@
|
|||
<div class="ovk-lw-container">
|
||||
<div class="ovk-lw--list">
|
||||
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
|
||||
{if sizeof($data) > 0}
|
||||
<table n:foreach="$data as $dat" border="0" style="font-size:11px;" class="post">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="54" valign="top">
|
||||
{include preview, x => $dat}
|
||||
</td>
|
||||
|
||||
<td width="345" valign="top">
|
||||
<div class="post-author">
|
||||
<a href="{include link, x => $dat}">
|
||||
<b>
|
||||
{include name, x => $dat}
|
||||
</b>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="post-content" style="padding: 4px;font-size: 11px;">
|
||||
{include description, x => $dat}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
{extends "@layout.xml"}
|
||||
|
||||
{block wrap}
|
||||
<div class="ovk-lw-container">
|
||||
<div class="ovk-lw--list">
|
||||
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
|
||||
|
||||
<div style="padding: 8px;">
|
||||
{include "components/paginator.xml", conf => (object) [
|
||||
"page" => $page,
|
||||
"count" => $count,
|
||||
"amount" => sizeof($data),
|
||||
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
|
||||
]}
|
||||
</div>
|
||||
{else}
|
||||
{ifset customErrorMessage}
|
||||
{include customErrorMessage}
|
||||
{if sizeof($data) > 0}
|
||||
<table n:foreach="$data as $dat" border="0" style="font-size:11px;" class="post">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="54" valign="top">
|
||||
{include preview, x => $dat}
|
||||
</td>
|
||||
|
||||
<td width="345" valign="top">
|
||||
<div class="post-author">
|
||||
<a href="{include link, x => $dat}">
|
||||
<b>
|
||||
{include name, x => $dat}
|
||||
</b>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="post-content" style="padding: 4px;font-size: 11px;">
|
||||
{include description, x => $dat}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
<div style="padding: 8px;">
|
||||
{include "components/paginator.xml", conf => (object) [
|
||||
"page" => $page,
|
||||
"count" => $count,
|
||||
"amount" => sizeof($data),
|
||||
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
|
||||
]}
|
||||
</div>
|
||||
{else}
|
||||
{include "components/nothing.xml"}
|
||||
{/ifset}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="ovk-lw--actions">
|
||||
{include actions}
|
||||
<hr/>
|
||||
<div n:if="$sorting ?? true" class="tile">
|
||||
<a href="?C=I;O=R" class="profile_link">{_"sort_randomly"}</a>
|
||||
<a href="?C=M;O=D" class="profile_link">{_"sort_up"}</a>
|
||||
<a href="?C=M;O=A" class="profile_link">{_"sort_down"}</a>
|
||||
{ifset customErrorMessage}
|
||||
{include customErrorMessage}
|
||||
{else}
|
||||
{include "components/nothing.xml"}
|
||||
{/ifset}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="ovk-lw--actions">
|
||||
{include actions}
|
||||
<hr/>
|
||||
<div n:if="$sorting ?? true" class="tile">
|
||||
<a href="?C=I;O=R" class="profile_link">{_"sort_randomly"}</a>
|
||||
<a href="?C=M;O=D" class="profile_link">{_"sort_up"}</a>
|
||||
<a href="?C=M;O=A" class="profile_link">{_"sort_down"}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
{var instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
|
||||
|
||||
<html n:if="!isset($parentModule) || substr($parentModule, 0, 21) === 'libchandler:absolute.'">
|
||||
<head>
|
||||
<title>
|
||||
{ifset title}{include title} - {/ifset}OpenVK
|
||||
{ifset title}{include title} - {/ifset}{$instance_name}
|
||||
</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="/assets/packages/static/openvk/img/icon.ico" />
|
||||
<meta name="application-name" content="OpenVK" />
|
||||
<meta name="application-name" content="{$instance_name}" />
|
||||
<meta n:ifset="$csrfToken" name="csrf" value="{$csrfToken}" />
|
||||
<script src="/language/{php echo getLanguage()}.js" crossorigin="anonymous"></script>
|
||||
{script "js/node_modules/jquery/dist/jquery.min.js"}
|
||||
{script "js/node_modules/umbrellajs/umbrella.min.js"}
|
||||
{script "js/l10n.js"}
|
||||
{script "js/openvk.cls.js"}
|
||||
|
||||
{ifset $thisUser}
|
||||
|
@ -16,11 +20,11 @@
|
|||
{css "css/nsfw-posts.css"}
|
||||
{/if}
|
||||
|
||||
{if !is_null($thisUser->getTheme())}
|
||||
{var theme = $thisUser->getTheme()}
|
||||
{if $theme->inheritDefault()}
|
||||
{if $theme !== null}
|
||||
{if $theme->inheritDefault()}
|
||||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/notifications.css"}
|
||||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
{/if}
|
||||
|
@ -28,11 +32,12 @@
|
|||
|
||||
<link rel="stylesheet" href="/themepack/{$theme->getId()}/{$theme->getVersion()}/stylesheet/styles.css" />
|
||||
{if $isXmas}
|
||||
<link rel="stylesheet" href="/themepack/{$theme->getId()}/{$theme->getVersion()}/stylesheet/xmas.css" />
|
||||
<link rel="stylesheet" href="/themepack/{$theme->getId()}/{$theme->getVersion()}/resource/xmas.css" />
|
||||
{/if}
|
||||
{else}
|
||||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/notifications.css"}
|
||||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
{/if}
|
||||
|
@ -53,6 +58,7 @@
|
|||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/nsfw-posts.css"}
|
||||
{css "css/notifications.css"}
|
||||
|
||||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
|
@ -71,6 +77,7 @@
|
|||
</div>
|
||||
|
||||
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['bellsAndWhistles']['testLabel']" id="test-label">FOR TESTING PURPOSES ONLY</div>
|
||||
<div class="notifications_global_wrap"></div>
|
||||
<div class="dimmer"></div>
|
||||
<div class="toTop">
|
||||
⬆ Вверх
|
||||
|
@ -78,8 +85,8 @@
|
|||
|
||||
<div class="layout">
|
||||
<div id="xhead" class="dm"></div>
|
||||
<div class="page_header">
|
||||
<a href="/" class="home_button" title="OpenVK">openvk</a>
|
||||
<div class="page_header {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}page_custom_header{/if}">
|
||||
<a href="/" class="home_button {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}home_button_custom{/if}" title="{$instance_name}">{$instance_name}</a>
|
||||
<div n:if="isset($thisUser) ? !$thisUser->isBanned() : true" class="header_navigation">
|
||||
{ifset $thisUser}
|
||||
<div class="link">
|
||||
|
@ -95,14 +102,17 @@
|
|||
<a href="/search">{_"header_search"}</a>
|
||||
</div>
|
||||
<div class="link">
|
||||
<a href="/support">{_"header_help"}</a>
|
||||
<a href="/support">
|
||||
{_"header_help"}
|
||||
<b n:if="$ticketAnsweredCount > 0">({$ticketAnsweredCount})</b>
|
||||
</a>
|
||||
</div>
|
||||
<div class="link">
|
||||
<a href="/logout">{_"header_log_out"}</a>
|
||||
<a href="/logout?hash={urlencode($csrfToken)}">{_"header_log_out"}</a>
|
||||
</div>
|
||||
<div class="link">
|
||||
<form action="/search" method="get">
|
||||
<input type="search" name="query" placeholder="{_"header_search"}" style="background: url('/assets/packages/static/openvk/img/search_icon.png') no-repeat 3px 4px; background-color: #fff; padding-left: 18px;width: 120px;" />
|
||||
<input type="search" name="query" placeholder="{_"header_search"}" style="height: 20px;background: url('/assets/packages/static/openvk/img/search_icon.png') no-repeat 3px 4px; background-color: #fff; padding-left: 18px;width: 120px;" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
@ -154,12 +164,19 @@
|
|||
{/if}
|
||||
</a>
|
||||
<a href="/settings" class="link">{_"my_settings"}</a>
|
||||
<div style="height: 1px;background: #CCC;margin: 4px 0 2px;"></div>
|
||||
{if $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
|
||||
{var canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
|
||||
{var canAccessHelpdesk = $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
|
||||
{var menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0}
|
||||
<div n:if="$canAccessAdminPanel || $canAccessHelpdesk || $menuLinksAvaiable" class="menu_divider"></div>
|
||||
{if $canAccessAdminPanel}
|
||||
<a href="/admin" class="link">Админ-панель</a>
|
||||
{/if}
|
||||
{if $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
|
||||
<a href="/support/tickets" class="link">Helpdesk</a>
|
||||
{if $canAccessHelpdesk}
|
||||
<a href="/support/tickets" class="link">Helpdesk
|
||||
{if $helpdeskTicketAnsweredCount > 0}
|
||||
(<b>{$helpdeskTicketNotAnsweredCount}</b>)
|
||||
{/if}
|
||||
</a>
|
||||
<a href="/admin/reports" class="link">Reports</a>
|
||||
{/if}
|
||||
<a
|
||||
|
@ -167,17 +184,23 @@
|
|||
href="{$menuItem['url']}"
|
||||
target="_blank"
|
||||
class="link">{$menuItem["name"]}</a>
|
||||
|
||||
<div id="_groupListPinnedGroups">
|
||||
<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>
|
||||
|
||||
</div>
|
||||
<a
|
||||
n:if="OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['enable']"
|
||||
href="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['link']}" >
|
||||
n:if="OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['enable']"
|
||||
href="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['link']}" >
|
||||
<img
|
||||
src="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['src']}"
|
||||
alt="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['caption']}"
|
||||
class="psa-poster"
|
||||
class="psa-poster"
|
||||
style="max-width: 100%; margin-top: 50px;" />
|
||||
</a>
|
||||
|
||||
{else}
|
||||
<a href="/support" class="link">Поддержка</a>
|
||||
<a href="/logout" class="link">Выйти</a>
|
||||
|
@ -248,19 +271,25 @@
|
|||
<a href="/language" class="link">{_footer_choose_language}</a>
|
||||
<a href="/privacy" class="link">{_footer_privacy}</a>
|
||||
</div>
|
||||
<p>OpenVK <a href="/about:openvk2">{php echo OPENVK_VERSION}</a> | PHP: {phpversion()} | DB: {$dbVersion}</p>
|
||||
<p n:ifcontent>
|
||||
<p>OpenVK <a href="/about:openvk">{php echo OPENVK_VERSION}</a> | PHP: {phpversion()} | DB: {$dbVersion}</p>
|
||||
<p n:ifcontent="ifcontent">
|
||||
{php echo OPENVK_ROOT_CONF["openvk"]["appearance"]["motd"]}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script src="https://rawgit.com/kawanet/msgpack-lite/master/dist/msgpack.min.js"></script>
|
||||
{script "js/node_modules/msgpack-lite/dist/msgpack.min.js"}
|
||||
{script "js/node_modules/soundjs/lib/soundjs.min.js"}
|
||||
{script "js/node_modules/ky/umd.js"}
|
||||
{script "js/messagebox.js"}
|
||||
{script "js/notifications.js"}
|
||||
{script "js/scroll.js"}
|
||||
{script "js/al_wall.js"}
|
||||
{script "js/al_api.js"}
|
||||
|
||||
{ifset $thisUser}
|
||||
{script "js/al_notifs.js"}
|
||||
{/ifset}
|
||||
|
||||
<script src="https://unpkg.com/fartscroll@1.0.0/fartscroll.js"></script>
|
||||
<script n:if="OPENVK_ROOT_CONF['openvk']['preferences']['bellsAndWhistles']['fartscroll']">
|
||||
fartscroll(400);
|
||||
|
@ -269,6 +298,10 @@
|
|||
<script n:if="OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['enable']"
|
||||
async defer data-domain="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['domain']}"
|
||||
src="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['server']}js/plausible.js"></script>
|
||||
|
||||
{ifset bodyScripts}
|
||||
{include bodyScripts}
|
||||
{/ifset}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
@ -36,10 +36,11 @@
|
|||
|
||||
<div style="padding: 8px;">
|
||||
{include "components/paginator.xml", conf => (object) [
|
||||
"page" => $page,
|
||||
"count" => $count,
|
||||
"amount" => sizeof($data),
|
||||
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
|
||||
"page" => $page,
|
||||
"count" => $count,
|
||||
"amount" => sizeof($data),
|
||||
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
|
||||
"atBottom" => true,
|
||||
]}
|
||||
</div>
|
||||
{else}
|
||||
|
|
|
@ -6,21 +6,10 @@
|
|||
{/block}
|
||||
|
||||
{block content}
|
||||
<b>OpenVK - универсальное средство поиска коллег основанное на структуре ВКонтакте.</b><br>
|
||||
<p>Мы хотим, чтобы друзья, однокурсники, одноклассники, соседи и коллеги всегда могли быть в контакте.</p>
|
||||
<p>
|
||||
Нас уже <b>{$stats->all}</b> пользователя и <b>{$stats->online}</b> из
|
||||
<b>{$stats->active}</b> активных сейчас в сети.
|
||||
</p>
|
||||
<p>С помощью этого сайта Вы можете:</p>
|
||||
<ul>
|
||||
<li><span>Найти людей, с которыми Вы когда-либо учились, работали или отдыхали.</span></li>
|
||||
<li><span>Узнать больше о людях, которые Вас окружают, и найти новых друзей.</span></li>
|
||||
<li><span>Всегда оставаться в контакте с теми, кто Вам дорог.</span></li>
|
||||
<li><span>Продвигать своё творчество и/или мнение.</span></li>
|
||||
</ul>
|
||||
{presenter "openvk!Support->knowledgeBaseArticle", "about"}
|
||||
<center>
|
||||
<a class="button" style="margin-right: 5px;cursor: pointer;" href="/login">{_"log_in"}</a>
|
||||
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable']" class="button" style="cursor: pointer;" href="/reg">{_"registration"}</a></center>
|
||||
</div>
|
||||
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable']" class="button" style="cursor: pointer;" href="/reg">{_"registration"}</a>
|
||||
</center>
|
||||
{* TO-DO: Add statistics about this instance as on mastodon.social *}
|
||||
{/block}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{block content}
|
||||
<div class="navigation">
|
||||
{foreach $languages as $language}
|
||||
<a href="language?lg={$language['code']}" class="link"><img src="/assets/packages/static/openvk/img/flags/{$language['flag']}.gif"> {$language['native_name']}</a>
|
||||
<a href="language?lg={$language['code']}&hash={urlencode($csrfToken)}" class="link"><img src="/assets/packages/static/openvk/img/flags/{$language['flag']}.gif"> {$language['native_name']}</a>
|
||||
{/foreach}
|
||||
</div>
|
||||
{/block}
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
#ovkLogo {
|
||||
float: right;
|
||||
border: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding-top: 6px;
|
||||
position: relative;
|
||||
}
|
||||
|
@ -97,7 +97,7 @@
|
|||
<tr class="h">
|
||||
<td>
|
||||
<h1 class="p" style="float: left;">OpenVK {=OPENVK_VERSION}</h1>
|
||||
<img id="ovkLogo" src="/assets/packages/static/openvk/img/logo.svg" alt="OpenVK Logo" />
|
||||
<img id="ovkLogo" src="/assets/packages/static/openvk/img/logo_full.svg" alt="OpenVK Logo" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -397,8 +397,8 @@
|
|||
<tr>
|
||||
<td class="e">
|
||||
Vladimir Barinov (veselcraft), Alexandra Katunina (rem-pai), Konstantin Kichulkin (kosfurler),
|
||||
Nikita Volkov (sup_ban), Daniil Myslivets (myslivets), Alexander Kotov (l-lacker),
|
||||
Alexey Assemblerov (BiosNod), Ilya Prokopenko (dsrev) and Vladimir Lapskiy (0x7d5)
|
||||
Nikita Volkov (sup_ban), Daniel Myslivets (myslivets), Alexander Kotov (l-lacker),
|
||||
Alexey Assemblerov (BiosNod), Ilya Prokopenko (dsrev) and Maxim Leshchenko (maksales / maksalees)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -412,7 +412,7 @@
|
|||
<tr>
|
||||
<td class="e">
|
||||
Vladimir Barinov (veselcraft) and Konstantin Kichulkin (kosfurler)<br/>
|
||||
OpenVK is a free open-source software that "cosplays" (or imitates) older versions of russian website VKontakte. VKontakte belongs to Pavel Durov and mail.ru.
|
||||
OpenVK is a free open-source software that "cosplays" (or imitates) older versions of russian website VKontakte. VKontakte belongs to Pavel Durov and VK Group.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -69,9 +69,19 @@
|
|||
Группы
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="aui-nav-heading">
|
||||
<strong>Платные услуги</strong>
|
||||
</div>
|
||||
<ul class="aui-nav">
|
||||
<li>
|
||||
<a href="/admin/files">
|
||||
Загруженные файлы
|
||||
<a href="/admin/vouchers">
|
||||
{_vouchers}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/gifts">
|
||||
Подарки
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -120,10 +130,28 @@
|
|||
</nav>
|
||||
</div>
|
||||
<section class="aui-page-panel-content">
|
||||
{ifset $flashMessage}
|
||||
{var type = ["err" => "error", "warn" => "warning", "info" => "basic", "succ" => "success"][$flashMessage->type]}
|
||||
<div class="aui-message aui-message-{$type}" style="margin-bottom: 15px;">
|
||||
<p class="title">
|
||||
<strong>{$flashMessage->title}</strong>
|
||||
</p>
|
||||
<p>{$flashMessage->msg|noescape}</p>
|
||||
</div>
|
||||
{/ifset}
|
||||
|
||||
{ifset preHeader}
|
||||
{include preHeader}
|
||||
{/ifset}
|
||||
|
||||
<header class="aui-page-header">
|
||||
<div class="aui-page-header-inner">
|
||||
<div class="aui-page-header-main">
|
||||
<h1>{include heading}</h1>
|
||||
{ifset headingWrap}
|
||||
{include headingWrap}
|
||||
{else}
|
||||
<h1>{include heading}</h1>
|
||||
{/ifset}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -141,5 +169,13 @@
|
|||
</section>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
{script "js/node_modules/jquery/dist/jquery.min.js"}
|
||||
{script "js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.js"}
|
||||
<script>AJS.tabs.setup();</script>
|
||||
|
||||
{ifset scripts}
|
||||
{include scripts}
|
||||
{/ifset}
|
||||
</body>
|
||||
</html>
|
||||
|
|
106
Web/Presenters/templates/Admin/Gift.xml
Normal file
106
Web/Presenters/templates/Admin/Gift.xml
Normal file
|
@ -0,0 +1,106 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{if $form->id === 0}
|
||||
Новый подарок
|
||||
{else}
|
||||
Подарок "{$form->name}"
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
{include title}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<form class="aui" method="POST" enctype="multipart/form-data">
|
||||
<div class="field-group">
|
||||
<label for="avatar">
|
||||
Изображение
|
||||
<span n:if="$form->id === 0" class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
{if $form->id === 0}
|
||||
<input type="file" name="pic" accept="image/jpeg,image/png,image/gif,image/webp" required="required" />
|
||||
{else}
|
||||
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
|
||||
<span class="aui-avatar-inner">
|
||||
<img id="pic" src="{$form->pic}" style="object-fit: cover;"></img>
|
||||
</span>
|
||||
</span>
|
||||
<input style="display: none;" id="picInput" type="file" name="pic" accept="image/jpeg,image/png,image/gif,image/webp" />
|
||||
<div class="description">
|
||||
<a id="picChange" href="javascript:false">Заменить изображение?</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label for="id">
|
||||
ID
|
||||
</label>
|
||||
<input class="text long-field" type="number" id="id" disabled="disabled" value="{$form->id}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="putin">
|
||||
Использований
|
||||
</label>
|
||||
<input class="text long-field" type="number" id="putin" disabled="disabled" value="{$form->usages}" />
|
||||
<div n:if="$form->usages > 0" class="description">
|
||||
<a href="javascript:$('#putin').value(0);">Обнулить?</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="name">
|
||||
Внутренее имя
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="name" name="name" value="{$form->name}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="price">
|
||||
Цена
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="number" id="price" name="price" min="0" value="{$form->price}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="limit">
|
||||
Ограничение
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="number" min="-1" id="limit" name="limit" value="{$form->limit}" />
|
||||
</div>
|
||||
<fieldset class="group">
|
||||
<legend></legend>
|
||||
<div class="checkbox" resolved="">
|
||||
<input n:attr="disabled => $form->id === 0, checked => $form->id === 0" class="checkbox" type="checkbox" name="reset_limit" id="reset_limit" />
|
||||
<span class="aui-form-glyph"></span>
|
||||
|
||||
<label for="reset_limit">Сбросить счётчик ограничений</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<input n:if="$form->id === 0" type="hidden" name="_cat" value="{$_GET['cat'] ?? 1}" />
|
||||
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input class="aui-button aui-button-primary submit" type="submit" value="{_save}">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/block}
|
||||
|
||||
{block scripts}
|
||||
<script>
|
||||
const TRANS_GIF = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
||||
|
||||
$("#picChange").click(_ => $("#picInput").click());
|
||||
$("#picInput").bind("change", e => {
|
||||
if(typeof e.target.files[0] === "undefined")
|
||||
$("#pic").prop("src", URL.createObjectURL(TRANS_GIF));
|
||||
|
||||
$("#pic").prop("src", URL.createObjectURL(e.target.files[0]));
|
||||
});
|
||||
</script>
|
||||
{/block}
|
56
Web/Presenters/templates/Admin/GiftCategories.xml
Normal file
56
Web/Presenters/templates/Admin/GiftCategories.xml
Normal file
|
@ -0,0 +1,56 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
Наборы подарков
|
||||
{/block}
|
||||
|
||||
{block headingWrap}
|
||||
<a style="float: right;" class="aui-button aui-button-primary" href="/admin/gifts/new.0.meta">
|
||||
{_create}
|
||||
</a>
|
||||
|
||||
<h1>Наборы подарков</h1>
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<div id="spacer" style="height: 8px;"></div>
|
||||
{if sizeof($categories) > 0}
|
||||
<table class="aui aui-table-list">
|
||||
<tbody>
|
||||
<tr n:foreach="$categories as $cat">
|
||||
<td style="vertical-align: middle;">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-folder-filled">{$cat->getName()}</span>
|
||||
{$cat->getName()}
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{ovk_proc_strtr($cat->getDescription(), 128)}
|
||||
</td>
|
||||
<td style="vertical-align: middle; text-align: right;">
|
||||
<a class="aui-button aui-button-primary" href="/admin/gifts/{$cat->getSlug()}.{$cat->getId()}.meta">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
|
||||
</a>
|
||||
|
||||
<a class="aui-button" href="/admin/gifts/{$cat->getSlug()}.{$cat->getId()}/">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-gallery">Открыть</span>
|
||||
Открыть
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{else}
|
||||
<center>
|
||||
<p>Наборов подарков нету. Чтобы создать подарок, создайте набор.</p>
|
||||
</center>
|
||||
{/if}
|
||||
|
||||
<div align="right">
|
||||
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($categories)) < $count}
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) + 1}">
|
||||
⭇ сюда
|
||||
</a>
|
||||
</div>
|
||||
{/block}
|
72
Web/Presenters/templates/Admin/GiftCategory.xml
Normal file
72
Web/Presenters/templates/Admin/GiftCategory.xml
Normal file
|
@ -0,0 +1,72 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{if $form->id === 0}
|
||||
Создать набор подарков
|
||||
{else}
|
||||
{$form->languages["master"]->name}
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
{include title}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<form class="aui" method="POST">
|
||||
<h3>Общие настройки</h3>
|
||||
<fieldset>
|
||||
<div class="field-group">
|
||||
<label for="id">
|
||||
ID
|
||||
</label>
|
||||
<input class="text long-field" type="number" id="id" name="id" disabled value="{$form->id}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="name_master">
|
||||
Наименование
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="name_master" name="name_master" value="{$form->languages['master']->name}" />
|
||||
<div class="description">Внутреннее название набора, которое будет использоваться, если не удаётся найти название на языке пользователя.</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="description_master">
|
||||
Описание
|
||||
<span class="aui-icon icon-required"></span>
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="description_master" name="description_master" value="{$form->languages['master']->description}" />
|
||||
<div class="description">Внутреннее описание набора, которое будет использоваться, если не удаётся найти название на языке пользователя.</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<h3>Языко-зависимые настройки</h3>
|
||||
<fieldset>
|
||||
{foreach $form->languages as $locale => $data}
|
||||
{continueIf $locale === "master"}
|
||||
|
||||
<div class="field-group">
|
||||
<label for="name_{$locale}">
|
||||
Наименование
|
||||
<img src="/assets/packages/static/openvk/img/flags/{$locale}.gif" alt="{$locale}" />
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="name_{$locale}" name="name_{$locale}" value="{$data->name}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="description_{$locale}">
|
||||
Описание
|
||||
<img src="/assets/packages/static/openvk/img/flags/{$locale}.gif" alt="{$locale}" />
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="description_{$locale}" name="description_{$locale}" value="{$data->description}" />
|
||||
</div>
|
||||
{/foreach}
|
||||
</fieldset>
|
||||
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input class="aui-button aui-button-primary submit" type="submit" value="{_save}">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/block}
|
82
Web/Presenters/templates/Admin/Gifts.xml
Normal file
82
Web/Presenters/templates/Admin/Gifts.xml
Normal file
|
@ -0,0 +1,82 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{$cat->getName()}
|
||||
{/block}
|
||||
|
||||
{block headingWrap}
|
||||
<a style="float: right;" class="aui-button aui-button-primary" href="/admin/gifts/id0?act=edit&cat={$cat->getId()}">
|
||||
{_create}
|
||||
</a>
|
||||
|
||||
<h1>Набор "{$cat->getName()}"</h1>
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
{if sizeof($gifts) > 0}
|
||||
<table class="aui aui-table-list">
|
||||
<thead>
|
||||
<th>Подарок</th>
|
||||
<th>Имя</th>
|
||||
<th>Цена</th>
|
||||
<th>Подарен</th>
|
||||
<th>Ограничение</th>
|
||||
<th>Сброс счётчика ограничений</th>
|
||||
<th>Действия</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr n:foreach="$gifts as $gift">
|
||||
<td style="vertical-align: middle; width: 0px;">
|
||||
<img style="max-width: 32px;" src="{$gift->getImage(2)}" alt="{$gift->getName()}" />
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{$gift->getName()}
|
||||
<span n:if="$gift->isFree()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">
|
||||
бесплатный
|
||||
</span>
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{$gift->getPrice()} голосов
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{$gift->getUsages()} раз
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{if $gift->getLimit() === INF}
|
||||
Отсутствует
|
||||
{else}
|
||||
Не более {$gift->getLimit()} дарений
|
||||
{/if}
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
{if !$gift->getLimitResetTime()}
|
||||
Никогда
|
||||
{else}
|
||||
Последний раз в
|
||||
{$gift->getLimitResetTime()->format("%a, %d %B %G")}
|
||||
{/if}
|
||||
</td>
|
||||
<td style="vertical-align: middle; text-align: right;">
|
||||
<a class="aui-button aui-button-primary" href="/admin/gifts/id{$gift->getId()}">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{else}
|
||||
<center>
|
||||
<p>Подарков нету. Нажмите на красивую кнопку вверху, чтобы создать первый.</p>
|
||||
</center>
|
||||
{/if}
|
||||
|
||||
<div align="right">
|
||||
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($gifts)) < $count}
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
|
||||
⭇ сюда
|
||||
</a>
|
||||
</div>
|
||||
{/block}
|
92
Web/Presenters/templates/Admin/Voucher.xml
Normal file
92
Web/Presenters/templates/Admin/Voucher.xml
Normal file
|
@ -0,0 +1,92 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{_edit}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
{_edit} №{$form->token ?? "undefined"}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<div style="margin: 8px -8px;" class="aui-tabs horizontal-tabs">
|
||||
<ul class="tabs-menu">
|
||||
<li class="menu-item active-tab">
|
||||
<a href="#info">Информация</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a href="#activators">{_voucher_activators}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tabs-pane active-pane" id="info">
|
||||
<form class="aui" method="POST">
|
||||
<div class="field-group">
|
||||
<label for="id">
|
||||
ID
|
||||
</label>
|
||||
<input class="text long-field" type="number" id="id" name="id" disabled value="{$form->id}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="token">
|
||||
Серийный номер
|
||||
</label>
|
||||
<input class="text long-field" type="text" id="token" name="token" value="{$form->token}" />
|
||||
<div class="description">Номер состоит из 24 символов, если формат неправильный или поле не заполнено, будет назначен автоматически.</div>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="coins">
|
||||
Количество голосов
|
||||
</label>
|
||||
<input class="text long-field" type="number" min="0" id="coins" name="coins" value="{$form->coins}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="rating">
|
||||
Количество рейтинга
|
||||
</label>
|
||||
<input class="text long-field" type="number" min="0" id="rating" name="rating" value="{$form->rating}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="usages">
|
||||
{if $form->id === 0}
|
||||
{_usages_total}
|
||||
{else}
|
||||
{_usages_left}
|
||||
{/if}
|
||||
</label>
|
||||
<input class="text long-field" type="number" min="-1" id="usages" name="usages" value="{$form->usages}" />
|
||||
<div class="description">Количество аккаунтов, которые могут использовать ваучер. Если написать -1, будет Infinity.</div>
|
||||
</div>
|
||||
|
||||
<div class="buttons-container">
|
||||
<div class="buttons">
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input class="aui-button aui-button-primary submit" type="submit" value="{_save}">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tabs-pane" id="activators">
|
||||
<table rules="none" class="aui aui-table-list">
|
||||
<tbody>
|
||||
<tr n:foreach="$form->users as $user">
|
||||
<td>
|
||||
<span class="aui-avatar aui-avatar-xsmall">
|
||||
<span class="aui-avatar-inner">
|
||||
<img src="{$user->getAvatarUrl()}" alt="{$user->getCanonicalName()}" role="presentation" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
|
||||
|
||||
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
|
||||
заблокирован
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
61
Web/Presenters/templates/Admin/Vouchers.xml
Normal file
61
Web/Presenters/templates/Admin/Vouchers.xml
Normal file
|
@ -0,0 +1,61 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{_vouchers}
|
||||
{/block}
|
||||
|
||||
{block headingWrap}
|
||||
<a style="float: right;" class="aui-button aui-button-primary" href="/admin/vouchers/id0">
|
||||
{_create}
|
||||
</a>
|
||||
|
||||
<h1>{_vouchers}</h1>
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<table class="aui aui-table-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Серийный номер</th>
|
||||
<th>Голоса</th>
|
||||
<th>Рейгтинг</th>
|
||||
<th>Осталось использований</th>
|
||||
<th>Состояние</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr n:foreach="$vouchers as $voucher">
|
||||
<td>{$voucher->getId()}</td>
|
||||
<td>{$voucher->getFormattedToken()}</td>
|
||||
<td>{$voucher->getCoins()}¢</td>
|
||||
<td>{$voucher->getRating()}</td>
|
||||
<td>{$voucher->getRemainingUsages() === INF ? "∞" : $voucher->getRemainingUsages()}</td>
|
||||
<td>
|
||||
{if $voucher->isExpired()}
|
||||
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">закончился</span>
|
||||
{else}
|
||||
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">активен</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<a class="aui-button aui-button-primary" href="/admin/vouchers/id{$voucher->getId()}">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
<div align="right">
|
||||
{var isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($vouchers)) < $count}
|
||||
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
|
||||
⭁ туда
|
||||
</a>
|
||||
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
|
||||
⭇ сюда
|
||||
</a>
|
||||
</div>
|
||||
{/block}
|
|
@ -17,6 +17,11 @@
|
|||
<label for="password">Новый пароль: </label>
|
||||
<input id="password" type="password" name="password" required />
|
||||
<br/><br/>
|
||||
{if $is2faEnabled}
|
||||
<label for="code">Код двухфакторной аутентификации: </label>
|
||||
<input id="code" type="text" name="code" required />
|
||||
<br/><br/>
|
||||
{/if}
|
||||
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="Сбросить пароль" class="button" style="float: right;" />
|
||||
|
|
38
Web/Presenters/templates/Auth/LoginSecondFactor.xml
Normal file
38
Web/Presenters/templates/Auth/LoginSecondFactor.xml
Normal file
|
@ -0,0 +1,38 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}{_"log_in"}{/block}
|
||||
|
||||
{block header}
|
||||
{_"log_in"}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<p>
|
||||
{_"two_factor_authentication_login"}
|
||||
</p>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<table cellspacing="7" cellpadding="0" width="40%" border="0" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span>{_code}: </span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="code" required />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="login" value="{$login}" />
|
||||
<input type="hidden" name="password" value="{$password}" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="{_'log_in'}" class="button" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
{/block}
|
|
@ -24,7 +24,7 @@
|
|||
</p>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<table cellspacing="7" cellpadding="0" width="40%" border="0" align="center">
|
||||
<table cellspacing="7" cellpadding="0" width="52%" border="0" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
|
@ -54,6 +54,14 @@
|
|||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span>{_"birth_date"}: </span>
|
||||
</td>
|
||||
<td>
|
||||
<input max={date('Y-m-d')} name="birthday" type="date"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
|
30
Web/Presenters/templates/Gifts/Confirm.xml
Normal file
30
Web/Presenters/templates/Gifts/Confirm.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{_send_gift}
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
|
||||
<a href="/gifts?act=pick&user={$user->getId()}">{_gift_select}</a> »
|
||||
<a href="/gifts?act=pick&user={$user->getId()}">{_collections}</a> »
|
||||
<a href="/gifts?act=menu&user={$user->getId()}&pack={$cat->getId()}">{$cat->getName(tr("__lang"))}</a> »
|
||||
{_confirm}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<center>
|
||||
<img class="gift_confirm_pic" style="max-width: 256px;" src="{$gift->getImage(2)}" alt="Подарок" />
|
||||
|
||||
<form style="width: 65%;" method="POST">
|
||||
<textarea name="comment" style="resize: vertical; height: 65px;" placeholder="{_gift_your_message}"></textarea>
|
||||
<br/><br/>
|
||||
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="{_send}" class="button" />
|
||||
<label>
|
||||
<input type="checkbox" name="anonymous"> {_as_anonymous}
|
||||
</label>
|
||||
</form>
|
||||
</center>
|
||||
{/block}
|
29
Web/Presenters/templates/Gifts/Menu.xml
Normal file
29
Web/Presenters/templates/Gifts/Menu.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
{extends "../@listView.xml"}
|
||||
|
||||
{block title}
|
||||
{_gift_select}
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
|
||||
<a href="/gifts?act=pick&user={$user->getId()}">{_gift_select}</a> »
|
||||
{_collections}
|
||||
{/block}
|
||||
|
||||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block link|strip|stripHtml}
|
||||
/gifts?act=menu&user={$user->getId()}&pack={$x->getId()}
|
||||
{/block}
|
||||
|
||||
{block preview}
|
||||
<img src="{$x->getThumbnailURL()}" width="75" alt="{$x->getName(tr('__lang'))}" />
|
||||
{/block}
|
||||
|
||||
{block name}
|
||||
{$x->getName(tr("__lang"))}
|
||||
{/block}
|
||||
|
||||
{block description}
|
||||
{$x->getDescription(tr("__lang"))}
|
||||
{/block}
|
58
Web/Presenters/templates/Gifts/Pick.xml
Normal file
58
Web/Presenters/templates/Gifts/Pick.xml
Normal file
|
@ -0,0 +1,58 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{_gift_select}
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
|
||||
<a href="/gifts?act=pick&user={$user->getId()}">{_gift_select}</a> »
|
||||
<a href="/gifts?act=pick&user={$user->getId()}">{_collections}</a> »
|
||||
{$cat->getName(tr("__lang"))}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<div class="gift_grid">
|
||||
<div n:foreach="$gifts as $gift" n:class="gift_sel, !$gift->canUse($thisUser) ? disabled" data-gift="{$gift->getId()}">
|
||||
<img class="gift_pic" src="{$gift->getImage(2)}" alt="{_gift}" />
|
||||
|
||||
<strong class="gift_price">
|
||||
{if $gift->isFree()}
|
||||
{_free_gift}
|
||||
{else}
|
||||
{tr('coins', $gift->getPrice())}
|
||||
{/if}
|
||||
</strong>
|
||||
|
||||
<strong class="gift_limit">
|
||||
{if $gift->getUsagesLeft($thisUser) !== INF}
|
||||
{tr("gifts_left", $gift->getUsagesLeft($thisUser))}
|
||||
{/if}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding: 8px;">
|
||||
{include "../components/paginator.xml", conf => (object) [
|
||||
"page" => $page,
|
||||
"count" => $count,
|
||||
"amount" => sizeof($gifts),
|
||||
"perPage" => OPENVK_DEFAULT_PER_PAGE,
|
||||
]}
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block bodyScripts}
|
||||
<script>
|
||||
$(".gift_sel").click(function() {
|
||||
let el = $(this);
|
||||
if(el.hasClass("disabled"))
|
||||
return false;
|
||||
|
||||
let link = "/gifts?act=confirm&user={$user->getId()}&pack={$cat->getId()}&elid=";
|
||||
let gift = el.data("gift");
|
||||
|
||||
window.location.assign(link + gift);
|
||||
});
|
||||
</script>
|
||||
{/block}
|
43
Web/Presenters/templates/Gifts/UserGifts.xml
Normal file
43
Web/Presenters/templates/Gifts/UserGifts.xml
Normal file
|
@ -0,0 +1,43 @@
|
|||
{extends "../@listView.xml"}
|
||||
|
||||
{block title}
|
||||
{tr("users_gifts", $user->getFirstName())}
|
||||
{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> »
|
||||
{_gifts}
|
||||
{/block}
|
||||
|
||||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block link|strip|stripHtml}
|
||||
javascript:false
|
||||
{/block}
|
||||
|
||||
{block preview}
|
||||
<img src="{$x->gift->getImage(2)}" width="75" alt="{_gift}" />
|
||||
{/block}
|
||||
|
||||
{block name}
|
||||
{_gift}
|
||||
{/block}
|
||||
|
||||
{block description}
|
||||
<table class="ugc-table" n:if="$hideInfo ? !$x->anon : true">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="nobold">{_sender}: </span></td>
|
||||
<td>
|
||||
<a href="{$x->sender->getURL()}">
|
||||
{$x->sender->getFullName()}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr n:if="!empty($x->caption)">
|
||||
<td><span class="nobold">{_comment}: </span></td>
|
||||
<td>{$x->caption}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/block}
|
|
@ -27,7 +27,7 @@
|
|||
<div class="container_gray">
|
||||
<h4>{_main_information}</h4>
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
<table cellspacing="7" cellpadding="0" width="40%" border="0" align="center">
|
||||
<table cellspacing="7" cellpadding="0" width="60%" border="0" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
|
@ -53,6 +53,14 @@
|
|||
<input type="text" name="shortcode" value="{$club->getShortcode()}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_website}: </span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="website" value="{$club->getWebsite()}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_avatar}: </span>
|
||||
|
@ -69,6 +77,17 @@
|
|||
<input type="checkbox" name="wall" value="1" {if $club->canPost()}checked{/if}/> {_group_allow_post_for_everyone}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_group_administrators_list}: </span>
|
||||
</td>
|
||||
<td>
|
||||
{var areAllAdminsHidden = $club->getManagersCount(true) == 0}
|
||||
<input type="radio" name="administrators_list_display" value="0" n:attr="checked => $club->getAdministratorsListDisplay() == 0, disabled => $areAllAdminsHidden" /> {_group_display_only_creator}<br>
|
||||
<input type="radio" name="administrators_list_display" value="1" n:attr="checked => $club->getAdministratorsListDisplay() == 1, disabled => $areAllAdminsHidden" /> {_group_display_all_administrators}<br>
|
||||
<input type="radio" name="administrators_list_display" value="2" n:attr="checked => $club->getAdministratorsListDisplay() == 2" /> {_group_dont_display_administrators_list}<br>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{extends "../@listView.xml"}
|
||||
{var iterator = $followers}
|
||||
{var $Manager = openvk\Web\Models\Entities\Manager::class}
|
||||
{var iterator = $onlyShowManagers ? $managers : $followers}
|
||||
{var count = $paginatorConf->count}
|
||||
{var page = $paginatorConf->page}
|
||||
{var perPage = 6}
|
||||
|
@ -9,6 +10,8 @@
|
|||
{block header}
|
||||
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a>
|
||||
» {_followers}
|
||||
<a n:if="!$onlyShowManagers" href="/club{$club->getId()}/followers?onlyAdmins=1" style="float: right;">{_all_followers}</a>
|
||||
<a n:if="$onlyShowManagers" href="/club{$club->getId()}/followers" style="float: right;">{_only_administrators}</a>
|
||||
{/block}
|
||||
|
||||
{block actions}
|
||||
|
@ -17,45 +20,98 @@
|
|||
|
||||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block tabs}
|
||||
{if $club->canBeModifiedBy($thisUser)}
|
||||
<div class="tab">
|
||||
<a href="/club{$club->getId()}/edit">
|
||||
{_main}
|
||||
</a>
|
||||
</div>
|
||||
<div id="activetabs" class="tab">
|
||||
<a id="act_tab_a" href="/club{$club->getId()}/followers">
|
||||
{_followers}
|
||||
</a>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<a href="/club{$club->getId()}/stats">
|
||||
{_statistics}
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
{block link|strip|stripHtml}
|
||||
/id{$x->getId()}
|
||||
/id{$x instanceof $Manager ? $x->getUserId() : $x->getId()}
|
||||
{/block}
|
||||
|
||||
{block preview}
|
||||
<img src="{$x->getAvatarURL()}" alt="{$x->getCanonicalName()}" width=75 />
|
||||
<img src="{$x instanceof $Manager ? $x->getUser()->getAvatarURL() : $x->getAvatarURL()}" alt="{$x instanceof $Manager ? $x->getUser()->getCanonicalName() : $x->getCanonicalName()}" width=75 />
|
||||
{/block}
|
||||
|
||||
{block name}
|
||||
{$x->getCanonicalName()}
|
||||
{$x instanceof $Manager ? $x->getUser()->getCanonicalName() : $x->getCanonicalName()}
|
||||
{/block}
|
||||
|
||||
{block description}
|
||||
{var user = $x instanceof $Manager ? $x->getUser() : $x}
|
||||
{var manager = $x instanceof $Manager ? $x : $club->getManager($user, !$club->canBeModifiedBy($thisUser))}
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">{_"gender"}: </span></td>
|
||||
<td>{$x->isFemale() ? "женский" : "мужской"}</td>
|
||||
<td>{$user->isFemale() ? "женский" : "мужской"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">{_"registration_date"}: </span></td>
|
||||
<td>{$x->getRegistrationTime()}</td>
|
||||
<td>{$user->getRegistrationTime()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">{_role}: </span></td>
|
||||
<td>
|
||||
{$club->canBeModifiedBy($x) ? tr("administrator") : tr("follower")}
|
||||
{$club->getOwner()->getId() == $user->getId() ? !$club->isOwnerHidden() || $club->canBeModifiedBy($thisUser) : !is_null($manager) ? tr("administrator") : tr("follower")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr n:if="$club->canBeModifiedBy($thisUser ?? NULL) && $club->getOwner()->getId() !== $x->getId()">
|
||||
<tr n:if="$manager && !empty($manager->getComment()) || $club->getOwner()->getId() === $user->getId() && !empty($club->getOwnerComment()) && (!$club->isOwnerHidden() || $club->canBeModifiedBy($thisUser))">
|
||||
<td width="120" valign="top"><span class="nobold">{_comment}: </span></td>
|
||||
<td>
|
||||
{if $club->getOwner()->getId() === $user->getId()}
|
||||
{$club->getOwnerComment()}
|
||||
{else}
|
||||
{$manager->getComment()}
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
<tr n:if="$club->canBeModifiedBy($thisUser ?? NULL)">
|
||||
<td width="120" valign="top"><span class="nobold">{_actions}: </span></td>
|
||||
<td>
|
||||
<a href="/club{$club->getId()}/setAdmin.jsp?user={$x->getId()}&hash={rawurlencode($csrfToken)}">
|
||||
{if $club->canBeModifiedBy($x)}
|
||||
<a href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hash={rawurlencode($csrfToken)}" n:if="$club->getOwner()->getId() !== $user->getId()">
|
||||
{if $manager}
|
||||
{_devote}
|
||||
{else}
|
||||
{_promote_to_admin}
|
||||
{/if}
|
||||
</a>
|
||||
{if $manager}
|
||||
|
|
||||
<a href="javascript:setClubAdminComment('{$club->getId()}', '{$manager->getUserId()}', '{rawurlencode($csrfToken)}')">
|
||||
{_set_comment}
|
||||
</a>
|
||||
{/if}
|
||||
<a n:if="$club->getOwner()->getId() === $user->getId()" href="javascript:setClubAdminComment('{$club->getId()}', '{$club->getOwner()->getId()}', '{rawurlencode($csrfToken)}')">
|
||||
{_set_comment}
|
||||
</a>
|
||||
{if $manager}
|
||||
|
|
||||
<a href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hidden={(int) !$manager->isHidden()}&hash={rawurlencode($csrfToken)}">
|
||||
{if $manager->isHidden()}{_hidden_yes}{else}{_hidden_no}{/if}
|
||||
</a>
|
||||
{/if}
|
||||
{if $club->getOwner()->getId() == $user->getId()}
|
||||
|
|
||||
<a href="/club{$club->getId()}/setAdmin.jsp?user={$user->getId()}&hidden={(int) !$club->isOwnerHidden()}&hash={rawurlencode($csrfToken)}">
|
||||
{if $club->isOwnerHidden()}{_hidden_yes}{else}{_hidden_no}{/if}
|
||||
</a>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}Статистика группы{/block}
|
||||
{block title}{$club->getName()} » {_statistics}{/block}
|
||||
|
||||
{block header}
|
||||
<a href="{$club->getURL()}">{$club->getName()}</a> » Статистика
|
||||
<a href="{$club->getURL()}">{$club->getName()}</a> » {_statistics}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<div class="tabs">
|
||||
<div class="tab">
|
||||
<a href="/club{$club->getId()}/edit">
|
||||
Настройки
|
||||
{_main}
|
||||
</a>
|
||||
</div>
|
||||
<div class="tab">
|
||||
|
@ -19,7 +19,7 @@
|
|||
</div>
|
||||
<div id="activetabs" class="tab">
|
||||
<a id="act_tab_a" href="javascript:void(0)">
|
||||
Статистика
|
||||
{_statistics}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,6 +29,14 @@
|
|||
<td><span class="nobold">{_"description"}:</span></td>
|
||||
<td>{$club->getDescription()}</td>
|
||||
</tr>
|
||||
<tr n:if="!is_null($club->getWebsite())">
|
||||
<td><span class="nobold">{_"website"}: </span></td>
|
||||
<td>
|
||||
<a href="{$club->getWebsite()}" rel="ugc" target="_blank">
|
||||
{$club->getWebsite()}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -105,17 +113,66 @@
|
|||
{_"group_type_open"}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div n:if="$club->getAdministratorsListDisplay() == 0">
|
||||
<div class="content_title_expanded" onclick="hidePanel(this);">
|
||||
{_"creator"}
|
||||
</div>
|
||||
<div style="padding:4px">
|
||||
<div class="avatar-list-item" style="padding: 8px;">
|
||||
{var author = $club->getOwner()}
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{$author->getURL()}"><b>{$author->getCanonicalName()}</b></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="avatar">
|
||||
<a href="{$author->getURL()}">
|
||||
<img class="ava" src="{$author->getAvatarUrl()}" />
|
||||
</a>
|
||||
</div>
|
||||
{* Это наверное костыль, ну да ладно *}
|
||||
<div n:class="info, mb_strlen($author->getCanonicalName()) < 22 ? info-centered" n:if="empty($club->getOwnerComment())">
|
||||
<a href="{$author->getURL()}" class="title">{$author->getCanonicalName()}</a>
|
||||
</div>
|
||||
<div class="info" n:if="!empty($club->getOwnerComment())">
|
||||
<a href="{$author->getURL()}" class="title">{$author->getCanonicalName()}</a>
|
||||
<div class="subtitle">{$club->getOwnerComment()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div n:if="$club->getAdministratorsListDisplay() == 1">
|
||||
{var managersCount = $club->getManagersCount(true)}
|
||||
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$managersCount});">
|
||||
{_"administrators"}
|
||||
</div>
|
||||
<div>
|
||||
<div class="content_subtitle">
|
||||
{tr("administrators", $managersCount)}
|
||||
<div style="float: right;">
|
||||
<a href="/club{$club->getId()}/followers?onlyAdmins=1">{_"all_title"}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="avatar-list">
|
||||
<div class="avatar-list-item" n:if="!$club->isOwnerHidden()">
|
||||
{var author = $club->getOwner()}
|
||||
<div class="avatar">
|
||||
<a href="{$author->getURL()}">
|
||||
<img class="ava" src="{$author->getAvatarUrl()}" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="info">
|
||||
<a href="{$author->getURL()}" class="title">{$author->getCanonicalName()}</a>
|
||||
<div class="subtitle" n:if="!empty($club->getOwnerComment())">{$club->getOwnerComment()}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="avatar-list-item" n:foreach="$club->getManagers(1, true) as $manager">
|
||||
{var user = $manager->getUser()}
|
||||
<div class="avatar">
|
||||
<a href="{$user->getURL()}">
|
||||
<img height="32" class="ava" src="{$user->getAvatarUrl()}" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="info">
|
||||
<a href="{$user->getURL()}" class="title">{$user->getCanonicalName()}</a>
|
||||
<div class="subtitle" n:if="!empty($manager->getComment())">{$manager->getComment()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div n:if="$albumsCount > 0">
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block link|strip|stripHtml}
|
||||
/note{$x->getOwner()->getId()}_{$x->getId()}
|
||||
/note{$x->getPrettyId()}
|
||||
{/block}
|
||||
|
||||
{block preview}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}Помощь{/block}
|
||||
{block title}{_menu_help}{/block}
|
||||
|
||||
{block header}
|
||||
{$ticket->getName()}
|
||||
|
@ -12,15 +12,15 @@
|
|||
{$ticket->getName()}
|
||||
</b>
|
||||
</a>
|
||||
<br></b>Автор: <a href="/id{$ticket->getUser()->getId()}">{$ticket->getUser()->getFullName()}</a> | {$ticket->getUser()->getRegistrationIP()} | Статус: {$ticket->getStatus()}
|
||||
<br></b>{_author}: <a href="/id{$ticket->getUser()->getId()}">{$ticket->getUser()->getFullName()}</a> | {$ticket->getUser()->getRegistrationIP()} | {_status}: {$ticket->getStatus()}.
|
||||
</div>
|
||||
<div class="text" style="padding-top: 10px;border-bottom: #ECECEC solid 1px;">
|
||||
{$ticket->getContext()}
|
||||
{$ticket->getText()|noescape}
|
||||
<br></br>
|
||||
</div>
|
||||
<div style="padding-top: 5px;">
|
||||
{$ticket->getTime()} |
|
||||
<a href="/support/delete/{$id}?hash={$csrfToken}">Удалить</a>
|
||||
<a href="/support/delete/{$id}?hash={$csrfToken}">{_delete}</a>
|
||||
</div><br/>
|
||||
<div>
|
||||
<form action="/al_comments.pl/create/support/reply/{$id}" method="post" style="margin:0;">
|
||||
|
@ -30,16 +30,17 @@
|
|||
</div>
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<br>
|
||||
<input type="submit" value="Ответить" class="button">
|
||||
<input type="submit" value="{_write}" class="button">
|
||||
<select name="status" style="width: unset;">
|
||||
<option value="1">Есть ответ</option>
|
||||
<option value="2">Закрыто</option>
|
||||
<option value="0">Вопрос на рассмотрении</option>
|
||||
<option value="1">{_support_status_1}</option>
|
||||
<option value="2">{_support_status_2}</option>
|
||||
<option value="0">{_support_status_0}</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
<br/>
|
||||
<p n:if="!$comments">Комментарии отсутствуют</p>
|
||||
<p n:if="!$comments">{_no_comments}</p>
|
||||
{var $printedSupportGreeting = false}
|
||||
<table n:foreach="$comments as $comment" border="0" style="font-size: 11px;" class="post">
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -50,7 +51,7 @@
|
|||
{else}
|
||||
<td width="54" valign="top">
|
||||
<img
|
||||
src="https://avatars.mds.yandex.net/get-zen_doc/164147/pub_5b60816e2dc67000a862253e_5b6081d4e9151400a9f48625/scale_1200"
|
||||
src="{$comment->getAvatar()}"
|
||||
style="max-width: 50px; filter: hue-rotate({$comment->getColorRotation()}deg);" />
|
||||
</td>
|
||||
{/if}
|
||||
|
@ -59,14 +60,14 @@
|
|||
<div class="post-author">
|
||||
<a href="{$comment->getUser()->getURL()}"><b>
|
||||
{$comment->getUser()->getFullName()}
|
||||
</b></a> написал<br>
|
||||
</b></a> {($comment->getUser()->isFemale() ? tr("post_writes_f") : tr("post_writes_m"))}<br>
|
||||
<a href="#" class="date">{$comment->getTime()}</a>
|
||||
</div>
|
||||
{elseif ($comment->getUType() === 1)}
|
||||
<div class="post-author">
|
||||
<a href="javascript:false">
|
||||
<b>
|
||||
{=OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["supportName"]} №{$comment->getAgentNumber()}
|
||||
{$comment->getAuthorName()}
|
||||
</b>
|
||||
</a>
|
||||
{if $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
|
||||
|
@ -76,18 +77,32 @@
|
|||
</span>
|
||||
</a>
|
||||
{/if}
|
||||
написал<br>
|
||||
{_post_writes_m}<br>
|
||||
<a href="#" class="date">{$comment->getTime()}</a>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="post-content" id="{$comment->getId()}">
|
||||
<div class="text" id="text{$comment->getId()}">
|
||||
{$comment->getContext()|noescape}
|
||||
{if $comment->getUType() === 1 && !$printedSupportGreeting}
|
||||
{var $printedSupportGreeting = true}
|
||||
{tr("support_greeting_hi", $ticket->getUser()->getFullName())}
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
{$comment->getText()|noescape}
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
{tr("support_greeting_regards", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"])|noescape}
|
||||
{else}
|
||||
{$comment->getText()|noescape}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{if $comment->getUType() === 0}
|
||||
<div class="post-menu">
|
||||
<a href="/support/comment/{$comment->getId()}/delete">Удалить</a>
|
||||
</div>
|
||||
<div class="post-menu">
|
||||
<a href="/support/comment/{$comment->getId()}/delete">{_delete}</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}Помощь{/block}
|
||||
{block title}{_menu_help}{/block}
|
||||
|
||||
{block header}
|
||||
Помощь
|
||||
{_menu_help}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
|
@ -14,13 +14,13 @@
|
|||
{if $thisUser}
|
||||
<div class="tabs">
|
||||
<div n:attr="id => ($isMain ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($isMain ? 'act_tab_a' : 'ki')" href="/support">Часто задаваемые вопросы</a>
|
||||
<a n:attr="id => ($isMain ? 'act_tab_a' : 'ki')" href="/support">{_support_faq}</a>
|
||||
</div>
|
||||
<div n:attr="id => ($isList ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($isList ? 'act_tab_a' : 'ki')" href="/support?act=list">Список обращений</a>
|
||||
<a n:attr="id => ($isList ? 'act_tab_a' : 'ki')" href="/support?act=list">{_support_list}</a>
|
||||
</div>
|
||||
<div n:attr="id => ($isNew ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($isNew ? 'act_tab_a' : 'ki')" href="/support?act=new">Новое обращение</a>
|
||||
<a n:attr="id => ($isNew ? 'act_tab_a' : 'ki')" href="/support?act=new">{_support_new}</a>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
|
@ -28,19 +28,19 @@
|
|||
{if $isNew}
|
||||
<div class="new">
|
||||
<form action="/support" method="post" style="margin:0;">
|
||||
<center><input name="name" style="width: 80%;resize: vertical;" placeholder="Введите тему вашего обращения"></center><br>
|
||||
<center><textarea name="text" style="width: 80%;resize: vertical;" placeholder="Опишите проблему или предложение"></textarea></center><br>
|
||||
<center><input name="name" style="width: 80%;resize: vertical;" placeholder="{_support_new_title}"></center><br>
|
||||
<center><textarea name="text" style="width: 80%;resize: vertical;" placeholder="{_support_new_content}"></textarea></center><br>
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<center><input type="submit" value="Написать" class="button" style="margin-left:70%;"></center><br>
|
||||
<center><input type="submit" value="{_write}" class="button" style="margin-left:70%;"></center><br>
|
||||
</form>
|
||||
</div>
|
||||
{/if}{/if}
|
||||
|
||||
{if $isMain}
|
||||
<h4>Часто задаваемые вопросы</h4><br>
|
||||
<h4>{_support_faq}</h4><br>
|
||||
<div class="faq">
|
||||
<div id="faqhead">Для кого этот сайт?</div>
|
||||
<div id="faqcontent">Сайт предназначен для поиска друзей и знакомых, а также просмотр данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке. Также этот сайт подойдёт для ностальгираторов и тех, кто решил слезть с трубы "ВКонтакте", которого клон и является.<br></div>
|
||||
<div id="faqhead">{_support_faq_title}</div>
|
||||
<div id="faqcontent">{_support_faq_content}</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
@ -49,7 +49,7 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<td width="54" valign="top">
|
||||
<center><img src="/assets/packages/static/openvk/img/note_icon.png" alt="Заметка" style="margin-top: 17px;"></center>
|
||||
<center><img src="/assets/packages/static/openvk/img/note_icon.png" alt="{_support_ticket}" style="margin-top: 17px;"></center>
|
||||
</td>
|
||||
<td width="345" valign="top">
|
||||
<div class="post-author">
|
||||
|
@ -58,7 +58,7 @@
|
|||
</a>
|
||||
</div>
|
||||
<div class="post-content" style="padding: 4px;font-size: 11px;">
|
||||
Статус: {$ticket->getStatus()}
|
||||
{_status}: {$ticket->getStatus()}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -5,18 +5,18 @@
|
|||
{/block}
|
||||
|
||||
{block header}
|
||||
Helpdesk » Тикеты
|
||||
Helpdesk » {_support_tickets}
|
||||
{/block}
|
||||
|
||||
{block tabs}
|
||||
<div n:attr="id => ($act === 'open' ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($act === 'open' ? 'act_tab_a' : 'ki')" href="?">Открытые</a>
|
||||
<a n:attr="id => ($act === 'open' ? 'act_tab_a' : 'ki')" href="?">{_support_opened}</a>
|
||||
</div>
|
||||
<div n:attr="id => ($act === 'answered' ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($act === 'answered' ? 'act_tab_a' : 'ki')" href="?act=answered">С ответом</a>
|
||||
<a n:attr="id => ($act === 'answered' ? 'act_tab_a' : 'ki')" href="?act=answered">{_support_answered}</a>
|
||||
</div>
|
||||
<div n:attr="id => ($act === 'closed' ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($act === 'closed' ? 'act_tab_a' : 'ki')" href="?act=closed">Закрытые</a>
|
||||
<a n:attr="id => ($act === 'closed' ? 'act_tab_a' : 'ki')" href="?act=closed">{_support_closed}</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
|
@ -28,7 +28,7 @@
|
|||
|
||||
{block preview}
|
||||
<center>
|
||||
<img src="/assets/packages/static/openvk/img/note_icon.png" alt="Тикет" style="margin-top: 8px;">
|
||||
<img src="/assets/packages/static/openvk/img/note_icon.png" alt="{_support_ticket}" style="margin-top: 8px;">
|
||||
</center>
|
||||
{/block}
|
||||
|
||||
|
@ -40,5 +40,5 @@
|
|||
{var author = $x->getUser()}
|
||||
|
||||
{ovk_proc_strtr($x->getContext(), 50)}<br/>
|
||||
<span class="nobold">Автор: </span> <a href="{$author->getURL()}">{$author->getCanonicalName()}</a>
|
||||
<span class="nobold">{_author}: </span> <a href="{$author->getURL()}">{$author->getCanonicalName()}</a>
|
||||
{/block}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}Помощь{/block}
|
||||
{block title}{_menu_help}{/block}
|
||||
|
||||
{block header}
|
||||
{$ticket->getName()}
|
||||
|
@ -13,15 +13,15 @@
|
|||
{$ticket->getName()}
|
||||
</b>
|
||||
</a>
|
||||
<br></b>Статус: {$ticket->getStatus()}
|
||||
<br></b>{_status}: {$ticket->getStatus()}
|
||||
</div>
|
||||
<div class="text" style="padding-top: 10px;border-bottom: #ECECEC solid 1px;">
|
||||
{$ticket->getContext()}
|
||||
{$ticket->getText()|noescape}
|
||||
<br></br>
|
||||
</div>
|
||||
<div style="padding-top: 5px;">
|
||||
{$ticket->getTime()} |
|
||||
<a href="/support/delete/{$id}?hash={$csrfToken}">Удалить</a>
|
||||
<a href="/support/delete/{$id}?hash={$csrfToken}">{_delete}</a>
|
||||
</div>
|
||||
{if $ticket->getType() !== 2}
|
||||
<br>
|
||||
|
@ -33,12 +33,13 @@
|
|||
</div>
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<br>
|
||||
<input type="submit" value="Написать" class="button">
|
||||
<input type="submit" value="{_write}" class="button">
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
</br>
|
||||
<p n:if="!$comments">Комментарии отсутствуют</p>
|
||||
<p n:if="!$comments">{_no_comments}</p>
|
||||
{var $printedSupportGreeting = false}
|
||||
<table n:foreach="$comments as $comment" border="0" style="font-size: 11px;" class="post">
|
||||
<tbody>
|
||||
<tr>
|
||||
|
@ -49,7 +50,7 @@
|
|||
{else}
|
||||
<td width="54" valign="top">
|
||||
<img
|
||||
src="https://avatars.mds.yandex.net/get-zen_doc/164147/pub_5b60816e2dc67000a862253e_5b6081d4e9151400a9f48625/scale_1200"
|
||||
src="{$comment->getAvatar()}"
|
||||
style="max-width: 50px; filter: hue-rotate({$comment->getColorRotation()}deg);" />
|
||||
</td>
|
||||
{/if}
|
||||
|
@ -58,24 +59,40 @@
|
|||
<div class="post-author">
|
||||
<a href="{$comment->getUser()->getURL()}"><b>
|
||||
{$comment->getUser()->getFullName()}
|
||||
</b></a> написал<br>
|
||||
</b></a> {($comment->getUser()->isFemale() ? tr("post_writes_f") : tr("post_writes_m"))}<br>
|
||||
<a href="#" class="date">{$comment->getTime()}</a>
|
||||
</div>
|
||||
{elseif ($comment->getUType() === 1)}
|
||||
<div class="post-author">
|
||||
<a href="#"><b>
|
||||
{=OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["supportName"]} №{$comment->getAgentNumber()}
|
||||
</b></a> написал<br>
|
||||
<a href="javascript:false">
|
||||
<b>
|
||||
{$comment->getAuthorName()}
|
||||
</b>
|
||||
</a>
|
||||
{_post_writes_m}<br>
|
||||
<a href="#" class="date">{$comment->getTime()}</a>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="post-content" id="{$comment->getId()}">
|
||||
<div class="text" id="text{$comment->getId()}">
|
||||
{$comment->getContext()|noescape}
|
||||
{if $comment->getUType() === 1 && !$printedSupportGreeting}
|
||||
{var $printedSupportGreeting = true}
|
||||
{tr("support_greeting_hi", $ticket->getUser()->getFullName())}
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
{$comment->getText()|noescape}
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
{tr("support_greeting_regards", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"])|noescape}
|
||||
{else}
|
||||
{$comment->getText()|noescape}
|
||||
{/if}
|
||||
</div>
|
||||
{if $comment->getUType() === 0}
|
||||
<div class="post-menu">
|
||||
<a href="/support/comment/{$comment->getId()}/delete">Удалить</a>
|
||||
<a href="/support/comment/{$comment->getId()}/delete">{_delete}</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -173,6 +173,14 @@
|
|||
<input type="text" name="telegram" value="{$user->getTelegram()}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_"personal_website"}: </span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="website" value="{$user->getWebsite()}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_"city"}: </span>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{extends "../@listView.xml"}
|
||||
{var iterator = $user->getClubs($page)}
|
||||
{var count = $user->getClubCount()}
|
||||
{var iterator = $user->getClubs($page, $admin)}
|
||||
{var count = $user->getClubCount($admin)}
|
||||
|
||||
{block title}{_"groups"}{/block}
|
||||
|
||||
|
@ -18,14 +18,23 @@
|
|||
</div>
|
||||
{/block}
|
||||
|
||||
{block actions}
|
||||
<div class="tile">
|
||||
<a href="javascript:alert('Не запилил')" class="profile_link">Поиск групп</a>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{* BEGIN ELEMENTS DESCRIPTION *}
|
||||
|
||||
{block tabs}
|
||||
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
|
||||
<div {if !$admin}id="activetabs"{/if} class="tab">
|
||||
<a {if !$admin}id="act_tab_a"{/if} href="/groups{$user->getId()}">
|
||||
{_groups}
|
||||
</a>
|
||||
</div>
|
||||
<div {if $admin}id="activetabs"{/if} class="tab">
|
||||
<a {if $admin}id="act_tab_a"{/if} href="/groups{$user->getId()}?act=managed">
|
||||
{_managed}
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
{/block}
|
||||
|
||||
{block link|strip|stripHtml}
|
||||
{$x->getURL()}
|
||||
{/block}
|
||||
|
@ -40,4 +49,23 @@
|
|||
|
||||
{block description}
|
||||
{$x->getDescription()}
|
||||
{if $x->canBeModifiedBy($thisUser ?? NULL)}
|
||||
{var clubPinned = $thisUser->isClubPinned($x)}
|
||||
<table n:if="$clubPinned || $thisUser->getPinnedClubCount() <= 10">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">{_actions}: </span></td>
|
||||
<td>
|
||||
<a href="/groups_pin?club={$x->getId()}&hash={rawurlencode($csrfToken)}" id="_pinGroup" data-group-name="{$x->getName()}" data-group-url="{$x->getUrl()}">
|
||||
{if $clubPinned}
|
||||
{_remove_from_left_menu}
|
||||
{else}
|
||||
{_add_to_left_menu}
|
||||
{/if}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
{/block}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
{var isMain = $mode === 'main'}
|
||||
{var isPrivacy = $mode === 'privacy'}
|
||||
{var isFinance = $mode === 'finance'}
|
||||
{var isFinanceTU = $mode === 'finance.top-up'}
|
||||
{var isInterface = $mode === 'interface'}
|
||||
|
||||
<div class="tabs">
|
||||
|
@ -19,8 +20,8 @@
|
|||
<div n:attr="id => ($isPrivacy ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($isPrivacy ? 'act_tab_a' : 'ki')" href="/settings?act=privacy">{_"privacy"}</a>
|
||||
</div>
|
||||
<div n:attr="id => ($isFinance ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($isFinance ? 'act_tab_a' : 'ki')" href="/settings?act=finance">{_points}</a>
|
||||
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce']" n:attr="id => (($isFinance || $isFinanceTU) ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => (($isFinance || $isFinanceTU) ? 'act_tab_a' : 'ki')" href="/settings?act=finance">{_points}</a>
|
||||
</div>
|
||||
<div n:attr="id => ($isInterface ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($isInterface ? 'act_tab_a' : 'ki')" href="/settings?act=interface">{_"interface"}</a>
|
||||
|
@ -58,6 +59,14 @@
|
|||
<input type="password" name="repeat_pass" style="width: 100%;" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr n:if="$user->is2faEnabled()">
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">{_"2fa_code"}</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="code" style="width: 100%;" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
|
@ -70,6 +79,70 @@
|
|||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
<h4>{_two_factor_authentication}</h4>
|
||||
<table cellspacing="7" cellpadding="0" width="60%" border="0" align="center">
|
||||
<tbody>
|
||||
{if $user->is2faEnabled()}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="accent-box">
|
||||
{_two_factor_authentication_enabled}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: center;">
|
||||
<a class="button" href="javascript:viewBackupCodes()">{_view_backup_codes}</a>
|
||||
<a class="button" href="javascript:disableTwoFactorAuth()">{_disable}</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<script>
|
||||
function viewBackupCodes() {
|
||||
MessageBox("Просмотр резервных кодов", `
|
||||
<form id="back-codes-view-form" method="post" action="/settings/2fa">
|
||||
<label for="password">Пароль</label>
|
||||
<input type="password" id="password" name="password" required />
|
||||
<input type="hidden" name="hash" value={$csrfToken} />
|
||||
</form>
|
||||
`, ["Просмотреть", "Отменить"], [
|
||||
() => {
|
||||
document.querySelector("#back-codes-view-form").submit();
|
||||
}, Function.noop
|
||||
]);
|
||||
}
|
||||
|
||||
function disableTwoFactorAuth() {
|
||||
MessageBox("Отключить 2FA", `
|
||||
<form id="two-factor-auth-disable-form" method="post" action="/settings/2fa/disable">
|
||||
<label for="password">Пароль</label>
|
||||
<input type="password" id="password" name="password" required />
|
||||
<input type="hidden" name="hash" value={$csrfToken} />
|
||||
</form>
|
||||
`, ["Отключить", "Отменить"], [
|
||||
() => {
|
||||
document.querySelector("#two-factor-auth-disable-form").submit();
|
||||
}, Function.noop
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
{else}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="accent-box">
|
||||
{_two_factor_authentication_disabled}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: center;">
|
||||
<a class="button" href="/settings/2fa">{_connect}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
<h4>{_your_email_address}</h4>
|
||||
<table cellspacing="7" cellpadding="0" width="60%" border="0" align="center">
|
||||
<tbody>
|
||||
|
@ -265,11 +338,26 @@
|
|||
<b>
|
||||
{_on_your_account}<br/>
|
||||
<span style="font-size: 50px;">{$thisUser->getCoins()}</span><br/>
|
||||
{_points_count}
|
||||
{_points_count}<br/><br/>
|
||||
<small><a href="?act=finance.top-up">[{_have_voucher}?]</a></small>
|
||||
</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{elseif $isFinanceTU}
|
||||
|
||||
<p>{_voucher_explanation} {_voucher_explanation_ex}</p>
|
||||
<form action="/settings?act=finance.top-up" method="POST" enctype="multipart/form-data">
|
||||
<input type="text" name="key0" size="6" placeholder="123456" required="required" style="display: inline-block; width: 50px; text-align: center;" /> -
|
||||
<input type="text" name="key1" size="6" placeholder="789012" required="required" style="display: inline-block; width: 50px; text-align: center;" /> -
|
||||
<input type="text" name="key2" size="6" placeholder="345678" required="required" style="display: inline-block; width: 50px; text-align: center;" /> -
|
||||
<input type="text" name="key3" size="6" placeholder="90ABCD" required="required" style="display: inline-block; width: 50px; text-align: center;" />
|
||||
<br/><br/>
|
||||
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="{_redeem}" class="button" />
|
||||
</form>
|
||||
|
||||
{elseif $isInterface}
|
||||
|
||||
<h4>{_ui_settings_interface}</h4>
|
||||
|
|
18
Web/Presenters/templates/User/TwoFactorAuthCodes.xml
Normal file
18
Web/Presenters/templates/User/TwoFactorAuthCodes.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}{_"my_settings"} - {_"two_factor_authentication"}{/block}
|
||||
|
||||
{block header}
|
||||
<a href="/settings">{_"my_settings"}</a> » {_"two_factor_authentication"}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<h4>{_"backup_codes"}</h4>
|
||||
<p>{_"two_factor_authentication_backup_codes_1"}</p>
|
||||
<p>{_"two_factor_authentication_backup_codes_2"|noescape}</p>
|
||||
|
||||
<ol style="columns: 2; text-align: center;">
|
||||
<li n:foreach="$codes as $code">{$code}</li>
|
||||
</ol>
|
||||
|
||||
<p>{_"two_factor_authentication_backup_codes_3"}</p>
|
||||
{/block}
|
48
Web/Presenters/templates/User/TwoFactorAuthSettings.xml
Normal file
48
Web/Presenters/templates/User/TwoFactorAuthSettings.xml
Normal file
|
@ -0,0 +1,48 @@
|
|||
{extends "../@layout.xml"}
|
||||
{block title}{_"my_settings"} - {_"two_factor_authentication"}{/block}
|
||||
|
||||
{block header}
|
||||
<a href="/settings">{_"my_settings"}</a> » {_"two_factor_authentication"}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
{_"two_factor_authentication_settings_1"|noescape}
|
||||
<p>{_"two_factor_authentication_settings_2"}</p>
|
||||
<div style="text-align: center;">
|
||||
<img src="data:image/png;base64,{$qrCode}">
|
||||
</div>
|
||||
<p>{tr("two_factor_authentication_settings_3", $secret)|noescape}</p>
|
||||
<p>{_"two_factor_authentication_settings_4"}</p>
|
||||
<form method="POST">
|
||||
<table cellspacing="7" cellpadding="0" width="40%" border="0" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span>{_code}: </span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="code" required />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span>{_password}: </span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="password" name="password" required />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="secret" value="{$secret}" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="{_enable}" class="button" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
{/block}
|
|
@ -34,10 +34,10 @@
|
|||
|
||||
<!-- DEBUG: ONLINE REPORT: static {$user->getOnline()->timestamp()}s adjusted {$user->getOnline()->timestamp() + 2505600}s real {time()}s -->
|
||||
<div n:if="$user->getOnline()->timestamp() + 2505600 > time()" style="float:right;">
|
||||
{if $diff->i <= 5}
|
||||
<span><b>{_online}</b></span>
|
||||
{if $user->isOnline()}
|
||||
<span><b>{_online}</b></span>
|
||||
{else}
|
||||
<span>{_was_online} {$user->getOnline()}</span>
|
||||
<span>{_was_online} {$user->getOnline()}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div n:if="$user->onlineStatus() == 2" style="float:right;">
|
||||
|
@ -76,6 +76,9 @@
|
|||
{/if}
|
||||
|
||||
{if $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)}
|
||||
<a href="/admin/users/id{$user->getId()}" class="profile_link">
|
||||
{_manage_user_action}
|
||||
</a>
|
||||
<a href="javascript:banUser()" class="profile_link">
|
||||
{_ban_user_action}
|
||||
</a>
|
||||
|
@ -84,6 +87,8 @@
|
|||
</a>
|
||||
{/if}
|
||||
|
||||
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce']" href="/gifts?act=pick&user={$user->getId()}" class="profile_link">{_send_gift}</a>
|
||||
|
||||
{var subStatus = $user->getSubscriptionStatus($thisUser)}
|
||||
{if $subStatus === 0}
|
||||
<form action="/setSub/user" method="post">
|
||||
|
@ -120,8 +125,8 @@
|
|||
<div n:if="isset($thisUser) && !$thisUser->prefersNotToSeeRating()" class="profile-hints">
|
||||
{var completeness = $user->getProfileCompletenessReport()}
|
||||
|
||||
<div class="completeness-gauge">
|
||||
<div style="width: {$completeness->total}%"></div>
|
||||
<div n:class="completeness-gauge, $completeness->total >= 100 ? completeness-gauge-gold">
|
||||
<div style="width: {$completeness->percent}%"></div>
|
||||
<span>{$completeness->total}%</span>
|
||||
</div>
|
||||
|
||||
|
@ -150,6 +155,30 @@
|
|||
{/if}
|
||||
</div>
|
||||
<br />
|
||||
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && ($giftCount = $user->getGiftCount()) > 0">
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$giftCount});">
|
||||
{_gifts}
|
||||
</div>
|
||||
<div>
|
||||
<div class="content_subtitle">
|
||||
{tr("gifts", $giftCount)}
|
||||
<div style="float:right;">
|
||||
<a href="/gifts{$user->getId()}">{_all_title}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ovk-avView">
|
||||
<div class="ovk-avView--el" n:foreach="$user->getGifts(1, 3) as $giftDescriptor">
|
||||
{var hideInfo = !is_null($thisUser) ? ($giftDescriptor->anon ? $thisUser->getId() !== $user->getId() : false) : false}
|
||||
|
||||
<a href="{$hideInfo ? 'javascript:false' : $giftDescriptor->sender->getURL()}">
|
||||
<img class="ava"
|
||||
src="{$giftDescriptor->gift->getImage(2)}"
|
||||
alt="{$hideInfo ? tr('gift') : ($giftDescriptor->caption ?? tr('gift'))}" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div n:if="$user->getFriendsCount() > 0 && $user->getPrivacyPermission('friends.read', $thisUser ?? NULL)">
|
||||
{var friendCount = $user->getFriendsCount()}
|
||||
|
||||
|
@ -227,7 +256,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div n:if="$videosCount > 0 && $user->getPrivacyPermission('videos.read', $thisUser ?? NULL)">
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$albumsCount});">
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$videosCount});">
|
||||
{_videos}
|
||||
</div>
|
||||
<div>
|
||||
|
@ -238,21 +267,22 @@
|
|||
</div>
|
||||
</div>
|
||||
<div style="padding: 5px;">
|
||||
<div class="ovk-video" style="margin-bottom: 1rem; padding: 0 11px;" n:foreach="$videos as $video">
|
||||
<div style="width: 170px;" align="center">
|
||||
<div class="ovk-video" n:foreach="$videos as $video">
|
||||
<a href="/video{$video->getPrettyId()}" class="preview" align="center">
|
||||
<img
|
||||
src="{$video->getThumbnailURL()}"
|
||||
style="max-width: 170px; margin: auto;" />
|
||||
</div>
|
||||
style="max-width: 170px; max-height: 127px; margin: auto;" />
|
||||
</a>
|
||||
<div>
|
||||
<b><a href="/video{$video->getPrettyId()}">{ovk_proc_strtr($video->getName(), 30)}</a></b>
|
||||
<b><a href="/video{$video->getPrettyId()}">{ovk_proc_strtr($video->getName(), 30)}</a></b><br>
|
||||
<span style="font-size: 10px;">{$video->getPublicationTime()} | {_comments} ({$video->getCommentsCount()})</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div n:if="$notesCount > 0 && $user->getPrivacyPermission('notes.read', $thisUser ?? NULL)">
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$albumsCount});">
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$notesCount});">
|
||||
{_notes}
|
||||
</div>
|
||||
<div>
|
||||
|
@ -266,13 +296,13 @@
|
|||
<div style="padding: 5px 8px 15px 8px;">
|
||||
<ul class="notes_titles" n:foreach="$notes as $note">
|
||||
<li class="written">
|
||||
<a href="/note{$user->getId()}_{$note->getId()}">
|
||||
<a href="/note{$note->getPrettyId()}">
|
||||
{$note->getName()}
|
||||
</a>
|
||||
<small>
|
||||
{$note->getPublicationTime()}
|
||||
<span class="divide">|</span>
|
||||
<a href="/note{$user->getId()}_{$note->getId()}">{_comments}</a>
|
||||
<a href="/note{$note->getPrettyId()}">{_comments}</a>
|
||||
</small>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -322,52 +352,64 @@
|
|||
|
||||
<div class="right_big_block">
|
||||
<div class="page_info">
|
||||
|
||||
<div class="accountInfo clearFix">
|
||||
<div class="profileName">
|
||||
<h2>{$user->getFullName()}</h2>
|
||||
{if !is_null($user->getStatus())}
|
||||
<div class="page_status">{$user->getStatus()}</div>
|
||||
{elseif isset($thisUser) && $user->getId() == $thisUser->getId()}
|
||||
<div class="page_status">
|
||||
<a href="/edit" class="edit_link">[ {_"change_status"} ]</a>
|
||||
<div n:if="!is_null($alert = $user->getAlert())" class="user-alert">{$alert}</div>
|
||||
{var thatIsThisUser = isset($thisUser) && $user->getId() == $thisUser->getId()}
|
||||
<div n:if="$thatIsThisUser" class="page_status_popup" id="status_editor" style="display: none;">
|
||||
<form method="post" action="/edit?act=status">
|
||||
<div style="margin-bottom: 10px;">
|
||||
<input type="text" name="status" size="50" value="{$user->getStatus()}" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div><div>
|
||||
<table id="basicInfo" class="ugc-table" border="0" cellspacing="0" cellpadding="0" border="0" cellspacing="0" cellpadding="0" n:if=" $user->getPrivacyPermission('page.info.read', $thisUser ?? NULL)">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="label"><span class="nobold">{_"gender"}: </span></td>
|
||||
<td class="data">{$user->isFemale() ? tr("female") : tr("male")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><span class="nobold">{_"relationship"}:</span></td>
|
||||
<td class="data">{var $marialStatus = $user->getMaritalStatus()}{_"relationship_$marialStatus"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><span class="nobold">{_"registration_date"}: </span></td>
|
||||
<td class="data">{$user->getRegistrationTime()}</td>
|
||||
</tr>
|
||||
<tr n:if="!is_null($user->getHometown())">
|
||||
<td class="label"><span class="nobold">{_"hometown"}:</span></td>
|
||||
<td class="data">{$user->getHometown()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><span class="nobold">{_"politViews"}:</span></td>
|
||||
<td class="data">{var $pviews = $user->getPoliticalViews()}{_"politViews_$pviews"}</td>
|
||||
</tr>
|
||||
{if $user->getBirthday() != 0}
|
||||
<tr>
|
||||
<td class="label"><span class="nobold">{_"birth_date"}:</span></td>
|
||||
<td class="data">{date('d F Y',$user->getBirthday())}, {date('Y') - date('Y', $user->getBirthday())} {_"years"}</td>
|
||||
</tr>
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" class="button" value="{_'save'}" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="accountInfo clearFix">
|
||||
<div class="profileName">
|
||||
<h2>{$user->getFullName()}</h2>
|
||||
{if !is_null($user->getStatus())}
|
||||
<div n:class="page_status, $thatIsThisUser ? page_status_edit_button" n:attr="id => $thatIsThisUser ? page_status_text : NULL">{$user->getStatus()}</div>
|
||||
{elseif $thatIsThisUser}
|
||||
<div class="page_status">
|
||||
<div n:class="edit_link, $thatIsThisUser ? page_status_edit_button" id="page_status_text">[ {_"change_status"} ]</div>
|
||||
</div>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<table id="basicInfo" class="ugc-table" border="0" cellspacing="0" cellpadding="0" border="0" cellspacing="0" cellpadding="0" n:if=" $user->getPrivacyPermission('page.info.read', $thisUser ?? NULL)">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="label"><span class="nobold">{_"gender"}: </span></td>
|
||||
<td class="data">{$user->isFemale() ? tr("female") : tr("male")}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><span class="nobold">{_"relationship"}:</span></td>
|
||||
<td class="data">{var $marialStatus = $user->getMaritalStatus()}{_"relationship_$marialStatus"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><span class="nobold">{_"registration_date"}: </span></td>
|
||||
<td class="data">{$user->getRegistrationTime()}</td>
|
||||
</tr>
|
||||
<tr n:if="!is_null($user->getHometown())">
|
||||
<td class="label"><span class="nobold">{_"hometown"}:</span></td>
|
||||
<td class="data">{$user->getHometown()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label"><span class="nobold">{_"politViews"}:</span></td>
|
||||
<td class="data">{var $pviews = $user->getPoliticalViews()}{_"politViews_$pviews"}</td>
|
||||
</tr>
|
||||
{if $user->getBirthday() > 0}
|
||||
<tr>
|
||||
<td class="label"><span class="nobold">{_"birth_date"}:</span></td>
|
||||
<td class="data">{date('d F Y',$user->getBirthday())},
|
||||
{tr("years", $user->getAge())}</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div n:if=" $user->getPrivacyPermission('page.info.read', $thisUser ?? NULL)">
|
||||
<div n:if="$user->getPrivacyPermission('page.info.read', $thisUser ?? NULL)">
|
||||
<div class="content_title_expanded" onclick="hidePanel(this);">
|
||||
{_"information"}
|
||||
</div>
|
||||
|
@ -375,7 +417,6 @@
|
|||
{capture $contactInfo_Tmp}
|
||||
<table class="ugc-table" border="0" cellspacing="0" cellpadding="0" border="0" cellspacing="0" cellpadding="0" n:ifcontent>
|
||||
<tbody n:ifcontent>
|
||||
<!--sse-->
|
||||
<tr n:if="!is_null($user->getContactEmail())">
|
||||
<td class="label"><span class="nobold">{_"email"}: </span></td>
|
||||
<td>
|
||||
|
@ -392,7 +433,14 @@
|
|||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<!--/sse-->
|
||||
<tr n:if="!is_null($user->getWebsite())">
|
||||
<td class="label"><span class="nobold">{_"personal_website"}: </span></td>
|
||||
<td>
|
||||
<a href="{$user->getWebsite()}" rel="ugc" target="_blank">
|
||||
{$user->getWebsite()}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr n:if="!is_null($user->getCity())">
|
||||
<td class="label"><span class="nobold">{_"city"}:</span></td>
|
||||
<td class="data">{$user->getCity()}</td>
|
||||
|
@ -440,26 +488,21 @@
|
|||
{/capture}
|
||||
<div>
|
||||
<div style="padding: 10px 8px 15px 8px;" n:ifcontent>
|
||||
<h4 style="border-bottom: none; font-size: 11px; padding: 0; display: inline-block;">{_"contact_information"} {ifset $thisUser}{if $thisUser->getId() == $user->getId()}<a href="/edit?act=contacts" class="edit_link">[ {_"edit"} ]</a>{/if}{/ifset}</h4>
|
||||
{if !empty($contactInfo_Tmp)}
|
||||
<h4 style="border-bottom: none; font-size: 11px; padding: 0; display: inline-block;">{_"contact_information"} {ifset $thisUser}{if $thisUser->getId() == $user->getId()}<a href="/edit?act=contacts" class="edit_link">[ {_"edit"} ]</a>{/if}{/ifset}</h4>
|
||||
{if !empty($contactInfo_Tmp)}
|
||||
{$contactInfo_Tmp|noescape}
|
||||
{else}
|
||||
<div style="padding: 15px;color:gray;text-align: center;">{_no_information_provided}</div>
|
||||
{/if}
|
||||
<br>
|
||||
{$contactInfo_Tmp|noescape}
|
||||
{else}
|
||||
<div style="padding: 15px;color:gray;text-align: center;">{_no_information_provided}</div>
|
||||
{/if}
|
||||
<br>
|
||||
<h4 style="border-bottom: none; font-size: 11px; padding: 0; display: inline-block;">{_"personal_information"} {ifset $thisUser}{if $thisUser->getId() == $user->getId()}<a href="/edit?act=interests" class="edit_link">[ {_"edit"} ]</a>{/if}{/ifset}</h4>
|
||||
{if !empty($uInfo_Tmp)}
|
||||
{$uInfo_Tmp|noescape}
|
||||
{else}
|
||||
<div style="padding-top: 15px;color:gray;text-align: center;">{_no_information_provided}</div>
|
||||
{/if}
|
||||
<h4 style="border-bottom: none; font-size: 11px; padding: 0; display: inline-block;">{_"personal_information"} {ifset $thisUser}{if $thisUser->getId() == $user->getId()}<a href="/edit?act=interests" class="edit_link">[ {_"edit"} ]</a>{/if}{/ifset}</h4>
|
||||
{if !empty($uInfo_Tmp)}
|
||||
{$uInfo_Tmp|noescape}
|
||||
{else}
|
||||
<div style="padding-top: 15px;color:gray;text-align: center;">{_no_information_provided}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<p n:if="empty($contactInfo_Tmp) && empty($uInfo_Tmp)">
|
||||
Пользователь предпочёл оставить о себе только воздух тайны.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{presenter "openvk!Wall->wallEmbedded", $user->getId()}
|
||||
|
@ -509,6 +552,19 @@
|
|||
]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script n:if="isset($thisUser) && $user->getId() == $thisUser->getId()">
|
||||
function setStatusEditorShown(shown) {
|
||||
document.getElementById("status_editor").style.display = shown ? "block" : "none";
|
||||
}
|
||||
|
||||
document.addEventListener("click", event => {
|
||||
if(!event.target.closest("#status_editor") && !event.target.closest("#page_status_text"))
|
||||
setStatusEditorShown(false);
|
||||
});
|
||||
|
||||
document.getElementById("page_status_text").onclick = setStatusEditorShown.bind(this, true);
|
||||
</script>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
|
|
|
@ -14,26 +14,8 @@
|
|||
{/block}
|
||||
|
||||
{block content}
|
||||
<div>
|
||||
<div class="content_title_expanded" onclick="hidePanel(this);">
|
||||
{_"publish_post"}
|
||||
</div>
|
||||
<div style="margin: 0 5px;"><br>
|
||||
<form action="/wall{$thisUser->getId()}/makePost" method="POST" enctype="multipart/form-data" style="margin:0;">
|
||||
<textarea id="wall-post-input" name="text" style="width: 100%;resize: none;"></textarea>
|
||||
<input type="file" name="_pic_attachment" accept="image/*" style="display:none;" />
|
||||
<input type="hidden" name="type" value="1" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<div></div>
|
||||
<br/>
|
||||
<input type="submit" value="{_'write'}" class="button" />
|
||||
<div style="float: right;">
|
||||
<a href="javascript:void(document.querySelector(`input[name=_pic_attachment]`).click());">
|
||||
{_"attach_photo"}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="postFeedWrapper">
|
||||
{include "../components/textArea.xml", route => "/wall" . $thisUser->getId() . "/makePost"}
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
|
@ -41,7 +23,7 @@
|
|||
{foreach $posts as $post}
|
||||
<a name="postGarter={$post->getId()}"></a>
|
||||
|
||||
{include "../components/post.xml", post => $post, onWallOf => true}
|
||||
{include "../components/post.xml", post => $post, onWallOf => true, commentSection => true}
|
||||
{/foreach}
|
||||
{include "../components/paginator.xml", conf => $paginatorConf}
|
||||
<br/>
|
||||
|
|
|
@ -15,52 +15,8 @@
|
|||
{block content}
|
||||
<div class="content_divider">
|
||||
<div>
|
||||
<!-- TODO: Move the creating post form to dedicated file -->
|
||||
<div n:if="$canPost" class="content_subtitle">
|
||||
<div id="write" style="padding: 5px 0;" >
|
||||
<form action="/wall{$owner}/makePost" method="post" enctype="multipart/form-data" style="margin:0;">
|
||||
<textarea id="wall-post-input" placeholder="{_write}" name="text" style="width: 100%;resize: none;" class="expanded-textarea"></textarea>
|
||||
<div>
|
||||
<!-- padding to fix <br/> bug -->
|
||||
</div>
|
||||
<div id="post-buttons">
|
||||
<div class="post-upload">
|
||||
Вложение: <span>(unknown)</span>
|
||||
</div>
|
||||
<div class="post-opts">
|
||||
{if !is_null($thisUser) && $owner < 0 && $club->canBeModifiedBy($thisUser)}
|
||||
<script>
|
||||
function onWallAsGroupClick(el) {
|
||||
_display = el.checked ? "block" : "none";
|
||||
document.querySelector("#forceSignOpt").style.display = _display;
|
||||
}
|
||||
</script>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" name="as_group" onchange="onWallAsGroupClick(this)" /> От имени сообщества
|
||||
</label>
|
||||
<label id="forceSignOpt" style="display: none;">
|
||||
<input type="checkbox" name="force_sign" /> Подпись автора
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<label>
|
||||
<input type="checkbox" name="nsfw" /> Содержит NSFW-контент
|
||||
</label>
|
||||
</div>
|
||||
<input type="file" name="_pic_attachment" accept="image/*" style="display:none;" />
|
||||
<input type="hidden" name="type" value="1" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<br/>
|
||||
<input type="submit" value="{_'write'}" class="button" />
|
||||
<div style="float: right;">
|
||||
<a href="javascript:void(document.querySelector(`input[name=_pic_attachment]`).click());">
|
||||
{_attach_photo}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{include "../components/textArea.xml", route => "/wall$owner/makePost"}
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
|
@ -68,7 +24,7 @@
|
|||
{foreach $posts as $post}
|
||||
<a name="postGarter={$post->getId()}"></a>
|
||||
|
||||
{include "../components/post.xml", post => $post}
|
||||
{include "../components/post.xml", post => $post, commentSection => true}
|
||||
{/foreach}
|
||||
{include "../components/paginator.xml", conf => $paginatorConf}
|
||||
{else}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{if $attachment instanceof \openvk\Web\Models\Entities\Photo}
|
||||
{if !$attachment->isDeleted()}
|
||||
<a href="/photo{$attachment->getPrettyId()}">
|
||||
{var link = "/photo" . ($attachment->isAnonymous() ? ("s/" . base_convert((string) $attachment->getId(), 10, 32)) : $attachment->getPrettyId())}
|
||||
<a href="{$link}">
|
||||
<img class="media" src="{$attachment->getURL()}" alt="{$attachment->getDescription()}" />
|
||||
</a>
|
||||
{else}
|
||||
|
@ -8,6 +9,8 @@
|
|||
<img class="media" src="/assets/packages/static/openvk/img/camera_200.png" alt="{_"attach_no_longer_available"}" />
|
||||
</a>
|
||||
{/if}
|
||||
{elseif $attachment instanceof \openvk\Web\Models\Entities\Video}
|
||||
<video class="media" src="{$attachment->getURL()}" controls="controls"></video>
|
||||
{elseif $attachment instanceof \openvk\Web\Models\Entities\Post}
|
||||
{php $GLOBALS["_nesAttGloCou"] = (isset($GLOBALS["_nesAttGloCou"]) ? $GLOBALS["_nesAttGloCou"] : 0) + 1}
|
||||
{if $GLOBALS["_nesAttGloCou"] > 2}
|
||||
|
|
|
@ -1,36 +1,44 @@
|
|||
{var author = $comment->getOwner()}
|
||||
{var $Club = openvk\Web\Models\Entities\Club::class}
|
||||
|
||||
<a name="cid={$comment->getId()}"></a>
|
||||
<table border="0" style="font-size: 11px;" class="post">
|
||||
<table border="0" style="font-size: 11px;" class="post comment" id="_comment{$comment->getId()}" data-comment-id="{$comment->getId()}" data-owner-id="{$author->getId()}" data-from-group="{$comment->getOwner() instanceof $Club}">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="54" valign="top">
|
||||
<td width="30" valign="top">
|
||||
<img
|
||||
src="{$author->getAvatarURL()}"
|
||||
width="50" />
|
||||
width="30"
|
||||
class="cCompactAvatars" />
|
||||
</td>
|
||||
<td width="345" valign="top">
|
||||
<td width="100%" valign="top">
|
||||
<div class="post-author">
|
||||
<a href="{$author->getURL()}"><b>
|
||||
{$author->getCanonicalName()}
|
||||
</b></a> {$author->isFemale() ? tr("post_writes_f") : tr("post_writes_m")}<br/>
|
||||
<a href="/comment{$comment->getId()}" class="date">{$comment->getPublicationTime()}</a>
|
||||
</b></a><br/>
|
||||
</div>
|
||||
<div class="post-content" id="{$comment->getId()}">
|
||||
<div class="text" id="text{$comment->getId()}">
|
||||
{$comment->getText()|noescape}
|
||||
|
||||
<div n:ifcontent class="attachments_b">
|
||||
<div class="attachment" n:foreach="$comment->getChildren() as $attachment" data-localized-nsfw-text="{_nsfw_warning}">
|
||||
{include "attachment.xml", attachment => $attachment}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu">
|
||||
<a href="#_comment{$comment->getId()}" class="date">{$comment->getPublicationTime()}</a> |
|
||||
{var canDelete = $comment->getOwner()->getId() == $thisUser->getId()}
|
||||
{var canDelete = $canDelete || $comment->getTarget()->getOwner()->getId() == $thisUser->getId()}
|
||||
{if $canDelete}
|
||||
<a href="/comment{$comment->getId()}/delete">{_"delete"}</a>
|
||||
<a href="/comment{$comment->getId()}/delete">{_"delete"}</a> |
|
||||
{/if}
|
||||
|
||||
<a class="comment-reply">Ответить</a>
|
||||
<div style="float: right; font-size: .7rem;">
|
||||
<a href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}">
|
||||
<a class="post-like-button" href="/comment{$comment->getId()}/like?hash={rawurlencode($csrfToken)}">
|
||||
<div class="heart" style="{if $comment->hasLikeFrom($thisUser)}opacity: 1;{else}opacity: 0.4;{/if}"></div>
|
||||
<span class="likeCnt">{$comment->getLikesCount()}</span>
|
||||
<span class="likeCnt">{if $comment->getLikesCount() > 0}{$comment->getLikesCount()}{/if}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
<h4>{_"comments"} ({$count})</h4>
|
||||
|
||||
<div n:ifset="$thisUser">
|
||||
<form action="/al_comments.pl/create/{$model}/{$parent->getId()}" method="POST" style="margin-top: 2pt;">
|
||||
<textarea name="text" style="width: 411px; margin: 0px; height: 53px; resize: none;"></textarea>
|
||||
<br/>
|
||||
<input type="hidden" value="{$csrfToken}" name="hash" />
|
||||
<input type="submit" value="{_'write'}" class="button" style="bottom-right: 50px;" />
|
||||
</form>
|
||||
{var commentsURL = "/al_comments.pl/create/$model/" . $parent->getId()}
|
||||
{var club = $parent instanceof \openvk\Web\Models\Entities\Post && $parent->getTargetWall() < 0 ? (new openvk\Web\Models\Repositories\Clubs)->get(abs($parent->getTargetWall())) : NULL}
|
||||
{include "textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), club => $club}
|
||||
</div>
|
||||
|
||||
{if sizeof($comments) > 0}
|
||||
|
@ -28,3 +25,5 @@
|
|||
{/if} -->
|
||||
{_"comments_tip"}
|
||||
{/if}
|
||||
|
||||
{script "js/al_comments.js"}
|
||||
|
|
|
@ -4,8 +4,3 @@
|
|||
<a href="{$user->getURL()}"><b>{$user->getCanonicalName()}</b></a>
|
||||
{$notification->getDateTime()} {_nt_liked_yours}
|
||||
<a href="/wall{$post->getPrettyId()}"><b>{_nt_post_nominative}</b></a> {_nt_from} {$post->getPublicationTime()}.
|
||||
|
||||
<?php
|
||||
// костыльно скрыл лол, сами исправите проблему - гфх
|
||||
//{tr('notifications_like', '<a href="'.$user->getURL().'"><b>'.$user->getCanonicalName().'</b></a>', '<a href="/wall'.$post->getPrettyId().'"><b>', '</b></a>', $post->getPublicationTime())}
|
||||
?>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{var gift = $notification->getModel(0)}
|
||||
{var sender = $notification->getModel(1)}
|
||||
|
||||
<a href="{$sender->getURL()}"><b>{$sender->getCanonicalName()}</b></a> отправил вам {$notification->getDateTime()} подарок.
|
|
@ -1,14 +1,16 @@
|
|||
<div n:if="!($conf->page === 1 && $conf->count <= $conf->perPage)" class="paginator">
|
||||
<br/>
|
||||
<center>
|
||||
<a n:if="$conf->page != 1"
|
||||
href="?{http_build_query(array_merge($_GET, ['p' => ($conf->page - 1)]), 'k', '&', PHP_QUERY_RFC3986)}"
|
||||
style="float: left;"><< {_paginator_back}</a>
|
||||
|
||||
{tr("paginator_page", $conf->page)}
|
||||
|
||||
<a n:if="$conf->count > (($conf->page - 1) * $conf->perPage + $conf->amount) && $conf->amount > 0"
|
||||
href="?{http_build_query(array_merge($_GET, ['p' => ($conf->page + 1)]), 'k', '&', PHP_QUERY_RFC3986)}"
|
||||
style="float: right;">{_paginator_next} >></a>
|
||||
</center>
|
||||
{var $space = 2}
|
||||
{var $pageCount = ceil($conf->count / $conf->perPage)}
|
||||
|
||||
<div n:if="!($conf->page === 1 && $conf->count <= $conf->perPage)" n:class="paginator, $conf->atBottom ? paginator-at-bottom">
|
||||
{if $conf->page > $space}
|
||||
<a n:attr="class => ($conf->page === 1 ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => 1]), 'k', '&', PHP_QUERY_RFC3986)}">«</a>
|
||||
{/if}
|
||||
{for $j = $conf->page - ($space-1); $j <= $conf->page + ($space-1); $j++}
|
||||
{if $j > 0 && $j <= $pageCount}
|
||||
<a n:attr="class => ($conf->page === $j ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => $j]), 'k', '&', PHP_QUERY_RFC3986)}">{$j}</a>
|
||||
{/if}
|
||||
{/for}
|
||||
{if $conf->page <= $pageCount-$space}
|
||||
<a n:attr="class => ($conf->page === $pageCount ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => $pageCount]), 'k', '&', PHP_QUERY_RFC3986)}">»</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
{var microblogEnabled = isset($thisUser) ? $thisUser->hasMicroblogEnabled() : false}
|
||||
{if !$post->isPostedOnBehalfOfGroup()}
|
||||
{var then = date_create("@" . $post->getOwner()->getOnline()->timestamp())}
|
||||
{var now = date_create()}
|
||||
{var diff = date_diff($now, $then)}
|
||||
{/if}
|
||||
|
||||
{if $microblogEnabled}
|
||||
{include "post/microblogpost.xml", post => $post}
|
||||
{include "post/microblogpost.xml", post => $post, diff => $diff, commentSection => $commentSection}
|
||||
{else}
|
||||
{include "post/oldpost.xml", post => $post}
|
||||
{include "post/oldpost.xml", post => $post, diff => $diff}
|
||||
{/if}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
{var author = $post->getOwner()}
|
||||
{var comments = $post->getLastComments(3)}
|
||||
{var commentsCount = $post->getCommentsCount()}
|
||||
|
||||
{var commentTextAreaId = $post === null ? rand(1,300) : $post->getId()}
|
||||
|
||||
<table border="0" style="font-size: 11px;" n:class="post, !$compact ? post-divider, $post->isExplicit() ? post-nsfw">
|
||||
<tbody>
|
||||
|
@ -7,6 +11,11 @@
|
|||
<img
|
||||
src="{$author->getAvatarURL()}"
|
||||
width="{ifset $compact}25{else}50{/ifset}" />
|
||||
{if !$post->isPostedOnBehalfOfGroup() && !$compact}
|
||||
<span n:if="$author->isOnline()" class="post-online">
|
||||
{_online}
|
||||
</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td width="100%" valign="top">
|
||||
<div class="post-author">
|
||||
|
@ -18,24 +27,22 @@
|
|||
{if $author->isVerified()}<img class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">{/if}
|
||||
{ifset $compact}<br>
|
||||
<a href="/wall{$post->getPrettyId()}" class="date">
|
||||
{if $post->isPinned()}
|
||||
{$post->getPublicationTime()},
|
||||
{_pinned}
|
||||
{else}
|
||||
{$post->getPublicationTime()}
|
||||
{/if}
|
||||
{$post->getPublicationTime()}
|
||||
</a>
|
||||
|
||||
{/ifset}
|
||||
{if $post->isPinned()}
|
||||
<span class="nobold">{_pinned}</span>
|
||||
{/if}
|
||||
{if $post->canBeDeletedBy($thisUser) && !($forceNoDeleteLink ?? false) && !isset($compact)}
|
||||
<a class="delete" href="/wall{$post->getPrettyId()}/delete"></a>
|
||||
{/if}
|
||||
|
||||
{if $post->canBePinnedBy($thisUser) && !($forceNoPinLink ?? false) && !isset($compact)}
|
||||
{if $post->isPinned()}
|
||||
<a class="delete" href="/wall{$post->getPrettyId()}/pin?act=unpin&hash={rawurlencode($csrfToken)}"></a>
|
||||
<a class="pin" href="/wall{$post->getPrettyId()}/pin?act=unpin&hash={rawurlencode($csrfToken)}"></a>
|
||||
{else}
|
||||
<a class="delete" href="/wall{$post->getPrettyId()}/pin?act=pin&hash={rawurlencode($csrfToken)}"></a>
|
||||
<a class="pin" href="/wall{$post->getPrettyId()}/pin?act=pin&hash={rawurlencode($csrfToken)}"></a>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -69,23 +76,15 @@
|
|||
|
||||
|
||||
{if !($forceNoCommentsLink ?? false)}
|
||||
<a href="/wall{$post->getPrettyId()}#comments">
|
||||
{if $post->getCommentsCount() > 0}
|
||||
{_"comments"} (<b>{$post->getCommentsCount()}</b>)
|
||||
{else}
|
||||
{_"comments"}
|
||||
{/if}
|
||||
<a n:if="$commentsCount == 0" href="javascript:expand_comment_textarea({$commentTextAreaId})">
|
||||
{_"comment"}
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="like_wrap">
|
||||
<a class="post-share-button" href="/wall{$post->getPrettyId()}/repost?hash={rawurlencode($csrfToken)}"
|
||||
class="post-like-button">
|
||||
<a class="post-share-button" href="javascript:repostPost('{$post->getPrettyId()}', '{rawurlencode($csrfToken)}')">
|
||||
<div class="repost-icon" style="opacity: 0.4;"></div>
|
||||
<span class="likeCnt">{$post->getRepostCount()}</span>
|
||||
<span class="likeCnt">{if $post->getRepostCount() > 0}{$post->getRepostCount()}{/if}</span>
|
||||
</a>
|
||||
|
||||
{var liked = $post->hasLikeFrom($thisUser)}
|
||||
|
@ -93,14 +92,23 @@
|
|||
class="post-like-button"
|
||||
data-liked="{(int) $liked}"
|
||||
data-likes="{$post->getLikesCount()}">
|
||||
<div class="heart" style="{if $liked}opacity: 1;{else}opacity: 0.4;{/if}"></div>
|
||||
<span class="likeCnt">{$post->getLikesCount()}</span>
|
||||
<div class="heart" id="{if $liked}liked{/if}"></div>
|
||||
<span class="likeCnt">{if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if}</span>
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu-s">
|
||||
<!-- kosfurler -->
|
||||
<div n:if="$commentSection == true && $compact == false" class="post-menu-s">
|
||||
{if $commentsCount > 3}
|
||||
<a href="/wall{$post->getPrettyId()}" class="expand_button">{_view_other_comments}</a>
|
||||
{/if}
|
||||
{foreach $comments as $comment}
|
||||
{include "../comment.xml", comment => $comment, $compact => true}
|
||||
{/foreach}
|
||||
<div n:ifset="$thisUser" id="commentTextArea{$commentTextAreaId}" n:attr="style => ($commentsCount == 0 ? 'display: none;')" class="commentsTextFieldWrap">
|
||||
{var commentsURL = "/al_comments.pl/create/posts/" . $post->getId()}
|
||||
{include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue