Merge branch 'master' into feature-reports

This commit is contained in:
Ilya Prokopenko 2021-12-25 22:36:02 +07:00 committed by GitHub
commit 4aadd7cd30
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2710 changed files with 6341 additions and 921 deletions

1
.gitignore vendored
View file

@ -9,5 +9,6 @@ tmp/*
!tmp/themepack_artifacts/.gitkeep !tmp/themepack_artifacts/.gitkeep
themepacks/* themepacks/*
!themepacks/.gitkeep !themepacks/.gitkeep
!themepacks/openvk_modern
storage/* storage/*
!storage/.gitkeep !storage/.gitkeep

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "Web/static/img/oxygen-icons"]
path = Web/static/img/oxygen-icons
url = https://github.com/KDE/oxygen-icons5.git

View file

@ -1,27 +0,0 @@
# OpenVK Installation Instructions
* * *
1. Install Composer, Node.js, Yarn and [Chandler](https://github.com/openvk/chandler)
2. Install [commitcaptcha](https://github.com/openvk/commitcaptcha)/[chandler-recaptcha](https://github.com/openvk/chandler-recaptcha) and OpenVK as Chandler extensions and enable them like this:
```bash
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/
```
3. Import install/init-static-db.sql to same database you installed Chandler to
4. Import install/init-event-db.sql to separate database
5. Rename openvk-example.yml to openvk.yml and change options
6. Run `composer install` in OpenVK directory
7. Move to Web/static/js and execute `yarn install`
8. 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.
Full example installation instruction for CentOS 8 is also available [here](docs/centos8_install.md).

72
README.md Normal file
View file

@ -0,0 +1,72 @@
# <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).
## 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`
## Instances
* **[openvk.su](https://openvk.su/)**
* [social.fetbuk.ru](http://social.fetbuk.ru/)
## 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).
If you want, you can add your instance to the list above so that people can register there.
### Installation procedure
1. 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.
2. Install [commitcaptcha](https://github.com/openvk/commitcaptcha) and OpenVK as Chandler extensions like this:
```
git clone https://github.com/openvk/openvk /path/to/chandler/extensions/available/openvk
git clone https://github.com/openvk/commitcaptcha /path/to/chandler/extensions/available/commitcaptcha
```
3. 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/
```
4. Import `install/init-static-db.sql` to **same database** you installed Chandler to
5. Import `install/init-event-db.sql` to **separate database**
6. Copy `openvk-example.yml` to `openvk.yml` and change options
7. Run `composer install` in OpenVK directory
8. Move to `Web/static/js` and execute `yarn install`
9. 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.
Full example installation instruction for CentOS 8 is also available [here](docs/centos8_install.md).
### 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.
## 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 at this email: **openvk [at] tutanota [dot] com**
<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>

View file

@ -1,72 +0,0 @@
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>

View file

@ -130,6 +130,16 @@ class Club extends RowModel
return $this->getRecord()->administrators_list_display; return $this->getRecord()->administrators_list_display;
} }
function isEveryoneCanCreateTopics(): bool
{
return (bool) $this->getRecord()->everyone_can_create_topics;
}
function isDisplayTopicsAboveWallEnabled(): bool
{
return (bool) $this->getRecord()->display_topics_above_wall;
}
function getType(): int function getType(): int
{ {
return $this->getRecord()->type; return $this->getRecord()->type;

View file

@ -37,8 +37,19 @@ class Comment extends Post
if($honourFlags && $this->isPostedOnBehalfOfGroup()) { if($honourFlags && $this->isPostedOnBehalfOfGroup()) {
if($this->getTarget() instanceof Post) if($this->getTarget() instanceof Post)
return (new Clubs)->get(abs($this->getTarget()->getTargetWall())); return (new Clubs)->get(abs($this->getTarget()->getTargetWall()));
if($this->getTarget() instanceof Topic)
return $this->getTarget()->getClub();
} }
return parent::getOwner($honourFlags, $real); return parent::getOwner($honourFlags, $real);
} }
function canBeDeletedBy(User $user): bool
{
return $this->getOwner()->getId() == $user->getId() ||
$this->getTarget()->getOwner()->getId() == $user->getId() ||
$this->getTarget() instanceof Post && $this->getTarget()->getTargetWall() < 0 && (new Clubs)->get(abs($this->getTarget()->getTargetWall()))->canBeModifiedBy($user) ||
$this->getTarget() instanceof Topic && $this->getTarget()->canBeModifiedBy($user);
}
} }

View 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 CoinsTransferNotification extends Notification
{
protected $actionCode = 9602;
function __construct(User $receiver, User $sender, int $value, string $message)
{
parent::__construct($receiver, $receiver, $sender, time(), $value . " " . $message);
}
}

View file

@ -1,13 +1,8 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Models\Entities; namespace openvk\Web\Models\Entities;
use openvk\Web\Util\DateTime; use openvk\Web\Util\DateTime;
use Nette\Database\Table\ActiveRow;
use openvk\Web\Models\RowModel; use openvk\Web\Models\RowModel;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Repositories\Users; use openvk\Web\Models\Repositories\Users;
use Chandler\Database\DatabaseConnection as DB;
use Nette\InvalidStateException as ISE;
use Nette\Database\Table\Selection;
class Ticket extends RowModel class Ticket extends RowModel
{ {
@ -22,14 +17,7 @@ class Ticket extends RowModel
function getStatus(): string function getStatus(): string
{ {
if ($this->getRecord()->type === 0) return tr("support_status_" . $this->getRecord()->type);
{
return tr("support_status_0");
} elseif ($this->getRecord()->type === 1) {
return tr("support_status_1");
} elseif ($this->getRecord()->type === 2) {
return tr("support_status_2");
}
} }
function getType(): int function getType(): int
@ -58,17 +46,7 @@ class Ticket extends RowModel
function isDeleted(): bool function isDeleted(): bool
{ {
if ($this->getRecord()->deleted === 0) return (bool) $this->getRecord()->deleted;
{
return false;
} elseif ($this->getRecord()->deleted === 1) {
return true;
}
}
function authorId(): int
{
return $this->getRecord()->user_id;
} }
function getUser(): user function getUser(): user
@ -76,6 +54,11 @@ class Ticket extends RowModel
return (new Users)->get($this->getRecord()->user_id); return (new Users)->get($this->getRecord()->user_id);
} }
function getUserId(): int
{
return $this->getRecord()->user_id;
}
function isAd(): bool /* Эх, костыли... */ function isAd(): bool /* Эх, костыли... */
{ {
return false; return false;

View file

@ -1,13 +1,8 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Models\Entities; namespace openvk\Web\Models\Entities;
use openvk\Web\Util\DateTime; use openvk\Web\Util\DateTime;
use Nette\Database\Table\ActiveRow;
use openvk\Web\Models\RowModel; use openvk\Web\Models\RowModel;
use Chandler\Database\DatabaseConnection; use openvk\Web\Models\Repositories\{Users, SupportAliases, Tickets};
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 class TicketComment extends RowModel
{ {
@ -35,6 +30,11 @@ class TicketComment extends RowModel
return (new Users)->get($this->getRecord()->user_id); return (new Users)->get($this->getRecord()->user_id);
} }
function getTicket(): Ticket
{
return (new Tickets)->get($this->getRecord()->ticket_id);
}
function getAuthorName(): string function getAuthorName(): string
{ {
if($this->getUType() === 0) if($this->getUType() === 0)
@ -113,5 +113,19 @@ class TicketComment extends RowModel
return false; # Кооостыыыль!!! return false; # Кооостыыыль!!!
} }
function getMark(): ?int
{
return $this->getRecord()->mark;
}
function isLikedByUser(): ?bool
{
$mark = $this->getMark();
if(is_null($mark))
return NULL;
else
return $mark === 1;
}
use Traits\TRichText; use Traits\TRichText;
} }

View file

