Merge branch 'master' into similartovk

This commit is contained in:
KosFurler 2022-02-19 01:32:46 +03:00 committed by GitHub
commit a9eb0ab31b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1188 additions and 172 deletions

204
Email/verify-email.eml.latte Executable file
View file

@ -0,0 +1,204 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Подтверждение Email</title>
<link rel="stylesheet" href="foundation.css" />
<style>
.container {
border-top: 5px solid pink;
}
</style>
</head>
<body>
<table class="body" data-made-with-foundation="">
<tr>
<td class="float-center" align="center" valign="top">
<center>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<table class="container">
<tr>
<td>
<table class="row header">
<tr>
<th class="small-12 large-12 columns first last">
<table>
<tr>
<th>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<h4 class="text-center">Подтверждение Email</h4>
</th>
</tr>
</table>
</th>
</tr>
</table>
<table class="row">
<tr>
<th class="small-12 large-12 columns first last">
<table class="row">
<tr>
<td>
<center>
<img src="pictures/lock.jpeg" align="center" class="float-center" width=128 height=128 />
</center>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<hr/>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<p class="text-left">
Здравствуйте, {$name}! Вы вероятно зарегистрировались на одном из инстансов OpenVK. Чтобы ваш аккаунт активировался, необходимо подтвердить Email.
</p>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<table class="button large expand success">
<tr>
<td>
<table>
<tr>
<td>
<center>
<a href="http://{$_SERVER['HTTP_HOST']}/regFinish?key={rawurlencode($key)}" align="center" class="float-center">Подтвердить Email!</a>
</center>
</td>
</tr>
</table>
</td>
</tr>
</table>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<p class="text-left">
Если кнопка не работает, вы можете попробовать скопировать и вставить эту ссылку в адресную строку вашего веб-обозревателя:
</p>
<table class="callout">
<tr>
<th class="callout-inner primary">
<a href="http://{$_SERVER['HTTP_HOST']}/regFinish?key={$key}" style="color: #000; text-decoration: none;">
http://{$_SERVER['HTTP_HOST']}/regFinish?key={$key}
</a>
</th>
</tr>
</table>
<p class="text-left">
Обратите внимание на то, что эту ссылку нельзя:
</p>
<ul>
<li>Передавать другим людям (даже друзьям, питомцам, соседам, любимым девушкам)</li>
<li>Использовать, если прошло более двух дней с её генерации</li>
</ul>
<table class="callout">
<tr>
<th class="callout-inner alert">
<p>
Ещё раз <b>обратите внимание</b> на то, что данную ссылку или письмо <b>ни в коем случае нельзя</b> передавать другим людям! Даже если они представляются службой поддержки.<br/>
Это письмо предназначено исключительно для одноразового, <b>непосредственного</b> использования владельцем аккаунта.
</p>
</th>
</tr>
</table>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<p class="text-right">
С уважением, овк-тян.
</p>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<hr/>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<p class="text-left">
<small>
Вы получили это письмо так как кто-то или вы зарегистрировались на инстансе OpenVK. Это не рассылка и от неё нельзя отписаться. Если вы всё равно хотите перестать получать подобные письма, деактивируйте ваш аккаунт.
</small>
</p>
</td>
</tr>
</table>
</th>
</tr>
</table>
</td>
</tr>
</table>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
</center>
</td>
</tr>
</table>
</body>
</html>

View file