@ -0,0 +1,86 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Repositories\Clubs;
use openvk\Web\Util\DateTime;
class Topic extends Postable
{
protected $tableName = "topics";
protected $upperNodeReferenceColumnName = "group";
/**
* 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())
return $this->getClub();
return parent::getOwner($real);
}
function getClub(): Club
{
return (new Clubs)->get($this->getRecord()->group);
}
function getTitle(): string
{
return $this->getRecord()->title;
}
function isClosed(): bool
{
return (bool) $this->getRecord()->closed;
}
function isPinned(): bool
{
return (bool) $this->getRecord()->pinned;
}
function getPrettyId(): string
{
return $this->getRecord()->group . "_" . $this->getVirtualId();
}
function isPostedOnBehalfOfGroup(): bool
{
return ($this->getRecord()->flags & 0b10000000) > 0;
}
function isDeleted(): bool
{
return (bool) $this->getRecord()->deleted;
}
function canBeModifiedBy(User $user): bool
{
return $this->getOwner(false)->getId() === $user->getId() || $this->getClub()->canBeModifiedBy($user);
}
function getLastComment(): ?Comment
{
$array = iterator_to_array($this->getLastComments(1));
return isset($array[0]) ? $array[0] : NULL;
}
function getUpdateTime(): DateTime
{
$lastComment = $this->getLastComment();
if(!is_null($lastComment))
return $lastComment->getPublicationTime();
else
return $this->getEditTime() ?? $this->getPublicationTime();
}
function deleteTopic(): void
{
$this->setDeleted(1);
$this->unwire();
$this->save();
}
}

View file

@ -1,5 +1,6 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits; namespace openvk\Web\Models\Entities\Traits;
use Wkhooy\ObsceneCensorRus;
trait TRichText trait TRichText
{ {
@ -58,9 +59,9 @@ trait TRichText
if($proc) { if($proc) {
$rel = $this->isAd() ? "sponsored" : "ugc"; $rel = $this->isAd() ? "sponsored" : "ugc";
$text = $this->formatLinks($text); $text = $this->formatLinks($text);
$text = preg_replace("%@(id|club)([0-9]++) \(([\p{L} 0-9]+)\)%Xu", "[$1$2|$3]", $text); $text = preg_replace("%@([A-Za-z0-9]++) \(([\p{L} 0-9]+)\)%Xu", "[$1|$2]", $text);
$text = preg_replace("%@(id|club)([0-9]++)%Xu", "[$1$2|@$1$2]", $text); $text = preg_replace("%([\n\r\s]|^)(@([A-Za-z0-9]++))%Xu", "$1[$3|@$3]", $text);
$text = preg_replace("%\[(id|club)([0-9]++)\|([\p{L} 0-9@]+)\]%Xu", "<a href='/$1$2'>$3</a>", $text); $text = preg_replace("%\[([A-Za-z0-9]++)\|([\p{L} 0-9@]+)\]%Xu", "<a href='/$1'>$2</a>", $text);
$text = preg_replace("%(#([\p{L}_-]++[0-9]*[\p{L}_-]*))%Xu", "<a href='/feed/hashtag/$2'>$1</a>", $text); $text = preg_replace("%(#([\p{L}_-]++[0-9]*[\p{L}_-]*))%Xu", "<a href='/feed/hashtag/$2'>$1</a>", $text);
$text = $this->formatEmojis($text); $text = $this->formatEmojis($text);
} }
@ -69,6 +70,9 @@ trait TRichText
$text = nl2br($text); $text = nl2br($text);
} }
if(OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["christian"])
ObsceneCensorRus::filterText($text);
return $text; return $text;
} }
} }

View file

@ -317,14 +317,14 @@ class User extends RowModel
return $this->getRecord()->notification_offset; return $this->getRecord()->notification_offset;
} }
function getBirthday(): ?int function getBirthday(): ?DateTime
{ {
return $this->getRecord()->birthday; return new DateTime($this->getRecord()->birthday);
} }
function getAge(): ?int function getAge(): ?int
{ {
return (int)floor((time() - $this->getBirthday()) / mktime(0, 0, 0, 1, 1, 1971)); return (int)floor((time() - $this->getBirthday()->timestamp()) / YEAR);
} }
function get2faSecret(): ?string function get2faSecret(): ?string
@ -353,6 +353,7 @@ class User extends RowModel
"notes", "notes",
"groups", "groups",
"news", "news",
"links",
], ],
])->get($id); ])->get($id);
} }
@ -539,6 +540,8 @@ class User extends RowModel
$manager = $club->getManager($this); $manager = $club->getManager($this);
if(!is_null($manager)) if(!is_null($manager))
return $manager->isClubPinned(); return $manager->isClubPinned();
return false;
} }
function getMeetings(int $page = 1): \Traversable function getMeetings(int $page = 1): \Traversable
@ -766,6 +769,7 @@ class User extends RowModel
"notes", "notes",
"groups", "groups",
"news", "news",
"links",
], ],
])->set($id, (int) $status)->toInteger(); ])->set($id, (int) $status)->toInteger();

View file

@ -49,5 +49,14 @@ class TicketComments
// return $this->toTicket($this->tickets->get($id)); // return $this->toTicket($this->tickets->get($id));
// } // }
function get(int $id): ?TicketComment
{
$comment = $this->comments->get($id);;
if (!is_null($comment))
return new TicketComment($comment);
else
return NULL;
}
use \Nette\SmartObject; use \Nette\SmartObject;
} }

View file

@ -0,0 +1,73 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use openvk\Web\Models\Entities\Topic;
use openvk\Web\Models\Entities\Club;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
class Topics
{
private $context;
private $topics;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->topics = $this->context->table("topics");
}
private function toTopic(?ActiveRow $ar): ?Topic
{
return is_null($ar) ? NULL : new Topic($ar);
}
function get(int $id): ?Topic
{
return $this->toTopic($this->topics->get($id));
}
function getTopicById(int $club, int $topic): ?Topic
{
return $this->toTopic($this->topics->where(["group" => $club, "virtual_id" => $topic, "deleted" => 0])->fetch());
}
function getClubTopics(Club $club, int $page = 1, ?int $perPage = NULL): \Traversable
{
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
// Get pinned topics first
$query = "SELECT `id` FROM `topics` WHERE `pinned` = 1 AND `group` = ? AND `deleted` = 0 UNION SELECT `id` FROM `topics` WHERE `pinned` = 0 AND `group` = ? AND `deleted` = 0";
$query .= " LIMIT " . $perPage . " OFFSET " . ($page - 1) * $perPage;
foreach(DatabaseConnection::i()->getConnection()->query($query, $club->getId(), $club->getId()) as $topic) {
$topic = $this->get($topic->id);
if(!$topic) continue;
yield $topic;
}
}
function getClubTopicsCount(Club $club): int
{
return sizeof($this->topics->where([
"group" => $club->getId(),
"deleted" => false
]));
}
function find(Club $club, string $query): \Traversable
{
return new Util\EntityStream("Topic", $this->topics->where("title LIKE ? AND group = ? AND deleted = 0", "%$query%", $club->getId()));
}
function getLastTopics(Club $club, ?int $count = NULL): \Traversable
{
$topics = $this->topics->where([
"group" => $club->getId(),
"deleted" => false
])->page(1, $count ?? OPENVK_DEFAULT_PER_PAGE)->order("created DESC");
foreach($topics as $topic)
yield $this->toTopic($topic);
}
}

View file

@ -53,5 +53,22 @@ class Users
]; ];
} }
function getByAddress(string $address): ?User
{
if(substr_compare($address, "/", -1) === 0)
$address = substr($address, 0, iconv_strlen($address) - 1);
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
if(strpos($address, $serverUrl . "/") === 0)
$address = substr($address, iconv_strlen($serverUrl) + 1);
if(strpos($address, "id") === 0) {
$user = $this->get((int) substr($address, 2));
if($user) return $user;
}
return $this->getByShortUrl($address);
}
use \Nette\SmartObject; use \Nette\SmartObject;
} }

View file

@ -6,6 +6,7 @@ use openvk\Web\Models\Entities\PasswordReset;
use openvk\Web\Models\Repositories\IPs; use openvk\Web\Models\Repositories\IPs;
use openvk\Web\Models\Repositories\Users; use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\Repositories\Restores; use openvk\Web\Models\Repositories\Restores;
use openvk\Web\Util\Validator;
use Chandler\Session\Session; use Chandler\Session\Session;
use Chandler\Security\User as ChandlerUser; use Chandler\Security\User as ChandlerUser;
use Chandler\Security\Authenticator; use Chandler\Security\Authenticator;
@ -32,17 +33,6 @@ final class AuthPresenter extends OpenVKPresenter
parent::__construct(); parent::__construct();
} }
private function emailValid(string $email): bool
{
if(empty($email)) return false;
$email = trim($email);
[$user, $domain] = explode("@", $email);
$domain = idn_to_ascii($domain) . ".";
return checkdnsrr($domain, "MX");
}
private function ipValid(): bool private function ipValid(): bool
{ {
$ip = (new IPs)->get(CONNECTING_IP); $ip = (new IPs)->get(CONNECTING_IP);
@ -62,7 +52,7 @@ final class AuthPresenter extends OpenVKPresenter
if(!is_null($refLink = $this->queryParam("ref"))) { if(!is_null($refLink = $this->queryParam("ref"))) {
$pieces = explode(" ", $refLink, 2); $pieces = explode(" ", $refLink, 2);
if(sizeof($pieces) !== 2) if(sizeof($pieces) !== 2)
$this->flashFail("err", "Пригласительная ссылка кривая", "Пригласительная ссылка недействительна."); $this->flashFail("err", tr("error"), tr("referral_link_invalid"));
[$ref, $hash] = $pieces; [$ref, $hash] = $pieces;
$ref = hexdec($ref); $ref = hexdec($ref);
@ -70,10 +60,10 @@ final class AuthPresenter extends OpenVKPresenter
$referer = (new Users)->get($ref); $referer = (new Users)->get($ref);
if(!$referer) if(!$referer)
$this->flashFail("err", "Пригласительная ссылка кривая", "Пригласительная ссылка недействительна."); $this->flashFail("err", tr("error"), tr("referral_link_invalid"));
if($referer->getRefLinkId() !== $refLink) if($referer->getRefLinkId() !== $refLink)
$this->flashFail("err", "Пригласительная ссылка кривая", "Пригласительная ссылка недействительна."); $this->flashFail("err", tr("error"), tr("referral_link_invalid"));
} }
$this->template->referer = $referer; $this->template->referer = $referer;
@ -82,20 +72,20 @@ final class AuthPresenter extends OpenVKPresenter
$this->assertCaptchaCheckPassed(); $this->assertCaptchaCheckPassed();
if(!OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable'] && !$referer) if(!OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable'] && !$referer)
$this->flashFail("err", "Подозрительная попытка регистрации", "Регистрация отключена системным администратором."); $this->flashFail("err", tr("failed_to_register"), tr("registration_disabled"));
if(!$this->ipValid()) if(!$this->ipValid())
$this->flashFail("err", "Подозрительная попытка регистрации", "Вы пытались зарегистрироваться из подозрительного места."); $this->flashFail("err", tr("suspicious_registration_attempt"), tr("suspicious_registration_attempt_comment"));
if(!$this->emailValid($this->postParam("email"))) if(!Validator::i()->emailValid($this->postParam("email")))
$this->flashFail("err", "Неверный email адрес", "Email, который вы ввели, не является корректным."); $this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment"));
if (strtotime($this->postParam("birthday")) > time()) if (strtotime($this->postParam("birthday")) > time())
$this->flashFail("err", "Неверная дата рождения", "Дату рождения, которую вы ввели, не является корректным."); $this->flashFail("err", tr("invalid_birth_date"), tr("invalid_birth_date_comment"));
$chUser = ChandlerUser::create($this->postParam("email"), $this->postParam("password")); $chUser = ChandlerUser::create($this->postParam("email"), $this->postParam("password"));
if(!$chUser) if(!$chUser)
$this->flashFail("err", "Не удалось зарегистрироваться", "Пользователь с таким email уже существует."); $this->flashFail("err", tr("failed_to_register"), tr("user_already_exists"));
$user = new User; $user = new User;
$user->setUser($chUser->getId()); $user->setUser($chUser->getId());
@ -131,10 +121,10 @@ final class AuthPresenter extends OpenVKPresenter
$user = $this->db->table("ChandlerUsers")->where("login", $this->postParam("login"))->fetch(); $user = $this->db->table("ChandlerUsers")->where("login", $this->postParam("login"))->fetch();
if(!$user) if(!$user)
$this->flashFail("err", "Не удалось войти", "Неверное имя пользователя или пароль. <a href='/restore.pl'>Забыли пароль?</a>"); $this->flashFail("err", tr("login_failed"), tr("invalid_username_or_password"));
if(!$this->authenticator->verifyCredentials($user->id, $this->postParam("password"))) if(!$this->authenticator->verifyCredentials($user->id, $this->postParam("password")))
$this->flashFail("err", "Не удалось войти", "Неверное имя пользователя или пароль. <a href='/restore.pl'>Забыли пароль?</a>"); $this->flashFail("err", tr("login_failed"), tr("invalid_username_or_password"));
$secret = $user->related("profiles.user")->fetch()["2fa_secret"]; $secret = $user->related("profiles.user")->fetch()["2fa_secret"];
$code = $this->postParam("code"); $code = $this->postParam("code");
@ -148,7 +138,7 @@ final class AuthPresenter extends OpenVKPresenter
$ovkUser = new User($user->related("profiles.user")->fetch()); $ovkUser = new User($user->related("profiles.user")->fetch());
if(!($code === (new Totp)->GenerateToken(Base32::decode($secret)) || $ovkUser->use2faBackupCode((int) $code))) { if(!($code === (new Totp)->GenerateToken(Base32::decode($secret)) || $ovkUser->use2faBackupCode((int) $code))) {
$this->flash("err", "Не удалось войти", tr("incorrect_2fa_code")); $this->flash("err", tr("login_failed"), tr("incorrect_2fa_code"));
return; return;
} }
} }
@ -170,11 +160,11 @@ final class AuthPresenter extends OpenVKPresenter
} }
if(!$this->db->table("ChandlerUsers")->where("id", $uuid)) if(!$this->db->table("ChandlerUsers")->where("id", $uuid))
$this->flashFail("err", "Ошибка манипуляции токенами", "Пользователь не найден."); $this->flashFail("err", tr("token_manipulation_error"), tr("profile_not_found"));
$this->assertPermission('openvk\Web\Models\Entities\User', 'substitute', 0); $this->assertPermission('openvk\Web\Models\Entities\User', 'substitute', 0);
Session::i()->set("_su", $uuid); Session::i()->set("_su", $uuid);
$this->flash("succ", "Профиль изменён", "Ваш активный профиль был изменён."); $this->flash("succ", tr("profile_changed"), tr("profile_changed_comment"));
$this->redirect("/", static::REDIRECT_TEMPORARY); $this->redirect("/", static::REDIRECT_TEMPORARY);
exit; exit;
} }
@ -193,8 +183,9 @@ final class AuthPresenter extends OpenVKPresenter
{ {
$request = $this->restores->getByToken(str_replace(" ", "+", $this->queryParam("key"))); $request = $this->restores->getByToken(str_replace(" ", "+", $this->queryParam("key")));
if(!$request || !$request->isStillValid()) { if(!$request || !$request->isStillValid()) {
$this->flash("err", "Ошибка манипулирования токеном", "Токен недействителен или истёк"); $this->flash("err", tr("token_manipulation_error"), tr("token_manipulation_error_comment"));
$this->redirect("/"); $this->redirect("/");
return;
} }
$this->template->is2faEnabled = $request->getUser()->is2faEnabled(); $this->template->is2faEnabled = $request->getUser()->is2faEnabled();
@ -217,7 +208,7 @@ final class AuthPresenter extends OpenVKPresenter
$this->authenticator->authenticate($user->getId()); $this->authenticator->authenticate($user->getId());
$request->delete(false); $request->delete(false);
$this->flash("succ", "Успешно", "Ваш пароль был успешно сброшен."); $this->flash("succ", tr("information_-1"), tr("password_successfully_reset"));
$this->redirect("/settings"); $this->redirect("/settings");
} }
} }
@ -231,16 +222,16 @@ final class AuthPresenter extends OpenVKPresenter
$uRow = $this->db->table("ChandlerUsers")->where("login", $this->postParam("login"))->fetch(); $uRow = $this->db->table("ChandlerUsers")->where("login", $this->postParam("login"))->fetch();
if(!$uRow) { if(!$uRow) {
#Privacy of users must be protected. We will not tell if email is bound to a user or not. #Privacy of users must be protected. We will not tell if email is bound to a user or not.
$this->flashFail("succ", "Успешно", "Если вы зарегистрированы, вы получите инструкции на email."); $this->flashFail("succ", tr("information_-1"), tr("password_reset_email_sent"));
} }
$user = $this->users->getByChandlerUser(new ChandlerUser($uRow)); $user = $this->users->getByChandlerUser(new ChandlerUser($uRow));
if(!$user) if(!$user)
$this->flashFail("err", "Ошибка", "Непредвиденная ошибка при сбросе пароля."); $this->flashFail("err", tr("error"), tr("password_reset_error"));
$request = $this->restores->getLatestByUser($user); $request = $this->restores->getLatestByUser($user);
if(!is_null($request) && $request->isNew()) if(!is_null($request) && $request->isNew())
$this->flashFail("err", "Ошибка доступа", "Нельзя делать это так часто, извините."); $this->flashFail("err", tr("forbidden"), tr("password_reset_rate_limit_error"));
$resetObj = new PasswordReset; $resetObj = new PasswordReset;
$resetObj->setProfile($user->getId()); $resetObj->setProfile($user->getId());
@ -253,7 +244,7 @@ final class AuthPresenter extends OpenVKPresenter
$this->sendmail($uRow->login, "password-reset", $params); #Vulnerability possible $this->sendmail($uRow->login, "password-reset", $params); #Vulnerability possible
$this->flashFail("succ", "Успешно", "Если вы зарегистрированы, вы получите инструкции на email."); $this->flashFail("succ", tr("information_-1"), tr("password_reset_email_sent"));
} }
} }
} }

View file

@ -1,16 +1,17 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Comment, Photo, Video, User}; use openvk\Web\Models\Entities\{Comment, Photo, Video, User, Topic, Post};
use openvk\Web\Models\Entities\Notifications\CommentNotification; use openvk\Web\Models\Entities\Notifications\CommentNotification;
use openvk\Web\Models\Repositories\Comments; use openvk\Web\Models\Repositories\{Comments, Clubs};
final class CommentPresenter extends OpenVKPresenter final class CommentPresenter extends OpenVKPresenter
{ {
private $models = [ private $models = [
"posts" => "openvk\\Web\\Models\\Repositories\\Posts", "posts" => "openvk\\Web\\Models\\Repositories\\Posts",
"photos" => "openvk\\Web\\Models\\Repositories\\Photos", "photos" => "openvk\\Web\\Models\\Repositories\\Photos",
"videos" => "openvk\\Web\\Models\\Repositories\\Videos", "videos" => "openvk\\Web\\Models\\Repositories\\Videos",
"notes" => "openvk\\Web\\Models\\Repositories\\Notes", "notes" => "openvk\\Web\\Models\\Repositories\\Notes",
"topics" => "openvk\\Web\\Models\\Repositories\\Topics",
]; ];
function renderLike(int $id): void function renderLike(int $id): void
@ -38,8 +39,16 @@ final class CommentPresenter extends OpenVKPresenter
$entity = $repo->get($eId); $entity = $repo->get($eId);
if(!$entity) $this->notFound(); if(!$entity) $this->notFound();
if($entity instanceof Topic && $entity->isClosed())
$this->notFound();
if($entity instanceof Post && $entity->getTargetWall() < 0)
$club = (new Clubs)->get(abs($entity->getTargetWall()));
else if($entity instanceof Topic)
$club = $entity->getClub();
$flags = 0; $flags = 0;
if($this->postParam("as_group") === "on") if($this->postParam("as_group") === "on" && !is_null($club) && $club->canBeModifiedBy($this->user->identity))
$flags |= 0b10000000; $flags |= 0b10000000;
$photo = NULL; $photo = NULL;
@ -106,9 +115,8 @@ final class CommentPresenter extends OpenVKPresenter
$comment = (new Comments)->get($id); $comment = (new Comments)->get($id);
if(!$comment) $this->notFound(); if(!$comment) $this->notFound();
if($comment->getOwner()->getId() !== $this->user->id) if(!$comment->canBeDeletedBy($this->user->identity))
if($comment->getTarget()->getOwner()->getId() !== $this->user->id) $this->throwError(403, "Forbidden", "У вас недостаточно прав чтобы редактировать этот ресурс.");
$this->throwError(403, "Forbidden", "У вас недостаточно прав чтобы редактировать этот ресурс.");
$comment->delete(); $comment->delete();
$this->flashFail( $this->flashFail(

View file

@ -2,7 +2,8 @@
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Club, Photo}; use openvk\Web\Models\Entities\{Club, Photo};
use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification; use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification;
use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers}; use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics};
use Chandler\Security\Authenticator;
final class GroupPresenter extends OpenVKPresenter final class GroupPresenter extends OpenVKPresenter
{ {
@ -28,6 +29,8 @@ final class GroupPresenter extends OpenVKPresenter
$this->template->club = $club; $this->template->club = $club;
$this->template->albums = (new Albums)->getClubAlbums($club, 1, 3); $this->template->albums = (new Albums)->getClubAlbums($club, 1, 3);
$this->template->albumsCount = (new Albums)->getClubAlbumsCount($club); $this->template->albumsCount = (new Albums)->getClubAlbumsCount($club);
$this->template->topics = (new Topics)->getLastTopics($club, 3);
$this->template->topicsCount = (new Topics)->getClubTopicsCount($club);
} }
} }
@ -204,6 +207,8 @@ final class GroupPresenter extends OpenVKPresenter
$club->setShortcode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode")); $club->setShortcode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode"));
$club->setWall(empty($this->postParam("wall")) ? 0 : 1); $club->setWall(empty($this->postParam("wall")) ? 0 : 1);
$club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display")); $club->setAdministrators_List_Display(empty($this->postParam("administrators_list_display")) ? 0 : $this->postParam("administrators_list_display"));
$club->setEveryone_Can_Create_Topics(empty($this->postParam("everyone_can_create_topics")) ? 0 : 1);
$club->setDisplay_Topics_Above_Wall(empty($this->postParam("display_topics_above_wall")) ? 0 : 1);;
$website = $this->postParam("website") ?? ""; $website = $this->postParam("website") ?? "";
if(empty($website)) if(empty($website))
@ -269,4 +274,38 @@ final class GroupPresenter extends OpenVKPresenter
$this->notFound(); $this->notFound();
} }
} }
function renderChangeOwner(int $id, int $newOwnerId): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
if($_SERVER['REQUEST_METHOD'] !== "POST")
$this->redirect("/groups" . $this->user->id);
if(!Authenticator::verifyHash($this->postParam("password"), $this->user->identity->getChandlerUser()->getRaw()->passwordHash))
$this->flashFail("err", tr("error"), tr("incorrect_password"));
$club = $this->clubs->get($id);
$newOwner = (new Users)->get($newOwnerId);
if($this->user->id !== $club->getOwner()->getId())
$this->flashFail("err", tr("error"), tr("forbidden"));
$club->setOwner($newOwnerId);
$club->addManager($this->user->identity);
$oldOwnerManager = $club->getManager($this->user->identity);
$oldOwnerManager->setHidden($club->isOwnerHidden());
$oldOwnerManager->setComment($club->getOwnerComment());
$oldOwnerManager->save();
$newOwnerManager = $club->getManager($newOwner);
$club->setOwner_Hidden($newOwnerManager->isHidden());
$club->setOwner_Comment($newOwnerManager->getComment());
$club->removeManager($newOwner);
$club->save();
$this->flashFail("succ", tr("information_-1"), tr("group_owner_setted", $newOwner->getCanonicalName(), $club->getName()));
}
} }

View file

@ -19,6 +19,8 @@ final class NotesPresenter extends OpenVKPresenter
{ {
$user = (new Users)->get($owner); $user = (new Users)->get($owner);
if(!$user) $this->notFound(); if(!$user) $this->notFound();
if(!$user->getPrivacyPermission('notes.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$this->template->notes = $this->notes->getUserNotes($user, (int)($this->queryParam("p") ?? 1)); $this->template->notes = $this->notes->getUserNotes($user, (int)($this->queryParam("p") ?? 1));
$this->template->count = $this->notes->getUserNotesCount($user); $this->template->count = $this->notes->getUserNotesCount($user);
@ -36,6 +38,8 @@ final class NotesPresenter extends OpenVKPresenter
$note = $this->notes->getNoteById($owner, $note_id); $note = $this->notes->getNoteById($owner, $note_id);
if(!$note || $note->getOwner()->getId() !== $owner || $note->isDeleted()) if(!$note || $note->getOwner()->getId() !== $owner || $note->isDeleted())
$this->notFound(); $this->notFound();
if(!$note->getOwner()->getPrivacyPermission('notes.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$this->template->cCount = $note->getCommentsCount(); $this->template->cCount = $note->getCommentsCount();
$this->template->cPage = (int) ($this->queryParam("p") ?? 1); $this->template->cPage = (int) ($this->queryParam("p") ?? 1);

View file

@ -34,10 +34,10 @@ abstract class OpenVKPresenter extends SimplePresenter
])); ]));
} }
protected function setTempTheme(string $theme): void protected function setTempTheme(string $theme): void
{ {
Session::i()->set("_tempTheme", $theme); Session::i()->set("_tempTheme", $theme);
} }
protected function flashFail(string $type, string $title, ?string $message = NULL, ?int $code = NULL): void protected function flashFail(string $type, string $title, ?string $message = NULL, ?int $code = NULL): void
{ {
@ -80,7 +80,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$loginUrl .= "?jReturnTo=" . rawurlencode($currentUrl); $loginUrl .= "?jReturnTo=" . rawurlencode($currentUrl);
} }
$this->flash("err", "Недостаточно прав", "Чтобы просматривать эту страницу, нужно зайти на сайт."); $this->flash("err", tr("login_required_error"), tr("login_required_error_comment"));
header("HTTP/1.1 302 Found"); header("HTTP/1.1 302 Found");
header("Location: $loginUrl"); header("Location: $loginUrl");
exit; exit;
@ -91,7 +91,7 @@ abstract class OpenVKPresenter extends SimplePresenter
{ {
if(is_null($this->user)) { if(is_null($this->user)) {
if($model !== "user") { if($model !== "user") {
$this->flash("info", "Недостаточно прав", "Чтобы просматривать эту страницу, нужно зайти на сайт."); $this->flash("info", tr("login_required_error"), tr("login_required_error_comment"));
header("HTTP/1.1 302 Found"); header("HTTP/1.1 302 Found");
header("Location: /login"); header("Location: /login");
@ -111,13 +111,13 @@ abstract class OpenVKPresenter extends SimplePresenter
if($throw) if($throw)
throw new SecurityPolicyViolationException("Permission error"); throw new SecurityPolicyViolationException("Permission error");
else else
$this->flashFail("err", "Недостаточно прав", "У вас недостаточно прав чтобы выполнять это действие."); $this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
} }
protected function assertCaptchaCheckPassed(): void protected function assertCaptchaCheckPassed(): void
{ {
if(!check_captcha()) if(!check_captcha())
$this->flashFail("err", "Неправильно введены символы", "Пожалуйста, убедитесь, что вы правильно заполнили поле с капчей."); $this->flashFail("err", tr("captcha_error"), tr("captcha_error_comment"));
} }
protected function willExecuteWriteAction(): void protected function willExecuteWriteAction(): void
@ -131,7 +131,7 @@ abstract class OpenVKPresenter extends SimplePresenter
exit("Хакеры? Интересно..."); exit("Хакеры? Интересно...");
} }
$this->flashFail("err", "Чумба, ты совсем ёбнутый?", "Сходи к мозгоправу, попей колёсики. В OpenVK нельзя вбрасывать щитпосты так часто. Код исключения: $res."); $this->flashFail("err", tr("rate_limit_error"), tr("rate_limit_error_comment", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"], $res));
} }
} }
@ -198,6 +198,7 @@ abstract class OpenVKPresenter extends SimplePresenter
header("HTTP/1.1 403 Forbidden"); header("HTTP/1.1 403 Forbidden");
$this->getTemplatingEngine()->render(__DIR__ . "/templates/@banned.xml", [ $this->getTemplatingEngine()->render(__DIR__ . "/templates/@banned.xml", [
"thisUser" => $this->user->identity, "thisUser" => $this->user->identity,
"csrfToken" => $GLOBALS["csrfToken"],
]); ]);
exit; exit;
} }
@ -221,25 +222,23 @@ abstract class OpenVKPresenter extends SimplePresenter
{ {
parent::onBeforeRender(); parent::onBeforeRender();
if(!is_null($this->user)) { $theme = NULL;
if(Session::i()->get("_tempTheme")) {
$theme = Themepacks::i()[Session::i()->get("_tempTheme", "ovk")];
Session::i()->set("_tempTheme", NULL);
} else if($this->requestParam("themePreview")) {
$theme = Themepacks::i()[$this->requestParam("themePreview")];
} else if($this->user->identity !== null && $this->user->identity->getTheme()) {
$theme = $this->user->identity->getTheme(); $theme = $this->user->identity->getTheme();
if(!is_null($theme) && $theme->overridesTemplates()) {
$this->template->_templatePath = $theme->getBaseDir() . "/tpl";
}
} }
$this->template->theme = $theme;
if(!is_null($theme) && $theme->overridesTemplates())
$this->template->_templatePath = $theme->getBaseDir() . "/tpl";
if(!is_null(Session::i()->get("_error"))) { if(!is_null(Session::i()->get("_error"))) {
$this->template->flashMessage = json_decode(Session::i()->get("_error")); $this->template->flashMessage = json_decode(Session::i()->get("_error"));
Session::i()->set("_error", NULL); 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();
// Знаю, каша ебаная, целестора рефактор всё равно сделает :)))
} }
} }

View file

@ -29,6 +29,8 @@ final class PhotosPresenter extends OpenVKPresenter
if($owner > 0) { if($owner > 0) {
$user = $this->users->get($owner); $user = $this->users->get($owner);
if(!$user) $this->notFound(); if(!$user) $this->notFound();
if (!$user->getPrivacyPermission('photos.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$this->template->albums = $this->albums->getUserAlbums($user, $this->queryParam("p") ?? 1); $this->template->albums = $this->albums->getUserAlbums($user, $this->queryParam("p") ?? 1);
$this->template->count = $this->albums->getUserAlbumsCount($user); $this->template->count = $this->albums->getUserAlbumsCount($user);
$this->template->owner = $user; $this->template->owner = $user;
@ -129,13 +131,20 @@ final class PhotosPresenter extends OpenVKPresenter
if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted()) if($album->getPrettyId() !== $owner . "_" . $id || $album->isDeleted())
$this->notFound(); $this->notFound();
if($owner > 0 /* bc we currently don't have perms for clubs */) {
$ownerObject = (new Users)->get($owner);
if(!$ownerObject->getPrivacyPermission('photos.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
}
$this->template->album = $album; $this->template->album = $album;
$this->template->photos = iterator_to_array( $album->getPhotos( (int) ($this->queryParam("p") ?? 1) ) ); $this->template->photos = iterator_to_array( $album->getPhotos( (int) ($this->queryParam("p") ?? 1), 20) );
$this->template->paginatorConf = (object) [ $this->template->paginatorConf = (object) [
"count" => $album->getPhotosCount(), "count" => $album->getPhotosCount(),
"page" => $this->queryParam("p") ?? 1, "page" => $this->queryParam("p") ?? 1,
"amount" => sizeof($this->template->photos), "amount" => sizeof($this->template->photos),
"perPage" => OPENVK_DEFAULT_PER_PAGE, "perPage" => 20,
"atBottom" => true
]; ];
} }

View file

@ -2,13 +2,9 @@
namespace openvk\Web\Presenters; namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\Ticket; use openvk\Web\Models\Entities\Ticket;
use openvk\Web\Models\Repositories\Tickets; use openvk\Web\Models\Repositories\Tickets;
//
use openvk\Web\Models\Entities\TicketComment; use openvk\Web\Models\Entities\TicketComment;
use openvk\Web\Models\Repositories\TicketComments; use openvk\Web\Models\Repositories\TicketComments;
// use openvk\Web\Models\Repositories\Users; use openvk\Web\Util\Telegram;
use openvk\Web\Models\RowModel;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
use Chandler\Session\Session; use Chandler\Session\Session;
use Netcarver\Textile; use Netcarver\Textile;
@ -21,7 +17,7 @@ final class SupportPresenter extends OpenVKPresenter
function __construct(Tickets $tickets, TicketComments $ticketComments) function __construct(Tickets $tickets, TicketComments $ticketComments)
{ {
$this->tickets = $tickets; $this->tickets = $tickets;
$this->comments = $ticketComments; $this->comments = $ticketComments;
parent::__construct(); parent::__construct();
@ -29,19 +25,14 @@ final class SupportPresenter extends OpenVKPresenter
function renderIndex(): void function renderIndex(): void
{ {
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->template->mode = in_array($this->queryParam("act"), ["faq", "new", "list"]) ? $this->queryParam("act") : "faq"; $this->template->mode = in_array($this->queryParam("act"), ["faq", "new", "list"]) ? $this->queryParam("act") : "faq";
$tickets = $this->tickets->getTicketsByuId($this->user->id); $tickets = $this->tickets->getTicketsByuId($this->user->id);
if ($tickets) { if($tickets)
$this->template->tickets = $tickets; $this->template->tickets = $tickets;
} if($_SERVER["REQUEST_METHOD"] === "POST") {
if(!empty($this->postParam("name")) && !empty($this->postParam("text"))) {
if($_SERVER["REQUEST_METHOD"] === "POST")
{
if(!empty($this->postParam("name")) && !empty($this->postParam("text")))
{
$this->assertNoCSRF(); $this->assertNoCSRF();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
@ -52,12 +43,23 @@ final class SupportPresenter extends OpenVKPresenter
$ticket->setText($this->postParam("text")); $ticket->setText($this->postParam("text"));
$ticket->setcreated(time()); $ticket->setcreated(time());
$ticket->save(); $ticket->save();
$helpdeskChat = OPENVK_ROOT_CONF["openvk"]["credentials"]["telegram"]["helpdeskChat"];
if($helpdeskChat) {
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
$ticketText = ovk_proc_strtr($this->postParam("text"), 1500);
$telegramText = "<b>📬 Новый тикет!</b>\n\n";
$telegramText .= "<a href='$serverUrl/support/reply/{$ticket->getId()}'>{$ticket->getName()}</a>\n";
$telegramText .= "$ticketText\n\n";
$telegramText .= "Автор: <a href='$serverUrl{$ticket->getUser()->getURL()}'>{$ticket->getUser()->getCanonicalName()}</a> ({$ticket->getUser()->getRegistrationIP()})\n";
Telegram::send($helpdeskChat, $telegramText);
}
header("HTTP/1.1 302 Found"); header("HTTP/1.1 302 Found");
header("Location: /support/view/" . $ticket->getId()); header("Location: /support/view/" . $ticket->getId());
} else { } else {
$this->flashFail("err", "Ошибка", "Вы не ввели имя или текст "); $this->flashFail("err", tr("error"), tr("you_have_not_entered_name_or_text"));
} }
// $this->template->test = 'cool post';
} }
} }
@ -88,48 +90,49 @@ final class SupportPresenter extends OpenVKPresenter
function renderView(int $id): void function renderView(int $id): void
{ {
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$ticket = $this->tickets->get($id); $ticket = $this->tickets->get($id);
$ticketComments1 = $this->comments->getCommentsById($id); $ticketComments = $this->comments->getCommentsById($id);
if(!$ticket || $ticket->isDeleted() != 0 || $ticket->authorId() !== $this->user->id) { if(!$ticket || $ticket->isDeleted() != 0 || $ticket->getUserId() !== $this->user->id) {
$this->notFound(); $this->notFound();
} else { } else {
$this->template->ticket = $ticket; $this->template->ticket = $ticket;
$this->template->comments = $ticketComments1; $this->template->comments = $ticketComments;
$this->template->id = $id; $this->template->id = $id;
} }
} }
function renderDelete(int $id): void function renderDelete(int $id): void
{ {
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
if (!empty($id)) {
$ticket = $this->tickets->get($id); if(!empty($id)) {
if (!$ticket || $ticket->isDeleted() != 0 || $ticket->authorId() !== $this->user->id) $ticket = $this->tickets->get($id);
{ if(!$ticket || $ticket->isDeleted() != 0 || $ticket->getUserId() !== $this->user->id && !$this->hasPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0)) {
$this->notFound(); $this->notFound();
} else { } else {
$ticket->delete(); header("HTTP/1.1 302 Found");
header("HTTP/1.1 302 Found"); if($ticket->getUserId() !== $this->user->id && $this->hasPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0))
header("Location: /support/tickets");
else
header("Location: /support"); header("Location: /support");
} $ticket->delete();
} }
}
} }
function renderMakeComment(int $id): void function renderMakeComment(int $id): void
{ {
$ticket = $this->tickets->get($id); $ticket = $this->tickets->get($id);
if($ticket->isDeleted() === 1 || $ticket->getType() === 2 || $ticket->authorId() !== $this->user->id) { if($ticket->isDeleted() === 1 || $ticket->getType() === 2 || $ticket->getUserId() !== $this->user->id) {
header("HTTP/1.1 403 Forbidden"); header("HTTP/1.1 403 Forbidden");
header("Location: /support/view/" . $id); header("Location: /support/view/" . $id);
exit; exit;
} }
if($_SERVER["REQUEST_METHOD"] === "POST") if($_SERVER["REQUEST_METHOD"] === "POST") {
{ if(!empty($this->postParam("text"))) {
if(!empty($this->postParam("text")))
{
$ticket->setType(0); $ticket->setType(0);
$ticket->save(); $ticket->save();
@ -147,7 +150,7 @@ final class SupportPresenter extends OpenVKPresenter
header("HTTP/1.1 302 Found"); header("HTTP/1.1 302 Found");
header("Location: /support/view/" . $id); header("Location: /support/view/" . $id);
} else { } else {
$this->flashFail("err", "Ошибка", "Вы не ввели текст"); $this->flashFail("err", tr("error"), tr("you_have_not_entered_text"));
} }
} }
} }
@ -156,10 +159,14 @@ final class SupportPresenter extends OpenVKPresenter
{ {
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0); $this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$ticket = $this->tickets->get($id); $ticket = $this->tickets->get($id);
if(!$ticket || $ticket->isDeleted() != 0)
$this->notFound();
$ticketComments = $this->comments->getCommentsById($id); $ticketComments = $this->comments->getCommentsById($id);
$this->template->ticket = $ticket; $this->template->ticket = $ticket;
$this->template->comments = $ticketComments; $this->template->comments = $ticketComments;
$this->template->id = $id; $this->template->id = $id;
} }
function renderAnswerTicketReply(int $id): void function renderAnswerTicketReply(int $id): void
@ -168,12 +175,10 @@ final class SupportPresenter extends OpenVKPresenter
$ticket = $this->tickets->get($id); $ticket = $this->tickets->get($id);
if($_SERVER["REQUEST_METHOD"] === "POST") if($_SERVER["REQUEST_METHOD"] === "POST") {
{
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
if(!empty($this->postParam("text")) && !empty($this->postParam("status"))) if(!empty($this->postParam("text")) && !empty($this->postParam("status"))) {
{
$ticket->setType($this->postParam("status")); $ticket->setType($this->postParam("status"));
$ticket->save(); $ticket->save();
@ -185,14 +190,13 @@ final class SupportPresenter extends OpenVKPresenter
$comment->setTicket_id($id); $comment->setTicket_id($id);
$comment->setCreated(time()); $comment->setCreated(time());
$comment->save(); $comment->save();
} elseif (empty($this->postParam("text"))) { } elseif(empty($this->postParam("text"))) {
$ticket->setType($this->postParam("status")); $ticket->setType($this->postParam("status"));
$ticket->save(); $ticket->save();
} }
$this->flashFail("succ", "Тикет изменён", "Изменения вступят силу через несколько секунд."); $this->flashFail("succ", tr("ticket_changed"), tr("ticket_changed_comment"));
} }
} }
function renderKnowledgeBaseArticle(string $name): void function renderKnowledgeBaseArticle(string $name): void
@ -220,4 +224,24 @@ final class SupportPresenter extends OpenVKPresenter
$this->template->heading = $heading; $this->template->heading = $heading;
$this->template->content = $parser->parse($content); $this->template->content = $parser->parse($content);
} }
function renderRateAnswer(int $id, int $mark): void
{
$this->willExecuteWriteAction();
$this->assertUserLoggedIn();
$this->assertNoCSRF();
$comment = $this->comments->get($id);
if($this->user->id !== $comment->getTicket()->getUser()->getId())
exit(header("HTTP/1.1 403 Forbidden"));
if($mark !== 1 && $mark !== 2)
exit(header("HTTP/1.1 400 Bad Request"));
$comment->setMark($mark);
$comment->save();
exit(header("HTTP/1.1 200 OK"));
}
} }

View file

@ -0,0 +1,194 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Topic, Club, Comment, Photo, Video};
use openvk\Web\Models\Repositories\{Topics, Clubs};
final class TopicsPresenter extends OpenVKPresenter
{
private $topics;
private $clubs;
function __construct(Topics $topics, Clubs $clubs)
{
$this->topics = $topics;
$this->clubs = $clubs;
parent::__construct();
}
function renderBoard(int $id): void
{
$this->assertUserLoggedIn();
$club = $this->clubs->get($id);
if(!$club)
$this->notFound();
$this->template->club = $club;
$page = (int) ($this->queryParam("p") ?? 1);
$query = $this->queryParam("query");
if($query) {
$results = $this->topics->find($club, $query);
$this->template->topics = $results->page($page);
$this->template->count = $results->size();
} else {
$this->template->topics = $this->topics->getClubTopics($club, $page);
$this->template->count = $this->topics->getClubTopicsCount($club);
}
$this->template->paginatorConf = (object) [
"count" => $this->template->count,
"page" => $page,
"amount" => NULL,
"perPage" => OPENVK_DEFAULT_PER_PAGE,
];
}
function renderTopic(int $clubId, int $topicId): void
{
$this->assertUserLoggedIn();
$topic = $this->topics->getTopicById($clubId, $topicId);
if(!$topic)
$this->notFound();
$this->template->topic = $topic;
$this->template->club = $topic->getClub();
$this->template->count = $topic->getCommentsCount();
$this->template->page = (int) ($this->queryParam("p") ?? 1);
$this->template->comments = iterator_to_array($topic->getComments($this->template->page));
}
function renderCreate(int $clubId): void
{
$this->assertUserLoggedIn();
$club = $this->clubs->get($clubId);
if(!$club)
$this->notFound();
if(!$club->isEveryoneCanCreateTopics() && !$club->canBeModifiedBy($this->user->identity))
$this->notFound();
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->willExecuteWriteAction();
$title = $this->postParam("title");
if(!$title)
$this->flashFail("err", tr("failed_to_create_topic"), tr("no_title_specified"));
$flags = 0;
if($this->postParam("as_group") === "on" && $club->canBeModifiedBy($this->user->identity))
$flags |= 0b10000000;
$topic = new Topic;
$topic->setGroup($club->getId());
$topic->setOwner($this->user->id);
$topic->setTitle(ovk_proc_strtr($title, 127));
$topic->setCreated(time());
$topic->setFlags($flags);
$topic->save();
// 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->flash("err", "Не удалось опубликовать комментарий", "Файл медиаконтента повреждён или слишком велик.");
$this->redirect("/topic" . $topic->getPrettyId(), static::REDIRECT_TEMPORARY);
}
if(!empty($this->postParam("text")) || $photo || $video) {
try {
$comment = new Comment;
$comment->setOwner($this->user->id);
$comment->setModel(get_class($topic));
$comment->setTarget($topic->getId());
$comment->setContent($this->postParam("text"));
$comment->setCreated(time());
$comment->setFlags($flags);
$comment->save();
} catch (\LengthException $ex) {
$this->flash("err", "Не удалось опубликовать комментарий", "Комментарий слишком большой.");
$this->redirect("/topic" . $topic->getPrettyId(), static::REDIRECT_TEMPORARY);
}
if(!is_null($photo))
$comment->attach($photo);
if(!is_null($video))
$comment->attach($video);
}
$this->redirect("/topic" . $topic->getPrettyId(), static::REDIRECT_TEMPORARY);
}
$this->template->club = $club;
$this->template->graffiti = (bool) ovkGetQuirk("comments.allow-graffiti");
}
function renderEdit(int $clubId, int $topicId): void
{
$this->assertUserLoggedIn();
$topic = $this->topics->getTopicById($clubId, $topicId);
if(!$topic)
$this->notFound();
if(!$topic->canBeModifiedBy($this->user->identity))
$this->notFound();
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->willExecuteWriteAction();
$title = $this->postParam("title");
if(!$title)
$this->flashFail("err", tr("failed_to_change_topic"), tr("no_title_specified"));
$topic->setTitle(ovk_proc_strtr($title, 127));
$topic->setClosed(empty($this->postParam("close")) ? 0 : 1);
if($topic->getClub()->canBeModifiedBy($this->user->identity))
$topic->setPinned(empty($this->postParam("pin")) ? 0 : 1);
$topic->save();
$this->flash("succ", tr("changes_saved"), tr("topic_changes_saved_comment"));
$this->redirect("/topic" . $topic->getPrettyId(), static::REDIRECT_TEMPORARY);
}
$this->template->topic = $topic;
$this->template->club = $topic->getClub();
}
function renderDelete(int $clubId, int $topicId): void
{
$this->assertUserLoggedIn();
$this->assertNoCSRF();
$topic = $this->topics->getTopicById($clubId, $topicId);
if(!$topic)
$this->notFound();
if(!$topic->canBeModifiedBy($this->user->identity))
$this->notFound();
$this->willExecuteWriteAction();
$topic->deleteTopic();
$this->redirect("/board" . $topic->getClub()->getId(), static::REDIRECT_TEMPORARY);
}
}