@ -18,7 +18,6 @@ Updating the source code is done with this command: `git pull`
* **[openvk.su](https://openvk.su/)**
* [social.fetbuk.ru](http://social.fetbuk.ru/)
* [openvk.zavsc.pw](https://openvk.zavsc.pw/)
## Can I create my own OpenVK instance?
@ -48,7 +47,7 @@ ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/ext
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
4. Import `install/init-static-db.sql` to **same database** you installed Chandler to and import all sqls from `install/sqls` to **same database**
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
@ -75,11 +74,12 @@ 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.
* Telegram chat: Go to [our channel](https://t.me/openvkenglish) and open discussion in our channel menu.
* [Reddit](https://www.reddit.com/r/openvk/)
* [Discussions](https://github.com/openvk/openvk/discussions)
* Matrix chat: #openvk:matrix.org
**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**
**Attention**: bug tracker, telegram and matrix 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">

View file

@ -18,7 +18,6 @@ _[English](README.md)_
* **[openvk.su](https://openvk.su/)**
* [social.fetbuk.ru](http://social.fetbuk.ru/)
* [openvk.zavsc.pw](https://openvk.zavsc.pw/)
## Могу ли я создать свою собственную инстанцию OpenVK?
@ -48,7 +47,7 @@ ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/ext
ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/
```
4. Импортируйте `install/init-static-db.sql` в **ту же базу данных**, в которую вы установили Chandler
4. Импортируйте `install/init-static-db.sql` в **ту же базу данных**, в которую вы установили Chandler, и импортируйте все SQL файлы из папки `install/sqls` в **ту же базу данных**
5. Импортируйте `install/init-event-db.sql` в **отдельную базу данных**
6. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры
7. Запустите `composer install` в директории OpenVK
@ -78,8 +77,9 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
* Telegram-чат: Перейдите на [наш канал](https://t.me/openvkch) и откройте обсуждение в меню нашего канала.
* [Reddit](https://www.reddit.com/r/openvk/)
* [Обсуждения](https://github.com/openvk/openvk/discussions)
* Чат в Matrix: #ovk:matrix.org
**Внимание**: баг-трекер и телеграм-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [at] tutanota [dot] com**.
**Внимание**: баг-трекер, телеграм- и matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **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">

57
VKAPI/README.md Normal file
View file

@ -0,0 +1,57 @@
# VK API Compatability layer for OpenVK
This directory contains VK api handlers, structures and relared
exceptions. It is still a work-in-progress functionality.
**Note**: requests to api are routed through
openvk.Web.Presenters.VKAPIPresenter, this dir contains only handlers.
## Implementing API methods
VK API methods have names like this: `example.test`. To implement a
method like this you will need to create a class `Example` in the
Handlers subdirectory. This class **must** extend VKAPIHandler and be
final.
Next step is to create test method. It **must** have a type hint that is
not void. Everything else is fine, the return value of method will be
authomatically converted to JSON and sent back to client.
### Parameters
Method arguments are parameters. To declare a parameter just create an
argument with the same name. You should also provide correct type hints
for them. Type conversion is done automatically if possible. If not
possible error №1 will be returned.
If parameter is not passed by client then router will pass default value
to argument. If there is no default value but argument accepts NULL then
NULL will be passed. If NULL is not acceptable, default value is
undefined and parameter is not passed, API will return missing parameter
error to client.
### Returning errors
To return an error, call fail method like this: `$this->fail(5,
"error")` (first argument is error code and second is error message).
You can also throw the exception manually: `throw new
APIErrorException("error", 5)` (class:
openvk.VKAPI.Exceptions.APIErrorException).
If you throw any exception that does not inherit APIErrorException then
API will return error №1 (unknown error) to client.
### Refering to user
To get user use `getUser` method: `$this->getUser()`. Keep in mind it
will return NULL if user is undefined (no access\_token passed or it is
invalid/expired or roaming authentification failed).
If you need to check whether user is defined use `userAuthorized`. This
method returns true if user is present and false if not.
If your method cant work without user context call `requireUser` and it
will automatically return unauthorized error.
### Working with data
You can use OpenVK models for that. However, **do not** return them
(either you will leak data or JSON conversion will fail). It is better
to create a response object and return it. It is also a good idea to
define a structure in Structures subdirectory.
Have a lot of fun <sup></sup>

View file

@ -1,31 +0,0 @@
h1. VK API Compatability layer for OpenVK
This directory contains VK api handlers, structures and relared exceptions. It is still a work-in-progress functionality.
**Note**: requests to api are routed through openvk.Web.Presenters.VKAPIPresenter, this dir contains only handlers.
h2. Implementing API methods
VK API methods have names like this: @example.test@. To implement a method like this you will need to create a class @Example@ in the Handlers subdirectory. This class **must** extend VKAPIHandler and be final.
Next step is to create %test% method. It **must** have a type hint that is not %void%. Everything else is fine, the return value of method will be authomatically converted to JSON and sent back to client.
h3. Parameters
Method arguments are parameters. To declare a parameter just create an argument with the same name. You should also provide correct type hints for them. Type conversion is done automatically if possible. If not possible error №1 will be returned.
If parameter is not passed by client then router will pass default value to argument. If there is no default value but argument accepts NULL then NULL will be passed. If NULL is not acceptable, default value is undefined and parameter is not passed, API will return missing parameter error to client.
h3. Returning errors
To return an error, call %fail% method like this: @$this->fail(5, "error")@ (first argument is error code and second is error message). You can also throw the exception manually: @throw new APIErrorException("error", 5)@ (class: openvk.VKAPI.Exceptions.APIErrorException).
If you throw any exception that does not inherit APIErrorException then API will return error №1 (unknown error) to client.
h3. Refering to user
To get user use @getUser@ method: @$this->getUser()@. Keep in mind it will return NULL if user is undefined (no access_token passed or it is invalid/expired or roaming authentification failed).
If you need to check whether user is defined use @userAuthorized@. This method returns true if user is present and false if not.
If your method can't work without user context call @requireUser@ and it will automatically return unauthorized error.
h3. Working with data
You can use OpenVK models for that. However, **do not** return them (either you will leak data or JSON conversion will fail). It is better to create a response object and return it. It is also a good idea to define a structure in Structures subdirectory.
Have a lot of fun ^^

View file

@ -40,7 +40,7 @@ class Club extends RowModel
function getAvatarUrl(): string
{
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
$serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
$avPhoto = $this->getAvatarPhoto();
return is_null($avPhoto) ? "$serverUrl/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURL();
@ -64,16 +64,6 @@ class Club extends RowModel
else
return "/club" . $this->getId();
}
/*
function getAvatarUrl(): string
{
$avAlbum = (new Albums)->getUserAvatarAlbum($this);
$avCount = $avAlbum->getPhotosCount();
$avPhotos = $avAlbum->getPhotos($avCount, 1);
$avPhoto = iterator_to_array($avPhotos)[0] ?? NULL;
return is_null($avPhoto) ? "/assets/packages/static/openvk/img/camera_200.png" : $avPhoto->getURL();
} */
function getName(): string
{

View file

@ -0,0 +1,10 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\RowModel;
use openvk\Web\Util\DateTime;
class EmailVerification extends PasswordReset
{
protected $tableName = "email_verifications";
}

View file

@ -1,6 +1,6 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Notifications;
use openvk\Web\Models\Entities\{User, Gift};
use openvk\Web\Models\Entities\User;
final class CoinsTransferNotification extends Notification
{

View file

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Notifications;
use openvk\Web\Models\Entities\User;
final class RatingUpNotification extends Notification
{
protected $actionCode = 9603;
function __construct(User $receiver, User $sender, int $value, string $message)
{
parent::__construct($receiver, $receiver, $sender, time(), $value . " " . $message);
}
}

View file

@ -34,12 +34,15 @@ class PasswordReset extends RowModel
*/
function isNew(): bool
{
return $this->getRecord()->timestamp > (time() - 301);
return $this->getRecord()->timestamp > (time() - (5 * MINUTE));
}
/**
* Token is valid only for 3 days.
*/
function isStillValid(): bool
{
return $this->getRecord()->timestamp > (time() - 172801);
return $this->getRecord()->timestamp > (time() - (3 * DAY));
}
function verify(string $token): bool

View file

@ -64,7 +64,7 @@ trait TRichText
$text = preg_replace("%@([A-Za-z0-9]++) \(([\p{L} 0-9]+)\)%Xu", "[$1|$2]", $text);
$text = preg_replace("%([\n\r\s]|^)(@([A-Za-z0-9]++))%Xu", "$1[$3|@$3]", $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("%([\n\r\s]|^)(#([\p{L}_-]++[0-9]*[\p{L}_-]*))%Xu", "$1<a href='/feed/hashtag/$3'>$2</a>", $text);
$text = $this->formatEmojis($text);
}

View file

@ -5,6 +5,7 @@ use openvk\Web\Util\DateTime;
use openvk\Web\Models\RowModel;
use openvk\Web\Models\Entities\{Photo, Message, Correspondence, Gift};
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Gifts, Notifications};
use openvk\Web\Models\Exceptions\InvalidUserNameException;
use Nette\Database\Table\ActiveRow;
use Chandler\Database\DatabaseConnection;
use Chandler\Security\User as ChandlerUser;
@ -103,7 +104,7 @@ class User extends RowModel
function getAvatarUrl(): string
{
$serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"];
$serverUrl = ovk_scheme(true) . $_SERVER["HTTP_HOST"];
if($this->getRecord()->deleted)
return "$serverUrl/assets/packages/static/openvk/img/camera_200.png";
@ -739,6 +740,27 @@ class User extends RowModel
return true;
}
function setFirst_Name(string $firstName): void
{
$firstName = mb_convert_case($firstName, MB_CASE_TITLE);
if(!preg_match('%^[\p{Lu}\p{Lo}]\p{Mn}?(?:[\p{L&}\p{Lo}]\p{Mn}?){1,16}$%u', $firstName))
throw new InvalidUserNameException;
$this->stateChanges("first_name", $firstName);
}
function setLast_Name(string $lastName): void
{
if(!empty($lastName))
{
$lastName = mb_convert_case($lastName, MB_CASE_TITLE);
if(!preg_match('%^[\p{Lu}\p{Lo}]\p{Mn}?([\p{L&}\p{Lo}]\p{Mn}?){1,16}(\-\g<1>+)?$%u', $lastName))
throw new InvalidUserNameException;
}
$this->stateChanges("last_name", $lastName);
}
function setNsfwTolerance(int $tolerance): void
{
$this->stateChanges("nsfw_tolerance", $tolerance);
@ -850,10 +872,10 @@ class User extends RowModel
function isDeleted(): bool
{
if ($this->getRecord()->deleted == 1)
return TRUE;
else
return FALSE;
if ($this->getRecord()->deleted == 1)
return TRUE;
else
return FALSE;
}
/**
@ -882,6 +904,12 @@ class User extends RowModel
{
return $this->getRecord()->website;
}
// ты устрица
function isActivated(): bool
{
return (bool) $this->getRecord()->activated;
}
use Traits\TSubscribable;
}

View file

@ -0,0 +1,7 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Exceptions;
final class InvalidUserNameException extends \UnexpectedValueException
{
protected $message = "Invalid real name supplied";
}

View file

@ -37,7 +37,7 @@ class Notifications
if(!$count) {
$query .= " ORDER BY timestamp DESC";
$query .= " LIMIT " . ($perPage ?? OPENVK_DEFAULT_PER_PAGE);
$query .= " OFFSET " . ((($page - 1) * $perPage) ?? OPENVK_DEFAULT_PER_PAGE);
$query .= " OFFSET " . (($page - 1) * ($perPage ?? OPENVK_DEFAULT_PER_PAGE));
}
return $query;

View file

@ -39,7 +39,7 @@ class Users
function find(string $query): Util\EntityStream
{
$query = "%$query%";
$result = $this->users->where("CONCAT_WS(' ', first_name, last_name) LIKE ?", $query)->where("deleted", 0);
$result = $this->users->where("CONCAT_WS(' ', first_name, last_name, pseudo) LIKE ?", $query)->where("deleted", 0);
return new Util\EntityStream("User", $result);
}

View file

@ -0,0 +1,33 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\EmailVerification;
use openvk\Web\Models\Entities\User;
use Nette\Database\Table\ActiveRow;
class Verifications
{
private $context;
private $verifications;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->verifications = $this->context->table("email_verifications");
}
function toEmailVerification(?ActiveRow $ar): ?EmailVerification
{
return is_null($ar) ? NULL : new EmailVerification($ar);
}
function getByToken(string $token): ?EmailVerification
{
return $this->toEmailVerification($this->verifications->where("key", $token)->fetch());
}
function getLatestByUser(User $user): ?EmailVerification
{
return $this->toEmailVerification($this->verifications->where("profile", $user->getId())->order("timestamp DESC")->fetch());
}
}

View file

@ -5,7 +5,7 @@ final class YouTubeVideoDriver extends VideoDriver
{
function getThumbnailURL(): string
{
return "https://img.youtube.com/vi/$this->id/mq3.jpg";
return "https://img.youtube.com/vi/$this->id/mqdefault.jpg";
}
function getURL(): string

View file

@ -8,6 +8,7 @@ use Chandler\Session\Session;
final class AboutPresenter extends OpenVKPresenter
{
protected $banTolerant = true;
protected $activationTolerant = true;
function renderIndex(): void
{

View file

@ -3,9 +3,11 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\IP;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Entities\PasswordReset;
use openvk\Web\Models\Entities\EmailVerification;
use openvk\Web\Models\Repositories\IPs;
use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\Repositories\Restores;
use openvk\Web\Models\Repositories\Verifications;
use openvk\Web\Util\Validator;
use Chandler\Session\Session;
use Chandler\Security\User as ChandlerUser;
@ -16,19 +18,22 @@ use lfkeitel\phptotp\{Base32, Totp};
final class AuthPresenter extends OpenVKPresenter
{
protected $banTolerant = true;
protected $activationTolerant = true;
private $authenticator;
private $db;
private $users;
private $restores;
private $verifications;
function __construct(Users $users, Restores $restores)
function __construct(Users $users, Restores $restores, Verifications $verifications)
{
$this->authenticator = Authenticator::i();
$this->db = DatabaseConnection::i()->getContext();
$this->users = $users;
$this->restores = $restores;
$this->verifications = $verifications;
parent::__construct();
}
@ -81,7 +86,7 @@ final class AuthPresenter extends OpenVKPresenter
$this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment"));
if (strtotime($this->postParam("birthday")) > time())
$this->flashFail("err", tr("invalid_birth_date"), tr("invalid_birth_date_comment"));
$this->flashFail("err", tr("invalid_birth_date"), tr("invalid_birth_date_comment"));
$chUser = ChandlerUser::create($this->postParam("email"), $this->postParam("password"));
if(!$chUser)
@ -96,12 +101,25 @@ final class AuthPresenter extends OpenVKPresenter
$user->setSince(date("Y-m-d H:i:s"));
$user->setRegistering_Ip(CONNECTING_IP);
$user->setBirthday(strtotime($this->postParam("birthday")));
$user->setActivated((int) !OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']);
$user->save();
if(!is_null($referer)) {
$user->toggleSubscription($referer);
$referer->toggleSubscription($user);
}
if (OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']) {
$verification = new EmailVerification;
$verification->setProfile($user->getId());
$verification->save();
$params = [
"key" => $verification->getKey(),
"name" => $user->getCanonicalName(),
];
$this->sendmail($user->getEmail(), "verify-email", $params); #Vulnerability possible
}
$this->authenticator->authenticate($chUser->getId());
$this->redirect("/id" . $user->getId(), static::REDIRECT_TEMPORARY);
@ -253,4 +271,48 @@ final class AuthPresenter extends OpenVKPresenter
$this->flashFail("succ", tr("information_-1"), tr("password_reset_email_sent"));
}
}
function renderResendEmail(): void
{
if(!is_null($this->user) && $this->user->identity->isActivated())
$this->redirect("/id" . $this->user->id, static::REDIRECT_TEMPORARY);
if($_SERVER["REQUEST_METHOD"] === "POST") {
$user = $this->user->identity;
if(!$user || $user->isDeleted() || $user->isActivated())
$this->flashFail("err", tr("error"), tr("email_error"));
$request = $this->verifications->getLatestByUser($user);
if(!is_null($request) && $request->isNew())
$this->flashFail("err", tr("forbidden"), tr("email_rate_limit_error"));
$verification = new EmailVerification;
$verification->setProfile($user->getId());
$verification->save();
$params = [
"key" => $verification->getKey(),
"name" => $user->getCanonicalName(),
];
$this->sendmail($user->getEmail(), "verify-email", $params); #Vulnerability possible
$this->flashFail("succ", tr("information_-1"), tr("email_sent"));
}
}
function renderVerifyEmail(): void
{
$request = $this->verifications->getByToken(str_replace(" ", "+", $this->queryParam("key")));
if(!$request || !$request->isStillValid()) {
$this->flash("err", tr("token_manipulation_error"), tr("token_manipulation_error_comment"));
$this->redirect("/");
} else {
$user = $request->getUser();
$user->setActivated(1);
$user->save();
$this->flash("success", tr("email_verify_success"));
$this->redirect("/");
}
}
}

View file

@ -13,6 +13,7 @@ use WhichBrowser;
abstract class OpenVKPresenter extends SimplePresenter
{
protected $banTolerant = false;
protected $activationTolerant = false;
protected $errorTemplate = "@error";
protected $user = NULL;
@ -212,6 +213,27 @@ abstract class OpenVKPresenter extends SimplePresenter
$this->template->userTainted = $user->isTainted();
if($this->user->identity->isDeleted()) {
/*
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⠶⠶⣶⠶⠶⠶⠶⠶⠶⠶⠶⠶⢶⠶⠶⠶⠤⠤⠤⠤⣄⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠋⠀⠀⠊⠀⠀⠀⠀⠀⠀⠀⠀⠒⠒⠒⠀⠀⠀⠀⠤⢤⣤⣄⠉⠉⠛⠛⠷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣰⠟⠀⠀⠀⠀⠀⠐⠋⢑⣤⣶⣶⣤⡢⡀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣄⡂⠀⠀⠶⢄⠙⢷⣤⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣸⡿⠚⠉⡀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⢢⠀⠀⡀⣰⣿⣿⣿⣿⣦⡀⠀⠀⠡⡀⢹⡆⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢀⣴⠏⠀⣀⣀⣀⡤⢤⣄⣠⣿⣿⣿⣿⣻⣿⣿⣷⠀⢋⣾⠈⠙⣶⠒⢿⣿⣿⣿⣿⡿⠟⠃⠀⡀⠡⠼⣧⡀⠀⠀⠀⠀⠀⠀
⠀⠀⢀⣴⣿⢃⡴⢊⢽⣶⣤⣀⠀⠊⠉⠉⡛⢿⣿⣿⣿⠿⠋⢀⡀⠁⠀⠀⢸⣁⣀⣉⣉⣉⡉⠀⠩⡡⠀⣩⣦⠀⠈⠻⣦⡀⠀⠀⠀⠀
⠀⢠⡟⢡⠇⡞⢀⠆⠀⢻⣿⣿⣷⣄⠀⢀⠈⠂⠈⢁⡤⠚⡟⠉⠀⣀⣀⠀⠈⠳⣍⠓⢆⢀⡠⢀⣨⣴⣿⣿⡏⢀⡆⠀⢸⡇⠀⠀⠀⠀
⠀⣾⠁⢸⠀⠀⢸⠀⠀⠀⠹⣿⣿⣿⣿⣶⣬⣦⣤⡈⠀⠀⠇⠀⠛⠉⣩⣤⣤⣤⣿⣤⣤⣴⣾⣿⣿⣿⣿⣿⣧⠞⠀⠀⢸⡇⠀⠀⠀⠀
⠀⢹⣆⠸⠀⠀⢸⠀⠀⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⣟⣛⠛⠛⣛⡛⠛⠛⣛⣋⡉⠉⣡⠶⢾⣿⣿⣿⣿⣿⣿⡇⠀⠀⢀⣾⠃⠀⠀⠀⠀
⠀⠀⠻⣆⡀⠀⠈⢂⠀⠀⠀⠠⡈⢻⣿⣿⣿⣿⡟⠁⠈⢧⡼⠉⠙⣆⡞⠁⠈⢹⣴⠃⠀⢸⣿⣿⣿⣿⣿⣿⠃⠀⡆⣾⠃⠀⠀⠀⠀⠀
⠀⠀⠀⠈⢻⣇⠀⠀⠀⠀⠀⠀⢡⠀⠹⣿⣿⣿⣷⡀⠀⣸⡇⠀⠀⣿⠁⠀⠀⠘⣿⠀⠀⠘⣿⣿⣿⣿⣿⣿⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠹⣇⠀⠠⠀⠀⠀⠀⠡⠐⢬⡻⣿⣿⣿⣿⣿⣷⣶⣶⣿⣦⣤⣤⣤⣿⣦⣶⣿⣿⣿⣿⣿⣿⣿⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠹⣧⡀⠡⡀⠀⠀⠀⠑⠄⠙⢎⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⢿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⠳⣤⡐⡄⠀⠀⠀⠈⠂⠀⠱⣌⠻⣿⣿⣿⣿⣿⣿⣿⠿⣿⠟⢻⡏⢻⣿⣿⣿⣿⣿⣿⣿⠀⢸⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⢮⣦⡀⠂⠀⢀⠀⠀⠈⠳⣈⠻⣿⣿⣿⡇⠘⡄⢸⠀⠀⣇⠀⣻⣿⣿⣿⣿⣿⡏⠀⠸⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⢶⣤⣄⡑⠄⠀⠀⠈⠑⠢⠙⠻⢷⣶⣵⣞⣑⣒⣋⣉⣁⣻⣿⠿⠟⠱⠃⡸⠀⣧⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⣷⣄⡀⠐⠢⣄⣀⡀⠀⠉⠉⠉⠉⠛⠙⠭⠭⠄⠒⠈⠀⠐⠁⢀⣿⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠷⢦⣤⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣒⡠⠄⣠⡾⠃⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠷⠶⣦⣤⣭⣤⣬⣭⣭⣴⠶⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀
*/
Authenticator::i()->logout();
Session::i()->set("_su", NULL);
$this->flashFail("err", tr("error"), tr("profile_not_found"));
@ -227,6 +249,17 @@ abstract class OpenVKPresenter extends SimplePresenter
]);
exit;
}
// ето для емейл уже надо (и по хорошему надо бы избавится от повторяющегося кода мда)
if(!$this->user->identity->isActivated() && !$this->activationTolerant) {
header("HTTP/1.1 403 Forbidden");
$this->getTemplatingEngine()->render(__DIR__ . "/templates/@email.xml", [
"thisUser" => $this->user->identity,
"csrfToken" => $GLOBALS["csrfToken"],
"isTimezoned" => Session::i()->get("_timezoneOffset"),
]);
exit;
}
if ($this->user->identity->onlineStatus() == 0) {
$this->user->identity->setOnline(time());

View file

@ -9,8 +9,9 @@ use openvk\Web\Models\Repositories\Albums;
use openvk\Web\Models\Repositories\Videos;
use openvk\Web\Models\Repositories\Notes;
use openvk\Web\Models\Repositories\Vouchers;
use openvk\Web\Models\Exceptions\InvalidUserNameException;
use openvk\Web\Util\Validator;
use openvk\Web\Models\Entities\Notifications\CoinsTransferNotification;
use openvk\Web\Models\Entities\Notifications\{CoinsTransferNotification, RatingUpNotification};
use Chandler\Security\Authenticator;
use lfkeitel\phptotp\{Base32, Totp};
use chillerlan\QRCode\{QRCode, QROptions};
@ -137,10 +138,18 @@ final class UserPresenter extends OpenVKPresenter
$this->willExecuteWriteAction($_GET['act'] === "status");
if($_GET['act'] === "main" || $_GET['act'] == NULL) {
$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"));
try {
$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"));
} catch(InvalidUserNameException $ex) {
$this->flashFail("err", tr("error"), tr("invalid_real_name"));
}
$user->setPseudo(empty($this->postParam("pseudo")) ? NULL : $this->postParam("pseudo"));
$user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status"));
$user->setHometown(empty($this->postParam("hometown")) ? NULL : $this->postParam("hometown"));
if (strtotime($this->postParam("birthday")) < time())
$user->setBirthday(strtotime($this->postParam("birthday")));
@ -179,7 +188,7 @@ final class UserPresenter extends OpenVKPresenter
$user->setCity(empty($this->postParam("city")) ? NULL : $this->postParam("city"));
$user->setAddress(empty($this->postParam("address")) ? NULL : $this->postParam("address"));
$website = $this->postParam("website") ?? "";
if(empty($website))
$user->setWebsite(NULL);
@ -510,4 +519,44 @@ final class UserPresenter extends OpenVKPresenter
$this->flashFail("succ", tr("information_-1"), tr("points_transfer_successful", tr("points_amount", $value), $receiver->getURL(), htmlentities($receiver->getCanonicalName())));
}
function renderIncreaseRating(): void
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction();
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
$this->flashFail("err", tr("error"), tr("feature_disabled"));
$receiverAddress = $this->postParam("receiver");
$value = (int) $this->postParam("value");
$message = $this->postParam("message");
if(!$receiverAddress || !$value)
$this->flashFail("err", tr("failed_to_increase_rating"), tr("not_all_information_has_been_entered"));
if($value < 0)
$this->flashFail("err", tr("failed_to_increase_rating"), tr("negative_rating_value"));
if(iconv_strlen($message) > 255)
$this->flashFail("err", tr("failed_to_increase_rating"), tr("message_is_too_long"));
$receiver = $this->users->getByAddress($receiverAddress);
if(!$receiver)
$this->flashFail("err", tr("failed_to_increase_rating"), tr("receiver_not_found"));
if($this->user->identity->getCoins() < $value)
$this->flashFail("err", tr("failed_to_increase_rating"), tr("you_dont_have_enough_points"));
$this->user->identity->setCoins($this->user->identity->getCoins() - $value);
$this->user->identity->save();
$receiver->setRating($receiver->getRating() + $value);
$receiver->save();
if($this->user->id !== $receiver->getId())
(new RatingUpNotification($receiver, $this->user->identity, $value, $message))->emit();
$this->flashFail("succ", tr("information_-1"), tr("rating_increase_successful", $receiver->getURL(), htmlentities($receiver->getCanonicalName()), $value));
}
}

View file

@ -5,6 +5,9 @@ use openvk\Web\Models\Entities\Notifications\{RepostNotification, WallPostNotifi
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums};
use Chandler\Database\DatabaseConnection;
use Nette\InvalidStateException as ISE;
use Bhaktaraz\RSSGenerator\Item;
use Bhaktaraz\RSSGenerator\Feed;
use Bhaktaraz\RSSGenerator\Channel;
final class WallPresenter extends OpenVKPresenter
{
@ -42,7 +45,7 @@ final class WallPresenter extends OpenVKPresenter
function renderWall(int $user, bool $embedded = false): void
{
if(false)
exit("Ошибка доступа: " . (string) random_int(0, 255));
exit(tr("forbidden") . ": " . (string) random_int(0, 255));
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
if(is_null($this->user)) {
@ -51,15 +54,15 @@ final class WallPresenter extends OpenVKPresenter
if(!$owner->isBanned())
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", tr("error"), "Ошибка доступа");
} else if($user < 0) {
$this->flashFail("err", tr("error"), tr("forbidden"));
} else if($user < 0) {
if($owner->canBeModifiedBy($this->user->identity))
$canPost = true;
else
$canPost = $owner->canPost();
} else {
$canPost = false;
}
}
if ($embedded == true) $this->template->_template = "components/wall.xml";
$this->template->oObj = $owner;
@ -82,6 +85,49 @@ final class WallPresenter extends OpenVKPresenter
{
$this->renderWall($user, true);
}
function renderRSS(int $user): void
{
if(false)
exit(tr("forbidden") . ": " . (string) random_int(0, 255));
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
if(is_null($this->user)) {
$canPost = false;
} else if($user > 0) {
if(!$owner->isBanned())
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", tr("error"), tr("forbidden"));
} else if($user < 0) {
if($owner->canBeModifiedBy($this->user->identity))
$canPost = true;
else
$canPost = $owner->canPost();
} else {
$canPost = false;
}
$posts = iterator_to_array($this->posts->getPostsFromUsersWall($user));
$feed = new Feed();
$channel = new Channel();
$channel->title(OPENVK_ROOT_CONF['openvk']['appearance']['name'])->url(ovk_scheme(true) . $_SERVER["SERVER_NAME"])->appendTo($feed);
foreach($posts as $post) {
$item = new Item();
$item
->title($post->getOwner()->getCanonicalName())
->description($post->getText())
->url(ovk_scheme(true).$_SERVER["SERVER_NAME"]."/wall{$post->getPrettyId()}")
->pubDate($post->getPublicationTime()->timestamp())
->appendTo($channel);
}
header("Content-Type: application/rss+xml");
exit($feed);
}
function renderFeed(): void
{
@ -167,12 +213,12 @@ final class WallPresenter extends OpenVKPresenter
$this->willExecuteWriteAction();
$wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1))
?? $this->flashFail("err", "Не удалось опубликовать пост", "Такого пользователя не существует.");
?? $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4"));
if($wall > 0) {
if(!$wallOwner->isBanned())
$canPost = $wallOwner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", "Ошибка доступа", "Вам нельзя писать на эту стену.");
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
} else if($wall < 0) {
if($wallOwner->canBeModifiedBy($this->user->identity))
$canPost = true;
@ -183,7 +229,7 @@ final class WallPresenter extends OpenVKPresenter
}
if(!$canPost)
$this->flashFail("err", "Ошибка доступа", "Вам нельзя писать на эту стену.");
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
if($wallOwner instanceof Club && $this->postParam("as_group") === "on" && $this->postParam("force_sign") !== "on" && $anon) {
@ -217,13 +263,13 @@ final class WallPresenter extends OpenVKPresenter
$video = Video::fastMake($this->user->id, $this->postParam("text"), $_FILES["_vid_attachment"], $anon);
}
} catch(\DomainException $ex) {
$this->flashFail("err", "Не удалось опубликовать пост", "Файл медиаконтента повреждён.");
$this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted"));
} catch(ISE $ex) {
$this->flashFail("err", "Не удалось опубликовать пост", "Файл медиаконтента повреждён или слишком велик.");
$this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted_or_too_large"));
}
if(empty($this->postParam("text")) && !$photo && !$video)
$this->flashFail("err", "Не удалось опубликовать пост", "Пост пустой или слишком большой.");
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big"));
try {
$post = new Post;
@ -236,7 +282,7 @@ final class WallPresenter extends OpenVKPresenter
$post->setNsfw($this->postParam("nsfw") === "on");
$post->save();
} catch (\LengthException $ex) {
$this->flashFail("err", "Не удалось опубликовать пост", "Пост слишком большой.");
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_too_big"));
}
if(!is_null($photo))
@ -270,7 +316,7 @@ final class WallPresenter extends OpenVKPresenter
$this->template->wallOwner = (new Users)->get($post->getTargetWall());
$this->template->isWallOfGroup = false;
if($this->template->wallOwner->isBanned())
$this->flashFail("err", tr("error"), "Ошибка доступа");
$this->flashFail("err", tr("error"), tr("forbidden"));
} else {
$this->template->wallOwner = (new Clubs)->get(abs($post->getTargetWall()));
$this->template->isWallOfGroup = true;
@ -334,7 +380,7 @@ final class WallPresenter extends OpenVKPresenter
$user = $this->user->id;
$wallOwner = ($wall > 0 ? (new Users)->get($wall) : (new Clubs)->get($wall * -1))
?? $this->flashFail("err", "Не удалось удалить пост", "Такого пользователя не существует.");
?? $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4"));
if($wall < 0) $canBeDeletedByOtherUser = $wallOwner->canBeModifiedBy($this->user->identity);
else $canBeDeletedByOtherUser = false;
@ -345,7 +391,7 @@ final class WallPresenter extends OpenVKPresenter
$post->delete();
}
} else {
$this->flashFail("err", "Не удалось удалить пост", "Вы не вошли в аккаунт.");
$this->flashFail("err", tr("failed_to_delete_post"), tr("login_required_error_comment"));
}
$this->redirect($wall < 0 ? "/club".($wall*-1) : "/id".$wall, static::REDIRECT_TEMPORARY);
@ -362,7 +408,7 @@ final class WallPresenter extends OpenVKPresenter
$this->notFound();
if(!$post->canBePinnedBy($this->user->identity))
$this->flashFail("err", "Ошибка доступа", "Вам нельзя закреплять этот пост.");
$this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
if(($this->queryParam("act") ?? "pin") === "pin") {
$post->pin();
@ -371,6 +417,6 @@ final class WallPresenter extends OpenVKPresenter
}
// TODO localize message based on language and ?act=(un)pin
$this->flashFail("succ", "Операция успешна", "Операция успешна.");
$this->flashFail("succ", tr("information_-1"), tr("changes_saved_comment"));
}
}

View file

@ -0,0 +1,19 @@
{extends "@layout.xml"}
{block title}{_ec_header}{/block}
{block header}
{_ec_header}
{/block}
{block content}
<div class="border-block center">
<h4>{_ec_title}</h4>
<p>{tr("ec_1", htmlentities($thisUser->getCanonicalName()))|noescape}<br/></p>
<p>{_ec_2}</p>
<p>
<form action="/reg/resend" method="post">
<input type="submit" class="button" value="{_ec_resend}">
</form>
</p>
</div>
{/block}

View file

@ -100,7 +100,7 @@
<div id="xhead" class="dm"></div>
<div class="page_header {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}page_custom_header{/if}">
<a href="/" class="home_button {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}home_button_custom{/if}" title="{$instance_name}">{$instance_name}</a>
<div n:if="isset($thisUser) ? !$thisUser->isBanned() : true" class="header_navigation">
<div n:if="isset($thisUser) ? (!$thisUser->isBanned() XOR !$thisUser->isActivated()) : true" class="header_navigation">
{ifset $thisUser}
<div class="link">
<a href="/">{_header_home}</a>
@ -142,7 +142,7 @@
<div class="sidebar">
<div class="navigation">
{ifset $thisUser}
{if !$thisUser->isBanned()}
{if !$thisUser->isBanned() XOR !$thisUser->isActivated()}
<a href="/edit" class="link edit-button">{_edit_button}</a>
<a href="{$thisUser->getURL()}" class="link">{_my_page}</a>
<a href="/friends{$thisUser->getId()}" class="link">{_my_friends}
@ -189,6 +189,8 @@
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['enable'] && $thisUser->getLeftMenuItemStatus('poster')" href="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['link']}" >
<img src="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['src']}" alt="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['caption']}" class="psa-poster" style="max-width: 100%; margin-top: 50px;" />
</a>
{elseif !$thisUser->isActivated()}
<a href="/logout?hash={urlencode($csrfToken)}" class="link">{_menu_logout}</a>
{else}
<a href="/support" class="link">{_menu_support}
{if $ticketAnsweredCount > 0}
@ -260,7 +262,7 @@
<a href="/privacy" class="link">{_footer_privacy}</a>
</div>
<p>OpenVK <a href="/about:openvk">{php echo OPENVK_VERSION}</a> | PHP: {phpversion()} | DB: {$dbVersion}</p>
<p n:ifcontent="ifcontent">
<p n:ifcontent>
{php echo OPENVK_ROOT_CONF["openvk"]["appearance"]["motd"]}
</p>
</div>

View file

@ -80,6 +80,14 @@
<input type="text" name="status" value="{$user->getStatus()}" />
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">{_"hometown"}: </span>
</td>
<td>
<input type="text" name="hometown" value="{$user->getHometown()}" />
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">{_"relationship"}: </span>

View file

@ -18,13 +18,13 @@
{block tabs}
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
<div {if !$admin}id="activetabs"{/if} class="tab">
<a {if !$admin}id="act_tab_a"{/if} href="/groups{$user->getId()}">
<div n:attr='id => ($admin ? false : "activetabs")' class="tab">
<a n:attr='id => ($admin ? false : "act_tab_a")' href="/groups{$user->getId()}">
{_groups}
</a>
</div>
<div {if $admin}id="activetabs"{/if} class="tab">
<a {if $admin}id="act_tab_a"{/if} href="/groups{$user->getId()}?act=managed">
<div n:attr='id => (!$admin ? false : "activetabs")' class="tab">
<a n:attr='id => (!$admin ? false : "act_tab_a")' href="/groups{$user->getId()}?act=managed">
{_managed}
</a>
</div>
@ -51,15 +51,17 @@
<img src="{$x->getAvatarUrl()}" width="75" alt="Фотография группы" />
{/block}
{block infotable}
{block name}{/block}
{block infoTable}
<table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
<td class="label"><span class="nobold">Название: </span></td>
<td class="label"><span class="nobold">{_name}: </span></td>
<td class="data"><a href="{$x->getURL()}">{$x->getName()}</a></td>
</tr>
<tr>
<td class="label"><span class="nobold">Размер:</span></td>
<td class="label"><span class="nobold">{_size}:</span></td>
<td class="data"><a href="/club{$x->getId()}/followers">{tr("participants", $x->getFollowersCount())}</a></td>
</tr>
</tbody>
@ -99,7 +101,7 @@
{block bottom}
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
<div class="groups_options">
<div id="gp_container" style="width: 200px;margin-right: 40px;">
<div id="gp_container" style="width: 200px; margin-right: 40px;">
<h4>{_open_new_group}</h4>
<span>{_open_group_desc}</span>
<form action="/groups_create">
@ -111,10 +113,10 @@
<span>{_search_group_desc}</span>
<form action="/search">
<input name="type" type="hidden" value="groups">
<input name="query" class="header_search_input" value="" style="background: none;width: 155px;padding-left: 3px;">
<input name="query" class="header_search_input" value="" style="background: none; width: 155px; padding-left: 3px;">
<button class="button">{_search_by_groups}</button>
</form>
</div>
</div>
{/if}
{/block}
{/block}

View file

@ -407,7 +407,7 @@
</td>
<td>
<select name="style_avatar">
<option value="0" {if $user->getStyleAvatar() == 0}selected{/if}>{_"default"}</option>
<option value="0" {if $user->getStyleAvatar() == 0}selected{/if}>{_"arbitrary_avatars"} ({_"default"})</option>
<option value="1" {if $user->getStyleAvatar() == 1}selected{/if}>{_"cut"}</option>
<option value="2" {if $user->getStyleAvatar() == 2}selected{/if}>{_"round_avatars"}</option>
</select>

View file

@ -72,6 +72,9 @@
<div id="profile_link" style="width: 194px;">
<a href="/edit" class="link">{_"edit_page"}</a>
</div>
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce']" id="profile_link" style="width: 194px;">
<a onClick="showIncreaseRatingDialog({$thisUser->getCoins()}, {ltrim($thisUser->getUrl(), '/')}, {$csrfToken})" class="link">{_increase_rating}</a>
</div>
{else}
{if $thisUser->getChandlerUser()->can("substitute")->model('openvk\Web\Models\Entities\User')->whichBelongsTo(0)}
<a href="/setSID/{$user->getChandlerUser()->getId()}?hash={rawurlencode($csrfToken)}" class="profile_link">

View file

@ -31,6 +31,5 @@
{/if}
<a n:if="$canDelete ?? false" class="profile_link" style="display:block;width:96%;" href="/wall{$post->getPrettyId()}/delete">{_delete}</a>
<a class="profile_link" style="display:block;width:96%;" href="/report.pl/{$post->getId()}?type=post">{_report}</a>
</div>
{/block}

View file

@ -7,10 +7,12 @@
<tbody>
<tr>
<td width="30" valign="top">
<img
src="{$author->getAvatarURL()}"
width="30"
class="cCompactAvatars" />
<a href="{$author->getURL()}">
<img
src="{$author->getAvatarURL()}"
width="30"
class="cCompactAvatars" />
</a>
</td>
<td width="100%" valign="top">
<div class="post-author">

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> {_increased_your_rating_by} {$value}%.
{if !empty($message)}
{_message}: "{$message}".
{/if}

View file

@ -8,15 +8,17 @@
<tbody>
<tr>
<td width="54" valign="top">
<img
src="{$author->getAvatarURL()}"
width="{ifset $compact}25{else}50{/ifset}"
{ifset $compact}class="cCompactAvatars"{/ifset} />
{if !$post->isPostedOnBehalfOfGroup() && !$compact}
<span n:if="$author->isOnline()" class="post-online">
{_online}
</span>
{/if}
<a href="{$author->getURL()}">
<img
src="{$author->getAvatarURL()}"
width="{ifset $compact}25{else}50{/ifset}"
{ifset $compact}class="cCompactAvatars"{/ifset} />
{if !$post->isPostedOnBehalfOfGroup() && !$compact}
<span n:if="$author->isOnline()" class="post-online">
{_online}
</span>
{/if}
</a>
</td>
<td width="100%" valign="top">
<div class="post-author">

View file

@ -4,14 +4,17 @@
<tbody>
<tr>
<td width="54" valign="top">
<img
src="{$author->getAvatarURL()}"
width="50" />
<a href="{$author->getURL()}">
<img
src="{$author->getAvatarURL()}"
width="50" />
</a>
{if !$post->isPostedOnBehalfOfGroup() && !($compact ?? false)}
<span n:if="$author->isOnline()" class="post-online">
{_online}
</span>
{/if}
</td>
<td width="100%" valign="top">
<div class="post-author">

View file

@ -31,7 +31,8 @@ services:
- openvk\Web\Models\Repositories\Notes
- openvk\Web\Models\Repositories\Tickets
- openvk\Web\Models\Repositories\Messages
- openvk\Web\Models\Repositories\Restores
- openvk\Web\Models\Repositories\Restores
- openvk\Web\Models\Repositories\Verifications
- openvk\Web\Models\Repositories\Notifications
- openvk\Web\Models\Repositories\TicketComments
- openvk\Web\Models\Repositories\IPs

View file

@ -59,6 +59,10 @@ routes:
handler: "Auth->restore"
- url: "/restore/internal-finish"
handler: "Auth->finishRestoringPassword"
- url: "/reg/resend"
handler: "Auth->resendEmail"
- url: "/regFinish"
handler: "Auth->verifyEmail"
- url: "/setSID/{slug}"
handler: "Auth->su"
- url: "/settings"
@ -69,6 +73,8 @@ routes:
handler: "User->disableTwoFactorAuth"
- url: "/coins_transfer"
handler: "User->coinsTransfer"
- url: "/increase_social_credits"
handler: "User->increaseRating"
- url: "/id{num}"
handler: "User->view"
- url: "/friends{num}"
@ -101,6 +107,8 @@ routes:
hashTag: ".++"
- url: "/wall{num}"
handler: "Wall->wall"
- url: "/wall{num}/rss"
handler: "Wall->rss"
- url: "/wall{num}/makePost"
handler: "Wall->makePost"
- url: "/wall{num}_{num}"

View file

@ -38,4 +38,5 @@ div.content > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(1) > td:nth
.cCompactAvatars {
width: 30px !important;
height: 30px !important;
object-fit: cover;
}

View file

@ -65,4 +65,5 @@ div.ovk-video > div > img
.cCompactAvatars {
width: 30px !important;
height: 30px !important;
object-fit: cover;
}

View file

@ -1886,7 +1886,24 @@ html {
.mb_tab div {
padding: 5px 7px;
}
}
#gp_container span {
display: block;
margin: 10px 0 15px;
}
#gp_container h4 {
font-size: 11px;
}
.container_gray .content:last-child {
margin-bottom: 0;
}
.group_info {
padding: 0 0 0 5px !important;
}
.mb_tab#active {
background-color: #898989;
@ -1898,4 +1915,20 @@ html {
.mb_tab#active a {
color: white;
}
}
.profile_thumb {
padding: 0px 10px 0px 0px;
width: 50px;
display: inline-block;
vertical-align: top;
}
.border-block {
box-shadow: inset 0px 0 0px 1px #b6bfca, inset 0px 0 0px 10px #d8dfe7;
width: 300px;
padding: 20px;
}
.center {
margin: 0 auto;
}

View file

@ -289,3 +289,78 @@ function supportFastAnswerDialogOnClick(answer) {
answerInput.value = answer;
answerInput.focus();
}
function ovk_proc_strtr(string, length = 0) {
const newString = string.substring(0, length);
return newString + (string !== newString ? "…" : "");
}
function showIncreaseRatingDialog(coinsCount, userUrl, hash) {
MessageBox(tr("increase_rating"), `
<div class="messagebox-content-header">
${tr("you_have_unused_votes", coinsCount)} <br />
<a href="/settings?act=finance.top-up">${tr("apply_voucher")} &raquo;</a>
</div>
<form action="/increase_social_credits" method="post" id="increase_rating_form" style="margin-top: 30px">
<table cellspacing="7" cellpadding="0" border="0" align="center">
<tbody>
<tr>
<td width="120" valign="top">
<span class="nobold">${tr("to_whom")}:</span>
</td>
<td>
<input type="text" name="receiver" style="width: 100%;" value="${userUrl}" />
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">${tr("increase_by")}:</span>
</td>
<td>
<input id="value_input" type="text" name="value" style="width: 100%;" />
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">${tr("message")}:</span>
</td>
<td>
<textarea name="message" style="width: 100%;"></textarea>
</td>
</tr>
<tr>
<td colspan="2">
<div class="menu_divider"></div>
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">${tr("price")}:</span>
</td>
<td>
<span id="rating_price">${tr("points_amount", 0)}</span> <small class="nobold" style="float: right;">(1% = ${tr("points_amount_one", 1)})</small>
</td>
</tr>
</tbody>
</table>
<input type="hidden" name="hash" value="${hash}" />
</form>
`, [tr("increase_rating_button"), tr("cancel")], [
() => {
document.querySelector("#increase_rating_form").submit();
},
Function.noop
]);
document.querySelector("#value_input").oninput = function () {
let value = Number(this.value);
value = isNaN(value) ? "?" : ovk_proc_strtr(String(value), 7);
if(!value.endsWith("…") && value != "?")
value = Number(value);
if(typeof value === "number")
document.querySelector("#rating_price").innerHTML = tr("points_amount", value);
else
document.querySelector("#rating_price").innerHTML = value + " " + tr("points_amount_other").replace("$1 ", "");
};
}

View file

@ -231,7 +231,7 @@ return (function() {
if(is_dir($gitDir = OPENVK_ROOT . "/.git") && $showCommitHash)
$ver = trim(`git --git-dir="$gitDir" log --pretty="%h" -n1 HEAD` ?? "Unknown version") . "-nightly";
else
$ver = "Build 15";
$ver = "Public Technical Preview 3";
// Unix time constants
define('MINUTE', 60);

View file

@ -13,7 +13,8 @@
"lfkeitel/phptotp": "dev-master",
"chillerlan/php-qrcode": "dev-main",
"vearutop/php-obscene-censor-rus": "dev-master",
"erusev/parsedown": "dev-master"
"erusev/parsedown": "dev-master",
"bhaktaraz/php-rss-generator": "dev-master"
},
"minimum-stability": "dev"
}

92
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3816f5fbf2c78a3e252637476cd2ae08",
"content-hash": "2c94032cae911ca438bbcfc46c346961",
"packages": [
{
"name": "al/emoji-detector",
@ -52,6 +52,55 @@
},
"time": "2020-06-26T09:10:17+00:00"
},
{
"name": "bhaktaraz/php-rss-generator",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/bhaktaraz/php-rss-generator.git",
"reference": "53cf11db18d87e65973e6df453fb8c1382e5a3bd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bhaktaraz/php-rss-generator/zipball/53cf11db18d87e65973e6df453fb8c1382e5a3bd",
"reference": "53cf11db18d87e65973e6df453fb8c1382e5a3bd",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
"Bhaktaraz\\RSSGenerator\\": "Source/Bhaktaraz/RSSGenerator/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bhaktaraz Bhatta",
"email": "bhattabhakta@gmail.com"
}
],
"description": "Simple RSS generator library for PHP 5.5 or later.",
"homepage": "https://github.com/bhaktaraz/php-rss-generator",
"keywords": [
"Facebook product feed generator",
"feed",
"generator",
"rss",
"writer"
],
"support": {
"issues": "https://github.com/bhaktaraz/php-rss-generator/issues",
"source": "https://github.com/bhaktaraz/php-rss-generator/tree/master"
},
"time": "2021-03-15T10:59:47+00:00"
},
{
"name": "chillerlan/php-qrcode",
"version": "dev-main",
@ -520,12 +569,12 @@
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
},
"files": [
"src/functions_include.php"
]
],
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -600,12 +649,12 @@
"source": {
"type": "git",
"url": "https://github.com/JamesHeinrich/getID3.git",
"reference": "46346ff3bea96a63f1a1d58ee4eabc79471d0ec8"
"reference": "2279f7caca2d761dfc580dd02b401e7a1ff69dfe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/46346ff3bea96a63f1a1d58ee4eabc79471d0ec8",
"reference": "46346ff3bea96a63f1a1d58ee4eabc79471d0ec8",
"url": "https://api.github.com/repos/JamesHeinrich/getID3/zipball/2279f7caca2d761dfc580dd02b401e7a1ff69dfe",
"reference": "2279f7caca2d761dfc580dd02b401e7a1ff69dfe",
"shasum": ""
},
"require": {
@ -660,7 +709,7 @@
"issues": "https://github.com/JamesHeinrich/getID3/issues",
"source": "https://github.com/JamesHeinrich/getID3/tree/master"
},
"time": "2022-01-03T16:59:52+00:00"
"time": "2022-02-03T17:07:51+00:00"
},
{
"name": "komeiji-satori/curl",
@ -1069,12 +1118,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Idn\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Idn\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -1154,12 +1203,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"classmap": [
"Resources/stubs"
]
@ -1235,12 +1284,12 @@
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php72\\": ""
},
"files": [
"bootstrap.php"
]
],
"psr-4": {
"Symfony\\Polyfill\\Php72\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -1512,7 +1561,8 @@
"lfkeitel/phptotp": 20,
"chillerlan/php-qrcode": 20,
"vearutop/php-obscene-censor-rus": 20,
"erusev/parsedown": 20
"erusev/parsedown": 20,
"bhaktaraz/php-rss-generator": 20
},
"prefer-stable": false,
"prefer-lowest": false,

View file

@ -0,0 +1,9 @@
ALTER TABLE `profiles` ADD `activated` tinyint(3) NULL DEFAULT '1' AFTER `2fa_secret`;
CREATE TABLE IF NOT EXISTS `email_verifications` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`profile` bigint(20) unsigned NOT NULL,
`key` char(64) COLLATE utf8mb4_general_nopad_ci NOT NULL,
`timestamp` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_nopad_ci;

View file

@ -37,6 +37,13 @@
"password_reset_error" = "Անկանխատեսելի սխալ գաղտնաբառի զրոյացման ժամանակ։";
"password_reset_rate_limit_error" = "Հազար ներողություն, բայց այդպես շատ հաճախ չի կարելի անել։";
"email_sent" = "Նամակը հաջողությամբ ուղարկվել է։";
"email_sent_desc" = "Եթե ձեր էլ․ հասցեն գոյություն ունի, Դուք կստանաք նամակը։";
"email_error" = "Անկանխատեսելի սխալ նամակ ուղարկելու ժամանակ։";
"email_rate_limit_error" = "Կներեք, չափից շատ նամակ ուղարկել չի կարելի։";
"email_verify_success" = "Ձեր E-Mail-ը հաստատված է։ Հաճելի՛ ժամանց։";
"registration_disabled_info" = "Գրանցումները անջատված են համակարգային ադմինիստրատորի կողմից։ Հնարավորության դեպքում, խնդրեք հրավերք արդեն գրանցված օգտատերերից։";
"registration_closed" = "Գրանցումը փակ է։";
"invites_you_to" = "<strong>$1</strong> ձեզ հրավիրում է $2";
@ -137,6 +144,8 @@
/* Wall */
"feed" = "Լուրեր";
"post_writes_m" = "գրել է";
"post_writes_f" = "գրել է";
"post_writes_g" = "հրապարակել են";
@ -147,6 +156,9 @@
"delete" = "Ջնջել";
"comments" = "Մեկնաբանություններ";
"share" = "Կիսվել";
"pin" = "Ամրացնել";
"unpin" = "Հետ ամրացնել";
"pinned" = "ամրացված";
"comments_tip" = "Եղե՛ք առաջինը ով կթողնի իր կարծիքը։";
"your_comment" = "Ձեր մեկնաբանությունը";
"comments" = "Մեկնաբանություններ";
@ -157,7 +169,6 @@
"wall_few" = "$1 գրություն";
"wall_many" = "$1 գրություն";
"wall_other" = "$1 գրություն";
"publish_post" = "Ավելացնել գրություն";
"view_other_comments" = "Կարդալ մեկնաբանությունները";
@ -172,7 +183,10 @@
"contains_nsfw" = "Պարունակում է NSFW մատերիալ";
"nsfw_warning" = "Այս պոստը կարող է պարունակել 18+ մատերիալ։";
"report" = "Բողոքարկել";
"attach" = "Ամրացնել";
"attach_photo" = "Ամրացնել նկար";
"attach_video" = "Ամրացնել վիդեո";
"draw_graffiti" = "Նկարել գրաֆիտի";
"no_posts_abstract" = "Այստեղ ոչ ոք, ոչինչ․․․ դեռ չի գրել․․․";
"attach_no_longer_available" = "Այս հավելվածը այլևս հասանելի չէ";
"open_post" = "Բացել գրությունը";
@ -220,6 +234,7 @@
"subscriptions" = "Բաժանորդագրություններ";
"join_community" = "Մտնել խումբ";
"leave_community" = "Լքել խումբը";
"check_community" = "Դիտել խումբը";
"min_6_community" = "Անվանումը չպետք է լինի 6 նշից պակաս";
"participants" = "Անդամներ";
"groups" = "Խմբեր";
@ -237,6 +252,7 @@
"only_administrators" = "Միայն ադմինիստրատորները";
"website" = "Վեբկայք";
"managed" = "Կառավարվում է";
"size" = "Չափ";
"administrators_one" = "$1 ադմինիստրատոր";
"administrators_few" = "$1 ադմինիստրատոր";
@ -245,12 +261,14 @@
"role" = "Դեր";
"administrator" = "Ադմինիստրատոր";
"promote_to_admin" = "Բարձրացնել դեպի ադմինիստրատոր";
"promote_to_owner" = "Դարձնել տեր";
"devote" = "Հետ բողոքարկել";
"group_allow_post_for_everyone" = "Թույլ տար գրություններ թողնել բոլորին";
"set_comment" = "Փոփոխել մեկնաբանությունը";
"hidden_yes" = "Թաքցված է";
"hidden_no" = "Թաքցված չէ";
"group_allow_post_for_everyone" = "Թույլատրել հրապարակել բոլորին";
"group_hide_from_global_feed" = "Չցույց տալ հրապարակությունները ընդհանուր լրահոսում։";
"statistics" = "Ստատիստիկա";
"group_administrators_list" = "Ադմինների ցուցակ";
"group_display_only_creator" = "Ցույց տալ միայն խմբի ստեղծողին";
@ -273,12 +291,22 @@
"groups_many" = "$1 խումբ";
"groups_other" = "$1 խումբ";
"groups_list_zero" = "Դուք չեք կայանում ոչ մի խմբում";
"groups_list_one" = "Դուք կաք միայն մեկ խմբում";
"groups_list_other" = "Դուք կաք $1 խմբում";
"meetings_zero" = "Ոչ մի հանդիպում";
"meetings_one" = "Մեկ հանդիպում";
"meetings_few" = "$1 հանդիպում";
"meetings_many" = "$1 հանդիպում";
"meetings_other" = "$1 հանդիպում";
"open_new_group" = "Նոր խումբ բացել";
"open_group_desc" = "Չե՞ք կարող խումբ գտնել, բացեք ձերը․․․";
"search_group" = "Խմբի որոնում";
"search_by_groups" = "Որոնում ըստ խմբերի";
"search_group_desc" = "Այստեղ դուք կարող եք փնտրել խմբեր և ընտրել ձեզ ամենահարմարը․․․";
/* Albums */
"create" = "Ստեղծել";
@ -309,8 +337,8 @@
"text_note" = "Պարունակություն";
"create_note" = "Ստեղծել նշում";
"actions" = "Գործողություններ";
"feed" = "Լուրեր";
"publish_post" = "Ավելացնել գրություն";
"edit_note" = "Խմբագրել նշումը";
"edited" = "Խմբագրված է";
"notes_zero" = "Ոչ մի նշում չկա";
"notes_one" = "Մեկ նշում";
@ -337,9 +365,11 @@
"menu_registration" = "Գրանցում";
"menu_help" = "Օգնություն";
"header_home" = "Գլխավոր";
"menu_logout" = "Դուրս գալ";
"menu_support" = "Օգնություն";
"header_home" = "գլխավոր";
"header_groups" = "խմբեր";
"header_donate" = "օգնել";
"header_people" = "մարդիկ";
"header_invite" = "հրավիրել";
"header_help" = "օգնություն";
@ -351,18 +381,14 @@
"left_menu_donate" = "Աջակցել";
"footer_about_instance" = "հոսքի մասին";
"footer_blog" = "բլոգ";
"footer_help" = "օգնություն";
"footer_developers" = "մշակողներին";
"footer_choose_language" = "ընտրել լեզուն";
"footer_privacy" = "գաղտնիություն";
"notes_zero" = "Ոչ մի նշում չկա";
"notes_one" = "Մեկ նշում";
"notes_few" = "$1 նշում";
"notes_many" = "$1 նշում";
"notes_other" = "$1 նշում";
/* Settings */
"main" = "Հիմնական";
@ -387,9 +413,12 @@
"cut" = "Կտրվածք";
"round_avatars" = "Կլոր ավատար";
"apply_style_for_this_device" = "Հաստատել տեսքը միայն այս սարքի համար";
"search_for_groups" = "Խմբերի որոնում";
"search_for_people" = "Մարդկանց որոնում";
"search_button" = "Որոնել";
"search_placeholder" = "Գրեք ցանկացած անուն, անվանում կամ բառ";
"results_zero" = "Ոչ մի արդյունք";
"results_one" = "Մեկ արդյունք";
"results_few" = "$1 արդյունք";
@ -405,6 +434,7 @@
"privacy_setting_see_friends" = "Ում են երևում իմ ընկերները";
"privacy_setting_add_to_friends" = "Ով կարող է ինձ ընկեր կոչել";
"privacy_setting_write_wall" = "Ով կարող է գրել իմ պատին";
"privacy_setting_write_messages" = "Ով կարող է ինձ նամակներ գրել";
"privacy_value_anybody" = "Բոլոր ցանկացողները";
"privacy_value_anybody_dative" = "Բոլոր ցանկացողներին";
"privacy_value_users" = "OpenVKի օգտատերերին";
@ -429,6 +459,11 @@
"ui_settings_rating_show" = "Ցուցադրել";
"ui_settings_rating_hide" = "Թաքցնել";
"additional_links" = "Հավելյալ հղումներ";
"ad_poster" = "Գովազդային վահանակ";
/* Two-factor authentication */
"two_factor_authentication" = "Երկքայլ աուտենտիֆիկացիա";
"two_factor_authentication_disabled" = "Ապահովում է հուսալի պաշտպանում ջարդումից․ անհրաժեշտ է ներմուծել 2FA-ով ստացված կոդը։";
"two_factor_authentication_enabled" = "Երկքայլ աուտենտիֆիկացիան միացված է։ Ձեր էջը պաշտպանված է։";
@ -513,6 +548,7 @@
"nt_post_instrumental" = "պոստով";
"nt_note_instrumental" = "նշումով";
"nt_photo_instrumental" = "նկարով";
"nt_topic_instrumental" = "թեմայով";
/* Time */
@ -523,6 +559,10 @@
"time_today" = "այսօր";
"time_yesterday" = "երեկ";
"points" = "Ձայն";
"points_count" = "ձայն";
"on_your_account" = "ձեր հաշվում";
"your_email_address" = "Ձեր էլեկտրոնային հասցեն";
"your_page_address" = "Ձեր էջի հասցեն";
"page_address" = "Էջի հասցեն";
@ -593,6 +633,11 @@
"gifts_few" = "$1 նվեր";
"gifts_many" = "$1 նվեր";
"gifts_other" = "$1 նվեր";
"gifts_left" = "Մնաց $1 նվեր";
"gifts_left_one" = "Մնաց մեկ նվեր";
"gifts_left_few" = "$1 նվեր մնաց";
"gifts_left_many" = "$1 նվեր մնաց";
"gifts_left_other" = "$1 նվեր մնաց";
"send_gift" = "Ուղարկել նվեր";
@ -612,9 +657,6 @@
"users_gifts" = "Նվերներ";
"comment" = "Մեկնաբանություն";
"sender" = "Ուղարկող";
/* Support */
"support_opened" = "Բաց";
@ -638,11 +680,29 @@
"support_new_title" = "Գրե՛ք ձեր դիմումի վերնագիրը";
"support_new_content" = "Նկարագրե՛ք խնդիրը կամ առաջարկը";
"support_rate_good_answer" = "Սա լավ պատասխան է";
"support_rate_bad_answer" = "Սա վատ պատասխան է";
"support_good_answer_user" = "Դուք թողել եք դրական ակնարկ։";
"support_bad_answer_user" = "Դուք թողել եք բացասական ակնարկ։";
"support_good_answer_agent" = "Օգտատերը թողել է դրական ակնարկ";
"support_bad_answer_agent" = "Օգտատերը թողել է բացասական ակնարկ";
"support_rated_good" = "Դուք թողել եք դրական ակնարկ պատասխանի մասին։";
"support_rated_bad" = "Դուք թողել եք բացասական ակնարկ պատասխանի մասին։";
"wrong_parameters" = "Հարցման սխալ կարգավորումներ";
"fast_answers" = "Արագ պատասխաններ";
"comment" = "Մեկնաբանություն";
"sender" = "Ուղարկող";
"author" = "Հեղինակ";
"you_have_not_entered_text" = "Դուք չեք գրել տեքստ";
"you_have_not_entered_name_or_text" = "Դուք անուն կամ տեքստ չեք գրել";
"ticket_changed" = "Տոմսը փոփոխված է";
"ticket_changed_comment" = "Փոփոխությունները ուժի մեջ կմտնեն մի քանի վայրկյանից։";
/* Invite */
"invite" = "Հրավիրել";
@ -658,6 +718,14 @@
"banned_2" = "Պատճառը հետևյալն է․ <b>$1</b>. Ափսոս, բայց մենք ստիպված Ձեզ հավերժ ենք կասեցրել;";
"banned_3" = "Դուք դեռ կարող եք <a href=\"/support?act=new\">գրել նամակ աջակցության ծառայությանը</a>, եթե համարում եք որ դա սխալմունք է, կամ էլ կարող եք <a href=\"/logout?hash=$1\">դուրս գալ</a>։";
/* Registration confirm */
"ec_header" = "Գրանցման հաստատում";
"ec_title" = "Շնորհակալությո՛ւն";
"ec_1" = "<b>$1</b>, Ձեր գրանցումը համարյա ավարտվել է։ Մի քանի վայրկյանի ընթացքում ձեր էլ․ փոստին պետք է գա հաստատման հղում։";
"ec_2" = "Եթե ինչ-որ պատճառներով Ձեզ չի հասել նամակը, ստուգեք Սպամի պանակը։ Եթե այնտեղ էլ նամակը չգտնեք, դուք կարող եք նորից ուղարկել նամակը։";
"ec_resend" = "Վերաուղարկել նամակը";
/* Discussions */
"discussions" = "Քննարկումներ";
@ -785,6 +853,36 @@
"about_openvk" = "OpenVK-ի մասին";
"about_this_instance" = "Այս հոսքի մասին";
"rules" = "Կանոններ";
"most_popular_groups" = "Ամենահայտնի խմբերը";
"on_this_instance_are" = "Այս հոսքում․";
"about_users_one" = "<b>Մեկ</b> օգտատեր";
"about_users_few" = "<b>$1</b> օգտատեր";
"about_users_many" = "<b>$1</b> օգտատեր";
"about_users_other" = "<b>$1</b> օգտատեր";
"about_online_users_one" = "<b>Մեկ</b> օգտատեր է ցանցի մեջ";
"about_online_users_few" = "<b>$1</b> օգտատեր է ցանցի մեջ";
"about_online_users_many" = "<b>$1</b> օգտատեր է ցանցի մեջ";
"about_online_users_other" = "<b>$1</b> օգտատեր է ցանցի մեջ";
"about_active_users_one" = "<b>Մեկ</b> ակտիվ օգտատեր";
"about_active_users_few" = "<b>$1</b> ակտիվ օգտատեր";
"about_active_users_many" = "<b>$1</b> ակտիվ օգտատեր";
"about_active_users_other" = "<b>$1</b> ակտիվ օգտատեր";
"about_groups_one" = "<b>Մեկ</b> խումբ";
"about_groups_few" = "<b>$1</b> խումբ";
"about_groups_many" = "<b>$1</b> խումբ";
"about_groups_other" = "<b>$1</b> խումբ";
"about_wall_posts_one" = "<b>Մեկ</b> գրություն պատերի վրա";
"about_wall_posts_few" = "<b>$1</b> գրություն պատերի վրա";
"about_wall_posts_many" = "<b>$1</b> գրություն պատերի վրա";
"about_wall_posts_other" = "<b>$1</b> գրություն պատերի վրա";
/* Dialogs */
"ok" = "ОК";

View file

@ -35,6 +35,13 @@
"password_reset_error" = "An unexpected error occurred while resetting the password.";
"password_reset_rate_limit_error" = "You can't do it that often, sorry.";
"email_sent" = "Mail has been successfully sent.";
"email_sent_desc" = "If your email addess exists, you will receive instructions.";
"email_error" = "An unexpected error occurred while sending the email.";
"email_rate_limit_error" = "You can't do it that often, sorry.";
"email_verify_success" = "Your Email has been verified. Have a great time!";
"registration_disabled_info" = "Registration has been disabled by the system administrator. If possible, ask for an invitation from your friend, if he is registered on this site.";
"registration_closed" = "Registration is closed.";
"invites_you_to" = "<strong>$1</strong> invites you to $2";
@ -216,6 +223,7 @@
"subscriptions" = "Subscriptions";
"join_community" = "Join community";
"leave_community" = "Leave community";
"check_community" = "View community";
"min_6_community" = "Name of the group must have more that 6 characters";
"participants" = "Participants";
"groups" = "Groups";
@ -233,6 +241,7 @@
"only_administrators" = "Only administrators";
"website" = "Website";
"managed" = "Managed";
"size" = "Size";
"administrators_one" = "$1 administrator";
"administrators_other" = "$1 administrators";
@ -265,6 +274,10 @@
"groups_one" = "$1 group";
"groups_other" = "$1 groups";
"groups_list_zero" = "You are not a participant in any group";
"groups_list_one" = "You are participating in one group";
"groups_list_other" = "You are a participant of $1 groups";
"meetings_zero" = "No meetings";
"meetings_one" = "$1 meeting";
"meetings_other" = "$1 meetings";
@ -301,7 +314,7 @@
"note" = "Note";
"name_note" = "Title";
"text_note" = "Content";
"create_note" = "Create note";
"create_note" = "Add note";
"edit_note" = "Edit note";
"actions" = "Actions";
@ -312,6 +325,10 @@
"notes_other" = "$1 notes";
"notes_start_screen" = "With notes, you can share your events with friends and see what's going on with them.";
"notes_list_zero" = "No notes found";
"notes_list_one" = "$1 note found";
"notes_list_other" = "$1 notes found";
/* Menus */
/* Note that is string need to fit into the "My Page" link */
@ -376,9 +393,11 @@
"avatars_style" = "Avatar style";
"style" = "Style";
"default" = "Default";
"cut" = "Cut";
"round_avatars" = "Round avatars";
"default" = "default";
"arbitrary_avatars" = "Arbitrary";
"cut" = "Square";
"round_avatars" = "Round";
"apply_style_for_this_device" = "Apply style only for this device";
@ -566,6 +585,21 @@
"receiver_not_found" = "The receiver was not found.";
"you_dont_have_enough_points" = "You don't have enough votes.";
"increase_rating" = "Increase rating";
"increase_rating_button" = "Increase";
"to_whom" = "To whom";
"increase_by" = "Increase by";
"price" = "Price";
"you_have_unused_votes" = "You have $1 unused votes on your balance.";
"apply_voucher" = "Apply voucher";
"failed_to_increase_rating" = "Failed to increase rating";
"rating_increase_successful" = "You have successfully increased rating of <b><a href=\"$1\">$2</a></b> by <b>$3%</b>.";
"negative_rating_value" = "We cannot steal rating from another person, sorry.";
"increased_your_rating_by" = "increased your rating by";
/* Gifts */
"gift" = "Gift";
@ -655,6 +689,14 @@
"banned_2" = "And the reason for this is simple: <b>$1</b>. Unfortunately, this time we had to block you forever.";
"banned_3" = "You can still <a href=\"/support?act=new\">write to the support</a> if you think there was an error or <a href=\"/logout?hash=$1\">logout</a>.";
/* Registration confirm */
"ec_header" = "Registration confirmation";
"ec_title" = "Thanks!";
"ec_1" = "<b>$1</b>, your registration is almost done. In a few minutes you should receive an mail with a link to confirm your email address.";
"ec_2" = "If for some reason you don't get the mail, check your spam folder. If you don't find the email there, you can resend it.";
"ec_resend" = "Resend mail";
/* Discussions */
"discussions" = "Discussions";
@ -730,6 +772,8 @@
"invalid_email_address" = "Invalid Email address";
"invalid_email_address_comment" = "The Email you entered is not correct.";
"invalid_real_name" = "Please, enter your real name. It'll be easier for your friends to find you like this.";
"invalid_birth_date" = "Invalid date of birth";
"invalid_birth_date_comment" = "The date of birth you entered is not correct.";
@ -758,6 +802,14 @@
"captcha_error" = "Incorrect characters entered";
"captcha_error_comment" = "Please make sure you fill in the captcha field correctly.";
"failed_to_publish_post" = "Failed to publish post";
"failed_to_delete_post" = "Failed to delete post";
"media_file_corrupted" = "The media content file is corrupted.";
"media_file_corrupted_or_too_large" = "The media content file is corrupted or too large.";
"post_is_empty_or_too_big" = "The post is empty or too big.";
"post_is_too_big" = "The post is too big.";
/* Admin actions */
"login_as" = "Login as $1";

View file

@ -9,6 +9,8 @@
"home" = "Басты бет";
"welcome" = "Қош келдіңіз!";
"to_top" = "Жоғарыға";
/* Login */
"log_in" = "Логин";
@ -36,6 +38,13 @@
"password_reset_error" = "Құпиясөзді қалпына келтіру кезінде күтпеген қате орын алды.";
"password_reset_rate_limit_error" = "Кешіріңіз, бірақ осы әрекетті сонай жиі жасай алмайсыз.";
"email_sent" = "Хат сәтті жіберілді.";
"email_sent_desc" = "Сіз енгізген электрондық пошта бар болса оған нұсқаулар келеді.";
"email_error" = "Электрондық хатты жіберу кезінде күтпеген қате орын алды.";
"email_rate_limit_error" = "Бұл әрекетті жиі жасай алмайсыз.";
"email_verify_success" = "Электрондық поштаңыз расталды. Осы әлеуметтік желіде жақсы уақыт өткізуді тілейміз!";
"registration_disabled_info" = "Жүйе әкімшісі тіркеуді өшірді. Мүмкіндігінше, егер досыңыз сайтта тіркелген болса одан шақыру сілтемесін сұраңыз.";
"registration_closed" = "Тіркелу жабық";
"invites_you_to" = "<strong>$1</strong> сізді $2 желіне шақырады.";
@ -189,7 +198,8 @@
"follower" = "Жазылман";
"login_as" = "Кім ретінде кіру:";
"friends_delete" = "Достарымнан жою";
"friends_reject" = "Өтінімді қабылдамау";
"friends_add" = "Достарыма қосу";
"friends_reject" = "Өтінімнен бас тарту";
"friends_accept" = "Өтінімді қабылдау";
"send_message" = "Хабарлама жазу";
"incoming_req" = "Жазылушылар";
@ -216,6 +226,7 @@
"subscriptions" = "Жазылымдар";
"join_community" = "Топқа кіру";
"leave_community" = "Топтан шығу";
"check_community" = "Топты қарау";
"min_6_community" = "Топтың аты тым болмаса 6 әріптен көп болу керек";
"participants" = "Қатысушылар";
"groups" = "Топтар";
@ -232,6 +243,8 @@
"all_followers" = "Барлық жазылмандар";
"only_administrators" = "Тек әкімшілер";
"website" = "Сайт";
"managed" = "Басқаратын";
"size" = "Көлемі";
"administrators_one" = "$1 әкімші";
"administrators_other" = "$1 әкімші";
@ -245,6 +258,7 @@
"hidden_yes" = "Жасырылған: Ия";
"hidden_no" = "Жасырылған: Жоқ";
"group_allow_post_for_everyone" = "Бәріне жазба жазуға рұқсат ету";
"group_hide_from_global_feed" = "Жаһандық арнада жазбаларды көрсетпеу";
"statistics" = "Статистика";
"group_administrators_list" = "Әкімшілер тізімі";
"group_display_only_creator" = "Тек топты құрған кісіні көрсету";
@ -263,10 +277,20 @@
"groups_one" = "$1 топ";
"groups_other" = "$1 топ";
"groups_list_zero" = "Сіз ешқандай топқа қатыспайсыз.";
"groups_list_one" = "Сіз бір топқа қатысасыз";
"groups_list_other" = "Сіз $1 топтардың қатысушысысыз";
"meetings_zero" = "Кездесулер жоқ";
"meetings_one" = "$1 кездесу";
"meetings_other" = "$1 кездесу";
"open_new_group" = "Жаңа топ ашу";
"open_group_desc" = "Өзіңізге сай топ таба алмай жүрсіз бе? Өзіңіз ашып алыңыз!";
"search_group" = "Топ іздеу";
"search_by_groups" = "Топтардан іздеу";
"search_group_desc" = "Мұнда сіз бар топтарға шолу жасай аласыз және қажеттіліктеріңізге сай топты таңдай аласыз...";
/* Albums */
"create" = "Жасау";
@ -294,13 +318,21 @@
"name_note" = "Тақырыбы";
"text_note" = "Мазмұны";
"create_note" = "Жолжазба жазу";
"edit_note" = "Жолжазбаны өндеу";
"actions" = "Әрекеттер";
"feed" = "Жаңалықтар";
"publish_post" = "Жазба қосу";
"edited" = "Өнделген";
"notes_zero" = "Жолжазбалар жоқ";
"notes_one" = "$1 жолжазба";
"notes_other" = "$1 жолжазба";
"notes_start_screen" = "Жолжазбалар арқылы оқиғаларды достарыңызбен бөлісуге және олармен не болып жатқанын көруге болады.";
"notes_list_zero" = "Еш жолжазба табылған жоқ";
"notes_list_one" = "$1 жолжазба табылды";
"notes_list_other" = "$1 жолжазба табылды";
/* Menus */
@ -337,6 +369,7 @@
"left_menu_donate" = "Қайырымдылық жасау";
"footer_about_instance" = "инстанция туралы";
"footer_blog" = "блог";
"footer_help" = "көмек";
"footer_developers" = "әзірлеушілерге";
@ -360,12 +393,16 @@
"new_password" = "Жаңа құпиясөз";
"repeat_password" = "Қайталап жазыңызшы";
"avatars_style" = "Аватар стилі";
"avatars_style" = "Аватарлардың стилі";
"style" = "Стилі";
"default" = "Әдепкі";
"cut" = "Кесілген";
"round_avatars" = "Дөңгелектелген";
"default" = "әдепкі";
"arbitrary_avatars" = "Ерікті"
"cut" = "Шаршы";
"round_avatars" = "Дөңгелек";
"apply_style_for_this_device" = "Стильді тек осы девайс үшін қосу";
"search_for_groups" = "Топтар іздеу";
"search_for_people" = "Кісілер іздеу";
@ -391,6 +428,7 @@
"ui_settings_rating_hide" = "Жасыру";
"additional_links" = "Қосымша сілтемелер";
"ad_poster" = "Жарнама постері";
"two_factor_authentication" = "Екі факторлы аутентификация";
"two_factor_authentication_disabled" = "Бұзылудан сенімді қорғаныс қызметін қамтамасыз етеді: парақшаға кіру үшін 2FA қосымшасында алынған кодты енгізу керек.";
@ -543,11 +581,26 @@
"points_transfer_successful" = "Сіз сәтті <b>$1</b> <b><a href=\"$2\">$3</a></b> аталатын пайдаланушыға аудардыңыз.";
"not_all_information_has_been_entered" = "Барлық мәлімет терілген жоқ";
"negative_transfer_value" = "Өкінішке орай біз басқа кісіден дауыс ұрлай алмаймыз.";
"negative_transfer_value" = "Кешіріңіз, бірақ біз басқа адамнан дауыс ұрлай алмаймыз.";
"message_is_too_long" = "Хабар тым ұзын.";
"receiver_not_found" = "Қабылдаушы табылған жоқ.";
"you_dont_have_enough_points" = "Сізде жеткілікті дауыс саны жоқ.";
"increase_rating" = "Рейтингті көтеру";
"increase_rating_button" = "Көтеру";
"to_whom" = "Кімге";
"increase_by" = "Неше есе көтеру";
"price" = "Бағасы";
"you_have_unused_votes" = "Сіздің шотыңызда $1 қолданылмаған дауыс бар.";
"apply_voucher" = "Ваучерді қолдану";
"failed_to_increase_rating" = "Рейтингті көтеру мүмкін болмады";
"rating_increase_successful" = "Сіз <b><a href=\"$1\">$2</a></b> атты пайдаланушының рейтингің сәтті <b>$3%</b> есе көтердіңіз.";
"negative_rating_value" = "Кешіріңіз, бірақ біз басқа адамның рейтингің ұрлай алмаймыз.";
"increased_your_rating_by" = "сіздің рейтингіңізді көтерді";
/* Gifts */
"gift" = "Сыйлық";
@ -637,6 +690,14 @@
"banned_2" = "Мұның себебі қарапайым: <b>$1</b>. Өкінішке орай, осы жолы сізді мәңгілікке блоктауға мәжбүр болдық.";
"banned_3" = "Егер сізді қателесіп блоктады деп ойласаңыз, <a href=\"/support?act=new\">қолдау қызметіне жаза аласыз</a> немесе <a href=\"/logout?hash=$1\">жүйеден шыға аласыз</a>.";
/* Registration confirm */
"ec_header" = "Тіркеуді растау";
"ec_title" = "Рахмет!";
"ec_1" = "<b>$1</b>, тіркелуіңіз аяқталуға жақын. Бірнеше минуттан кейін электрондық поштаңызға растау сілтемесі бар хат келуі керек.";
"ec_2" = "Егер қандай да бір себептермен хат келмесе, спам қалтасын тексеріңіз. Хатты ол жерден таппасаңыз, оны қайта жіберуге болады.";
"ec_resend" = "Қайта жіберу";
/* Discussions */
"discussions" = "Талқылаулар";
@ -718,7 +779,7 @@
"invalid_telegram_name" = "Telegram парақшаның аты қате";
"invalid_telegram_name_comment" = "Сіз енгізген Telegram-дағы парақшаның аты дұрыс емес.";
"token_manipulation_error" = "Токенді өңдеу қатесі";
"token_manipulation_error" = "Токенді өндеу қатесі";
"token_manipulation_error_comment" = "Токен жарамсыз немесе мерзімі өтіп кеткен";
"profile_changed" = "Парақша өзгерілді";
@ -740,6 +801,14 @@
"captcha_error" = "Қате таңбалар енгізілді";
"captcha_error_comment" = "Капча өрісін дұрыс толтырғаныңызға көз жеткізіңіз.";
"failed_to_publish_post" = "Постты жариялау мүмкін болмады";
"failed_to_delete_post" = "Постты жоюға мүмкін болмады";
"media_file_corrupted" = "Медиа мазмұн файлы бүлінген.";
"media_file_corrupted_or_too_large" = "Медиа мазмұн файлы бүлінген немесе тым үлкен.";
"post_is_empty_or_too_big" = "Жазбаңызда түк те жоқ немесе ол тым үлкен.";
"post_is_too_big" = "Жазбаңыз тым үлкен.";
/* Admin actions */
"login_as" = "$1 ретінде кіру";
@ -758,6 +827,26 @@
"about_openvk" = "OpenVK туралы";
"about_this_instance" = "Инстанция туралы";
"rules" = "Ережелер";
"most_popular_groups" = "Ең танымал топтар";
"on_this_instance_are" = "Осы инстанцияда:";
"about_users_one" = "<b>1</b> қолданушы";
"about_users_other" = "<b>$1</b> қолданушы";
"about_online_users_one" = "<b>1</b> желідегі пайдаланушы";
"about_online_users_other" = "<b>$1</b> желідегі пайдаланушы";
"about_active_users_one" = "<b>1</b> белсенді пайдаланушы";
"about_active_users_other" = "<b>$1</b> белсенді пайдаланушы";
"about_groups_one" = "<b>1</b> топ";
"about_groups_other" = "<b>$1</b> топ";
"about_wall_posts_one" = "<b>1</b> жазба";
"about_wall_posts_other" = "<b>$1</b> жазба";
/* Dialogs */
"ok" = "ОК";

View file

@ -36,6 +36,13 @@
"password_reset_error" = "Непредвиденная ошибка при сбросе пароля.";
"password_reset_rate_limit_error" = "Нельзя делать это так часто, извините.";
"email_sent" = "Письмо было успешно отправлено.";
"email_sent_desc" = "Если ваш электронный адрес существует, вы получите письмо.";
"email_error" = "Непредвиденная ошибка при отправке письма.";
"email_rate_limit_error" = "Нельзя делать это так часто, извините.";
"email_verify_success" = "Ваш Email был подтверждён. Приятного времяпрепровождения!";
"registration_disabled_info" = "Регистрация отключена системным администратором. При возможности попросите приглашение у вашего знакомого, если он зарегистрирован на этом сайте.";
"registration_closed" = "Регистрация закрыта.";
"invites_you_to" = "<strong>$1</strong> приглашает вас в $2";
@ -255,6 +262,7 @@
"only_administrators" = "Только администраторы";
"website" = "Сайт";
"managed" = "Управляемые";
"size" = "Размер";
"administrators_one" = "$1 администратор";
"administrators_few" = "$1 администратора";
@ -420,12 +428,14 @@
"new_password" = "Новый пароль";
"repeat_password" = "Повторите пароль";
"avatars_style" = "Отображение аватар";
"avatars_style" = "Отображение аватаров";
"style" = "Стиль";
"default" = "По умолчанию";
"cut" = "Обрезка";
"round_avatars" = "Круглый аватар";
"default" = "по умолчанию";
"arbitrary_avatars" = "Произвольные";
"cut" = "Квадратные";
"round_avatars" = "Круглые";
"apply_style_for_this_device" = "Применить стиль только для этого устройства";
@ -619,6 +629,21 @@
"receiver_not_found" = "Получатель не найден.";
"you_dont_have_enough_points" = "У вас недостаточно голосов.";
"increase_rating" = "Повысить рейтинг";
"increase_rating_button" = "Повысить";
"to_whom" = "Кому";
"increase_by" = "Повысить на";
"price" = "Стоимость";
"you_have_unused_votes" = "У Вас $1 неиспользованных голоса на балансе.";
"apply_voucher" = "Применить ваучер";
"failed_to_increase_rating" = "Не удалось повысить рейтинг";
"rating_increase_successful" = "Вы успешно повысыли рейтинг <b><a href=\"$1\">$2</a></b> на <b>$3%</b>.";
"negative_rating_value" = "Мы не можем украсть рейтинг у другого человека, извините.";
"increased_your_rating_by" = "повысил ваш рейтинг на";
/* Gifts */
"gift" = "Подарок";
@ -713,6 +738,14 @@
"banned_2" = "А причина этому проста: <b>$1</b>. К сожалению, на этот раз нам пришлось заблокировать вас навсегда.";
"banned_3" = "Вы всё ещё можете <a href=\"/support?act=new\">написать в службу поддержки</a>, если считаете что произошла ошибка или <a href=\"/logout?hash=$1\">выйти</a>.";
/* Registration confirm */
"ec_header" = "Подтверждение регистрации";
"ec_title" = "Спасибо!";
"ec_1" = "<b>$1</b>, ваша регистрация почти закончена. В течении нескольких минут на ваш адрес E-mail должно прийти письмо с ссылкой для подтверждения вашего адреса почты.";
"ec_2" = "Если по каким-то причинам вам не пришло письмо, то проверьте папку Спам. Если письма не окажется и там, то вы можете переотправить письмо.";
"ec_resend" = "Переотправить письмо";
/* Discussions */
"discussions" = "Обсуждения";
@ -794,6 +827,8 @@
"invalid_email_address" = "Неверный Email адрес";
"invalid_email_address_comment" = "Email, который вы ввели, не является корректным.";
"invalid_real_name" = "Пожалуйста, используйте реальные имена. Так вашим тульпам будет легче найти вас.";
"invalid_telegram_name" = "Неверное имя Telegram аккаунта";
"invalid_telegram_name_comment" = "Вы ввели неверное имя аккаунта Telegram.";
@ -822,6 +857,14 @@
"captcha_error" = "Неправильно введены символы";
"captcha_error_comment" = "Пожалуйста, убедитесь, что вы правильно заполнили поле с капчей.";
"failed_to_publish_post" = "Не удалось опубликовать пост";
"failed_to_delete_post" = "Не удалось удалить пост";
"media_file_corrupted" = "Файл медиаконтента повреждён.";
"media_file_corrupted_or_too_large" = "Файл медиаконтента повреждён или слишком велик.";
"post_is_empty_or_too_big" = "Пост пустой или слишком большой.";
"post_is_too_big" = "Пост слишком большой.";
/* Admin actions */
"login_as" = "Войти как $1";

View file

@ -364,9 +364,11 @@
"avatars_style" = "Отображеніе портрета";
"style" = "Стиль";
"default" = "По умолчанію";
"cut" = "Обрѣзка";
"round_avatars" = "Круглый портретъ";
"default" = "по умолчанію";
"arbitrary_avatars" = "Произвольные​";
"cut" = "Квадратные​";
"round_avatars" = "Круглые";
"search_for_groups" = "Поискъ обществъ";
"search_for_people" = "Поискъ людей";