View file

@ -9,6 +9,8 @@ use openvk\Web\Models\Repositories\Albums;
use openvk\Web\Models\Repositories\Videos; use openvk\Web\Models\Repositories\Videos;
use openvk\Web\Models\Repositories\Notes; use openvk\Web\Models\Repositories\Notes;
use openvk\Web\Models\Repositories\Vouchers; use openvk\Web\Models\Repositories\Vouchers;
use openvk\Web\Util\Validator;
use openvk\Web\Models\Entities\Notifications\CoinsTransferNotification;
use Chandler\Security\Authenticator; use Chandler\Security\Authenticator;
use lfkeitel\phptotp\{Base32, Totp}; use lfkeitel\phptotp\{Base32, Totp};
use chillerlan\QRCode\{QRCode, QROptions}; use chillerlan\QRCode\{QRCode, QROptions};
@ -53,6 +55,8 @@ final class UserPresenter extends OpenVKPresenter
$page = abs($this->queryParam("p") ?? 1); $page = abs($this->queryParam("p") ?? 1);
if(!$user) if(!$user)
$this->notFound(); $this->notFound();
elseif (!$user->getPrivacyPermission('friends.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
else else
$this->template->user = $user; $this->template->user = $user;
@ -77,11 +81,13 @@ final class UserPresenter extends OpenVKPresenter
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$user = $this->users->get($id); $user = $this->users->get($id);
if(!$user) { if(!$user)
$this->notFound(); $this->notFound();
} else { elseif (!$user->getPrivacyPermission('groups.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
else {
$this->template->user = $user; $this->template->user = $user;
$this->template->page = $this->queryParam("p") ?? 1; $this->template->page = (int) ($this->queryParam("p") ?? 1);
$this->template->admin = $this->queryParam("act") == "managed"; $this->template->admin = $this->queryParam("act") == "managed";
} }
} }
@ -127,87 +133,99 @@ final class UserPresenter extends OpenVKPresenter
if(!$id) if(!$id)
$this->notFound(); $this->notFound();
$user = $this->users->get($id); $user = $this->users->get($id);
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
if($_GET['act'] === "main" || $_GET['act'] == NULL) { if($_GET['act'] === "main" || $_GET['act'] == NULL) {
$user->setFirst_Name(empty($this->postParam("first_name")) ? $user->getFirstName() : $this->postParam("first_name")); $user->setFirst_Name(empty($this->postParam("first_name")) ? $user->getFirstName() : $this->postParam("first_name"));
$user->setLast_Name(empty($this->postParam("last_name")) ? "" : $this->postParam("last_name")); $user->setLast_Name(empty($this->postParam("last_name")) ? "" : $this->postParam("last_name"));
$user->setPseudo(empty($this->postParam("pseudo")) ? NULL : $this->postParam("pseudo")); $user->setPseudo(empty($this->postParam("pseudo")) ? NULL : $this->postParam("pseudo"));
$user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status")); $user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status"));
if (strtotime($this->postParam("birthday")) < time()) if (strtotime($this->postParam("birthday")) < time())
$user->setBirthday(strtotime($this->postParam("birthday"))); $user->setBirthday(strtotime($this->postParam("birthday")));
if ($this->postParam("marialstatus") <= 8 && $this->postParam("marialstatus") >= 0) if ($this->postParam("marialstatus") <= 8 && $this->postParam("marialstatus") >= 0)
$user->setMarital_Status($this->postParam("marialstatus")); $user->setMarital_Status($this->postParam("marialstatus"));
if ($this->postParam("politViews") <= 9 && $this->postParam("politViews") >= 0) if ($this->postParam("politViews") <= 9 && $this->postParam("politViews") >= 0)
$user->setPolit_Views($this->postParam("politViews")); $user->setPolit_Views($this->postParam("politViews"));
if ($this->postParam("gender") <= 1 && $this->postParam("gender") >= 0) if ($this->postParam("gender") <= 1 && $this->postParam("gender") >= 0)
$user->setSex($this->postParam("gender")); $user->setSex($this->postParam("gender"));
if(!empty($this->postParam("phone")) && $this->postParam("phone") !== $user->getPhone()) { if(!empty($this->postParam("phone")) && $this->postParam("phone") !== $user->getPhone()) {
if(!OPENVK_ROOT_CONF["openvk"]["credentials"]["smsc"]["enable"]) if(!OPENVK_ROOT_CONF["openvk"]["credentials"]["smsc"]["enable"])
$this->flashFail("err", tr("error_segmentation"), "котлетки"); $this->flashFail("err", tr("error_segmentation"), "котлетки");
$code = $user->setPhoneWithVerification($this->postParam("phone")); $code = $user->setPhoneWithVerification($this->postParam("phone"));
if(!Sms::send($this->postParam("phone"), "OPENVK - Your verification code is: $code")) if(!Sms::send($this->postParam("phone"), "OPENVK - Your verification code is: $code"))
$this->flashFail("err", tr("error_segmentation"), "котлетки: Remote err!"); $this->flashFail("err", tr("error_segmentation"), "котлетки: Remote err!");
} }
} elseif($_GET['act'] === "contacts") { } elseif($_GET['act'] === "contacts") {
if(empty($this->postParam("email_contact")) || Validator::i()->emailValid($this->postParam("email_contact")))
$user->setEmail_Contact(empty($this->postParam("email_contact")) ? NULL : $this->postParam("email_contact")); $user->setEmail_Contact(empty($this->postParam("email_contact")) ? NULL : $this->postParam("email_contact"));
$user->setTelegram(empty($this->postParam("telegram")) ? NULL : ltrim($this->postParam("telegram"), "@")); else
$user->setCity(empty($this->postParam("city")) ? NULL : $this->postParam("city")); $this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment"));
$user->setAddress(empty($this->postParam("address")) ? NULL : $this->postParam("address"));
$website = $this->postParam("website") ?? ""; $telegram = $this->postParam("telegram");
if(empty($website)) if(empty($telegram) || Validator::i()->telegramValid($telegram))
$user->setWebsite(NULL); if(strpos($telegram, "t.me/") === 0)
$user->setTelegram(empty($telegram) ? NULL : substr($telegram, 5));
else else
$user->setWebsite((!parse_url($website, PHP_URL_SCHEME) ? "https://" : "") . $website); $user->setTelegram(empty($telegram) ? NULL : ltrim($telegram, "@"));
} elseif($_GET['act'] === "interests") { else
$user->setInterests(empty($this->postParam("interests")) ? NULL : ovk_proc_strtr($this->postParam("interests"), 300)); $this->flashFail("err", tr("invalid_telegram_name"), tr("invalid_telegram_name_comment"));
$user->setFav_Music(empty($this->postParam("fav_music")) ? NULL : ovk_proc_strtr($this->postParam("fav_music"), 300));
$user->setFav_Films(empty($this->postParam("fav_films")) ? NULL : ovk_proc_strtr($this->postParam("fav_films"), 300));
$user->setFav_Shows(empty($this->postParam("fav_shows")) ? NULL : ovk_proc_strtr($this->postParam("fav_shows"), 300));
$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->setCity(empty($this->postParam("city")) ? NULL : $this->postParam("city"));
$user->save(); $user->setAddress(empty($this->postParam("address")) ? NULL : $this->postParam("address"));
header("HTTP/1.1 302 Found"); $website = $this->postParam("website") ?? "";
header("Location: /id" . $user->getId()); if(empty($website))
exit; $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));
$user->setFav_Films(empty($this->postParam("fav_films")) ? NULL : ovk_proc_strtr($this->postParam("fav_films"), 300));
$user->setFav_Shows(empty($this->postParam("fav_shows")) ? NULL : ovk_proc_strtr($this->postParam("fav_shows"), 300));
$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 символов)");
} }
try { $user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status"));
$user->save(); $user->save();
} catch(\PDOException $ex) {
if($ex->getCode() == 23000)
$this->flashFail("err", tr("error"), tr("error_shorturl"));
else
throw $ex;
}
$this->flash("succ", tr("changes_saved"), tr("changes_saved_comment")); header("HTTP/1.1 302 Found");
header("Location: /id" . $user->getId());
exit;
} }
$this->template->mode = in_array($this->queryParam("act"), [ try {
"main", "contacts", "interests", "avatar" $user->save();
]) ? $this->queryParam("act") } catch(\PDOException $ex) {
: "main"; if($ex->getCode() == 23000)
$this->flashFail("err", tr("error"), tr("error_shorturl"));
else
throw $ex;
}
$this->template->user = $user; $this->flash("succ", tr("changes_saved"), tr("changes_saved_comment"));
}
$this->template->mode = in_array($this->queryParam("act"), [
"main", "contacts", "interests", "avatar"
]) ? $this->queryParam("act")
: "main";
$this->template->user = $user;
} }
function renderVerifyPhone(): void function renderVerifyPhone(): void
@ -358,6 +376,7 @@ final class UserPresenter extends OpenVKPresenter
"menu_notatoj" => "notes", "menu_notatoj" => "notes",
"menu_grupoj" => "groups", "menu_grupoj" => "groups",
"menu_novajoj" => "news", "menu_novajoj" => "news",
"menu_ligiloj" => "links",
]; ];
foreach($settings as $checkbox => $setting) foreach($settings as $checkbox => $setting)
$user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox)); $user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox));
@ -447,4 +466,42 @@ final class UserPresenter extends OpenVKPresenter
$this->user->identity->save(); $this->user->identity->save();
$this->flashFail("succ", tr("information_-1"), tr("two_factor_authentication_disabled_message")); $this->flashFail("succ", tr("information_-1"), tr("two_factor_authentication_disabled_message"));
} }
function renderCoinsTransfer(): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
$receiverAddress = $this->postParam("receiver");
$value = (int) $this->postParam("value");
$message = $this->postParam("message");
if(!$receiverAddress || !$value)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("not_all_information_has_been_entered"));
if($value < 0)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("negative_transfer_value"));
if(iconv_strlen($message) > 255)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("message_is_too_long"));
$receiver = $this->users->getByAddress($receiverAddress);
if(!$receiver)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("receiver_not_found"));
if($this->user->identity->getCoins() < $value)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("you_dont_have_enough_points"));
if($this->user->id !== $receiver->getId()) {
$this->user->identity->setCoins($this->user->identity->getCoins() - $value);
$this->user->identity->save();
$receiver->setCoins($receiver->getCoins() + $value);
$receiver->save();
(new CoinsTransferNotification($receiver, $this->user->identity, $value, $message))->emit();
}
$this->flashFail("succ", tr("information_-1"), tr("points_transfer_successful", tr("points_amount", $value), $receiver->getURL(), htmlentities($receiver->getCanonicalName())));
}
} }

View file

@ -22,6 +22,8 @@ final class VideosPresenter extends OpenVKPresenter
{ {
$user = $this->users->get($id); $user = $this->users->get($id);
if(!$user) $this->notFound(); if(!$user) $this->notFound();
if(!$user->getPrivacyPermission('videos.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$this->template->user = $user; $this->template->user = $user;
$this->template->videos = $this->videos->getByUser($user, (int) ($this->queryParam("p") ?? 1)); $this->template->videos = $this->videos->getByUser($user, (int) ($this->queryParam("p") ?? 1));
@ -38,6 +40,8 @@ final class VideosPresenter extends OpenVKPresenter
{ {
$user = $this->users->get($owner); $user = $this->users->get($owner);
if(!$user) $this->notFound(); if(!$user) $this->notFound();
if(!$user->getPrivacyPermission('videos.read', $this->user->identity ?? NULL))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
if($this->videos->getByOwnerAndVID($owner, $vId)->isDeleted()) $this->notFound(); if($this->videos->getByOwnerAndVID($owner, $vId)->isDeleted()) $this->notFound();

View file

@ -197,7 +197,7 @@ final class WallPresenter extends OpenVKPresenter
} }
$flags = 0; $flags = 0;
if($this->postParam("as_group") === "on") if($this->postParam("as_group") === "on" && $wallOwner instanceof Club && $wallOwner->canBeModifiedBy($this->user->identity))
$flags |= 0b10000000; $flags |= 0b10000000;
if($this->postParam("force_sign") === "on") if($this->postParam("force_sign") === "on")
$flags |= 0b01000000; $flags |= 0b01000000;

View file

@ -1,22 +1,20 @@
{extends "@layout.xml"} {extends "@layout.xml"}
{block title}Вам бан{/block} {block title}{_"banned_title"}{/block}
{block header} {block header}
Вы были верискокнуты {_"banned_header"}
{/block} {/block}
{block content} {block content}
<center> <center>
<img src="/assets/packages/static/openvk/img/oof.apng" alt="Пользователь заблокирован." style="width: 20%;" /> <img src="/assets/packages/static/openvk/img/oof.apng" alt="{_'banned_alt'}" style="width: 20%;" />
</center> </center>
<p> <p>
Извините, <b>{$thisUser->getCanonicalName()}</b>, но вы были верискокнуты.<br/> {tr("banned_1", htmlentities($thisUser->getCanonicalName()))|noescape}<br/>
А причина этому проста: <b>{$thisUser->getBanReason()}</b>. К сожалению, на этот раз {tr("banned_2", htmlentities($thisUser->getBanReason()))|noescape}
нам пришлось заблокировать вас навсегда.
</p> </p>
<hr/> <hr/>
<p> <p>
Вы всё ещё можете <a href="/support?act=new">написать в службу поддержки</a>, если считаете что произошла ошибка {tr("banned_3", urlencode($csrfToken))|noescape}
или <a href="/logout">выйти</a>.
</p> </p>
{/block} {/block}

View file

@ -96,10 +96,12 @@
<a href="/search?type=groups">{_"header_groups"}</a> <a href="/search?type=groups">{_"header_groups"}</a>
</div> </div>
<div class="link"> <div class="link">
<a href="/donate">{_"header_donate"}</a> <a href="/search">{_"header_search"}</a>
</div> </div>
<div class="link"> <div class="link">
<a href="/search">{_"header_search"}</a> <a href="/invite">
{_"header_invite"}
</a>
</div> </div>
<div class="link"> <div class="link">
<a href="/support"> <a href="/support">
@ -166,24 +168,25 @@
<a href="/settings" class="link">{_"my_settings"}</a> <a href="/settings" class="link">{_"my_settings"}</a>
{var canAccessAdminPanel = $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 canAccessHelpdesk = $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
{var menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0} {var menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0 && $thisUser->getLeftMenuItemStatus('links')}
<div n:if="$canAccessAdminPanel || $canAccessHelpdesk || $menuLinksAvaiable" class="menu_divider"></div> <div n:if="$canAccessAdminPanel || $canAccessHelpdesk || $menuLinksAvaiable" class="menu_divider"></div>
{if $canAccessAdminPanel} {if $canAccessAdminPanel}
<a href="/admin" class="link">Админ-панель</a> <a href="/admin" class="link">Админ-панель</a>
{/if} {/if}
{if $canAccessHelpdesk} {if $canAccessHelpdesk}
<a href="/support/tickets" class="link">Helpdesk <a href="/support/tickets" class="link">Helpdesk
{if $helpdeskTicketAnsweredCount > 0} {if $helpdeskTicketNotAnsweredCount > 0}
(<b>{$helpdeskTicketNotAnsweredCount}</b>) (<b>{$helpdeskTicketNotAnsweredCount}</b>)
{/if} {/if}
</a> </a>
<a href="/admin/reports" class="link">Reports</a> <a href="/admin/reports" class="link">Reports</a>
{/if} {/if}
<a <a
n:if="$thisUser->getLeftMenuItemStatus('links')"
n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem" n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem"
href="{$menuItem['url']}" href="{$menuItem['url']}"
target="_blank" target="_blank"
class="link">{$menuItem["name"]}</a> class="link">{strpos($menuItem["name"], "@") === 0 ? tr(substr($menuItem["name"], 1)) : $menuItem["name"]}</a>
<div id="_groupListPinnedGroups"> <div id="_groupListPinnedGroups">
<div n:if="$thisUser->getPinnedClubCount() > 0" class="menu_divider"></div> <div n:if="$thisUser->getPinnedClubCount() > 0" class="menu_divider"></div>
<a <a
@ -202,8 +205,8 @@
style="max-width: 100%; margin-top: 50px;" /> style="max-width: 100%; margin-top: 50px;" />
</a> </a>
{else} {else}
<a href="/support" class="link">Поддержка</a> <a href="/support" class="link">{_"menu_support"}</a>
<a href="/logout" class="link">Выйти</a> <a href="/logout?hash={urlencode($csrfToken)}" class="link">{_"menu_logout"}</a>
{/if} {/if}
{else} {else}
<form id="fastLogin" action="/login" method="POST" enctype="multipart/form-data"> <form id="fastLogin" action="/login" method="POST" enctype="multipart/form-data">

View file

@ -29,6 +29,9 @@
{include description, x => $dat} {include description, x => $dat}
</td> </td>
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px">
{include actions, x => $dat}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -1,14 +1,14 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title}Пригласить{/block} {block title}{_invite}{/block}
{block header} {block header}
Пригласить {_invite}
{/block} {/block}
{block content} {block content}
Вы можете пригласить своих друзей или знакомых в сеть с помощью индивидуальной ссылки:<br><br> {_"you_can_invite"}<br><br>
<center> <center>
<input type="text" readonly value="https://{$_SERVER["HTTP_HOST"]}/reg?ref={rawurlencode($thisUser->getRefLinkId())}" size="50" /> <input type="text" readonly value="https://{$_SERVER["HTTP_HOST"]}/reg?ref={rawurlencode($thisUser->getRefLinkId())}" size="50" />
</center> </center>
<p>Приложите эту ссылку к вашему сообщению. Пользователь зарегистрируется, и он сразу появится у вас в друзьях.</p> <p>{_"you_can_invite_2"}</p>
{/block} {/block}

View file

@ -1,9 +1,9 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title}Об OpenVK{/block} {block title}{_about_openvk}{/block}
{block header} {block header}
Об OpenVK {_about_openvk}
{/block} {/block}
{block content} {block content}
@ -343,7 +343,7 @@
<tbody> <tbody>
<tr class="h"> <tr class="h">
<th>Name</th> <th>Name</th>
<th>Status</th> <th style="width: 50px;">Status</th>
<th>Version</th> <th>Version</th>
<th>Description</th> <th>Description</th>
<th>Author</th> <th>Author</th>

View file

@ -1,29 +1,29 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title} {block title}
Восстановление доступа {_"access_recovery"}
{/block} {/block}
{block header} {block header}
Восстановить доступ к странице {_"page_access_recovery"}
{/block} {/block}
{block content} {block content}
<p> <p>
Введите ваш новый пароль. Все текущие сеансы будут приостановлены и токены доступа будут аннулированы. {_"access_recovery_info_2"}
</p> </p>
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
<label for="password">Новый пароль: </label> <label for="password">{_"new_password"}: </label>
<input id="password" type="password" name="password" required /> <input id="password" type="password" name="password" required />
<br/><br/> <br/><br/>
{if $is2faEnabled} {if $is2faEnabled}
<label for="code">Код двухфакторной аутентификации: </label> <label for="code">{_"2fa_code_2"}: </label>
<input id="code" type="text" name="code" required /> <input id="code" type="text" name="code" required />
<br/><br/> <br/><br/>
{/if} {/if}
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="Сбросить пароль" class="button" style="float: right;" /> <input type="submit" value="{_'reset_password'}" class="button" style="float: right;" />
</form> </form>
{/block} {/block}

View file

@ -7,20 +7,20 @@
{block headIncludes} {block headIncludes}
{if !$referer} {if !$referer}
<meta name="description" content="Зарегистрируйтесь в OpenVK прямо сейчас!" /> <meta name="description" content="{tr('register_meta_desc', OPENVK_ROOT_CONF['openvk']['appearance']['name'])}" />
{else} {else}
<meta property="og:title" content="{$referer->getFullName()} приглашает вас в OpenVK!" /> <meta property="og:title" content="{tr('register_referer_meta_title', $referer->getFullName(), OPENVK_ROOT_CONF['openvk']['appearance']['name'])}" />
<meta property="og:image" content="{$referer->getAvatarUrl()}" /> <meta property="og:image" content="{$referer->getAvatarUrl()}" />
<meta name="description" <meta name="description"
content="Присоединяйтесь к {$referer->getFullName()} и множеству других пользователей в OpenVK!" /> content="{tr('register_referer_meta_desc', $referer->getFullName(), OPENVK_ROOT_CONF['openvk']['appearance']['name'])}" />
{/if} {/if}
{/block} {/block}
{block content} {block content}
{if OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable'] || $referer} {if OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable'] || $referer}
<p n:if="!is_null($referer)" align="center"> <p n:if="!is_null($referer)" align="center">
<strong>{$referer->getFullName()}</strong> приглашает вас в OpenVK! {tr("invites_you_to", $referer->getFullName(), OPENVK_ROOT_CONF['openvk']['appearance']['name'])|noescape}
</p> </p>
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
@ -102,9 +102,9 @@
</form> </form>
{else} {else}
<center> <center>
<img src="/assets/packages/static/openvk/img/oof.apng" alt="Регистрация закрыта." style="width: 20%;" /> <img src="/assets/packages/static/openvk/img/oof.apng" alt="{_'registration_closed'}" style="width: 20%;" />
<p> <p>
Регистрация отключена системным администратором. При возможности попросите приглашение у вашего знакомого, если он зарегистрирован на этом сайте. {_"registration_disabled_info"}
{if OPENVK_ROOT_CONF['openvk']['preferences']['registration']['reason']} {if OPENVK_ROOT_CONF['openvk']['preferences']['registration']['reason']}
<br/><br/><b>{php echo OPENVK_ROOT_CONF['openvk']['preferences']['registration']['reason']}</b> <br/><br/><b>{php echo OPENVK_ROOT_CONF['openvk']['preferences']['registration']['reason']}</b>
{/if} {/if}

View file

@ -1,24 +1,24 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title} {block title}
Восстановление доступа {_"access_recovery"}
{/block} {/block}
{block header} {block header}
Восстановить доступ к странице {_"page_access_recovery"}
{/block} {/block}
{block content} {block content}
<p> <p>
Забыли пароль? Не волнуйтесь, введите ваши данные и мы отправим вам email с инструкциями по восстановлению аккаунта. {_"access_recovery_info"}
</p> </p>
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
<label for="login">Логин: </label> <label for="login">{_"log_in"}: </label>
<input id="login" type="text" name="login" required /> <input id="login" type="text" name="login" required />
<br/><br/> <br/><br/>
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="Сбросить пароль" class="button" style="float: right;" /> <input type="submit" value="{_'reset_password'}" class="button" style="float: right;" />
</form> </form>
{/block} {/block}

View file

@ -24,7 +24,7 @@
{/block} {/block}
{block description} {block description}
<table class="ugc-table" n:if="$hideInfo ? !$x->anon : true"> <table class="ugc-table" n:if="$hideInfo ? (!$x->anon || $x->sender->getId() === $thisUser->getId()) : true">
<tbody> <tbody>
<tr> <tr>
<td><span class="nobold">{_sender}: </span></td> <td><span class="nobold">{_sender}: </span></td>

View file

@ -69,7 +69,7 @@
<input type="file" name="ava" accept="image/*" /> <input type="file" name="ava" accept="image/*" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td width="120" valign="top"> <td width="120" valign="top">
<span class="nobold">{_wall}: </span> <span class="nobold">{_wall}: </span>
</td> </td>
@ -77,6 +77,15 @@
<input type="checkbox" name="wall" value="1" {if $club->canPost()}checked{/if}/> {_group_allow_post_for_everyone} <input type="checkbox" name="wall" value="1" {if $club->canPost()}checked{/if}/> {_group_allow_post_for_everyone}
</td> </td>
</tr> </tr>
<tr>
<td width="120" valign="top">
<span class="nobold">{_discussions}: </span>
</td>
<td>
<input type="checkbox" name="everyone_can_create_topics" value="1" n:attr="checked => $club->isEveryoneCanCreateTopics()" /> {_everyone_can_create_topics}<br>
<input type="checkbox" name="display_topics_above_wall" value="1" n:attr="checked => $club->isDisplayTopicsAboveWallEnabled()" /> {_display_list_of_topics_above_wall}
</td>
</tr>
<tr> <tr>
<td width="120" valign="top"> <td width="120" valign="top">
<span class="nobold">{_group_administrators_list}: </span> <span class="nobold">{_group_administrators_list}: </span>

View file

@ -14,10 +14,6 @@
<a n:if="$onlyShowManagers" href="/club{$club->getId()}/followers" style="float: right;">{_only_administrators}</a> <a n:if="$onlyShowManagers" href="/club{$club->getId()}/followers" style="float: right;">{_only_administrators}</a>
{/block} {/block}
{block actions}
{/block}
{* BEGIN ELEMENTS DESCRIPTION *} {* BEGIN ELEMENTS DESCRIPTION *}
{block tabs} {block tabs}
@ -81,39 +77,67 @@
{/if} {/if}
</td> </td>
</tr> </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={$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> </tbody>
</table> </table>
<script n:if="$club->getOwner()->getId() != $user->getId() && $manager && $thisUser->getId() == $club->getOwner()->getId()">
console.log("gayshit");
console.log("сам такой");
function changeOwner(club, newOwner) {
const action = "/groups/" + club + "/setNewOwner/" + newOwner;
MessageBox({_group_changeowner_modal_title}, `
{tr("group_changeowner_modal_text", htmlentities($user->getFullName()))|noescape}
<br/><br/>
<form id="transfer-owner-permissions-form" method="post">
<label for="password">{_password|noescape}</label>
<input type="password" id="password" name="password" required />
<input type="hidden" name="hash" value={$csrfToken} />
</form>
`, [{_transfer}, {_cancel}], [
() => {
$("#transfer-owner-permissions-form").attr("action", action);
document.querySelector("#transfer-owner-permissions-form").submit();
}, Function.noop
]);
}
</script>
{/block}
{block actions}
{var user = $x instanceof $Manager ? $x->getUser() : $x}
{var manager = $x instanceof $Manager ? $x : $club->getManager($user, !$club->canBeModifiedBy($thisUser))}
{if $club->canBeModifiedBy($thisUser ?? NULL)}
<a class="profile_link" 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 $club->getOwner()->getId() != $user->getId() && $manager && $thisUser->getId() == $club->getOwner()->getId()}
<a class="profile_link" href="javascript:changeOwner({$club->getId()}, {$user->getId()})">
{_promote_to_owner}
</a>
{/if}
{if $manager}
<a class="profile_link" href="javascript:setClubAdminComment('{$club->getId()}', '{$manager->getUserId()}', '{rawurlencode($csrfToken)}')">
{_set_comment}
</a>
{/if}
<a class="profile_link" n:if="$club->getOwner()->getId() === $user->getId()" href="javascript:setClubAdminComment('{$club->getId()}', '{$club->getOwner()->getId()}', '{rawurlencode($csrfToken)}')">
{_set_comment}
</a>
{if $manager}
<a class="profile_link" 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 class="profile_link" 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}
{/if}
{/block} {/block}

View file

@ -41,45 +41,54 @@
</table> </table>
</div> </div>
<div n:if="$club->getFollowersCount() > 0"> <div n:if="$club->getFollowersCount() > 0">
{var followersCount = $club->getFollowersCount()} {var followersCount = $club->getFollowersCount()}
<div class="content_title_expanded" onclick="hidePanel(this, {$followersCount});"> <div class="content_title_expanded" onclick="hidePanel(this, {$followersCount});">
{_participants} {_participants}
</div> </div>
<div> <div>
<div class="content_subtitle"> <div class="content_subtitle">
{tr("participants", $followersCount)} {tr("participants", $followersCount)}
<div style="float:right;"> <div style="float:right;">
<a href="/club{$club->getId()}/followers">{_all_title}</a> <a href="/club{$club->getId()}/followers">{_all_title}</a>
</div>
</div> </div>
<div style="padding-left: 5px;"> </div>
<table <div style="padding-left: 5px;" class="content_list long">
n:foreach="$club->getFollowers(1) as $follower" <div class="cl_element" n:foreach="$club->getFollowers(1) as $follower">
n:class="User" <div class="cl_avatar">
style="text-align:center;display:inline-block;width:62px" <a href="{$follower->getURL()}">
cellspacing=4> <img class="ava" src="{$follower->getAvatarUrl()}" />
<tbody> </a>
<tr> </div>
<td> <a href="{$follower->getURL()}" class="cl_name">
<a href="{$follower->getURL()}"> <text class="cl_fname">{$follower->getFirstName()}</text>
<img src="{$follower->getAvatarUrl()}" width="50" /> <text class="cl_lname">{$follower->getLastName()}</text>
</a> </a>
</td>
</tr>
<tr>
<td>
<a href="{$follower->getURL()}">{$follower->getFirstName()}</a>
</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
</div>
<div n:if="($topicsCount > 0 || $club->isEveryoneCanCreateTopics() || ($thisUser && $club->canBeModifiedBy($thisUser))) && $club->isDisplayTopicsAboveWallEnabled()">
<div class="content_title_expanded" onclick="hidePanel(this, {$topicsCount});">
{_discussions}
</div>
<div>
<div class="content_subtitle">
{tr("topics", $topicsCount)}
<div style="float: right;">
<a href="/board{$club->getId()}">{_"all_title"}</a>
</div>
</div>
<div>
<div n:foreach="$topics as $topic" class="topic-list-item" style="padding: 8px;">
<b><a href="/topic{$topic->getPrettyId()}">{$topic->getTitle()}</a></b><br>
<span class="nobold">{tr("updated_at", $topic->getUpdateTime())}</span>
</div>
</div>
</div>
</div>
{presenter "openvk!Wall->wallEmbedded", -$club->getId()} {presenter "openvk!Wall->wallEmbedded", -$club->getId()}
</div> </div>
<div class="right_small_block"> <div class="right_small_block">
<a href="{$club->getAvatarLink()|nocheck}"> <a href="{$club->getAvatarLink()|nocheck}">
@ -197,12 +206,31 @@
</div> </div>
<div> <div>
<b><a href="/album{$album->getPrettyId()}">{$album->getName()}</a></b><br> <b><a href="/album{$album->getPrettyId()}">{$album->getName()}</a></b><br>
<span class="nobold">Обновлён {$album->getEditTime() ?? $album->getCreationTime()}</span> <span class="nobold">{tr("updated_at", $album->getEditTime() ?? $album->getCreationTime())}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div n:if="($topicsCount > 0 || $club->isEveryoneCanCreateTopics() || ($thisUser && $club->canBeModifiedBy($thisUser))) && !$club->isDisplayTopicsAboveWallEnabled()">
<div class="content_title_expanded" onclick="hidePanel(this, {$topicsCount});">
{_discussions}
</div>
<div>
<div class="content_subtitle">
{tr("topics", $topicsCount)}
<div style="float: right;">
<a href="/board{$club->getId()}">{_"all_title"}</a>
</div>
</div>
<div>
<div n:foreach="$topics as $topic" class="topic-list-item">
<b><a href="/topic{$topic->getPrettyId()}">{$topic->getTitle()}</a></b><br>
<span class="nobold">{tr("updated_at", $topic->getUpdateTime())}</span>
</div>
</div>
</div>
</div>
</div> </div>
{/block} {/block}

View file

@ -8,7 +8,7 @@
<div n:if="($online = $correspondent->getOnline()->timestamp()) < time() + 2678400" style="float: right;"> <div n:if="($online = $correspondent->getOnline()->timestamp()) < time() + 2678400" style="float: right;">
{var diff = date_diff(date_create(), date_create('@' . $online))} {var diff = date_diff(date_create(), date_create('@' . $online))}
{if 5 >= $diff->i} {if 5 >= $diff->i}
<span><b>Online</b></span> <span><b>{_online}</b></span>
{else} {else}
<span>{$correspondent->isFemale() ? "заходила" : "заходил"} {$correspondent->getOnline()}</span> <span>{$correspondent->isFemale() ? "заходила" : "заходил"} {$correspondent->getOnline()}</span>
{/if} {/if}

View file

@ -1,7 +1,7 @@
{extends "../@layout.xml"} {extends "../@layout.xml"}
{block title}{_my_messages}{/block} {block title}{_my_messages}{/block}
{block header}{/block} {block header}{_my_messages}{/block}
{block content} {block content}
<div class="tabs"> <div class="tabs">

View file

@ -19,13 +19,6 @@
</div> </div>
{/block} {/block}
{block actions}
<div class="tile">
<a class="profile_link" href="?">{_unreaded}</a>
<a class="profile_link" href="?act=archived">{_archive}</a>
</div>
{/block}
{* BEGIN ELEMENTS DESCRIPTION *} {* BEGIN ELEMENTS DESCRIPTION *}
{block link|strip|stripHtml} {block link|strip|stripHtml}

View file

@ -27,23 +27,24 @@
&nbsp;|&nbsp; &nbsp;|&nbsp;
<a href="/album{$album->getPrettyId()}/edit">{_"edit_album"}</a> <a href="/album{$album->getPrettyId()}/edit">{_"edit_album"}</a>
{/if} {/if}
<br/> <br/><br/>
{if $album->getPhotosCount() > 0} {if $album->getPhotosCount() > 0}
{foreach $photos as $photo} <div class="container_gray album-flex">
{php if($photo->isDeleted()) continue; } {foreach $photos as $photo}
<div class="album-photo" style="display: inline-table;"> {php if($photo->isDeleted()) continue; }
<a <div class="album-photo">
n:if="!is_null($thisUser) && $album->canBeModifiedBy($thisUser)" <a
href="/album{$album->getPrettyId()}/remove_photo.pl/{$photo->getId()}" class="album-photo--delete"> n:if="!is_null($thisUser) && $album->canBeModifiedBy($thisUser)"
&times; href="/album{$album->getPrettyId()}/remove_photo.pl/{$photo->getId()}" class="album-photo--delete">
</a> &times;
</a>
<a href="/photo{$photo->getPrettyId()}?from=album{$album->getId()}">
<img class="album-photo--image" src="{$photo->getURL()}" alt="{$photo->getDescription()}" style="width:unset;max-height:unset;max-width: 188px;" />
</a>
</div>
{/foreach}
<a href="/photo{$photo->getPrettyId()}?from=album{$album->getId()}">
<img class="album-photo--image" src="{$photo->getURL()}" alt="{$photo->getDescription()}" />
</a>
</div>
{/foreach}
</div>
{include "../components/paginator.xml", conf => $paginatorConf} {include "../components/paginator.xml", conf => $paginatorConf}
{else} {else}
{include "../components/nothing.xml"} {include "../components/nothing.xml"}

View file

@ -9,7 +9,7 @@
{/block} {/block}
{block header} {block header}
OpenVK » {=OPENVK_ROOT_CONF["openvk"]["appearance"]["name"]} »
{if $type === "users"} {if $type === "users"}
{tr("search_for_people")} {tr("search_for_people")}
{else} {else}
@ -17,20 +17,31 @@
{/if} {/if}
{/block} {/block}
{* BEGIN ELEMENTS DESCRIPTION *}
{block tabs} {block tabs}
<form style="margin-left: 12px;"> <div {if $type === "users"}id="activetabs"{/if} class="tab">
<input name="type" type="hidden" value="{$_GET['type'] ?? 'users'}" /> <a {if $type === "users"}id="act_tab_a"{/if} href="/search?type=users">
<input name="query" type="text" placeholder="{_"header_search"}" value="{$_GET['query'] ?? ''}" style="width: 90%" /> {_users}
<input type="submit" class="button" value="{_"search_button"}" style="width: 9%" /> </a>
</div>
<div {if $type === "groups"}id="activetabs"{/if} class="tab">
<a {if $type === "groups"}id="act_tab_a"{/if} href="/search?type=groups">
{_groups}
</a>
</div>
<form class="header_search_inputbt">
<input name="type" type="hidden" value="{$type ?? 'users'}" />
<input name="query" class="header_search_input" placeholder="{_search_placeholder}" value="{$_GET['query'] ?? ''}" />
<button class="button_search">{_search_button}</button>
</form> </form>
<p style="margin-left: 15px;"> <p style="margin-left: 15px; margin-top: 0;">
<b>{tr("results", $count)}</b> <b>{tr("results", $count)}</b>
</p> </p>
{/block} {/block}
{* BEGIN ELEMENTS DESCRIPTION *}
{block link|strip|stripHtml} {block link|strip|stripHtml}
{$x->getURL()} {$x->getURL()}
{/block} {/block}

View file

@ -73,7 +73,12 @@
{if $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)} {if $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
<a href="{$comment->getUser()->getURL()}"> <a href="{$comment->getUser()->getURL()}">
<span class="nobold"> <span class="nobold">
({$comment->getUser()->getFirstName()} {iconv_substr($comment->getUser()->getLastName(), 0, 1)}.) {var lastName = $comment->getUser()->getLastName()}
{if empty(trim($lastName))}
({$comment->getUser()->getFirstName()})
{else}
({$comment->getUser()->getFirstName()} {iconv_substr($lastName, 0, 1)}.)
{/if}
</span> </span>
</a> </a>
{/if} {/if}
@ -104,6 +109,18 @@
<a href="/support/comment/{$comment->getId()}/delete">{_delete}</a> <a href="/support/comment/{$comment->getId()}/delete">{_delete}</a>
</div> </div>
{/if} {/if}
{if $comment->getUType() === 1 && !is_null($comment->isLikedByUser())}
<div class="post-menu">
<strong>
{if $comment->isLikedByUser()}
{_support_good_answer_agent}
{else}
{_support_bad_answer_agent}
{/if}
</strong>
</div>
{/if}
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -6,6 +6,28 @@
{/block} {/block}
{block content} {block content}
<script>
function markAnswer(id, mark) {
let url = "/support/comment/" + id + "/rate/" + mark + "?hash=" + {urlencode($csrfToken)};
$.ajax(url, {
error: errorHandler,
success: success(id, mark)
});
}
function success(id, mark) {
if(mark == 1)
document.getElementById("markText-" + id).innerHTML = {_support_good_answer_user};
else
document.getElementById("markText-" + id).innerHTML = {_support_bad_answer_user};
document.getElementById("markLinks-" + id).remove();
}
function errorHandler(id, mark) {
document.getElementById("markText-" + id).innerHTML = {_error};
}
</script>
{if $ticket->isDeleted() == 0 } {if $ticket->isDeleted() == 0 }
<div class="post-author"> <div class="post-author">
<a href="#" style="font-size:13px;"> <a href="#" style="font-size:13px;">
@ -90,11 +112,34 @@
{$comment->getText()|noescape} {$comment->getText()|noescape}
{/if} {/if}
</div> </div>
{if $comment->getUType() === 0} {if $comment->getUType() === 0}
<div class="post-menu"> <div class="post-menu">
<a href="/support/comment/{$comment->getId()}/delete">{_delete}</a> <a href="/support/comment/{$comment->getId()}/delete">{_delete}</a>
</div> </div>
{/if} {/if}
{if $comment->getUType() === 1}
<div class="post-menu">
{var isLikedByUser = $comment->isLikedByUser()}
<strong id="markText-{$comment->getId()}">
{if !is_null($isLikedByUser)}
{if $comment->isLikedByUser()}
{_support_good_answer_user}
{else}
{_support_bad_answer_user}
{/if}
{/if}
</strong>
<div id="markLinks-{$comment->getId()}">
{if is_null($isLikedByUser)}
<a onClick="markAnswer({$comment->getId()}, 1)">{_support_rate_good_answer}</a>
|
<a onClick="markAnswer({$comment->getId()}, 2)">{_support_rate_bad_answer}</a>
{/if}
</div>
{/if}
</div>
</div> </div>
</td> </td>
</tr> </tr>

View file

@ -0,0 +1,61 @@
{extends "../@listView.xml"}
{var iterator = iterator_to_array($topics)}
{var page = $paginatorConf->page}
{block title}{_discussions} {$club->getCanonicalName()}{/block}
{block header}
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a> » {_discussions}
<div n:if="$club->isEveryoneCanCreateTopics() || $club->canBeModifiedBy($thisUser)" style="float: right;">
<a href="/board{$club->getId()}/create">{_create_topic}</a>
</div>
{/block}
{block tabs}
<form style="margin-left: 12px;">
<input name="query" class="header_search_input" placeholder="{_"header_search"}" value="{$_GET['query'] ?? ''}" style="width: 90%" />
<input type="submit" class="button" value="{_"search_button"}" style="width: 7%" />
</form>
<p style="margin-left: 15px;">
<b>{tr("results", $count)}</b>
</p>
{/block}
{block actions}
{/block}
{* BEGIN ELEMENTS DESCRIPTION *}
{block link|strip|stripHtml}
/topic{$x->getPrettyId()}
{/block}
{block preview}
{/block}
{block name}
{$x->getTitle()}
<div n:if="$x->isPinned()" class="pinned-mark"></div>
{/block}
{block description}
<div style="float: left;">
{tr("messages", $x->getCommentsCount())}
</div>
{var lastComment = $x->getLastComment()}
<div n:if="$lastComment" class="avatar-list-item" style="float: right;">
<div class="avatar">
<a href="{$lastComment->getOwner()->getURL()}">
<img class="ava" src="{$lastComment->getOwner()->getAvatarUrl()}" />
</a>
</div>
<div class="info">
<a href="{$lastComment->getOwner()->getURL()}" class="title">{$lastComment->getOwner()->getCanonicalName()}</a>
<div class="subtitle">{_replied} {$lastComment->getPublicationTime()}</div>
</div>
</div>
{/block}

View file

@ -0,0 +1,96 @@
{extends "../@layout.xml"}
{block title}{_new_topic}{/block}
{block header}
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a>
»
<a href="/board{$club->getId()}">{_discussions}</a>
»
{_new_topic}
{/block}
{block content}
<form method="POST" enctype="multipart/form-data">
<table cellspacing="7" cellpadding="0" width="80%" border="0" align="center">
<tbody>
<tr>
<td width="120" valign="top">
<span class="nobold">{_title}</span>
</td>
<td>
<input type="text" name="title" style="width: 100%;" />
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">{_text}</span>
</td>
<td>
<textarea id="wall-post-input1" name="text" style="width: 100%; resize: none;"></textarea>
<div n:if="$club->canBeModifiedBy($thisUser)" class="post-opts">
<label>
<input type="checkbox" name="as_group" onchange="onWallAsGroupClick(this)" /> {_post_as_group}
</label>
</div>
<div id="post-buttons1">
<div class="post-upload">
{_attachment}: <span>(unknown)</span>
</div>
<input type="file" class="postFileSel" id="postFilePic" name="_pic_attachment" accept="image/*" style="display: none;" />
<input type="file" class="postFileSel" id="postFileVid" name="_vid_attachment" accept="video/*" style="display: none;" />
<br/>
<div style="float: right; display: flex; flex-direction: column;">
<a href="javascript:void(u('#post-buttons1 #wallAttachmentMenu').toggleClass('hidden'));">
{_attach}
</a>
<div id="wallAttachmentMenu" class="hidden">
<a href="javascript:void(document.querySelector('#post-buttons1 input[name=_pic_attachment]').click());">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-x-egon.png" />
{_attach_photo}
</a>
<a href="javascript:void(document.querySelector('#post-buttons1 input[name=_vid_attachment]').click());">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-vnd.rn-realmedia.png" />
{_attach_video}
</a>
<a n:if="$graffiti ?? false" href="javascript:initGraffiti(1);">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" />
{_draw_graffiti}
</a>
</div>
</div>
</div>
</td>
</tr>
<tr>
<td>
</td>
<td>
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="{_create_topic}" class="button" />
</td>
</tr>
</tbody>
</table>
<input type="hidden" name="hash" value="{$csrfToken}" />
</form>
<script>
$(document).ready(() => {
u("#post-buttons1 .postFileSel").on("change", function() {
handleUpload.bind(this, 1)();
});
setupWallPostInputHandlers(1);
});
</script>
{if $graffiti}
{script "js/node_modules/react/dist/react-with-addons.min.js"}
{script "js/node_modules/react-dom/dist/react-dom.min.js"}
{script "js/vnd_literallycanvas.js"}
{css "js/node_modules/literallycanvas/lib/css/literallycanvas.css"}
{/if}
{/block}

View file

@ -0,0 +1,55 @@
{extends "../@layout.xml"}
{block title}{_edit_topic} "{$topic->getTitle()}"{/block}
{block header}
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a>
»
<a href="/board{$club->getId()}">{_discussions}</a>
»
{_edit_topic}
{/block}
{block content}
<div class="container_gray">
<b>{$topic->getTitle()}</b>
<br />
<a href="{$topic->getOwner()->getURL()}">{$topic->getOwner()->getCanonicalName()}</a>
</div>
<form method="POST" enctype="multipart/form-data" style="margin-top: 20px;">
<table cellspacing="7" cellpadding="0" width="80%" border="0" align="center">
<tbody>
<tr>
<td width="120" valign="top">
<span class="nobold">{_title}</span>
</td>
<td>
<input type="text" name="title" style="width: 100%;" value="{$topic->getTitle()}" />
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">{_topic_settings}</span>
</td>
<td>
{if $topic->getClub()->canBeModifiedBy($thisUser)}
<input type="checkbox" name="pin" n:attr="checked => $topic->isPinned()" /> {_pin_topic}<br />
{/if}
<input type="checkbox" name="close" n:attr="checked => $topic->isClosed()" /> {_close_topic}
</td>
</tr>
<tr>
<td>
<a class="button" href="/topic{$topic->getPrettyId()}/delete?hash={urlencode($csrfToken)}">{_delete_topic}</a>
</td>
<td>
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" value="{_save}" class="button" />
</td>
</tr>
</tbody>
</table>
<input type="hidden" name="hash" value="{$csrfToken}" />
</form>
{/block}

View file

@ -0,0 +1,29 @@
{extends "../@layout.xml"}
{block title}{_view_topic} "{$topic->getTitle()}"{/block}
{block header}
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a>
»
<a href="/board{$club->getId()}">{_discussions}</a>
»
{_view_topic}
<div style="float: right;" n:if="$topic->canBeModifiedBy($thisUser)">
<a href="/topic{$club->getId()}_{$topic->getVirtualId()}/edit">{_edit_topic_action}</a>
</div>
{/block}
{block content}
<div class="container_gray">
<b>{$topic->getTitle()}</b>
<br />
<a href="{$topic->getOwner()->getURL()}">{$topic->getOwner()->getCanonicalName()}</a>
<div class="nobold" style="float: right;">
{_created} {$topic->getPublicationTime()}
</div>
</div>
<div style="margin-top: 20px;">
<h4>{tr("topic_messages_count", $count)}</h4>
{include "../components/comments.xml", comments => $comments, count => $count, page => $page, model => "topics", club => $club, readOnly => $topic->isClosed(), showTitle => false, parent => $topic}
</div>
{/block}

View file

@ -135,7 +135,7 @@
<span class="nobold">{_"birth_date"}: </span> <span class="nobold">{_"birth_date"}: </span>
</td> </td>
<td> <td>
<input max={date('Y-m-d')} name="birthday" value={gmdate("Y-m-d", $user->getBirthday())} type="date"/> <input max={date('Y-m-d')} name="birthday" value={$user->getBirthday()->format('%Y-%m-%d')} type="date"/>
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -79,3 +79,39 @@
</tbody> </tbody>
</table> </table>
{/block} {/block}
{block actions}
{if $x->getId() !== $thisUser->getId()}
{var subStatus = $x->getSubscriptionStatus($thisUser)}
{if $subStatus === 0}
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="add" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_add"}" />
</form>
{elseif $subStatus === 1}
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="add" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_accept"}" />
</form>
{elseif $subStatus === 2}
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_reject"}" />
</form>
{elseif $subStatus === 3}
<a href="/im?sel={$x->getId()}" class="profile_link">{_"send_message"}</a>
<form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_delete"}" />
</form>
{/if}
{/if}
{/block}

View file

@ -49,23 +49,17 @@
{block description} {block description}
{$x->getDescription()} {$x->getDescription()}
{if $x->canBeModifiedBy($thisUser ?? NULL)} {/block}
{var clubPinned = $thisUser->isClubPinned($x)}
<table n:if="$clubPinned || $thisUser->getPinnedClubCount() <= 10"> {block actions}
<tbody> {var clubPinned = $thisUser->isClubPinned($x)}
<tr> {if $x->canBeModifiedBy($thisUser ?? NULL) && ($clubPinned || $thisUser->getPinnedClubCount() <= 10)}
<td width="120" valign="top"><span class="nobold">{_actions}: </span></td> <a class="profile_link" href="/groups_pin?club={$x->getId()}&hash={rawurlencode($csrfToken)}" id="_pinGroup" data-group-name="{$x->getName()}" data-group-url="{$x->getUrl()}">
<td> {if $clubPinned}
<a href="/groups_pin?club={$x->getId()}&hash={rawurlencode($csrfToken)}" id="_pinGroup" data-group-name="{$x->getName()}" data-group-url="{$x->getUrl()}"> {_remove_from_left_menu}
{if $clubPinned} {else}
{_remove_from_left_menu} {_add_to_left_menu}
{else} {/if}
{_add_to_left_menu} </a>
{/if}
</a>
</td>
</tr>
</tbody>
</table>
{/if} {/if}
{/block} {/block}

View file

@ -211,7 +211,7 @@
<option value="3" {if $user->getPrivacySetting('page.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option> <option value="3" {if $user->getPrivacySetting('page.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option>
<option value="2" {if $user->getPrivacySetting('page.read') == 2}selected{/if}>{_privacy_value_users}</option> <option value="2" {if $user->getPrivacySetting('page.read') == 2}selected{/if}>{_privacy_value_users}</option>
<option value="1" {if $user->getPrivacySetting('page.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option> <option value="1" {if $user->getPrivacySetting('page.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option>
<option value="0" {if $user->getPrivacySetting('page.read') == 0}selected{/if}>{_privacy_value_only_me_and_super_capite_dative}</option> <option value="0" {if $user->getPrivacySetting('page.read') == 0}selected{/if}>{_privacy_value_only_me_dative}</option>
</select> </select>
</td> </td>
</tr> </tr>
@ -224,7 +224,7 @@
<option value="3" {if $user->getPrivacySetting('page.info.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option> <option value="3" {if $user->getPrivacySetting('page.info.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option>
<option value="2" {if $user->getPrivacySetting('page.info.read') == 2}selected{/if}>{_privacy_value_users}</option> <option value="2" {if $user->getPrivacySetting('page.info.read') == 2}selected{/if}>{_privacy_value_users}</option>
<option value="1" {if $user->getPrivacySetting('page.info.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option> <option value="1" {if $user->getPrivacySetting('page.info.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option>
<option value="0" {if $user->getPrivacySetting('page.info.read') == 0}selected{/if}>{_privacy_value_only_me_and_super_capite_dative}</option> <option value="0" {if $user->getPrivacySetting('page.info.read') == 0}selected{/if}>{_privacy_value_only_me_dative}</option>
</select> </select>
</td> </td>
</tr> </tr>
@ -237,7 +237,7 @@
<option value="3" {if $user->getPrivacySetting('groups.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option> <option value="3" {if $user->getPrivacySetting('groups.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option>
<option value="2" {if $user->getPrivacySetting('groups.read') == 2}selected{/if}>{_privacy_value_users}</option> <option value="2" {if $user->getPrivacySetting('groups.read') == 2}selected{/if}>{_privacy_value_users}</option>
<option value="1" {if $user->getPrivacySetting('groups.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option> <option value="1" {if $user->getPrivacySetting('groups.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option>
<option value="0" {if $user->getPrivacySetting('groups.read') == 0}selected{/if}>{_privacy_value_only_me_and_super_capite_dative}</option> <option value="0" {if $user->getPrivacySetting('groups.read') == 0}selected{/if}>{_privacy_value_only_me_dative}</option>
</select> </select>
</td> </td>
</tr> </tr>
@ -250,7 +250,7 @@
<option value="3" {if $user->getPrivacySetting('photos.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option> <option value="3" {if $user->getPrivacySetting('photos.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option>
<option value="2" {if $user->getPrivacySetting('photos.read') == 2}selected{/if}>{_privacy_value_users}</option> <option value="2" {if $user->getPrivacySetting('photos.read') == 2}selected{/if}>{_privacy_value_users}</option>
<option value="1" {if $user->getPrivacySetting('photos.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option> <option value="1" {if $user->getPrivacySetting('photos.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option>
<option value="0" {if $user->getPrivacySetting('photos.read') == 0}selected{/if}>{_privacy_value_only_me_and_super_capite_dative}</option> <option value="0" {if $user->getPrivacySetting('photos.read') == 0}selected{/if}>{_privacy_value_only_me_dative}</option>
</select> </select>
</td> </td>
</tr> </tr>
@ -263,7 +263,7 @@
<option value="3" {if $user->getPrivacySetting('videos.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option> <option value="3" {if $user->getPrivacySetting('videos.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option>
<option value="2" {if $user->getPrivacySetting('videos.read') == 2}selected{/if}>{_privacy_value_users}</option> <option value="2" {if $user->getPrivacySetting('videos.read') == 2}selected{/if}>{_privacy_value_users}</option>
<option value="1" {if $user->getPrivacySetting('videos.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option> <option value="1" {if $user->getPrivacySetting('videos.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option>
<option value="0" {if $user->getPrivacySetting('videos.read') == 0}selected{/if}>{_privacy_value_only_me_and_super_capite_dative}</option> <option value="0" {if $user->getPrivacySetting('videos.read') == 0}selected{/if}>{_privacy_value_only_me_dative}</option>
</select> </select>
</td> </td>
</tr> </tr>
@ -276,7 +276,7 @@
<option value="3" {if $user->getPrivacySetting('notes.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option> <option value="3" {if $user->getPrivacySetting('notes.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option>
<option value="2" {if $user->getPrivacySetting('notes.read') == 2}selected{/if}>{_privacy_value_users}</option> <option value="2" {if $user->getPrivacySetting('notes.read') == 2}selected{/if}>{_privacy_value_users}</option>
<option value="1" {if $user->getPrivacySetting('notes.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option> <option value="1" {if $user->getPrivacySetting('notes.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option>
<option value="0" {if $user->getPrivacySetting('notes.read') == 0}selected{/if}>{_privacy_value_only_me_and_super_capite_dative}</option> <option value="0" {if $user->getPrivacySetting('notes.read') == 0}selected{/if}>{_privacy_value_only_me_dative}</option>
</select> </select>
</td> </td>
</tr> </tr>
@ -289,7 +289,7 @@
<option value="3" {if $user->getPrivacySetting('friends.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option> <option value="3" {if $user->getPrivacySetting('friends.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option>
<option value="2" {if $user->getPrivacySetting('friends.read') == 2}selected{/if}>{_privacy_value_users}</option> <option value="2" {if $user->getPrivacySetting('friends.read') == 2}selected{/if}>{_privacy_value_users}</option>
<option value="1" {if $user->getPrivacySetting('friends.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option> <option value="1" {if $user->getPrivacySetting('friends.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option>
<option value="0" {if $user->getPrivacySetting('friends.read') == 0}selected{/if}>{_privacy_value_only_me_and_super_capite_dative}</option> <option value="0" {if $user->getPrivacySetting('friends.read') == 0}selected{/if}>{_privacy_value_only_me_dative}</option>
</select> </select>
</td> </td>
</tr> </tr>
@ -300,7 +300,7 @@
<td> <td>
<select name="friends.add" style="width: 164px;"> <select name="friends.add" style="width: 164px;">
<option value="3" {if $user->getPrivacySetting('friends.add') == 2}selected{/if}>{_privacy_value_anybody}</option> <option value="3" {if $user->getPrivacySetting('friends.add') == 2}selected{/if}>{_privacy_value_anybody}</option>
<option value="0" {if $user->getPrivacySetting('friends.add') == 0}selected{/if}>{_privacy_value_super_capite}</option> <option value="0" {if $user->getPrivacySetting('friends.add') == 0}selected{/if}>{_privacy_value_nobody}</option>
</select> </select>
</td> </td>
</tr> </tr>
@ -312,7 +312,7 @@
<select name="wall.write" style="width: 164px;"> <select name="wall.write" style="width: 164px;">
<option value="2" {if $user->getPrivacySetting('wall.write') == 2}selected{/if}>{_privacy_value_anybody}</option> <option value="2" {if $user->getPrivacySetting('wall.write') == 2}selected{/if}>{_privacy_value_anybody}</option>
<option value="1" {if $user->getPrivacySetting('wall.write') == 1}selected{/if}>{_privacy_value_friends}</option> <option value="1" {if $user->getPrivacySetting('wall.write') == 1}selected{/if}>{_privacy_value_friends}</option>
<option value="0" {if $user->getPrivacySetting('wall.write') == 0}selected{/if}>{_privacy_value_only_me_and_super_capite}</option> <option value="0" {if $user->getPrivacySetting('wall.write') == 0}selected{/if}>{_privacy_value_only_me}</option>
</select> </select>
</td> </td>
</tr> </tr>
@ -332,6 +332,7 @@
<div style="width: 75%; display: inline-block;"> <div style="width: 75%; display: inline-block;">
{presenter "openvk!Support->knowledgeBaseArticle", "points"} {presenter "openvk!Support->knowledgeBaseArticle", "points"}
<center>{tr("also_you_can_transfer_points", $thisUser->getCoins(), rawurlencode($csrfToken))|noescape}</center>
</div> </div>
<div style="width: 22%; float: right;"> <div style="width: 22%; float: right;">
<p style="margin: 0; font-size: medium; text-align: center;"> <p style="margin: 0; font-size: medium; text-align: center;">
@ -505,6 +506,16 @@
<td> <td>
<span class="nobold">{_my_feed}</span> <span class="nobold">{_my_feed}</span>
</td> </td>
</tr><tr n:if="sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0">
<td width="120" valign="top" align="right" align="right">
<input
n:attr="checked => $user->getLeftMenuItemStatus('links')"
type="checkbox"
name="menu_ligiloj" />
</td>
<td>
<span class="nobold">{_additional_links}</span>
</td>
</tr> </tr>
<tr> <tr>
<td> <td>

View file

@ -3,6 +3,7 @@
{block title}{$user->getCanonicalName()}{/block} {block title}{$user->getCanonicalName()}{/block}
{block headIncludes} {block headIncludes}
{if $user->getPrivacyPermission('page.read', $thisUser ?? NULL)}
<!-- openGraph --> <!-- openGraph -->
<meta property="og:title" content="{$user->getCanonicalName()}" /> <meta property="og:title" content="{$user->getCanonicalName()}" />
<meta property="og:url" content="http://{$_SERVER['HTTP_HOST']}{$user->getURL()}" /> <meta property="og:url" content="http://{$_SERVER['HTTP_HOST']}{$user->getURL()}" />
@ -22,6 +23,9 @@
"url": {('http://') . $_SERVER['HTTP_HOST'] . $user->getURL()} "url": {('http://') . $_SERVER['HTTP_HOST'] . $user->getURL()}
} }
</script> </script>
{else}
<meta name="robots" content="noindex, noarchive">
{/if}
{/block} {/block}
{block header} {block header}
@ -87,25 +91,25 @@
</a> </a>
{/if} {/if}
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce']" href="/gifts?act=pick&user={$user->getId()}" class="profile_link">{_send_gift}</a> <a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && $user->getGiftCount() == 0" href="/gifts?act=pick&user={$user->getId()}" class="profile_link">{_send_gift}</a>
{var subStatus = $user->getSubscriptionStatus($thisUser)} {var subStatus = $user->getSubscriptionStatus($thisUser)}
{if $subStatus === 0} {if $subStatus === 0}
<form action="/setSub/user" method="post"> <form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="add" /> <input type="hidden" name="act" value="add" />
<input type="hidden" name="id" value="{$user->getId()}" /> <input type="hidden" name="id" value="{$user->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_add"}" /> <input type="submit" class="profile_link" value="{_"friends_add"}" />
</form> </form>
{elseif $subStatus === 1} {elseif $subStatus === 1}
<form action="/setSub/user" method="post"> <form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="add" /> <input type="hidden" name="act" value="add" />
<input type="hidden" name="id" value="{$user->getId()}" /> <input type="hidden" name="id" value="{$user->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<input type="submit" class="profile_link" value="{_"friends_accept"}" /> <input type="submit" class="profile_link" value="{_"friends_accept"}" />
</form> </form>
{elseif $subStatus === 2} {elseif $subStatus === 2}
<form action="/setSub/user" method="post"> <form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="rem" /> <input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$user->getId()}" /> <input type="hidden" name="id" value="{$user->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
@ -113,7 +117,7 @@
</form> </form>
{elseif $subStatus === 3} {elseif $subStatus === 3}
<a href="/im?sel={$user->getId()}" class="profile_link">{_"send_message"}</a> <a href="/im?sel={$user->getId()}" class="profile_link">{_"send_message"}</a>
<form action="/setSub/user" method="post"> <form action="/setSub/user" method="post" class="profile_link_form">
<input type="hidden" name="act" value="rem" /> <input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$user->getId()}" /> <input type="hidden" name="id" value="{$user->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
@ -121,6 +125,7 @@
</form> </form>
{/if} {/if}
{/if} {/if}
<a n:if="$user->getFollowersCount() > 0" href="/friends{$user->getId()}?act=incoming" class="profile_link">{tr("followers", $user->getFollowersCount())}</a>
</div> </div>
<div n:if="isset($thisUser) && !$thisUser->prefersNotToSeeRating()" class="profile-hints"> <div n:if="isset($thisUser) && !$thisUser->prefersNotToSeeRating()" class="profile-hints">
{var completeness = $user->getProfileCompletenessReport()} {var completeness = $user->getProfileCompletenessReport()}
@ -146,39 +151,15 @@
</a> </a>
<a n:if="in_array('telegram', $completeness->unfilled)" href="/edit?act=contacts"> <a n:if="in_array('telegram', $completeness->unfilled)" href="/edit?act=contacts">
<img src="/assets/packages/static/openvk/img/icon2.gif" /> <img src="/assets/packages/static/openvk/img/icon2.gif" />
Telegram (+10%) Telegram (+15%)
</a> </a>
<a n:if="in_array('status', $completeness->unfilled)" href="/edit"> <a n:if="in_array('status', $completeness->unfilled)" href="/edit">
<img src="/assets/packages/static/openvk/img/icon3.gif" /> <img src="/assets/packages/static/openvk/img/icon3.gif" />
{_status} (+10%) {_status} (+15%)
</a> </a>
{/if} {/if}
</div> </div>
<br /> <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)"> <div n:if="$user->getFriendsCount() > 0 && $user->getPrivacyPermission('friends.read', $thisUser ?? NULL)">
{var friendCount = $user->getFriendsCount()} {var friendCount = $user->getFriendsCount()}
@ -192,37 +173,17 @@
<a href="/friends{$user->getId()}">{_"all_title"}</a> <a href="/friends{$user->getId()}">{_"all_title"}</a>
</div> </div>
</div> </div>
<div class="ovk-avView"> <div class="content_list">
<div class="ovk-avView--el" n:foreach="$user->getFriends(1) as $friend"> <div class="cl_element" n:foreach="$user->getFriends(1) as $friend">
<a href="{$friend->getURL()}"> <div class="cl_avatar">
<img class="ava" src="{$friend->getAvatarUrl()}" /> <a href="{$friend->getURL()}">
<img class="ava" src="{$friend->getAvatarUrl()}" />
</a>
</div>
<a href="{$friend->getURL()}" class="cl_name">
<text class="cl_fname">{$friend->getFirstName()}</text>
<text class="cl_lname">{$friend->getLastName()}</text>
</a> </a>
<br/>
<a href="{$friend->getURL()}">{$friend->getFirstName()}</a>
</div>
</div>
</div>
</div>
<div n:if="$user->getFollowersCount() > 0">
{var followersCount = $user->getFollowersCount()}
<div class="content_title_expanded" onclick="hidePanel(this, {$followersCount});">
{_followers}
</div>
<div>
<div class="content_subtitle">
{tr("followers", $followersCount)}
<div style="float:right;">
<a href="/friends{$user->getId()}?act=incoming">{_"all_title"}</a>
</div>
</div>
<div class="ovk-avView">
<div class="ovk-avView--el" n:foreach="$user->getFollowers(1) as $follower">
<a href="{$follower->getURL()}">
<img class="ava" src="{$follower->getAvatarUrl()}" />
</a>
<br/>
<a href="{$follower->getURL()}">{$follower->getFirstName()}</a>
</div> </div>
</div> </div>
</div> </div>
@ -398,10 +359,10 @@
<td class="label"><span class="nobold">{_"politViews"}:</span></td> <td class="label"><span class="nobold">{_"politViews"}:</span></td>
<td class="data">{var $pviews = $user->getPoliticalViews()}{_"politViews_$pviews"}</td> <td class="data">{var $pviews = $user->getPoliticalViews()}{_"politViews_$pviews"}</td>
</tr> </tr>
{if $user->getBirthday() > 0} {if $user->getBirthday()->timestamp() > 0}
<tr> <tr>
<td class="label"><span class="nobold">{_"birth_date"}:</span></td> <td class="label"><span class="nobold">{_"birth_date"}:</span></td>
<td class="data">{date('d F Y',$user->getBirthday())}, <td class="data">{$user->getBirthday()->format('%e %B %Y')},
{tr("years", $user->getAge())}</td> {tr("years", $user->getAge())}</td>
</tr> </tr>
{/if} {/if}
@ -505,6 +466,35 @@
</div> </div>
</div> </div>
<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;">
{if OPENVK_ROOT_CONF['openvk']['preferences']['commerce']}
<a href="/gifts?act=pick&user={$user->getId()}">{_send_gift}</a> |
{/if}
<a href="/gifts{$user->getId()}">{_all_title}</a>
</div>
</div>
<div class="content_list long">
<div class="cl_element" style="width: 25%;" n:foreach="$user->getGifts(1, 4) as $giftDescriptor">
{var hideInfo = !is_null($thisUser) ? ($giftDescriptor->anon ? $thisUser->getId() !== $user->getId() : false) : false}
<div class="cl_avatar">
<a href="{$hideInfo ? 'javascript:false' : $giftDescriptor->sender->getURL()}">
<img style="width: 70px; max-height: 70px;"
src="{$giftDescriptor->gift->getImage(2)}"
alt="{$hideInfo ? tr('gift') : ($giftDescriptor->caption ?? tr('gift'))}" />
</a>
</div>
</div>
</div>
</div>
</div>
{presenter "openvk!Wall->wallEmbedded", $user->getId()} {presenter "openvk!Wall->wallEmbedded", $user->getId()}
<script n:if="isset($thisUser) && $thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL)"> <script n:if="isset($thisUser) && $thisUser->getChandlerUser()->can('access')->model('admin')->whichBelongsTo(NULL)">

View file

@ -1,7 +1,7 @@
<center> <center>
<img src="/assets/packages/static/openvk/img/oof.apng" alt="Пользователь заблокирован." style="width: 20%;" /> <img src="/assets/packages/static/openvk/img/oof.apng" alt="Пользователь заблокирован." style="width: 20%;" />
<p> <p>
К сожалению, нам пришлось заблокировать страницу пользователя <b>{$user->getFirstName()}</b>.<br/> {tr("user_banned", htmlentities($user->getFirstName()))|noescape}<br/>
Комментарий модератора: <b>{$user->getBanReason()}</b>. {_"user_banned_comment"} <b>{$user->getBanReason()}</b>.
</p> </p>
</center> </center>

View file

@ -46,4 +46,8 @@
}); });
</script> </script>
</center> </center>
{if isset($thisUser) && $thisUser->hasMicroblogEnabled()}
{script "js/al_comments.js"}
{/if}
{/block} {/block}

View file

@ -16,4 +16,8 @@
{/foreach} {/foreach}
{include "../components/paginator.xml", conf => $paginatorConf} {include "../components/paginator.xml", conf => $paginatorConf}
</center> </center>
{if isset($thisUser) && $thisUser->hasMicroblogEnabled()}
{script "js/al_comments.js"}
{/if}
{/block} {/block}

View file

@ -13,24 +13,28 @@
{/block} {/block}
{block content} {block content}
<div class="content_divider"> <div class="content_divider">
<div> <div>
<div n:if="$canPost" class="content_subtitle"> <div n:if="$canPost" class="content_subtitle">
{include "../components/textArea.xml", route => "/wall$owner/makePost"} {include "../components/textArea.xml", route => "/wall$owner/makePost"}
</div> </div>
<div class="content"> <div class="content">
{if sizeof($posts) > 0} {if sizeof($posts) > 0}
{foreach $posts as $post} {foreach $posts as $post}
<a name="postGarter={$post->getId()}"></a> <a name="postGarter={$post->getId()}"></a>
{include "../components/post.xml", post => $post, commentSection => true} {include "../components/post.xml", post => $post, commentSection => true}
{/foreach} {/foreach}
{include "../components/paginator.xml", conf => $paginatorConf} {include "../components/paginator.xml", conf => $paginatorConf}
{else} {else}
{_no_posts_abstract} {_no_posts_abstract}
{/if} {/if}
</div>
</div> </div>
</div> </div>
</div>
{if isset($thisUser) && $thisUser->hasMicroblogEnabled()}
{script "js/al_comments.js"}
{/if}
{/block} {/block}

View file

@ -1,8 +1,9 @@
{var author = $comment->getOwner()} {var author = $comment->getOwner()}
{var $Club = openvk\Web\Models\Entities\Club::class} {var $Club = openvk\Web\Models\Entities\Club::class}
{var postId = $comment->getTarget() instanceof \openvk\Web\Models\Entities\Post ? $comment->getTarget()->getId() : NULL}
<a name="cid={$comment->getId()}"></a> <a name="cid={$comment->getId()}"></a>
<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}"> <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}" n:attr="data-post-id => $postId">
<tbody> <tbody>
<tr> <tr>
<td width="30" valign="top"> <td width="30" valign="top">
@ -29,12 +30,10 @@
</div> </div>
<div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu"> <div n:if="isset($thisUser) &&! ($compact ?? false)" class="post-menu">
<a href="#_comment{$comment->getId()}" class="date">{$comment->getPublicationTime()}</a>&nbsp;| <a href="#_comment{$comment->getId()}" class="date">{$comment->getPublicationTime()}</a>&nbsp;|
{var canDelete = $comment->getOwner()->getId() == $thisUser->getId()} {if $comment->canBeDeletedBy($thisUser)}
{var canDelete = $canDelete || $comment->getTarget()->getOwner()->getId() == $thisUser->getId()}
{if $canDelete}
<a href="/comment{$comment->getId()}/delete">{_"delete"}</a>&nbsp;| <a href="/comment{$comment->getId()}/delete">{_"delete"}</a>&nbsp;|
{/if} {/if}
<a class="comment-reply">Ответить</a> <a class="comment-reply">{_"reply"}</a>
<div style="float: right; font-size: .7rem;"> <div style="float: right; font-size: .7rem;">
<a class="post-like-button" 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> <div class="heart" style="{if $comment->hasLikeFrom($thisUser)}opacity: 1;{else}opacity: 0.4;{/if}"></div>

View file

@ -1,16 +1,20 @@
<h4>{_"comments"} ({$count})</h4> <h4 n:if="$showTitle ?? true">{_"comments"} ({$count})</h4>
<div n:ifset="$thisUser"> <div n:ifset="$thisUser">
{var commentsURL = "/al_comments.pl/create/$model/" . $parent->getId()} {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} {var club = $parent instanceof \openvk\Web\Models\Entities\Post && $parent->getTargetWall() < 0 ? (new openvk\Web\Models\Repositories\Clubs)->get(abs($parent->getTargetWall())) : $club}
{include "textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), club => $club} {if !$readOnly}
{include "textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), club => $club}
{/if}
</div> </div>
{if sizeof($comments) > 0} {if sizeof($comments) > 0}
{foreach $comments as $comment} {foreach $comments as $comment}
{include "comment.xml", comment => $comment} {include "comment.xml", comment => $comment}
{/foreach} {/foreach}
{include "paginator.xml", conf => (object) ["page" => $page, "count" => $count, "amount" => sizeof($comments), "perPage" => 10]} <div style="margin-top: 11px;">
{include "paginator.xml", conf => (object) ["page" => $page, "count" => $count, "amount" => sizeof($comments), "perPage" => 10]}
</div>
{else} {else}
<!-- {if $model === "photos"} <!-- {if $model === "photos"}
<p>Будьте первым, кто оставит комментарий к этой фотографии</p> <p>Будьте первым, кто оставит комментарий к этой фотографии</p>

View file

@ -0,0 +1,6 @@
{extends "@default.xml"}
{var post = $notification->getModel(0)}
{block under}
{_nt_yours_adjective} <a href="/topic{$post->getPrettyId()}">{_nt_topic_instrumental}</a>
{/block}

View file

@ -0,0 +1,8 @@
{var sender = $notification->getModel(1)}
{var value = (int) explode(" ", $notification->getData(), 2)[0]}
{var message = explode(" ", $notification->getData(), 2)[1]}
<a href="{$sender->getURL()}"><b>{$sender->getCanonicalName()}</b></a> {_transferred_to_you} {tr("points_amount", $value)}.
{if !empty($message)}
{_message}: "{$message}".
{/if}

View file

@ -10,7 +10,8 @@
<td width="54" valign="top"> <td width="54" valign="top">
<img <img
src="{$author->getAvatarURL()}" src="{$author->getAvatarURL()}"
width="{ifset $compact}25{else}50{/ifset}" /> width="{ifset $compact}25{else}50{/ifset}"
{ifset $compact}class="cCompactAvatars"{/ifset} />
{if !$post->isPostedOnBehalfOfGroup() && !$compact} {if !$post->isPostedOnBehalfOfGroup() && !$compact}
<span n:if="$author->isOnline()" class="post-online"> <span n:if="$author->isOnline()" class="post-online">
{_online} {_online}
@ -25,6 +26,22 @@
</b> </b>
</a> </a>
{if $author->isVerified()}<img class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">{/if} {if $author->isVerified()}<img class="name-checkmark" src="/assets/packages/static/openvk/img/checkmark.png">{/if}
{if ($onWallOf ?? false) &&!$post->isPostedOnBehalfOfGroup() && $post->getOwnerPost() !== $post->getTargetWall()}
{var wallId = $post->getTargetWall()}
{var wallURL = $wallId > -1 ? "/id$wallId" : "/club" . abs($wallId)}
на
<a href="{$wallURL}">
<b>
{if isset($thisUser) && $thisUser->getId() === $wallId}
вашей
{/if}
стене
{if $wallId < 0}
группы
{/if}
</b>
</a>
{/if}
{ifset $compact}<br> {ifset $compact}<br>
<a href="/wall{$post->getPrettyId()}" class="date"> <a href="/wall{$post->getPrettyId()}" class="date">
{$post->getPublicationTime()} {$post->getPublicationTime()}

View file

@ -8,7 +8,7 @@
</div> </div>
<div id="post-buttons{$textAreaId}" style="display: none;"> <div id="post-buttons{$textAreaId}" style="display: none;">
<div class="post-upload"> <div class="post-upload">
Вложение: <span>(unknown)</span> {_attachment}: <span>(unknown)</span>
</div> </div>
<div n:if="$postOpts ?? true" class="post-opts"> <div n:if="$postOpts ?? true" class="post-opts">
{var anonEnabled = OPENVK_ROOT_CONF['openvk']['preferences']['wall']['anonymousPosting']['enable']} {var anonEnabled = OPENVK_ROOT_CONF['openvk']['preferences']['wall']['anonymousPosting']['enable']}
@ -35,7 +35,7 @@
{/if} {/if}
<label n:if="$anonEnabled" id="octoberAnonOpt"> <label n:if="$anonEnabled" id="octoberAnonOpt">
<input type="checkbox" name="anon" /> Анонимно <input type="checkbox" name="anon" /> {_"as_anonymous"}
</label> </label>
<label> <label>
@ -55,7 +55,7 @@
<input type="submit" value="{_'write'}" class="button" /> <input type="submit" value="{_'write'}" class="button" />
<div style="float: right; display: flex; flex-direction: column;"> <div style="float: right; display: flex; flex-direction: column;">
<a href="javascript:void(u('#post-buttons{$textAreaId} #wallAttachmentMenu').toggleClass('hidden'));"> <a href="javascript:void(u('#post-buttons{$textAreaId} #wallAttachmentMenu').toggleClass('hidden'));">
Прикрепить {_attach}
</a> </a>
<div id="wallAttachmentMenu" class="hidden"> <div id="wallAttachmentMenu" class="hidden">
@ -65,11 +65,11 @@
</a> </a>
<a href="javascript:void(document.querySelector('#post-buttons{$textAreaId} input[name=_vid_attachment]').click());"> <a href="javascript:void(document.querySelector('#post-buttons{$textAreaId} input[name=_vid_attachment]').click());">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-vnd.rn-realmedia.png" /> <img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-vnd.rn-realmedia.png" />
Прикрепить видео {_attach_video}
</a> </a>
<a n:if="$graffiti ?? false" href="javascript:initGraffiti({$textAreaId});"> <a n:if="$graffiti ?? false" href="javascript:initGraffiti({$textAreaId});">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" /> <img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" />
Нарисовать граффити {_draw_graffiti}
</a> </a>
</div> </div>
</div> </div>

View file

@ -25,3 +25,7 @@
</div> </div>
</div> </div>
</div> </div>
{if isset($thisUser) && $thisUser->hasMicroblogEnabled()}
{script "js/al_comments.js"}
{/if}

34
Web/Util/Telegram.php Normal file
View file

@ -0,0 +1,34 @@
<?php declare(strict_types=1);
namespace openvk\Web\Util;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\ClientException as GuzzleClientException;
class Telegram
{
static function send(string $to, string $text, bool $webPagePreview = false): bool
{
$conf = (object) OPENVK_ROOT_CONF["openvk"]["credentials"]["telegram"];
if(!$conf->enable)
return false;
try {
(new GuzzleClient)->request(
"POST",
"https://api.telegram.org/bot{$conf->token}/sendMessage",
[
"form_params" => [
"chat_id" => $to,
"text" => $text,
"disable_web_page_preview" => $webPagePreview ? "true" : "false",
"parse_mode" => "HTML",
]
]
);
} catch (GuzzleClientException $ex) {
trigger_error("Could not send Telegram message to $to: {$ex->getMessage()}", E_USER_WARNING);
return false;
}
return true;
}
}

26
Web/Util/Validator.php Normal file
View file

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace openvk\Web\Util;
use Chandler\Patterns\TSimpleSingleton;
class Validator
{
function emailValid(string $email): bool
{
if(empty($email)) return false;
$email = trim($email);
[$user, $domain] = explode("@", $email);
if(is_null($domain)) return false;
if(iconv_strlen($user) > 64) return false;
$domain = idn_to_ascii($domain) . ".";
return checkdnsrr($domain, "MX");
}
function telegramValid(string $telegram): bool
{
return (bool) preg_match("/^(?:t.me\/|@)?([a-zA-Z0-9_]{0,32})$/", $telegram);
}
use TSimpleSingleton;
}

View file

@ -20,6 +20,7 @@ services:
- openvk\Web\Presenters\AdminPresenter - openvk\Web\Presenters\AdminPresenter
- openvk\Web\Presenters\GiftsPresenter - openvk\Web\Presenters\GiftsPresenter
- openvk\Web\Presenters\MessengerPresenter - openvk\Web\Presenters\MessengerPresenter
- openvk\Web\Presenters\TopicsPresenter
- openvk\Web\Presenters\ThemepacksPresenter - openvk\Web\Presenters\ThemepacksPresenter
- openvk\Web\Presenters\VKAPIPresenter - openvk\Web\Presenters\VKAPIPresenter
- openvk\Web\Models\Repositories\Users - openvk\Web\Models\Repositories\Users
@ -38,4 +39,5 @@ services:
- openvk\Web\Models\Repositories\IPs - openvk\Web\Models\Repositories\IPs
- openvk\Web\Models\Repositories\Vouchers - openvk\Web\Models\Repositories\Vouchers
- openvk\Web\Models\Repositories\Gifts - openvk\Web\Models\Repositories\Gifts
- openvk\Web\Models\Repositories\Topics
- openvk\Web\Models\Repositories\ContentSearchRepository - openvk\Web\Models\Repositories\ContentSearchRepository

View file

@ -17,6 +17,8 @@ routes:
handler: "Support->AnswerTicket" handler: "Support->AnswerTicket"
- url: "/support/view/{num}" - url: "/support/view/{num}"
handler: "Support->view" handler: "Support->view"
- url: "/support/comment/{num}/rate/{num}"
handler: "Support->rateAnswer"
- url: "/al_comments.pl/create/support/{num}" - url: "/al_comments.pl/create/support/{num}"
handler: "Support->makeComment" handler: "Support->makeComment"
- url: "/al_comments.pl/create/support/reply/{num}" - url: "/al_comments.pl/create/support/reply/{num}"
@ -57,6 +59,8 @@ routes:
handler: "User->twoFactorAuthSettings" handler: "User->twoFactorAuthSettings"
- url: "/settings/2fa/disable" - url: "/settings/2fa/disable"
handler: "User->disableTwoFactorAuth" handler: "User->disableTwoFactorAuth"
- url: "/coins_transfer"
handler: "User->coinsTransfer"
- url: "/id{num}" - url: "/id{num}"
handler: "User->view" handler: "User->view"
- url: "/friends{num}" - url: "/friends{num}"
@ -73,6 +77,8 @@ routes:
handler: "Group->attend" handler: "Group->attend"
- url: "/al_comments.pl/create/{text}/{num}" - url: "/al_comments.pl/create/{text}/{num}"
handler: "Comment->makeComment" handler: "Comment->makeComment"
- url: "/groups/{num}/setNewOwner/{num}"
handler: "Group->changeOwner"
- url: "/comment{num}/like" - url: "/comment{num}/like"
handler: "Comment->like" handler: "Comment->like"
- url: "/comment{num}/delete" - url: "/comment{num}/delete"
@ -163,6 +169,16 @@ routes:
handler: "User->pinClub" handler: "User->pinClub"
- url: "/groups_create" - url: "/groups_create"
handler: "Group->create" handler: "Group->create"
- url: "/board{num}"
handler: "Topics->board"
- url: "/board{num}/create"
handler: "Topics->create"
- url: "/topic{num}_{num}"
handler: "Topics->topic"
- url: "/topic{num}_{num}/edit"
handler: "Topics->edit"
- url: "/topic{num}_{num}/delete"
handler: "Topics->delete"
- url: "/audios{num}" - url: "/audios{num}"
handler: "Audios->app" handler: "Audios->app"
- url: "/audios{num}.json" - url: "/audios{num}.json"

View file

@ -19,7 +19,7 @@ span {
font-weight: bold; font-weight: bold;
} }
.nobold { .nobold, nobold {
font-weight: normal; font-weight: normal;
color: gray; color: gray;
} }
@ -257,23 +257,29 @@ a {
.album-photo { .album-photo {
position: relative; position: relative;
background-color: darkgrey; width: 25%;
margin: 4pt; max-height: 140px;
width: calc(33% - 10pt); margin-bottom: 8px;
height: 82px;
text-align: center; text-align: center;
vertical-align: text-top; display: flex;
align-items: center;
justify-content: center;
} }
.album-photo img { .album-photo img {
width: 100%; width: unset;
max-height: 82px; max-height: 120px !important;
max-width: 83%;
vertical-align: top; vertical-align: top;
border: 1px #ccc solid;
padding: 8px;
background-color: #fff;
} }
.album-photo > .album-photo--delete { .album-photo > .album-photo--delete {
position: absolute; position: absolute;
right: 0; right: 0;
top: 0;
padding: 5px; padding: 5px;
margin: 4px; margin: 4px;
color: #fff; color: #fff;
@ -289,6 +295,11 @@ a {
opacity: 1; opacity: 1;
} }
.album-flex {
display: flex;
flex-wrap: wrap;
}
.name-checkmark { .name-checkmark {
margin-left: 2pt; margin-left: 2pt;
} }
@ -307,6 +318,10 @@ a {
cursor: pointer; cursor: pointer;
} }
.profile_link_form {
margin-bottom: 0;
}
#profile_links { #profile_links {
margin: 10px 0; margin: 10px 0;
} }
@ -315,6 +330,24 @@ a {
background: #ECECEC; background: #ECECEC;
} }
.action_links > .profile_link, .action_links > .profile_link_form > .profile_link {
width: 150px;
}
.profile_link.disable > a, .profile_link.disable {
cursor: not-allowed;
color: grey;
}
.profile_link.loading > a::after, .profile_link.loading::after {
content: "";
display: inline-block;
background-image: url('/assets/packages/static/openvk/img/loading_mini.gif');
width: 30px;
height: 7px;
margin-left: 5px;
}
.page_footer { .page_footer {
margin-left: 95px; margin-left: 95px;
padding-top: 5px; padding-top: 5px;
@ -683,6 +716,44 @@ span {
max-height: 63px; max-height: 63px;
} }
.content_list {
display: flex;
width: 200px;
flex-wrap: wrap;
}
.content_list.long {
width: 397px;
}
.content_list .cl_element {
width: 33%;
}
.content_list.long .cl_element {
width: 16.5%;
}
.content_list .cl_element .cl_avatar {
padding: 7px 7px 0 7px;
text-align: center;
}
.content_list .cl_element .cl_name {
padding: 0 3px;
text-align: center;
display: flex;
flex-direction: column;
}
.content_list .cl_element .cl_name .cl_lname {
font-size: 7pt;
}
.ava {
width: 45px;
}
table.User { table.User {
vertical-align: text-top; vertical-align: text-top;
} }
@ -696,6 +767,10 @@ table.User {
margin-bottom: -12px; margin-bottom: -12px;
} }
.container_gray.bottom {
border-bottom: #ebebeb solid 1px;
}
#auth .container_gray { #auth .container_gray {
margin-left: -10px; margin-left: -10px;
margin-bottom: -10px; margin-bottom: -10px;
@ -847,7 +922,7 @@ table.User {
} }
.messenger-app--messages---message.unread { .messenger-app--messages---message.unread {
background-color: #dcdcdc; background-color: #ededed;
padding: 5px; padding: 5px;
margin: -5px; margin: -5px;
padding-bottom: 1.2rem; padding-bottom: 1.2rem;
@ -1598,3 +1673,98 @@ body.scrolled .toTop:hover {
margin: 5px; margin: 5px;
border: 1px solid #C0CAD5; border: 1px solid #C0CAD5;
} }
.header_search {
background: #f7f7f7;
width: 607px;
padding: 10px;
border-bottom: 1px solid #e1e1e1;
}
.header_search_inputbt {
padding: 10px;
border-top: 1px solid #ebebeb;
display: flex;
margin-bottom: 0;
}
.header_search_input {
border: 1px solid #C0CAD5;
padding: 3px;
padding-left: 19px;
font-size: 11px;
font-family: tahoma, verdana, arial, sans-serif;
width: 549px;
background: #fff url('/assets/packages/static/openvk/img/search_icon.png') no-repeat;
background-position-x: 4px;
background-position-y: 4px;
}
.button_search {
border-radius: 2px;
border: #595959;
font-size: 11px;
outline: none;
white-space: nowrap;
background: #595959;
background-position: 0px -16px;
color: #fff;
padding: 4px 8px 4px;
text-shadow: 0 1px 0 #686868;
cursor: pointer;
text-decoration: none;
margin-left: 10px;
width: 7.5%;
}
.content_search {
width: 607px;
}
.content_search_list {
display: flex;
width: 100%;
padding: 10px;
}
.content_search_list:hover {
background: #f5f5f5;
}
.content_search_list_ava img{
width: 75px;
}
.content_search_list_ava{
width: 85px;
}
.content_search_list_name_h4 {
color: #2b587a;
font-weight: bold;
}
.content_search_list_span {
color: #7b7b7b;
margin-top: 5px;
}
.pinned-mark {
display: inline-block;
height: 16px;
width: 16px;
overflow: auto;
background: url("/assets/packages/static/openvk/img/pin.png") no-repeat 0px 0px;
vertical-align: middle;
}
.topic-list-item {
border-bottom: #e6e6e6 solid 1px;
padding: 4px;
}
.messagebox-content-header {
background: #F7F7F7;
margin: -20px;
padding: 10px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

BIN
Web/static/img/loading_mini.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

@ -1 +0,0 @@
Subproject commit 9d4eb793307e7af10c39c9da3b46646d97ad9dc2

View file

@ -0,0 +1 @@
system-run.png

View file

@ -0,0 +1 @@
run-build.png

View file

@ -0,0 +1 @@
appointment-new.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

View file

@ -0,0 +1 @@
folder-new.png

View file

@ -0,0 +1 @@
fork.png

View file

@ -0,0 +1 @@
im-user.png

View file

@ -0,0 +1 @@
flag-red.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 797 B

View file

@ -0,0 +1 @@
address-book-new.png

View file

@ -0,0 +1 @@
address-book-new.png

View file

@ -0,0 +1 @@
go-home.png

View file

@ -0,0 +1 @@
folder-new.png

View file

@ -0,0 +1 @@
document-import.png

View file

@ -0,0 +1 @@
folder-new.png

View file

@ -0,0 +1 @@
configure.png

View file

@ -0,0 +1 @@
edit-delete.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Some files were not shown because too many files have changed in this diff Show more