Merge remote-tracking branch 'origin/master' into feature-reports

This commit is contained in:
Ilya Prokopenko 2022-05-23 17:01:40 +03:00
commit f3570a11fd
67 changed files with 1555 additions and 679 deletions

11
CODE_OF_CONFLICT.md Normal file
View file

@ -0,0 +1,11 @@
The OpenVK development effort is a very personal process compared to "traditional" ways of developing software.
Your code and ideas behind it will be carefully reviewed, often resulting in critique and criticism.
The review will almost always require improvements to the code before it can be included in the repo.
Know that this happens because everyone involved wants to see the best possible solution for the overall success of OpenVK.
If however, anyone feels personally abused, threatened, or otherwise uncomfortable due to this process, that is not acceptable.
If so, please contact the community manager @WerySkok and report the issue.
As a reviewer of code, please strive to keep things civil and focused on the technical issues involved. We are all humans,
and frustrations can be high on both sides of the process.
Try to keep in mind the immortal words of Bill and Ted, "Be excellent to each other."

277
CODE_STYLE.md Normal file
View file

@ -0,0 +1,277 @@
# Names
## Namespace Names
Namespaces should be written in PascalCase.
## File Names
Code directories should have their name written in PascalCase. Code files should contain only one class and have the name of that class.
In case of multiple class definitions in one file, it's name should be the same as the "primary" class name.
Non-code directories, non-class and non-code files should be named in lisp-case.
## Variable Names
Variable names should be written in camelCase. This also applies to function arguments, class instance names and methods.
## Constant Names
Constants are written in SCREAMING_SNAKE_CASE, but should be declared case-insensetive.
## Class Names
Classes in OpenVK should belong to `openvk\` namespace and be in the corresponding directory (according to PSR-4). Names of classes should be written in PascalCase.
## Function Names
camelCase and snake_case are allowed, but first one is the recommended way. This rule does not apply to class methods, which are written in camelCase only.
---
# Coding Rules
## File header
All OpenVK files must start with `<?php` open-tag followed by `declare(strict_types=1);` on the same line. The next line must contain namespace.
The lines after must contain use-declarations, each on it's own line (usage of {} operator is OK), if there is any. Then there must be an empty line. Example:
```php
<?php declare(strict_types=1);
namespace openvk;
use Chandler\Database\DatabaseConnection;
use Nette\Utils\{Image, FileSystem};
class ...
```
## NULL
Null should be written as constant, all-uppercase: `NULL`.
## Nullable (optional) pointer arguments
Optional pointer arguments should default to `nullptr`: `function something(&int? $myPointer = nullptr)`. `nullptr` must be written in lisp-case (lowercase).
## Comparing to NULL
In OpenVK `is_null` is the preferred way to check for equality to NULL. `??` must be used in assignments and where else possible.
In case if value can either be NULL or truthy, "boolean not" should be used to check if value is not null: `if(!$var)`.
## Arrays
Arrays must be defined with modern syntax: `[1, 2, 3]` (NOT `array(1, 2, 3)`).
Same applies to `list` construction: use `[$a, , $b] = $arr;` instead of `list($a, , $b) = $arr;`
## Casts
Typecasts must be done with modern syntax where possible: `(type) $var`. Int-to-string, string-to-int, etc conversions should also be dont with modern casting
syntax where possible, but should use `ctype_` functions where needed. `gettype`, `settype` should be used in dynamic programming only.
## Goto
```goto``` should be avoided.
## `continue n; `
It is preferable to use `continue n`, `break n` instead of guarding flags:
```php
# COOL AND GOOD
foreach($a as $b)
foreach($b as $c)
if($b == $c)
break 2;
# BRUH
foreach($a as $b) {
$shouldBreak = false;
foreach($b as $c)
if($b == $c)
$shouldBreak = true;
if($shouldBreak)
break;
}
```
## Comments
In OpenVK we use Perl-style `#` for single-line comments.
---
# Formatting
## Variables
It is preferable to declare only one variable per line in the code.
## Indentation
All things in OpenVK must be properly indented by a sequence of 4 spaces. Not tabs. \
When there are several variable declarations listed together, line up the variables:
```php
# OK
$photos = (new Photos)->where("meow", true);
$photo = $photos->fetch();
$arr = [
"a" => 10,
"bb" => true,
];
# NOT OK
$photos = (new Photos)->where("meow", true);
$photo = $photos->fetch();
$arr = [
"a" => 10,
"bb" => true,
];
```
## Tab/Space
+ **Do not use tabs**. Use spaces, as tabs are defined differently for different editors and printers.
+ Put one space after a comma and semicolons: `exp(1, 2)` `for($i = 1; $i < 100; $i++)`
+ Put one space around assignment operators: `$a = 1`
+ Always put a space around conditional operators: `$a = ($a > $b) ? $a : $b`
+ Do not put spaces between unary operators and their operands, primary operators and keywords:
```php
# OK
-$a;
$a++;
$b[1] = $a;
fun($b);
if($a) { ... }
# NOT OK
- $a;
$a ++;
$b [1] = $a;
fun ($b);
if ($a) { ... }
```
## Blank Lines
+ Use blank lines to create paragraphs in the code or comments to make the code more understandable
+ Use blank lines before `return` statement if it isn't the only statement in the block
+ Use blank lines after shorthand if/else/etc
```php
# OK
if($a)
return $x;
doSomething();
return "yay";
# NOT OK
if($a) return $x; # return must be on separate line
doSomething(); # doSomething must be separated by an extra blank line after short if/else
return "yay"; # do use blank lines before return statement
```
## Method/Function Arguments
+ When all arguments for a function do not fit on one line, try to line up the first argument in each line:
![image](https://user-images.githubusercontent.com/34442450/167248563-21fb01be-181d-48b9-ac0c-dc953c0a12cf.png)
+ If the argument lists are still too long to fit on the line, you may line up the arguments with the method name instead.
## Maximum characters per line
Lines should be no more than 80 characters long.
## Usage of curly braces
+ Curly braces should be on separate line for class, method, and function definitions.
+ In loops, if/else, try/catch, switch constructions the opening brace should be on the same line as the operator.
+ Braces must be ommited if the block contains only one statement **AND** the related blocks are also single statemented.
+ Nested single-statement+operator blocks must not be surrounded by braces.
```php
# OK
class A
{
function doSomethingFunny(): int
{
return 2;
}
}
if(true) {
doSomething();
doSomethingElse();
} else {
doSomethingFunny();
}
if($a)
return false;
else
doSomething();
foreach($b as $c => $d)
if($c == $d)
unset($b[$c]);
# NOT OK
class A {
function doSomethingFunny(): int {
return 2;
}
}
if(true) {
doSomething();
doSomethingElse();
} else
doSomethingFunny(); # why?
if($a) {
return false;
} else {
doSomething();
}
foreach($b as $c => $d) {
if($c == $d)
unset($b[$c]);
}
# lmao
if($a) { doSomething(); } else doSomethingElse();
```
## if/else, try/catch
+ Operators must not be indented with space from their operands but must have 1-space margin from braces:
```php
# OK
if($a) {
doSomething();
doSomethingElse();
} else if($b) {
try {
nukeSaintPetersburg('😈');
} finally {
return PEACE;
}
}
# NOT OK
if ($a) { # do not add space between control flow operator IF and it's operand
doSomething();
doSomethingElse();
}elseif($b){ # do add margin from braces; also ELSE and IF should be separate here
try{
nukeSaintPetersburg('😈');
}finally{
return PEACE;
}
}
```
## Switches
+ `break` must be on same indentation level as the code of le case (not the case definiton itself)
+ If there is no need to `break` a comment `# NOTICE falling through` must be places instead
```php
# OK
switch($a) {
case 1:
echo $a;
break;
case 2:
echo $a++;
# NOTICE falling through
default:
echo "c";
}
# NOT OK
switch($a) {
case 1:
echo $a;
break;
case 2:
echo $a++;
default:
echo "c";
}
```

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']}/settings/change_email?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']}/settings/change_email?key={$key}" style="color: #000; text-decoration: none;">
http://{$_SERVER['HTTP_HOST']}/settings/change_email?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>
Вы получили это письмо так как кто-то или вы изменили адрес электронной почты. Это не рассылка и от неё нельзя отписаться. Если вы всё равно хотите перестать получать подобные письма, деактивируйте ваш аккаунт.
</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

@ -13,9 +13,9 @@ final class Account extends VKAPIRequestHandler
"last_name" => $this->getUser()->getLastName(),
"home_town" => $this->getUser()->getHometown(),
"status" => $this->getUser()->getStatus(),
"bdate" => "1.1.1970", // TODO
"bdate_visibility" => 0, // TODO
"phone" => "+420 ** *** 228", // TODO
"bdate" => "1.1.1970", # TODO
"bdate_visibility" => 0, # TODO
"phone" => "+420 ** *** 228", # TODO
"relation" => $this->getUser()->getMaritalStatus(),
"sex" => $this->getUser()->isFemale() ? 1 : 2
];
@ -25,12 +25,12 @@ final class Account extends VKAPIRequestHandler
{
$this->requireUser();
// Цiй метод є заглушка
# Цiй метод є заглушка
return (object) [
"2fa_required" => 0,
"country" => "CZ", // TODO
"eu_user" => false, // TODO
"country" => "CZ", # TODO
"eu_user" => false, # TODO
"https_required" => 1,
"intro" => 0,
"community_comments" => false,
@ -55,7 +55,7 @@ final class Account extends VKAPIRequestHandler
{
$this->requireUser();
// Цiй метод є заглушка
# Цiй метод є заглушка
return 1;
}
@ -73,6 +73,6 @@ final class Account extends VKAPIRequestHandler
"messages" => $this->getUser()->getUnreadMessagesCount()
];
// TODO: Filter
# TODO: Filter
}
}

View file

@ -25,7 +25,7 @@ final class Friends extends VKAPIRequestHandler
$usersApi = new Users($this->getUser());
if (!is_null($fields)) {
$response = $usersApi->get(implode(',', $friends), $fields, 0, $count); // FIXME
$response = $usersApi->get(implode(',', $friends), $fields, 0, $count); # FIXME
}
return (object) [

View file

@ -48,7 +48,7 @@ final class Groups extends VKAPIRequestHandler
"name" => "DELETED",
"deactivated" => "deleted"
];
}else if($clbs[$i] == null){
}else if($clbs[$i] == NULL){
}else{
$rClubs[$i] = (object)[
@ -95,10 +95,10 @@ final class Groups extends VKAPIRequestHandler
$clubs = new ClubsRepo;
if ($group_ids == null && $group_id != null)
if ($group_ids == NULL && $group_id != NULL)
$group_ids = $group_id;
if ($group_ids == null && $group_id == null)
if ($group_ids == NULL && $group_id == NULL)
$this->fail(100, "One of the parameters specified was missing or invalid: group_ids is undefined");
$clbs = explode(',', $group_ids);
@ -123,7 +123,7 @@ final class Groups extends VKAPIRequestHandler
"type" => "group",
"description" => "This group was deleted or it doesn't exist"
];
}else if($clbs[$i] == null){
}else if($clbs[$i] == NULL){
}else{
$response[$i] = (object)[

View file

@ -63,7 +63,7 @@ final class Likes extends VKAPIRequestHandler
return (object)[
"liked" => (int) $post->hasLikeFrom($user),
"copied" => 0 // TODO: handle this
"copied" => 0 # TODO: handle this
];
break;
default:

View file

@ -7,9 +7,9 @@ final class Users extends VKAPIRequestHandler
{
function get(string $user_ids = "0", string $fields = "", int $offset = 0, int $count = 100, User $authuser = null /* костыль(( */): array
{
// $this->requireUser();
# $this->requireUser();
if($authuser == null) $authuser = $this->getUser();
if($authuser == NULL) $authuser = $this->getUser();
$users = new UsersRepo;
if($user_ids == "0")
@ -34,7 +34,7 @@ final class Users extends VKAPIRequestHandler
"last_name" => "",
"deactivated" => "deleted"
];
}else if($usrs[$i] == null){
}else if($usrs[$i] == NULL){
}else{
$response[$i] = (object)[
@ -73,21 +73,21 @@ final class Users extends VKAPIRequestHandler
case 'photo_200':
$response[$i]->photo_50 = $usr->getAvatarURL("normal");
break;
case 'photo_200_orig': // вообще не ебу к чему эта строка ну пусть будет кек
case 'photo_200_orig': # вообще не ебу к чему эта строка ну пусть будет кек
$response[$i]->photo_50 = $usr->getAvatarURL("normal");
break;
case 'photo_400_orig':
$response[$i]->photo_50 = $usr->getAvatarURL("normal");
break;
// Она хочет быть выебанной видя матан
// Покайфу когда ты Виет а вокруг лишь дискриминант
# Она хочет быть выебанной видя матан
# Покайфу когда ты Виет а вокруг лишь дискриминант
case 'status':
if($usr->getStatus() != null)
if($usr->getStatus() != NULL)
$response[$i]->status = $usr->getStatus();
break;
case 'screen_name':
if($usr->getShortCode() != null)
if($usr->getShortCode() != NULL)
$response[$i]->screen_name = $usr->getShortCode();
break;
case 'friend_status':

View file

@ -23,56 +23,21 @@ final class Wall extends VKAPIRequestHandler
foreach ($posts->getPostsFromUsersWall((int)$owner_id, 1, $count, $offset) as $post) {
$from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
$attachments;
foreach($post->getChildren() as $attachment)
{
if($attachment instanceof \openvk\Web\Models\Entities\Photo)
{
$attachments = [];
foreach($post->getChildren() as $attachment) {
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
if($attachment->isDeleted())
continue;
$attachments[] = [
"type" => "photo",
"photo" => [
"album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : null,
"date" => $attachment->getPublicationTime()->timestamp(),
"id" => $attachment->getVirtualId(),
"album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : NULL,
"date" => $attachment->getPublicationTime()->timestamp(),
"id" => $attachment->getVirtualId(),
"owner_id" => $attachment->getOwner()->getId(),
"sizes" => array(
[
"height" => 2560,
"url" => $attachment->getURLBySizeId("normal"),
"type" => "m",
"width" => 2560,
],
[
"height" => 130,
"url" => $attachment->getURLBySizeId("tiny"),
"type" => "o",
"width" => 130,
],
[
"height" => 604,
"url" => $attachment->getURLBySizeId("normal"),
"type" => "p",
"width" => 604,
],
[
"height" => 807,
"url" => $attachment->getURLBySizeId("large"),
"type" => "q",
"width" => 807,
],
[
"height" => 1280,
"url" => $attachment->getURLBySizeId("larger"),
"type" => "r",
"width" => 1280,
],
[
"height" => 75, // Для временного компросима оставляю статическое число. Если каждый раз обращаться к файлу за количеством пикселов, то наступает пuпuська полная с производительностью, так что пока так
"url" => $attachment->getURLBySizeId("miniscule"),
"type" => "s",
"width" => 75,
]),
"text" => "",
"sizes" => array_values($attachment->getVkApiSizes()),
"text" => "",
"has_tags" => false
]
];
@ -86,10 +51,10 @@ final class Wall extends VKAPIRequestHandler
"date" => $post->getPublicationTime()->timestamp(),
"post_type" => "post",
"text" => $post->getText(),
"can_edit" => 0, // TODO
"can_edit" => 0, # TODO
"can_delete" => $post->canBeDeletedBy($this->getUser()),
"can_pin" => $post->canBePinnedBy($this->getUser()),
"can_archive" => false, // TODO MAYBE
"can_archive" => false, # TODO MAYBE
"is_archived" => false,
"is_pinned" => $post->isPinned(),
"attachments" => $attachments,
@ -115,7 +80,7 @@ final class Wall extends VKAPIRequestHandler
else
$groups[] = $from_id * -1;
$attachments = null; // free attachments so it will not clone everythingg
$attachments = NULL; # free attachments so it will not clone everythingg
}
if($extended == 1)
@ -170,9 +135,9 @@ final class Wall extends VKAPIRequestHandler
];
}
function getById(string $posts, int $extended = 0, string $fields = "", User $user = null)
function getById(string $posts, int $extended = 0, string $fields = "", User $user = NULL)
{
if($user == null) $user = $this->getUser(); // костыли костыли крылышки
if($user == NULL) $user = $this->getUser(); # костыли костыли крылышки
$items = [];
$profiles = [];
@ -195,7 +160,7 @@ final class Wall extends VKAPIRequestHandler
$attachments[] = [
"type" => "photo",
"photo" => [
"album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : null,
"album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : NULL,
"date" => $attachment->getPublicationTime()->timestamp(),
"id" => $attachment->getVirtualId(),
"owner_id" => $attachment->getOwner()->getId(),
@ -231,7 +196,7 @@ final class Wall extends VKAPIRequestHandler
"width" => 1280,
],
[
"height" => 75, // Для временного компросима оставляю статическое число. Если каждый раз обращаться к файлу за количеством пикселов, то наступает пuпuська полная с производительностью, так что пока так
"height" => 75, # Для временного компросима оставляю статическое число. Если каждый раз обращаться к файлу за количеством пикселов, то наступает пuпuська полная с производительностью, так что пока так
"url" => $attachment->getURLBySizeId("miniscule"),
"type" => "s",
"width" => 75,
@ -250,10 +215,10 @@ final class Wall extends VKAPIRequestHandler
"date" => $post->getPublicationTime()->timestamp(),
"post_type" => "post",
"text" => $post->getText(),
"can_edit" => 0, // TODO
"can_edit" => 0, # TODO
"can_delete" => $post->canBeDeletedBy($user),
"can_pin" => $post->canBePinnedBy($user),
"can_archive" => false, // TODO MAYBE
"can_archive" => false, # TODO MAYBE
"is_archived" => false,
"is_pinned" => $post->isPinned(),
"post_source" => (object)["type" => "vk"],
@ -279,7 +244,7 @@ final class Wall extends VKAPIRequestHandler
else
$groups[] = $from_id * -1;
$attachments = null; // free attachments so it will not clone everythingg
$attachments = NULL; # free attachments so it will not clone everythingg
}
}
@ -370,12 +335,12 @@ final class Wall extends VKAPIRequestHandler
if($signed == 1)
$flags |= 0b01000000;
// TODO: Compatible implementation of this
# TODO: Compatible implementation of this
try {
$photo = null;
$video = null;
$photo = NULL;
$video = NULL;
if($_FILES["photo"]["error"] === UPLOAD_ERR_OK) {
$album = null;
$album = NULL;
if(!$anon && $owner_id > 0 && $owner_id === $this->getUser()->getId())
$album = (new AlbumsRepo)->getUserWallAlbum($wallOwner);

View file

@ -99,6 +99,14 @@ class Club extends RowModel
{
return $this->getRecord()->about;
}
function getDescriptionHtml(): ?string
{
if(!is_null($this->getDescription()))
return nl2br(htmlspecialchars($this->getDescription(), ENT_DISALLOWED | ENT_XHTML));
else
return NULL;
}
function getShortCode(): ?string
{
@ -302,8 +310,8 @@ class Club extends RowModel
{
$manager = (new Managers)->getByUserAndClub($user->getId(), $this->getId());
if ($ignoreHidden && $manager !== null && $manager->isHidden())
return null;
if ($ignoreHidden && $manager !== NULL && $manager->isHidden())
return NULL;
return $manager;
}

View file

@ -131,7 +131,7 @@ class Correspondence
*/
function getPreviewMessage(): ?Message
{
$messages = $this->getMessages(1, null, 1);
$messages = $this->getMessages(1, NULL, 1);
return $messages[0] ?? NULL;
}

View file

@ -0,0 +1,15 @@
<?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 EmailChangeVerification extends PasswordReset
{
protected $tableName = "email_change_verifications";
function getNewEmail(): string
{
return $this->getRecord()->new_email;
}
}

View file

@ -126,7 +126,7 @@ class Message extends RowModel
],
"timing" => [
"sent" => (string) $this->getSendTime()->format("%e %B %G" . tr("time_at_sp") . "%X"),
"edited" => is_null($this->getEditTime()) ? null : (string) $this->getEditTime(),
"edited" => is_null($this->getEditTime()) ? NULL : (string) $this->getEditTime(),
],
"text" => $this->getText(),
"read" => !$this->isUnread(),

View file

@ -165,8 +165,11 @@ class Photo extends Media
foreach($manifest->Size as $size)
$mappings[(string) $size["id"]] = (string) $size["vkId"];
foreach($sizes as $id => $meta)
$res[$mappings[$id] ?? $id] = $meta;
foreach($sizes as $id => $meta) {
$type = $mappings[$id] ?? $id;
$meta->type = $type;
$res[$type] = $meta;
}
return $res;
}

View file

@ -87,7 +87,7 @@ abstract class Postable extends Attachable
]));
}
// TODO add pagination
# TODO add pagination
function getLikers(): \Traversable
{
$sel = DB::i()->getContext()->table("likes")->where([

View file

@ -85,6 +85,11 @@ class User extends RowModel
{
return (bool) $this->getRecord()->microblog;
}
function getMainPage(): int
{
return $this->getRecord()->main_page;
}
function getChandlerGUID(): string
{
@ -877,6 +882,17 @@ class User extends RowModel
return true;
}
function changeEmail(string $email): void
{
DatabaseConnection::i()->getContext()->table("ChandlerUsers")
->where("id", $this->getChandlerUser()->getId())->update([
"login" => $email
]);
$this->stateChanges("email", $email);
$this->save();
}
function adminNotify(string $message): bool
{
$admId = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"];
@ -928,7 +944,7 @@ class User extends RowModel
return $this->getRecord()->website;
}
// ты устрица
# ты устрица
function isActivated(): bool
{
return (bool) $this->getRecord()->activated;

View file

@ -63,7 +63,7 @@ class Video extends Media
if(!file_exists($this->getFileName())) {
if((time() - $this->getRecord()->last_checked) > 3600) {
// TODO notify that video processor is probably dead
# TODO notify that video processor is probably dead
}
return false;

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\EmailChangeVerification;
use openvk\Web\Models\Entities\User;
use Nette\Database\Table\ActiveRow;
class EmailChangeVerifications
{
private $context;
private $verifications;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->verifications = $this->context->table("email_change_verifications");
}
function toEmailChangeVerification(?ActiveRow $ar): ?EmailChangeVerification
{
return is_null($ar) ? NULL : new EmailChangeVerification($ar);
}
function getByToken(string $token): ?EmailChangeVerification
{
return $this->toEmailChangeVerification($this->verifications->where("key", $token)->fetch());
}
function getLatestByUser(User $user): ?EmailChangeVerification
{
return $this->toEmailChangeVerification($this->verifications->where("profile", $user->getId())->order("timestamp DESC")->fetch());
}
}

View file

@ -39,7 +39,7 @@ class Notes
if(!is_null($note))
return new Note($note);
else
return null;
return NULL;
}
function getUserNotesCount(User $user): int

View file

@ -96,7 +96,7 @@ class Posts
if(!is_null($post))
return new Post($post);
else
return null;
return NULL;
}

View file

@ -1,8 +1,5 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
// use openvk\Web\Models\Entities\Ticket;
// use openvk\Web\Models\Entities\User;
// use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\Entities\TicketComment;
use Chandler\Database\DatabaseConnection;
@ -21,32 +18,6 @@ class TicketComments
{
foreach($this->comments->where(['ticket_id' => $ticket_id, 'deleted' => 0]) as $comment) yield new TicketComment($comment);
}
// private function toTicket(?ActiveRow $ar): ?Ticket
// {
// return is_null($ar) ? NULL : new Ticket($ar);
// }
// function getTicketsByuId(int $user_id): \Traversable
// {
// foreach($this->tickets->where(['user_id' => $user_id, 'deleted' => 0]) as $ticket) yield new Ticket($ticket);
// }
// function getRequestById(int $req_id): ?Ticket
// {
// $requests = $this->tickets->where(['id' => $req_id])->fetch();
// if(!is_null($requests))
// return new Req($requests);
// else
// return null;
// }
// function get(int $id): ?Ticket
// {
// return $this->toTicket($this->tickets->get($id));
// }
function get(int $id): ?TicketComment
{

View file

@ -50,7 +50,7 @@ class Tickets
if(!is_null($requests))
return new Req($requests);
else
return null;
return NULL;
}

View file

@ -35,7 +35,7 @@ class Topics
{
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
// Get pinned topics first
# 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;

View file

@ -14,7 +14,12 @@ final class AboutPresenter extends OpenVKPresenter
{
if(!is_null($this->user)) {
header("HTTP/1.1 302 Found");
header("Location: /id" . $this->user->id);
if($this->user->identity->getMainPage())
header("Location: /feed");
else
header("Location: /id" . $this->user->id);
exit;
}
@ -85,7 +90,7 @@ final class AboutPresenter extends OpenVKPresenter
if(is_null($lg))
$this->throwError(404, "Not found", "Language is not found");
header("Content-Type: application/javascript");
echo "window.lang = " . json_encode($localizer->export($lang)) . ";"; // привет хардкод :DDD
echo "window.lang = " . json_encode($localizer->export($lang)) . ";"; # привет хардкод :DDD
exit;
}
@ -120,7 +125,7 @@ final class AboutPresenter extends OpenVKPresenter
function renderHumansTxt(): void
{
// :D
# :D
header("HTTP/1.1 302 Found");
header("Location: https://github.com/openvk/openvk#readme");

View file

@ -23,7 +23,7 @@ final class AdminPresenter extends OpenVKPresenter
private function warnIfNoCommerce(): void
{
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
$this->flash("warn", "Коммерция отключена системным администратором", "Настройки ваучеров и подарков будут сохранены, но не будут оказывать никакого влияния.");
$this->flash("warn", tr("admin_commerce_disabled"), tr("admin_commerce_disabled_desc"));
}
private function searchResults(object $repo, &$count)
@ -70,14 +70,14 @@ final class AdminPresenter extends OpenVKPresenter
$user->setLast_Name($this->postParam("last_name"));
$user->setPseudo($this->postParam("nickname"));
$user->setStatus($this->postParam("status"));
$user->setVerified(empty($this->postParam("verify") ? 0 : 1));
if($user->onlineStatus() != $this->postParam("online")) $user->setOnline(intval($this->postParam("online")));
if(!$user->setShortCode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode")))
$this->flash("err", tr("error"), tr("error_shorturl_incorrect"));
$user->changeEmail($this->postParam("email"));
if($user->onlineStatus() != $this->postParam("online")) $user->setOnline(intval($this->postParam("online")));
$user->setVerified(empty($this->postParam("verify") ? 0 : 1));
$user->save();
break;
}
}

View file

@ -60,7 +60,7 @@ final class CommentPresenter extends OpenVKPresenter
}
}
// TODO move to trait
# TODO move to trait
try {
$photo = NULL;
$video = NULL;

View file

@ -89,7 +89,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->template->club = $this->clubs->get($id);
$this->template->onlyShowManagers = $this->queryParam("onlyAdmins") == "1";
if($this->template->onlyShowManagers) {
$this->template->followers = null;
$this->template->followers = NULL;
$this->template->managers = $this->template->club->getManagers((int) ($this->queryParam("p") ?? 1), !$this->template->club->canBeModifiedBy($this->user->identity));
if($this->template->club->canBeModifiedBy($this->user->identity) || !$this->template->club->isOwnerHidden()) {
@ -99,7 +99,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->template->count = $this->template->club->getManagersCount();
} else {
$this->template->followers = $this->template->club->getFollowers((int) ($this->queryParam("p") ?? 1));
$this->template->managers = null;
$this->template->managers = NULL;
$this->template->count = $this->template->club->getFollowersCount();
}
@ -116,7 +116,7 @@ final class GroupPresenter extends OpenVKPresenter
$user = is_null($this->queryParam("user")) ? $this->postParam("user") : $this->queryParam("user");
$comment = $this->postParam("comment");
$removeComment = $this->postParam("removeComment") === "1";
$hidden = ["0" => false, "1" => true][$this->queryParam("hidden")] ?? null;
$hidden = ["0" => false, "1" => true][$this->queryParam("hidden")] ?? NULL;
//$index = $this->queryParam("index");
if(!$user)
$this->badRequest();

View file

@ -80,11 +80,11 @@ final class InternalAPIPresenter extends OpenVKPresenter
if ($postTZ != $sessionOffset || $sessionOffset == null) {
Session::i()->set("_timezoneOffset", $postTZ ? $postTZ : 3 * MINUTE );
$this->returnJson([
"success" => 1 // If it's new value
"success" => 1 # If it's new value
]);
} else {
$this->returnJson([
"success" => 2 // If it's the same value (if for some reason server will call this func)
"success" => 2 # If it's the same value (if for some reason server will call this func)
]);
}
} else {

View file

@ -106,7 +106,7 @@ final class MessengerPresenter extends OpenVKPresenter
$messages = [];
$correspondence = new Correspondence($this->user->identity, $correspondent);
foreach($correspondence->getMessages(1, $lastMsg === 0 ? null : $lastMsg) as $message)
foreach($correspondence->getMessages(1, $lastMsg === 0 ? NULL : $lastMsg) as $message)
$messages[] = $message->simplify();
header("Content-Type: application/json");

View file

@ -118,7 +118,7 @@ abstract class OpenVKPresenter extends SimplePresenter
return ($action === "register" || $action === "login");
}
return (bool) $this->user->raw->can($action)->model($model)->whichBelongsTo($context === -1 ? null : $context);
return (bool) $this->user->raw->can($action)->model($model)->whichBelongsTo($context === -1 ? NULL : $context);
}
protected function assertPermission(string $model, string $action, int $context, bool $throw = false): void
@ -252,7 +252,7 @@ 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", [
@ -290,7 +290,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$whichbrowser = new WhichBrowser\Parser(getallheaders());
$mobiletheme = OPENVK_ROOT_CONF["openvk"]["preferences"]["defaultMobileTheme"];
if($mobiletheme && $whichbrowser->isType('mobile') && Session::i()->get("_tempTheme") == null)
if($mobiletheme && $whichbrowser->isType('mobile') && Session::i()->get("_tempTheme") == NULL)
$this->setSessionTheme($mobiletheme);
$theme = NULL;
@ -301,7 +301,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$theme = Themepacks::i()[Session::i()->get("_sessionTheme", "ovk")];
} else if($this->requestParam("themePreview")) {
$theme = Themepacks::i()[$this->requestParam("themePreview")];
} else if($this->user->identity !== null && $this->user->identity->getTheme()) {
} else if($this->user->identity !== NULL && $this->user->identity->getTheme()) {
$theme = $this->user->identity->getTheme();
}

View file

@ -29,7 +29,7 @@ final class SearchPresenter extends OpenVKPresenter
if($query != "")
$this->assertUserLoggedIn();
// https://youtu.be/pSAWM5YuXx8
# https://youtu.be/pSAWM5YuXx8
$repos = [ "groups" => "clubs", "users" => "users" ];
$repo = $repos[$type] or $this->throwError(400, "Bad Request", "Invalid search entity $type.");

View file

@ -28,6 +28,41 @@ final class SupportPresenter extends OpenVKPresenter
$this->assertUserLoggedIn();
$this->template->mode = in_array($this->queryParam("act"), ["faq", "new", "list"]) ? $this->queryParam("act") : "faq";
if($this->template->mode === "faq") {
$lang = Session::i()->get("lang", "ru");
$base = OPENVK_ROOT . "/data/knowledgebase/faq";
if(file_exists("$base.$lang.md"))
$file = "$base.$lang.md";
else if(file_exists("$base.md"))
$file = "$base.md";
else
$file = NULL;
if(is_null($file)) {
$this->template->faq = [];
} else {
$lines = file($file);
$faq = [];
$index = 0;
foreach($lines as $line) {
if(strpos($line, "# ") === 0)
++$index;
$faq[$index][] = $line;
}
$this->template->faq = array_map(function($section) {
$title = substr($section[0], 2);
array_shift($section);
return [
$title,
(new Parsedown())->text(implode("\n", $section))
];
}, $faq);
}
}
$this->template->count = $this->tickets->getTicketsCountByUserId($this->user->id);
if($this->template->mode === "list") {
$this->template->page = (int) ($this->queryParam("p") ?? 1);
@ -79,12 +114,13 @@ final class SupportPresenter extends OpenVKPresenter
$act = $this->queryParam("act") ?? "open";
switch($act) {
default:
# NOTICE falling through
case "open":
$state = 0;
break;
break;
case "answered":
$state = 1;
break;
break;
case "closed":
$state = 2;
}

View file

@ -91,7 +91,7 @@ final class TopicsPresenter extends OpenVKPresenter
$topic->setFlags($flags);
$topic->save();
// TODO move to trait
# TODO move to trait
try {
$photo = NULL;
$video = NULL;

View file

@ -9,12 +9,15 @@ 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\Repositories\EmailChangeVerifications;
use openvk\Web\Models\Exceptions\InvalidUserNameException;
use openvk\Web\Util\Validator;
use openvk\Web\Models\Entities\Notifications\{CoinsTransferNotification, RatingUpNotification};
use openvk\Web\Models\Entities\EmailChangeVerification;
use Chandler\Security\Authenticator;
use lfkeitel\phptotp\{Base32, Totp};
use chillerlan\QRCode\{QRCode, QROptions};
use Nette\Database\UniqueConstraintViolationException;
final class UserPresenter extends OpenVKPresenter
{
@ -132,7 +135,7 @@ final class UserPresenter extends OpenVKPresenter
if(!$id)
$this->notFound();
$user = $this->users->get($id);
if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->willExecuteWriteAction($_GET['act'] === "status");
@ -300,7 +303,7 @@ final class UserPresenter extends OpenVKPresenter
if(!$id)
$this->notFound();
if(in_array($this->queryParam("act"), ["finance", "finance.top-up"]) && !OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
$this->flashFail("err", tr("error"), tr("feature_disabled"));
@ -312,7 +315,7 @@ final class UserPresenter extends OpenVKPresenter
if($this->postParam("old_pass") && $this->postParam("new_pass") && $this->postParam("repeat_pass")) {
if($this->postParam("new_pass") === $this->postParam("repeat_pass")) {
if($this->user->identity->is2faEnabled()) {
$code = $this->postParam("code");
$code = $this->postParam("password_change_code");
if(!($code === (new Totp)->GenerateToken(Base32::decode($this->user->identity->get2faSecret())) || $this->user->identity->use2faBackupCode((int) $code)))
$this->flashFail("err", tr("error"), tr("incorrect_2fa_code"));
}
@ -323,6 +326,46 @@ final class UserPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error"), tr("error_new_password"));
}
}
if($this->postParam("new_email")) {
if(!Validator::i()->emailValid($this->postParam("new_email")))
$this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment"));
if(!Authenticator::verifyHash($this->postParam("email_change_pass"), $user->getChandlerUser()->getRaw()->passwordHash))
$this->flashFail("err", tr("error"), tr("incorrect_password"));
if($user->is2faEnabled()) {
$code = $this->postParam("email_change_code");
if(!($code === (new Totp)->GenerateToken(Base32::decode($user->get2faSecret())) || $user->use2faBackupCode((int) $code)))
$this->flashFail("err", tr("error"), tr("incorrect_2fa_code"));
}
if($this->postParam("new_email") !== $user->getEmail()) {
if (OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']) {
$request = (new EmailChangeVerifications)->getLatestByUser($user);
if(!is_null($request) && $request->isNew())
$this->flashFail("err", tr("forbidden"), tr("email_rate_limit_error"));
$verification = new EmailChangeVerification;
$verification->setProfile($user->getId());
$verification->setNew_Email($this->postParam("new_email"));
$verification->save();
$params = [
"key" => $verification->getKey(),
"name" => $user->getCanonicalName(),
];
$this->sendmail($this->postParam("new_email"), "change-email", $params); #Vulnerability possible
$this->flashFail("succ", tr("information_-1"), tr("email_change_confirm_message"));
}
try {
$user->changeEmail($this->postParam("new_email"));
} catch(UniqueConstraintViolationException $ex) {
$this->flashFail("err", tr("error"), tr("user_already_exists"));
}
}
}
if(!$user->setShortCode(empty($this->postParam("sc")) ? NULL : $this->postParam("sc")))
$this->flashFail("err", tr("error"), tr("error_shorturl_incorrect"));
@ -376,6 +419,9 @@ final class UserPresenter extends OpenVKPresenter
if(in_array($this->postParam("nsfw"), [0, 1, 2]))
$user->setNsfwTolerance((int) $this->postParam("nsfw"));
if(in_array($this->postParam("main_page"), [0, 1]))
$user->setMain_Page((int) $this->postParam("main_page"));
} else if($_GET['act'] === "lMenu") {
$settings = [
"menu_bildoj" => "photos",
@ -400,11 +446,7 @@ final class UserPresenter extends OpenVKPresenter
throw $ex;
}
$this->flash(
"succ",
"Изменения сохранены",
"Новые данные появятся на вашей странице."
);
$this->flash("succ", tr("changes_saved"), tr("changes_saved_comment"));
}
$this->template->mode = in_array($this->queryParam("act"), [
"main", "privacy", "finance", "finance.top-up", "interface"
@ -456,7 +498,7 @@ final class UserPresenter extends OpenVKPresenter
$this->template->secret = $secret;
}
// Why are these crutch? For some reason, the QR code is not displayed if you just pass the render output to the view
# Why are these crutch? For some reason, the QR code is not displayed if you just pass the render output to the view
$issuer = OPENVK_ROOT_CONF["openvk"]["appearance"]["name"];
$email = $this->user->identity->getEmail();
@ -502,6 +544,9 @@ final class UserPresenter extends OpenVKPresenter
$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");
@ -517,7 +562,7 @@ final class UserPresenter extends OpenVKPresenter
$receiver = $this->users->getByAddress($receiverAddress);
if(!$receiver)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("receiver_not_found"));
$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"));
@ -574,4 +619,24 @@ final class UserPresenter extends OpenVKPresenter
$this->flashFail("succ", tr("information_-1"), tr("rating_increase_successful", $receiver->getURL(), htmlentities($receiver->getCanonicalName()), $value));
}
function renderEmailChangeFinish(): void
{
$request = (new EmailChangeVerifications)->getByToken(str_replace(" ", "+", $this->queryParam("key")));
if(!$request || !$request->isStillValid()) {
$this->flash("err", tr("token_manipulation_error"), tr("token_manipulation_error_comment"));
$this->redirect("/settings");
} else {
$request->delete(false);
try {
$request->getUser()->changeEmail($request->getNewEmail());
} catch(UniqueConstraintViolationException $ex) {
$this->flashFail("err", tr("error"), tr("user_already_exists"));
}
$this->flash("succ", tr("changes_saved"), tr("changes_saved_comment"));
$this->redirect("/settings");
}
}
}

View file

@ -414,7 +414,7 @@ final class WallPresenter extends OpenVKPresenter
$post->unpin();
}
// TODO localize message based on language and ?act=(un)pin
# TODO localize message based on language and ?act=(un)pin
$this->flashFail("succ", tr("information_-1"), tr("changes_saved_comment"));
}
}

View file

@ -17,7 +17,7 @@
{script "js/l10n.js"}
{script "js/openvk.cls.js"}
{if $isTimezoned == null}
{if $isTimezoned == NULL}
{script "js/timezone.js"}
{/if}
@ -26,7 +26,7 @@
{css "css/nsfw-posts.css"}
{/if}
{if $theme !== null}
{if $theme !== NULL}
{if $theme->inheritDefault()}
{css "css/style.css"}
{css "css/dialog.css"}
@ -103,7 +103,7 @@
<div n:if="isset($thisUser) ? (!$thisUser->isBanned() XOR !$thisUser->isActivated()) : true" class="header_navigation">
{ifset $thisUser}
<div class="link">
<a href="/" title="[Alt+Shift+,]" accesskey=",">{_header_home}</a>
<a href="/">{_header_home}</a>
</div>
<div class="link">
<a href="/search?type=groups">{_header_groups}</a>
@ -161,7 +161,7 @@
</a>
<a n:if="$thisUser->getLeftMenuItemStatus('notes')" href="/notes{$thisUser->getId()}" class="link">{_my_notes}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('groups')" href="/groups{$thisUser->getId()}" class="link">{_my_groups}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('news')" href="/feed" class="link" title="{_my_feed} [Alt+Shift+W]" accesskey="w">{_my_feed}</a>
<a n:if="$thisUser->getLeftMenuItemStatus('news')" href="/feed" class="link" title="{_my_feed} [Alt+Shift+,]" accesskey=",">{_my_feed}</a>
<a href="/notifications" class="link" title="{_my_feedback} [Alt+Shift+N]" accesskey="n">{_my_feedback}
{if $thisUser->getNotificationsCount() > 0}
(<b>{$thisUser->getNotificationsCount()}</b>)
@ -173,7 +173,7 @@
{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 && $thisUser->getLeftMenuItemStatus('links')}
<div n:if="$canAccessAdminPanel || $canAccessHelpdesk || $menuLinksAvaiable" class="menu_divider"></div>
<a href="/admin" class="link" n:if="$canAccessAdminPanel" title="Админ-панель [Alt+Shift+A]" accesskey="a">Админ-панель</a>
<a href="/admin" class="link" n:if="$canAccessAdminPanel" title="{_admin} [Alt+Shift+A]" accesskey="a">{_admin}</a>
<a href="/support/tickets" class="link" n:if="$canAccessHelpdesk">Helpdesk
{if $helpdeskTicketNotAnsweredCount > 0}
(<b>{$helpdeskTicketNotAnsweredCount}</b>)
@ -280,6 +280,8 @@
</p>
</div>
{include "components/cookies.xml"}
{script "js/node_modules/msgpack-lite/dist/msgpack.min.js"}
{script "js/node_modules/soundjs/lib/soundjs.min.js"}
{script "js/node_modules/ky/umd.js"}

View file

@ -1,3 +1,4 @@
{var $instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
@ -6,7 +7,7 @@
{var $css = file_get_contents(OPENVK_ROOT . "/Web/static/js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.css")}
{str_replace("fonts/", "/assets/packages/static/openvk/js/node_modules/@atlassian/aui/dist/aui/fonts/", $css)|noescape}
</style>
<title>{include title} - Админ-панель {=OPENVK_ROOT_CONF['openvk']['appearance']['name']}</title>
<title>{include title} - {_admin} {$instance_name}</title>
</head>
<body>
<div id="page">
@ -16,23 +17,15 @@
<div class="aui-header-primary">
<h1 id="logo" class="aui-header-logo aui-header-logo-textonly">
<a href="/admin">
<span class="aui-header-logo-device">{=OPENVK_ROOT_CONF['openvk']['appearance']['name']}</span>
<span class="aui-header-logo-device">{$instance_name}</span>
</a>
</h1>
</div>
<div n:if="$search ?? false" class="aui-header-secondary">
<ul class="aui-nav">
<form class="aui-quicksearch dont-default-focus ajs-dirty-warning-exempt">
<input
id="quickSearchInput"
autocomplete="off"
class="search"
type="text"
placeholder="{include searchTitle}"
value="{$_GET['q'] ?? ''}"
name="q"
accesskey="Q" />
<input type="hidden" value=1 name=p />
<input id="quickSearchInput" autocomplete="off" class="search" type="text" placeholder="{include searchTitle}" value="{$_GET['q'] ?? ''}" name="q" accesskey="Q" />
<input type="hidden" value=1 name=p />
</form>
</ul>
</div>
@ -46,83 +39,64 @@
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<div class="aui-nav-heading">
<strong>Обзор</strong>
<strong>{_admin_overview}</strong>
</div>
<ul class="aui-nav">
<li>
<a href="/admin">
Сводка
</a>
<a href="/admin">{_admin_overview_summary}</a>
</li>
</ul>
<div class="aui-nav-heading">
<strong>Пользовательский контент</strong>
<strong>{_admin_content}</strong>
</div>
<ul class="aui-nav">
<li>
<a href="/admin/users">
Пользователи
</a>
<a href="/admin/users">{_users}</a>
</li>
<li>
<a href="/admin/clubs">
Группы
</a>
<a href="/admin/clubs">{_groups}</a>
</li>
</ul>
<div class="aui-nav-heading">
<strong>Платные услуги</strong>
<strong>{_admin_services}</strong>
</div>
<ul class="aui-nav">
<li>
<a href="/admin/vouchers">
{_vouchers}
</a>
<a href="/admin/vouchers">{_vouchers}</a>
</li>
<li>
<a href="/admin/gifts">
Подарки
</a>
<a href="/admin/gifts">{_gifts}</a>
</li>
</ul>
<div class="aui-nav-heading">
<strong>Настройки</strong>
<strong>{_admin_settings}</strong>
</div>
<ul class="aui-nav">
<li>
<a href="/admin/settings/tuning">
Общие
</a>
<a href="/admin/settings/tuning">{_admin_settings_tuning}</a>
</li>
<li>
<a href="/admin/settings/appearance">
Внешний вид
</a>
<a href="/admin/settings/appearance">{_admin_settings_appearance}</a>
</li>
<li>
<a href="/admin/settings/security">
Безопасность
</a>
<a href="/admin/settings/security">{_admin_settings_security}</a>
</li>
<li>
<a href="/admin/settings/integrations">
Интеграции
</a>
<a href="/admin/settings/integrations">{_admin_settings_integrations}</a>
</li>
<li>
<a href="/admin/settings/system">
Система
</a>
<a href="/admin/settings/system">{_admin_settings_system}</a>
</li>
</ul>
<div class="aui-nav-heading">
<strong>Об OpenVK</strong>
<strong>{_admin_about}</strong>
</div>
<ul class="aui-nav">
<li>
<a href="/about:openvk">
Версия
</a>
<a href="/about:openvk">{_admin_about_version}</a>
</li>
<li>
<a href="/about">{_admin_about_instance}</a>
</li>
</ul>
</div>
@ -139,11 +113,11 @@
<p>{$flashMessage->msg|noescape}</p>
</div>
{/ifset}
{ifset preHeader}
{include preHeader}
{/ifset}
<header class="aui-page-header">
<div class="aui-page-header-inner">
<div class="aui-page-header-main">
@ -167,11 +141,11 @@
</section>
</footer>
</div>
{script "js/node_modules/jquery/dist/jquery.min.js"}
{script "js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.js"}
<script>AJS.tabs.setup();</script>
{ifset scripts}
{include scripts}
{/ifset}

View file

@ -1,191 +1,157 @@
{extends "@layout.xml"}
{block title}
Редактировать {$club->getCanonicalName()}
{_edit} {$club->getCanonicalName()}
{/block}
{block heading}
{$club->getCanonicalName()}
{/block}
{block content}
{var $isMain = $mode === 'main'}
{var $isBan = $mode === 'ban'}
{var $isFollowers = $mode === 'followers'}
{var $isMain = $mode === 'main'}
{var $isBan = $mode === 'ban'}
{var $isFollowers = $mode === 'followers'}
{if $isMain}
<!-- This main block -->
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li class="aui-nav-selected"><a href="?act=main">Главное</a></li>
<li><a href="?act=ban">Бан</a></li>
<li><a href="?act=followers">Участники</a></li>
</ul>
</div>
</div>
</nav>
<form class="aui" method="POST">
<div class="field-group">
<label for="avatar">
Аватарка
</label>
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
<span class="aui-avatar-inner">
<img src="{$club->getAvatarUrl('tiny')}" style="object-fit: cover;"></img>
</span>
</span>
</div>
<div class="field-group">
<label for="id">
ID
</label>
<input class="text medium-field" type="number" id="id" disabled value="{$club->getId()}" />
</div>
<div class="field-group">
<label for="id_owner">
ID владельца
</label>
<input class="text medium-field" type="text" id="id_owner" name="id_owner" value="{$club->getOwner()->getId()}" />
</div>
<div class="field-group">
<label for="name">
Название
</label>
<input class="text medium-field" type="text" id="name" name="name" value="{$club->getName()}" />
</div>
<div class="field-group">
<label for="about">
Описание
</label>
<input class="text medium-field" type="text" id="about" name="about" value="{$club->getDescription()}" />
</div>
<div class="field-group">
<label for="shortcode">
Адрес
</label>
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$club->getShortCode()}" />
</div>
<br/>
<div class="group">
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $club->isVerified()} checked {/if} />
<label for="verify">
Верификация
</label>
</div>
<div class="group">
<input class="toggle-large" type="checkbox" id="hide_from_global_feed" name="hide_from_global_feed" value="1" {if $club->isHideFromGlobalFeedEnabled()} checked {/if} />
<label for="hide_from_global_feed">
Не отображать записи в глобальной ленте
</label>
</div>
<hr/>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="Сохранить">
</div>
</div>
</form>
</div>
{/if}
{if $isBan}
<!-- This ban block -->
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li><a href="?act=main">Главное</a></li>
<li class="aui-nav-selected"><a href="?act=ban">Бан</a></li>
<li><a href="?act=followers">Участники</a></li>
</ul>
</div>
</div>
</nav>
<form class="aui" method="POST">
<div class="field-group">
<label for="ban_reason">
Причина бана
</label>
<input class="text" type="text" id="text-input" name="ban_reason" value="{$club->getBanReason()}" />
</div>
<hr/>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="Сохранить">
</div>
</div>
</form>
</div>
{/if}
{if $isFollowers}
<!-- This followers block -->
{var $followers = iterator_to_array($followers)}
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li><a href="?act=main">Главное</a></li>
<li><a href="?act=ban">Бан</a></li>
<li class="aui-nav-selected"><a href="?act=followers">Участники</a></li>
</ul>
</div>
</div>
</nav>
<table rules="none" class="aui aui-table-list">
<tbody>
<tr n:foreach="$followers as $follower">
<td>{$follower->getId()}</td>
<td>
<span class="aui-avatar aui-avatar-xsmall">
{if $isMain}
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li class="aui-nav-selected"><a href="?act=main">{_admin_tab_main}</a></li>
<li><a href="?act=ban">{_admin_tab_ban}</a></li>
<li><a href="?act=followers">{_admin_tab_followers}</a></li>
</ul>
</div>
</div>
</nav>
<form class="aui" method="POST">
<div class="field-group">
<label for="avatar">{_avatar}</label>
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
<span class="aui-avatar-inner">
<img src="{$follower->getAvatarUrl('miniscule')}" alt="{$follower->getCanonicalName()}" role="presentation" />
<img src="{$club->getAvatarUrl('tiny')}" style="object-fit: cover;"></img>
</span>
</span>
<a href="{$follower->getURL()}">{$follower->getCanonicalName()}</a>
<span n:if="$follower->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
заблокирован
</span>
</td>
<td>{$follower->isFemale() ? "Женский" : "Мужской"}</td>
<td>{$follower->getShortCode() ?? "(отсутствует)"}</td>
<td>{$follower->getRegistrationTime()}</td>
<td>
<a class="aui-button aui-button-primary" href="/admin/users/id{$follower->getId()}">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
</a>
</td>
</tr>
</tbody>
</table>
<div align="right">
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
</div>
</div>
{/if}
</div>
<div class="field-group">
<label for="id">ID</label>
<input class="text medium-field" type="number" id="id" disabled value="{$club->getId()}" />
</div>
<div class="field-group">
<label for="id_owner">{_admin_ownerid}</label>
<input class="text medium-field" type="text" id="id_owner" name="id_owner" value="{$club->getOwner()->getId()}" />
</div>
<div class="field-group">
<label for="name">{_admin_title}</label>
<input class="text medium-field" type="text" id="name" name="name" value="{$club->getName()}" />
</div>
<div class="field-group">
<label for="about">{_admin_description}</label>
<input class="text medium-field" type="text" id="about" name="about" value="{$club->getDescription()}" />
</div>
<div class="field-group">
<label for="shortcode">{_admin_shortcode}</label>
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$club->getShortCode()}" />
</div>
<br/>
<div class="group">
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $club->isVerified()} checked {/if} />
<label for="verify">{_admin_verification}</label>
</div>
<div class="group">
<input class="toggle-large" type="checkbox" id="hide_from_global_feed" name="hide_from_global_feed" value="1" {if $club->isHideFromGlobalFeedEnabled()} checked {/if} />
<label for="hide_from_global_feed">{_admin_club_excludeglobalfeed}</label>
</div>
<hr/>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="{_save}">
</div>
</div>
</form>
</div>
{/if}
{if $isBan}
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li><a href="?act=main">{_admin_tab_main}</a></li>
<li class="aui-nav-selected"><a href="?act=ban">{_admin_tab_ban}</a></li>
<li><a href="?act=followers">{_admin_tab_followers}</a></li>
</ul>
</div>
</div>
</nav>
<form class="aui" method="POST">
<div class="field-group">
<label for="ban_reason">{_admin_banreason}</label>
<input class="text" type="text" id="text-input" name="ban_reason" value="{$club->getBanReason()}" />
</div>
<hr/>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="{_save}">
</div>
</div>
</form>
</div>
{/if}
{if $isFollowers}
{var $followers = iterator_to_array($followers)}
<div class="aui-tabs horizontal-tabs">
<nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-navgroup-primary">
<ul class="aui-nav">
<li><a href="?act=main">{_admin_tab_main}</a></li>
<li><a href="?act=ban">{_admin_tab_ban}</a></li>
<li class="aui-nav-selected"><a href="?act=followers">{_admin_tab_followers}</a></li>
</ul>
</div>
</div>
</nav>
<table rules="none" class="aui aui-table-list">
<tbody>
<tr n:foreach="$followers as $follower">
<td>{$follower->getId()}</td>
<td>
<span class="aui-avatar aui-avatar-xsmall">
<span class="aui-avatar-inner">
<img src="{$follower->getAvatarUrl()}" alt="{$follower->getCanonicalName()}" role="presentation" />
</span>
</span>
<a href="{$follower->getURL()}">{$follower->getCanonicalName()}</a>
<span n:if="$follower->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_banned}</span>
</td>
<td>{$follower->isFemale() ? tr("female") : tr("male")}</td>
<td>{$follower->getShortCode() ?? "(" . tr("none") . ")"}</td>
<td>{$follower->getRegistrationTime()}</td>
<td>
<a class="aui-button aui-button-primary" href="/admin/users/id{$follower->getId()}">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
</a>
</td>
</tr>
</tbody>
</table>
<div align="right">
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">«</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">»</a>
</div>
</div>
{/if}
{/block}

View file

@ -2,14 +2,16 @@
{var $search = true}
{block title}
Группы
{_admin_club_search}
{/block}
{block heading}
Бутылки
{_groups}
{/block}
{block searchTitle}Поиск бутылок{/block}
{block searchTitle}
{include title}
{/block}
{block content}
{var $clubs = iterator_to_array($clubs)}
@ -18,12 +20,12 @@
<table class="aui aui-table-list">
<thead>
<tr>
<th>#</th>
<th>Имя</th>
<th>Автор</th>
<th>Описание</th>
<th>Короткий адрес</th>
<th>Действия</th>
<th>ID</th>
<th>{_admin_title}</th>
<th>{_admin_author}</th>
<th>{_admin_description}</th>
<th>{_admin_shortcode}</th>
<th>{_admin_actions}</th>
</tr>
</thead>
<tbody>
@ -49,11 +51,11 @@
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
</td>
<td>{$club->getDescription() ?? "(не указано)"}</td>
<td>{$club->getDescription() ?? "(" . tr("none") . ")"}</td>
<td>{$club->getShortCode()}</td>
<td>
<a class="aui-button aui-button-primary" href="/admin/clubs/id{$club->getId()}">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
</a>
</td>
</tr>
@ -63,11 +65,7 @@
<div align="right">
{var $isLast = ((10 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -2,9 +2,9 @@
{block title}
{if $form->id === 0}
Новый подарок
{_admin_newgift}
{else}
Подарок "{$form->name}"
{_gift} "{$form->name}"
{/if}
{/block}
@ -16,7 +16,7 @@
<form class="aui" method="POST" enctype="multipart/form-data">
<div class="field-group">
<label for="avatar">
Изображение
{_admin_image}
<span n:if="$form->id === 0" class="aui-icon icon-required"></span>
</label>
{if $form->id === 0}
@ -29,43 +29,39 @@
</span>
<input style="display: none;" id="picInput" type="file" name="pic" accept="image/jpeg,image/png,image/gif,image/webp" />
<div class="description">
<a id="picChange" href="javascript:false">Заменить изображение?</a>
<a id="picChange" href="javascript:false">{_admin_image_replace}</a>
</div>
{/if}
</div>
<div class="field-group">
<label for="id">
ID
</label>
<label for="id">ID</label>
<input class="text long-field" type="number" id="id" disabled="disabled" value="{$form->id}" />
</div>
<div class="field-group">
<label for="putin">
Использований
</label>
<input class="text long-field" type="number" id="putin" disabled="disabled" value="{$form->usages}" />
<label for="usages">{_admin_uses}</label>
<input class="text long-field" type="number" id="usages" disabled="disabled" value="{$form->usages}" />
<div n:if="$form->usages > 0" class="description">
<a href="javascript:$('#putin').value(0);">Обнулить?</a>
<a href="javascript:$('#usages').value(0);">{_admin_uses_reset}</a>
</div>
</div>
<div class="field-group">
<label for="name">
Внутренее имя
{_admin_name}
<span class="aui-icon icon-required"></span>
</label>
<input class="text long-field" type="text" id="name" name="name" value="{$form->name}" />
</div>
<div class="field-group">
<label for="price">
Цена
{_admin_price}
<span class="aui-icon icon-required"></span>
</label>
<input class="text long-field" type="number" id="price" name="price" min="0" value="{$form->price}" />
</div>
<div class="field-group">
<label for="limit">
Ограничение
{_admin_limits}
<span class="aui-icon icon-required"></span>
</label>
<input class="text long-field" type="number" min="-1" id="limit" name="limit" value="{$form->limit}" />
@ -75,13 +71,13 @@
<div class="checkbox" resolved="">
<input n:attr="disabled => $form->id === 0, checked => $form->id === 0" class="checkbox" type="checkbox" name="reset_limit" id="reset_limit" />
<span class="aui-form-glyph"></span>
<label for="reset_limit">Сбросить счётчик ограничений</label>
<label for="reset_limit">{_admin_limits_reset}</label>
</div>
</fieldset>
<input n:if="$form->id === 0" type="hidden" name="_cat" value="{$_GET['cat'] ?? 1}" />
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
@ -94,12 +90,12 @@
{block scripts}
<script>
const TRANS_GIF = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
$("#picChange").click(_ => $("#picInput").click());
$("#picInput").bind("change", e => {
if(typeof e.target.files[0] === "undefined")
$("#pic").prop("src", URL.createObjectURL(TRANS_GIF));
$("#pic").prop("src", URL.createObjectURL(e.target.files[0]));
});
</script>

View file

@ -1,7 +1,7 @@
{extends "@layout.xml"}
{block title}
Наборы подарков
{_admin_giftsets}
{/block}
{block headingWrap}
@ -9,7 +9,7 @@
{_create}
</a>
<h1>Наборы подарков</h1>
<h1>{_admin_giftsets}</h1>
{/block}
{block content}
@ -27,12 +27,11 @@
</td>
<td style="vertical-align: middle; text-align: right;">
<a class="aui-button aui-button-primary" href="/admin/gifts/{$cat->getSlug()}.{$cat->getId()}.meta">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
</a>
<a class="aui-button" href="/admin/gifts/{$cat->getSlug()}.{$cat->getId()}/">
<span class="aui-icon aui-icon-small aui-iconfont-gallery">Открыть</span>
Открыть
<span class="aui-icon aui-icon-small aui-iconfont-gallery">{_admin_open}</span>
</a>
</td>
</tr>
@ -40,17 +39,13 @@
</table>
{else}
<center>
<p>Наборов подарков нету. Чтобы создать подарок, создайте набор.</p>
<p>{_admin_giftsets_none}</p>
</center>
{/if}
<div align="right">
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($categories)) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) - 1}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?act={$act}&p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -2,7 +2,7 @@
{block title}
{if $form->id === 0}
Создать набор подарков
{_admin_giftsets_create}
{else}
{$form->languages["master"]->name}
{/if}
@ -14,7 +14,7 @@
{block content}
<form class="aui" method="POST">
<h3>Общие настройки</h3>
<h3>{_admin_commonsettings}</h3>
<fieldset>
<div class="field-group">
<label for="id">
@ -24,37 +24,37 @@
</div>
<div class="field-group">
<label for="name_master">
Наименование
{_admin_name}
<span class="aui-icon icon-required"></span>
</label>
<input class="text long-field" type="text" id="name_master" name="name_master" value="{$form->languages['master']->name}" />
<div class="description">Внутреннее название набора, которое будет использоваться, если не удаётся найти название на языке пользователя.</div>
<div class="description">{_admin_giftsets_title}</div>
</div>
<div class="field-group">
<label for="description_master">
Описание
{_admin_description}
<span class="aui-icon icon-required"></span>
</label>
<input class="text long-field" type="text" id="description_master" name="description_master" value="{$form->languages['master']->description}" />
<div class="description">Внутреннее описание набора, которое будет использоваться, если не удаётся найти название на языке пользователя.</div>
<div class="description">{_admin_giftsets_description}</div>
</div>
</fieldset>
<h3>Языко-зависимые настройки</h3>
<h3>{_admin_langsettings}</h3>
<fieldset>
{foreach $form->languages as $locale => $data}
{continueIf $locale === "master"}
<div class="field-group">
<label for="name_{$locale}">
Наименование
{_admin_name}
<img src="/assets/packages/static/openvk/img/flags/{$locale}.gif" alt="{$locale}" />
</label>
<input class="text long-field" type="text" id="name_{$locale}" name="name_{$locale}" value="{$data->name}" />
</div>
<div class="field-group">
<label for="description_{$locale}">
Описание
{_admin_description}
<img src="/assets/packages/static/openvk/img/flags/{$locale}.gif" alt="{$locale}" />
</label>
<input class="text long-field" type="text" id="description_{$locale}" name="description_{$locale}" value="{$data->description}" />

View file

@ -9,7 +9,7 @@
{_create}
</a>
<h1>Набор "{$cat->getName()}"</h1>
<h1>{_admin_giftset} "{$cat->getName()}"</h1>
{/block}
{block content}
@ -32,11 +32,11 @@
<td style="vertical-align: middle;">
{$gift->getName()}
<span n:if="$gift->isFree()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">
бесплатный
{_admin_price_free}
</span>
</td>
<td style="vertical-align: middle;">
{$gift->getPrice()} голосов
{tr("points_amount", $gift->getPrice())}
</td>
<td style="vertical-align: middle;">
{$gift->getUsages()} раз
@ -72,11 +72,8 @@
<div align="right">
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($gifts)) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -1,13 +1,13 @@
{extends "@layout.xml"}
{block title}
Сводка
{_admin_overview_summary}
{/block}
{block heading}
Сводка
{_admin_overview_summary}
{/block}
{block content}
Да!
┬─┬︵/(.□.)╯
{/block}

View file

@ -1,7 +1,7 @@
{extends "@layout.xml"}
{block title}
Редактировать {$user->getCanonicalName()}
{_edit} {$user->getCanonicalName()}
{/block}
{block heading}
@ -10,95 +10,70 @@
{block content}
<div class="aui-tabs horizontal-tabs">
<form class="aui" method="POST">
<div class="field-group">
<label for="avatar">
Аватарка
</label>
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
<span class="aui-avatar-inner">
<img src="{$user->getAvatarUrl('tiny')}" style="object-fit: cover;"></img>
<form class="aui" method="POST">
<div class="field-group">
<label for="avatar">{_avatar}</label>
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
<span class="aui-avatar-inner">
<img src="{$user->getAvatarUrl('tiny')}" style="object-fit: cover;"></img>
</span>
</span>
</span>
</div>
<div class="field-group">
<label for="id">
ID
</label>
<input class="text medium-field" type="number" id="id" disabled value="{$user->getId()}" />
</div>
<div class="field-group">
<label for="guid">
GUID
</label>
<input class="text medium-field" id="guid" disabled value="{$user->getChandlerUser()->getId()}" />
</div>
<div class="field-group">
<label for="registration_ip">
Первый IP
</label>
<input class="text medium-field" id="registration_ip" disabled value="{$user->getRegistrationIP()}" />
</div>
<div class="field-group">
<label for="first_name">
Имя
</label>
<input class="text medium-field" type="text" id="first_name" name="first_name" value="{$user->getFirstName()}" />
</div>
<div class="field-group">
<label for="last_name">
Фамилия
</label>
<input class="text medium-field" type="text" id="last_name" name="last_name" value="{$user->getLastName()}" />
</div>
<div class="field-group">
<label for="nickname">
Никнейм
</label>
<input class="text medium-field" type="text" id="nickname" name="nickname" value="{$user->getPseudo()}" />
</div>
<div class="field-group">
<label for="status">
Статус
</label>
<input class="text medium-field" type="text" id="status" name="status" value="{$user->getStatus()}" />
</div>
<div class="field-group">
<label for="email">
E-Mail
</label>
<input class="text medium-field" type="email" id="email" name="email" value="{$user->getEmail()}" />
</div>
<div class="field-group">
<label for="shortcode">
Адрес страницы
</label>
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$user->getShortCode()}" />
</div>
<hr>
<div class="field-group">
<label for="city">
Верификация
</label>
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $user->isVerified()} checked {/if} />
</div>
<div class="field-group">
<label for="city">
Онлайн статус
</label>
<select name="online" class="select">
<option value="0" {if $user->onlineStatus() > 2}selected{/if}>По-умолчанию</option>
<option value="1" {if $user->onlineStatus() == 1}selected{/if}>Инкогнито</option>
<option value="2" {if $user->onlineStatus() == 2}selected{/if}>Юзер умер</option>
</select>
</div>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="Сохранить">
</div>
</div>
</form>
<div class="field-group">
<label for="id">ID</label>
<input class="text medium-field" type="number" id="id" disabled value="{$user->getId()}" />
</div>
<div class="field-group">
<label for="guid">GUID</label>
<input class="text medium-field" id="guid" disabled value="{$user->getChandlerUser()->getId()}" />
</div>
<div class="field-group">
<label for="registration_ip">{_admin_first_known_ip}</label>
<input class="text medium-field" id="guid" disabled value="{$user->getRegistrationIP()}" />
</div>
<div class="field-group">
<label for="first_name">{_name}</label>
<input class="text medium-field" type="text" id="first_name" name="first_name" value="{$user->getFirstName()}" />
</div>
<div class="field-group">
<label for="last_name">{_surname}</label>
<input class="text medium-field" type="text" id="last_name" name="last_name" value="{$user->getLastName()}" />
</div>
<div class="field-group">
<label for="nickname">{_nickname}</label>
<input class="text medium-field" type="text" id="nickname" name="nickname" value="{$user->getPseudo()}" />
</div>
<div class="field-group">
<label for="status">{_status}</label>
<input class="text medium-field" type="text" id="status" name="status" value="{$user->getStatus()}" />
</div>
<div class="field-group">
<label for="email">E-Mail</label>
<input class="text medium-field" type="email" id="email" name="email" value="{$user->getEmail()}" />
</div>
<div class="field-group">
<label for="shortcode">{_admin_shortcode}</label>
<input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$user->getShortCode()}" />
</div>
<hr>
<div class="field-group">
<label for="city">{_admin_verification}</label>
<input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $user->isVerified()} checked {/if} />
</div>
<div class="field-group">
<label for="city">{_admin_user_online}</label>
<select name="online" class="select">
<option value="0" {if $user->onlineStatus() > 2}selected{/if}>{_admin_user_online_default}</option>
<option value="1" {if $user->onlineStatus() == 1}selected{/if}>{_admin_user_online_incognite}</option>
<option value="2" {if $user->onlineStatus() == 2}selected{/if}>{_admin_user_online_deceased}</option>
</select>
</div>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
<input class="button submit" type="submit" value="{_save}">
</div>
</div>
</form>
</div>
{/block}

View file

@ -2,14 +2,16 @@
{var $search = true}
{block title}
Пользователи
{_admin_user_search}
{/block}
{block heading}
Пиздюки
{_users}
{/block}
{block searchTitle}Поиск пиздюков{/block}
{block searchTitle}
{include title}
{/block}
{block content}
{var $users = iterator_to_array($users)}
@ -18,12 +20,12 @@
<table class="aui aui-table-list">
<thead>
<tr>
<th>#</th>
<th>Имя</th>
<th>Пол</th>
<th>Короткий адрес</th>
<th>Дата регистрации</th>
<th>Действия</th>
<th>ID</th>
<th>{_admin_name}</th>
<th>{_gender}</th>
<th>{_admin_shortcode}</th>
<th>{_registration_date}</th>
<th>{_admin_actions}</th>
</tr>
</thead>
<tbody>
@ -35,23 +37,21 @@
<img src="{$user->getAvatarUrl('miniscule')}" alt="{$user->getCanonicalName()}" style="object-fit: cover;" role="presentation" />
</span>
</span>
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
заблокирован
</span>
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_banned}</span>
</td>
<td>{$user->isFemale() ? "Женский" : "Мужской"}</td>
<td>{$user->getShortCode() ?? "(отсутствует)"}</td>
<td>{$user->isFemale() ? tr("female") : tr("male")}</td>
<td>{$user->getShortCode() ?? "(" . tr("none") . ")"}</td>
<td>{$user->getRegistrationTime()}</td>
<td>
<a class="aui-button aui-button-primary" href="/admin/users/id{$user->getId()}">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
</a>
{if $thisUser->getChandlerUser()->can("substitute")->model('openvk\Web\Models\Entities\User')->whichBelongsTo(0)}
<a class="aui-button" href="/setSID/{$user->getChandlerUser()->getId()}?hash={rawurlencode($csrfToken)}">
<span class="aui-icon aui-icon-small aui-iconfont-sign-in">Войти как</span>
<span class="aui-icon aui-icon-small aui-iconfont-sign-in">{_admin_loginas}</span>
</a>
{/if}
</td>
@ -61,12 +61,8 @@
<br/>
<div align="right">
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -5,45 +5,36 @@
{/block}
{block heading}
{_edit} {$form->token ?? "undefined"}
{_edit} #{$form->token ?? "undefined"}
{/block}
{block content}
<div style="margin: 8px -8px;" class="aui-tabs horizontal-tabs">
<ul class="tabs-menu">
<li class="menu-item active-tab">
<a href="#info">Информация</a>
<a href="#info">{_admin_tab_main}</a>
</li>
<li class="menu-item">
<a href="#activators">{_voucher_activators}</a>
</li>
</ul>
<div class="tabs-pane active-pane" id="info">
<form class="aui" method="POST">
<div class="field-group">
<label for="id">
ID
</label>
<label for="id">ID</label>
<input class="text long-field" type="number" id="id" name="id" disabled value="{$form->id}" />
</div>
<div class="field-group">
<label for="token">
Серийный номер
</label>
<label for="token">{_admin_voucher_serial}</label>
<input class="text long-field" type="text" id="token" name="token" value="{$form->token}" />
<div class="description">Номер состоит из 24 символов, если формат неправильный или поле не заполнено, будет назначен автоматически.</div>
<div class="description">{_admin_voucher_serial_desc}</div>
</div>
<div class="field-group">
<label for="coins">
Количество голосов
</label>
<label for="coins">{_admin_voucher_coins}</label>
<input class="text long-field" type="number" min="0" id="coins" name="coins" value="{$form->coins}" />
</div>
<div class="field-group">
<label for="rating">
Количество рейтинга
</label>
<label for="rating">{_admin_voucher_rating}</label>
<input class="text long-field" type="number" min="0" id="rating" name="rating" value="{$form->rating}" />
</div>
<div class="field-group">
@ -55,9 +46,9 @@
{/if}
</label>
<input class="text long-field" type="number" min="-1" id="usages" name="usages" value="{$form->usages}" />
<div class="description">Количество аккаунтов, которые могут использовать ваучер. Если написать -1, будет Infinity.</div>
<div class="description">{_admin_voucher_usages_desc}</div>
</div>
<div class="buttons-container">
<div class="buttons">
<input type="hidden" name="hash" value="{$csrfToken}" />
@ -66,7 +57,6 @@
</div>
</form>
</div>
<div class="tabs-pane" id="activators">
<table rules="none" class="aui aui-table-list">
<tbody>
@ -77,12 +67,10 @@
<img src="{$user->getAvatarUrl('miniscule')}" alt="{$user->getCanonicalName()}" role="presentation" />
</span>
</span>
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">
заблокирован
</span>
<span n:if="$user->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_banned}</span>
</td>
</tr>
</tbody>

View file

@ -8,7 +8,7 @@
<a style="float: right;" class="aui-button aui-button-primary" href="/admin/vouchers/id0">
{_create}
</a>
<h1>{_vouchers}</h1>
{/block}
@ -16,13 +16,13 @@
<table class="aui aui-table-list">
<thead>
<tr>
<th>#</th>
<th>Серийный номер</th>
<th>Голоса</th>
<th>Рейгтинг</th>
<th>Осталось использований</th>
<th>Состояние</th>
<th>Действия</th>
<th>ID</th>
<th>{_admin_voucher_serial}</th>
<th>{_coins}</th>
<th>{_admin_voucher_rating}</th>
<th>{_usages_left}</th>
<th>{_admin_voucher_status}</th>
<th>{_admin_actions}</th>
</tr>
</thead>
<tbody>
@ -34,28 +34,25 @@
<td>{$voucher->getRemainingUsages() === INF ? "∞" : $voucher->getRemainingUsages()}</td>
<td>
{if $voucher->isExpired()}
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">закончился</span>
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed">{_admin_voucher_status_closed}</span>
{else}
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">активен</span>
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-success">{_admin_voucher_status_opened}</span>
{/if}
</td>
<td>
<a class="aui-button aui-button-primary" href="/admin/vouchers/id{$voucher->getId()}">
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span>
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">{_edit}</span>
</a>
</td>
</tr>
</tbody>
</table>
<br/>
<div align="right">
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($vouchers)) < $count}
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">
⭁ туда
</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">
⭇ сюда
</a>
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}">&laquo;</a>
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}">&raquo;</a>
</div>
{/block}

View file

@ -29,7 +29,7 @@
</tr>
<tr>
<td><span class="nobold">{_"description"}:</span></td>
<td>{$club->getDescription()}</td>
<td>{$club->getDescriptionHtml()|noescape}</td>
</tr>
<tr n:if="!is_null($club->getWebsite())">
<td><span class="nobold">{_"website"}: </span></td>

View file

@ -51,9 +51,9 @@
{if $isMain}
<h4>{_support_faq}</h4><br />
<div class="faq">
<div id="faqhead">{_support_faq_title}</div>
<div id="faqcontent">{_support_faq_content}</div>
<div n:foreach="$faq as $section" class="faq">
<div id="faqhead">{$section[0]}</div>
<div id="faqcontent">{$section[1]|noescape}</div>
</div>
{/if}

View file

@ -64,7 +64,7 @@
<span class="nobold">{_"2fa_code"}</span>
</td>
<td>
<input type="text" name="code" style="width: 100%;" />
<input type="text" name="password_change_code" style="width: 100%;" />
</td>
</tr>
<tr>
@ -154,6 +154,38 @@
{$user->getEmail()}
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">{_new_email_address}</span>
</td>
<td>
<input type="email" name="new_email" style="width: 100%;" />
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">{_password}</span>
</td>
<td>
<input type="password" name="email_change_pass" style="width: 100%;" />
</td>
</tr>
<tr n:if="$user->is2faEnabled()">
<td width="120" valign="top">
<span class="nobold">{_"2fa_code"}</span>
</td>
<td>
<input type="text" name="email_change_code" style="width: 100%;" />
</td>
</tr>
<tr>
<td>
</td>
<td>
<input type="submit" value="{_save_email_address}" class="button" />
</td>
</tr>
</tbody>
</table>
<br/>
@ -447,24 +479,35 @@
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">NSFW-контент</span>
<span class="nobold">{_ui_settings_nsfw_content}</span>
</td>
<td>
<select name="nsfw">
<option value="0" {if $user->getNsfwTolerance() === 0}selected{/if}>Не показывать в глобальной ленте</option>
<option value="1" {if $user->getNsfwTolerance() === 1}selected{/if}>Только замазывать</option>
<option value="2" {if $user->getNsfwTolerance() === 2}selected{/if}>Показывать</option>
<option value="0" {if $user->getNsfwTolerance() === 0}selected{/if}>{_ui_settings_nsfw_content_dont_show}</option>
<option value="1" {if $user->getNsfwTolerance() === 1}selected{/if}>{_ui_settings_nsfw_content_blur}</option>
<option value="2" {if $user->getNsfwTolerance() === 2}selected{/if}>{_ui_settings_nsfw_content_show}</option>
</select>
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">Вид постов</span>
<span class="nobold">{_ui_settings_view_of_posts}</span>
</td>
<td>
<select name="microblog">
<option value="0" {if !$user->hasMicroblogEnabled()}selected{/if}>Старый</option>
<option value="1" {if $user->hasMicroblogEnabled()}selected{/if}>Микроблог</option>
<option value="0" {if !$user->hasMicroblogEnabled()}selected{/if}>{_ui_settings_view_of_posts_old}</option>
<option value="1" {if $user->hasMicroblogEnabled()}selected{/if}>{_ui_settings_view_of_posts_microblog}</option>
</select>
</td>
</tr>
<tr>
<td width="120" valign="top">
<span class="nobold">{_ui_settings_main_page}</span>
</td>
<td>
<select name="main_page">
<option value="0" {if !$user->getMainPage()}selected{/if}>{_my_page}</option>
<option value="1" {if $user->getMainPage()}selected{/if}>{_my_feed}</option>
</select>
</td>
</tr>

View file

@ -0,0 +1,35 @@
<div class="cookies-popup" style="display: none;">
<div class="contanier">
<div class="text">
{tr("cookies_popup_content")|noescape}
</div>
<div class="buttons">
<a href="javascript:agreeWithCookies()" class="button">
{_cookies_popup_agree}
</a>
</div>
</div>
</div>
<script>
let cookie = decodeURIComponent(document.cookie).split(";");
let cookiesAgreed = false;
for(let i = 0; i < cookie.length; i++) {
let c = cookie[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if(c == "cookiesAgreed=true") {
cookiesAgreed = true;
break;
}
}
if(!cookiesAgreed) {
u(".cookies-popup").nodes[0].style.display = "block";
}
function agreeWithCookies() {
document.cookie = "cookiesAgreed=true";
u(".cookies-popup").nodes[0].style.display = "none";
}
</script>

View file

@ -2,7 +2,7 @@
{var $comments = $post->getLastComments(3)}
{var $commentsCount = $post->getCommentsCount()}
{var $commentTextAreaId = $post === null ? rand(1,300) : $post->getId()}
{var $commentTextAreaId = $post === NULL ? rand(1,300) : $post->getId()}
<table border="0" style="font-size: 11px;" n:class="post, !$compact ? post-divider, $post->isExplicit() ? post-nsfw">
<tbody>

View file

@ -1,5 +1,5 @@
{php if(!isset($GLOBALS["textAreaCtr"])) $GLOBALS["textAreaCtr"] = 10;}
{var $textAreaId = ($post ?? NULL) === null ? (++$GLOBALS["textAreaCtr"]) : $post->getId()}
{var $textAreaId = ($post ?? NULL) === NULL ? (++$GLOBALS["textAreaCtr"]) : $post->getId()}
<div id="write" style="padding: 5px 0;" onfocusin="expand_wall_textarea({$textAreaId});">
<form action="{$route}" method="post" enctype="multipart/form-data" style="margin:0;">

View file

@ -73,6 +73,8 @@ routes:
handler: "User->disableTwoFactorAuth"
- url: "/settings/reset_theme"
handler: "User->resetThemepack"
- url: "/settings/change_email"
handler: "User->emailChangeFinish"
- url: "/coins_transfer"
handler: "User->coinsTransfer"
- url: "/increase_social_credits"

View file

@ -1913,4 +1913,25 @@ table td[width="120"] {
border-bottom: 1.5px solid #707070;
text-align: center;
user-select: none;
}
}
.cookies-popup {
position: fixed;
bottom: 0;
width: 100%;
height: 40px;
background: linear-gradient(#fff, #eee);
box-shadow: inset 0px 1px 0px #bbb, inset 0px 2px 0px #ddd;
}
.cookies-popup .contanier {
width: 760px;
display: flex;
margin: 0 auto;
align-items: center;
height: 100%;
}
.cookies-popup .contanier .text {
width: 100%;
}

View file

@ -2,32 +2,12 @@ Function.noop = () => {};
var _n_counter = 0;
var _activeWindow = true;
const _pageTitle = u("title").nodes[0].innerText;
var counter = 0;
/* this fucking dumb shit is broken :c
window.addEventListener('focus', () => {
_activeWindow = true;
closeAllNotifications();
window.addEventListener("focus", () => {
document.title = document.title.replace(/^\([0-9]+\) /, ""); // remove notification counter xD
});
window.addEventListener('blur', () => {_activeWindow = false});
function closeAllNotifications() {
var notifications = u(".notifications_global_wrap").nodes[0].children;
for (var i = 0; i < notifications.length; i++) {
setTimeout(() => {
console.log(i);
notifications.item(i).classList.add('disappears');
setTimeout(() => {notifications.item(i).remove()}, 500).bind(this);
}, 5000).bind(this);
}
} */
function NewNotification(title, body, avatar = null, callback = () => {}, time = 5000, count = true) {
if(avatar != null) {
avatar = '<avatar>' +
@ -62,18 +42,19 @@ function NewNotification(title, body, avatar = null, callback = () => {}, time =
}
function __closeNotification() {
if(document.visibilityState != "visible")
return setTimeout(() => {__closeNotification()}, time); // delay notif deletion
getPrototype().addClass('disappears');
setTimeout(() => {getPrototype().remove()}, 500);
return setTimeout(() => {getPrototype().remove()}, 500);
}
if(count == true) {
counter++;
document.title = `(${counter}) ${_pageTitle}`;
document.title = `(${counter}) ${document.title}`;
}
/* if(_activeWindow == true) { */
setTimeout(() => {__closeNotification()}, time);
/* } */
setTimeout(() => {__closeNotification()}, time);
notification.children('notification_title').children('a.close').on('click', function(e) {
__closeNotification();

View file

@ -171,7 +171,7 @@ function repostPost(id, hash) {
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = (function() {
if(xhr.responseText.indexOf("wall_owner") === -1)
MessageBox(tr('error'), tr('error_repost_fail'), tr('ok'), [Function.noop]);
MessageBox(tr('error'), tr('error_repost_fail'), [tr('ok')], [Function.noop]);
else {
let jsonR = JSON.parse(xhr.responseText);
NewNotification(tr('information_-1'), tr('shared_succ'), null, () => {window.location.href = "/wall" + jsonR.wall_owner});

View file

@ -136,7 +136,7 @@ function isLanguageAvailable($lg): bool
function getBrowsersLanguage(): array
{
if ($_SERVER['HTTP_ACCEPT_LANGUAGE'] != null) return mb_split(",", mb_split(";", $_SERVER['HTTP_ACCEPT_LANGUAGE'])[0]);
if ($_SERVER['HTTP_ACCEPT_LANGUAGE'] != NULL) return mb_split(",", mb_split(";", $_SERVER['HTTP_ACCEPT_LANGUAGE'])[0]);
else return array();
}
@ -144,7 +144,7 @@ function eventdb(): ?DatabaseConnection
{
$conf = OPENVK_ROOT_CONF["openvk"]["credentials"]["eventDB"];
if(!$conf["enable"])
return null;
return NULL;
$db = (object) $conf["database"];
return DatabaseConnection::connect([
@ -216,8 +216,8 @@ return (function() {
setlocale(LC_TIME, "POSIX");
// TODO: Default language in config
if(Session::i()->get("lang") == null) {
# TODO: Default language in config
if(Session::i()->get("lang") == NULL) {
$languages = array_reverse(getBrowsersLanguage());
foreach($languages as $lg) {
if(isLanguageAvailable($lg)) setLanguage($lg);
@ -233,7 +233,7 @@ return (function() {
else
$ver = "Public Technical Preview 3";
// Unix time constants
# Unix time constants
define('MINUTE', 60);
define('HOUR', 60 * MINUTE);
define('DAY', 24 * HOUR);

View file

@ -0,0 +1,2 @@
# Who is this website for?
The site is designed to find friends and acquaintances, as well as view user data. It is like a city directory, through which people can quickly find relevant information about a person.

View file

@ -0,0 +1,2 @@
# Для кого этот сайт?
Сайт предназначен для поиска друзей и знакомых, а также для просмотра данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке.

View file

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS `email_change_verifications` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`profile` bigint(20) unsigned NOT NULL,
`key` char(64) COLLATE utf8mb4_unicode_520_ci NOT NULL,
`new_email` varchar(90) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL,
`timestamp` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

View file

@ -0,0 +1 @@
ALTER TABLE `profiles` ADD COLUMN `main_page` tinyint(3) unsigned NOT NULL DEFAULT 0 AFTER `microblog`;

View file

@ -9,6 +9,7 @@
"home" = "Գլխավոր";
"welcome" = "Բարի գալուստ";
"to_top" = "Վերև";
/* Login */
@ -381,7 +382,6 @@
"left_menu_donate" = "Աջակցել";
"footer_about_instance" = "հոսքի մասին";
"footer_blog" = "բլոգ";
"footer_help" = "օգնություն";
@ -410,6 +410,8 @@
"style" = "Ոճ";
"default" = "Սովորական";
"arbitrary_avatars" = "Կամայական";
"cut" = "Կտրվածք";
"round_avatars" = "Կլոր ավատար";
@ -519,6 +521,8 @@
"videos_many" = "$1 տեսանյութ";
"videos_other" = "$1 տեսանյութ";
"view_video" = "Դիտում";
/* Notifications */
"feedback" = "Հետադարձ կապ";
@ -624,6 +628,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" = "Նվեր";
@ -703,6 +722,9 @@
"ticket_changed" = "Տոմսը փոփոխված է";
"ticket_changed_comment" = "Փոփոխությունները ուժի մեջ կմտնեն մի քանի վայրկյանից։";
"banned_in_support_1" = "Կներե՛ք, <b>$1</b>, բայց հիմա Ձեզ թույլատրված չէ դիմումներ ստեղծել։";
"banned_in_support_2" = "Դրա պատճառաբանությունը սա է․ <b>$1</b>։ Ցավո՛ք, այդ հնարավորությունը մենք Ձեզնից վերցրել ենք առհավետ։";
/* Invite */
"invite" = "Հրավիրել";
@ -711,9 +733,9 @@
/* Banned */
"banned_title" = "Բլոկավորված եք";
"banned_header" = "Ձեզ կասեցրել է կառլենի անհաջող բոցը։";
"banned_alt" = "Օգտատերը բլոկավորված է";
"banned_title" = "Արգելափակված եք";
"banned_header" = "Ձեզ կասեցրել է կարլենի անհաջող բոցը։";
"banned_alt" = "Օգտատերը արգելափակված է";
"banned_1" = "Կներե՛ք, <b>$1</b>, բայց Դուք կասեցված եք։";
"banned_2" = "Պատճառը հետևյալն է․ <b>$1</b>. Ափսոս, բայց մենք ստիպված Ձեզ հավերժ ենք կասեցրել;";
"banned_3" = "Դուք դեռ կարող եք <a href=\"/support?act=new\">գրել նամակ աջակցության ծառայությանը</a>, եթե համարում եք որ դա սխալմունք է, կամ էլ կարող եք <a href=\"/logout?hash=$1\">դուրս գալ</a>։";
@ -835,6 +857,7 @@
"captcha_error" = "Սխալ են գրված սիմվոլները";
"captcha_error_comment" = "Խնդրում ենք համոզվել, որ ճիշտ եք ներմուծել կապտչայի սիմվոլները։";
/* Admin actions */
"login_as" = "Մտնել ինչպես $1";
@ -843,7 +866,84 @@
"ban_user_action" = "Բլոկավորել օգտվողին";
"warn_user_action" = "Զգուշացնել օգտվողին";
/* Paginator (subject to delete) */
/* Admin panel */
"admin" = "Ադմին-վահանակ";
"admin_ownerid" = "Տիրոջ ID";
"admin_author" = "Հեղինակ";
"admin_name" = "Անուն";
"admin_title" = "Անվանում";
"admin_description" = "Նկարագրություն";
"admin_first_known_ip" = "Առաջին IP";
"admin_shortcode" = "Կարճ հասցե";
"admin_verification" = "Վերիֆիկացիա";
"admin_banreason" = "Արգելափակման պատճառ";
"admin_banned" = "արգելափակված է";
"admin_actions" = "Գործողություններ";
"admin_image" = "Նկար";
"admin_image_replace" = "Փոխե՞լ նկարը";
"admin_uses" = "Օգտագործումներ";
"admin_uses_reset" = "Զրոյացնե՞լ օգտագործումների քանակը";
"admin_limits" = "Սահմանափակումներ";
"admin_limits_reset" = "Զրոյացնել օգտագործումների քանակը";
"admin_open" = "Բացել";
"admin_loginas" = "Մուտք գործել ինչպես...";
"admin_commonsettings" = "Ընդհանուր կարգավորումներ";
"admin_langsettings" = "Լեզվից կախված կարգավորումներ";
"admin_tab_main" = "Գլխավոր";
"admin_tab_ban" = "Բլոկավորում";
"admin_tab_followers" = "Մասնակիցներ";
"admin_overview" = "Դիտում";
"admin_overview_summary" = "Ամփոփում";
"admin_content" = "Օգտատիրային կոնտենտ";
"admin_user_search" = "Օգտատերերի որոնում";
"admin_user_online" = "Օնլայն վիճակ";
"admin_user_online_default" = "Ըստ նախնականի";
"admin_user_online_incognito" = "Ինկոգնիտո";
"admin_user_online_deceased" = "Հանգուցյալ";
"admin_club_search" = "Խմբերի որոնում";
"admin_club_excludeglobalfeed" = "Չ՛ցույց տալ գլոբալ ժապավենում";
"admin_services" = "Վճարովի ծառայություններ";
"admin_newgift" = "Նոր նվեր";
"admin_price" = "Գին";
"admin_giftset" = "Նվերների հավաքախու";
"admin_giftsets" = "Նվերների հավաքախուներ";
"admin_giftsets_none" = "Նվերների հավաքածու չկա։ Ստեղծե՛ք հավաքածու նվեր ավելացնելու համար։";
"admin_giftsets_create" = "Ստեղծել նվերների հավաքածու";
"admin_giftsets_title" = "Հավաքածույի ներքին անվանում, եթե չի հաջողվում որոնել այն օգտատիրոջ լեզվով";
"admin_giftsets_description" = "Հավաքածույի ներքին նկարագրություն, եթե չի հաջողվում որոնել այն օգտատիրոջ լեզվով";
"admin_price_free" = "անվճար";
"admin_voucher_rating" = "Վարկանիշ";
"admin_voucher_serial" = "Սերիական համար";
"admin_voucher_serial_desc" = "Համարը բաղկացած է 24 նշից։ Եթե Դուք այն սխալ գրեք, այն կտրվի ավտոմատ։";
"admin_voucher_coins" = "Ձայների քանակ";
"admin_voucher_rating" = "Վարկանշի քանակ";
"admin_voucher_usages_desc" = "Վաուչերը օգտագործող ակկաունտների քանակ։ Եթե գրեք -1, ապա այն կլինի օգտագործել անվերջ։";
"admin_voucher_status" = "Կարգավիճակ";
"admin_voucher_status_opened" = "ակտիվ է";
"admin_voucher_status_closed" = "վերջացել է";
"admin_settings" = "Կարգավորումներ";
"admin_settings_tuning" = "Ընդհանուր";
"admin_settings_appearance" = "Արտաքին տեսք";
"admin_settings_security" = "Անվտանգություն";
"admin_settings_integrations" = "Ինտեգրացիաներ";
"admin_settings_system" = "Համակարգ";
"admin_about" = "OpenVK-ի մասին";
"admin_about_version" = "Վերսիա";
"admin_about_instance" = "Հոսք";
"admin_commerce_disabled" = "Կոմմերցիան անջատված է համակարգային ադմինիստրատորի կողմից";
"admin_commerce_disabled_desc" = "Վաուչերների և նվերների կարգավորումները կպահպանվեն, բայց ոչ մի ազդեցություն չեն ունենա։";
/* Paginator (deprecated) */
"paginator_back" = "Հետ";
"paginator_page" = "$1 էջ";
@ -857,6 +957,8 @@
"rules" = "Կանոններ";
"most_popular_groups" = "Ամենահայտնի խմբերը";
"on_this_instance_are" = "Այս հոսքում․";
"about_links" = "Հղումներ";
"instance_links" = "Հոսքերի հղումներ․";
"about_users_one" = "<b>Մեկ</b> օգտատեր";
"about_users_few" = "<b>$1</b> օգտատեր";

View file

@ -470,3 +470,7 @@
"comment" = "Каментарый";
"sender" = "Адпраўнік";
/* Cookies pop-up */
"cookies_popup_content" = "Усе хлопчыкi любяць пэчыва, таму гэты вэб-сайт выкарыстоўвае Cookies для таго, каб ідэнтыфікаваць вашу сесію і нічога болей. Звяртайцеся да нашай <a href='/privacy'>палiтыкi канфiдэнцыальнастi</a> для палучэння поўнай iнфармацыi.";
"cookies_popup_agree" = "Прынiмаю";

View file

@ -444,6 +444,8 @@
"your_page_address" = "Your address page";
"page_address" = "Address page";
"current_email_address" = "Current email address";
"new_email_address" = "New email address";
"save_email_address" = "Save email address";
"page_id" = "Page ID";
"you_can_also" = "You can also";
"delete_your_page" = "delete your page";
@ -454,10 +456,20 @@
"ui_settings_rating" = "Rating";
"ui_settings_rating_show" = "Show";
"ui_settings_rating_hide" = "Hide";
"ui_settings_nsfw_content" = "NSFW content";
"ui_settings_nsfw_content_dont_show" = "Don't show in global feed";
"ui_settings_nsfw_content_blur" = "Just blur";
"ui_settings_nsfw_content_show" = "Show";
"ui_settings_view_of_posts" = "View of posts";
"ui_settings_view_of_posts_old" = "Old";
"ui_settings_view_of_posts_microblog" = "Microblog";
"ui_settings_main_page" = "Main page";
"additional_links" = "Additional links";
"ad_poster" = "Ad poster";
"email_change_confirm_message" = "Please confirm your new email address for the change to take effect. We have sent instructions to it.";
/* Two-factor authentication */
"two_factor_authentication" = "Two-factor authentication";
@ -661,9 +673,6 @@
"support_list" = "List of tickets";
"support_new" = "New ticket";
"support_faq_title" = "Who is this website for?";
"support_faq_content" = "The site is designed to find friends and acquaintances, as well as view user data. It is like a city directory, through which people can quickly find relevant information about a person.";
"support_new_title" = "Enter the topic of your ticket";
"support_new_content" = "Describe the issue or suggestion";
@ -847,6 +856,84 @@
"ban_in_support_user_action" = "Ban in support";
"unban_in_support_user_action" = "Unban in support";
/* Admin panel */
"admin" = "Admin panel";
"admin_ownerid" = "Owner ID";
"admin_author" = "Author";
"admin_name" = "Name";
"admin_title" = "Title";
"admin_description" = "Description";
"admin_first_known_ip" = "First known IP";
"admin_shortcode" = "Short code";
"admin_verification" = "Verification";
"admin_banreason" = "Ban reason";
"admin_banned" = "banned";
"admin_gender" = "Gender";
"admin_registrationdate" = "Registration date";
"admin_actions" = "Actions";
"admin_image" = "Image";
"admin_image_replace" = "Replace the image?";
"admin_uses" = "Uses";
"admin_uses_reset" = "Reset the number of uses?";
"admin_limits" = "Limits";
"admin_limits_reset" = "Reset the number of limits";
"admin_open" = "Open";
"admin_loginas" = "Login as...";
"admin_commonsettings" = "Common settings";
"admin_langsettings" = "Language-dependent settings";
"admin_tab_main" = "General";
"admin_tab_ban" = "Ban";
"admin_tab_followers" = "Followers";
"admin_overview" = "Overview";
"admin_overview_summary" = "Summary";
"admin_content" = "User-generated content";
"admin_user_search" = "Search for users";
"admin_user_online" = "Online status";
"admin_user_online_default" = "Default";
"admin_user_online_incognito" = "Incognito";
"admin_user_online_deceased" = "Deceased";
"admin_club_search" = "Search for groups";
"admin_club_excludeglobalfeed" = "Do not display posts in the global feed";
"admin_services" = "Paid services";
"admin_newgift" = "New gift";
"admin_price" = "Price";
"admin_giftset" = "Gift set";
"admin_giftsets" = "Gift sets";
"admin_giftsets_none" = "There are no gift sets. Create a set to create a gift.";
"admin_giftsets_create" = "Create a gift set";
"admin_giftsets_title" = "The internal name of the set, which will be used if no name can be found in the user's language.";
"admin_giftsets_description" = "The internal description of the set, which will be used if no name can be found in the user's language.";
"admin_price_free" = "free";
"admin_voucher_rating" = "Rating";
"admin_voucher_serial" = "Serial number";
"admin_voucher_serial_desc" = "The number consists of 24 characters. If the format is incorrect or the field is not filled in, it will be assigned automatically.";
"admin_voucher_coins" = "Number of votes";
"admin_voucher_rating" = "Number of rating";
"admin_voucher_usages_desc" = "The number of accounts that can use the voucher. If you type -1, it will be infinity.";
"admin_voucher_status" = "Status";
"admin_voucher_status_opened" = "active";
"admin_voucher_status_closed" = "closed";
"admin_settings" = "Settings";
"admin_settings_tuning" = "General";
"admin_settings_appearance" = "Appearance";
"admin_settings_security" = "Security";
"admin_settings_integrations" = "Integrations";
"admin_settings_system" = "System";
"admin_about" = "About OpenVK";
"admin_about_version" = "Version";
"admin_about_instance" = "Instance";
"admin_commerce_disabled" = "Commerce has been disabled by the system administrator";
"admin_commerce_disabled_desc" = "The voucher and gift settings will be saved, but will have no effect.";
/* Paginator (deprecated) */
"paginator_back" = "Back";
@ -895,3 +982,8 @@
/* User alerts */
"user_alert_scam" = "This account has been reported a lot for scam. Please be careful, especially if he asked for money.";
/* Cookies pop-up */
"cookies_popup_content" = "All kids love cookie, so this website uses Cookies to identify your session and nothing more. Check <a href='/privacy'>our privacy policy</a> for more information.";
"cookies_popup_agree" = "Accept";

View file

@ -472,6 +472,8 @@
"your_page_address" = "Адрес Вашей страницы";
"page_address" = "Адрес страницы";
"current_email_address" = "Текущий адрес";
"new_email_address" = "Новый адрес";
"save_email_address" = "Сохранить адрес";
"page_id" = "ID страницы";
"you_can_also" = "Вы также можете";
"delete_your_page" = "удалить свою страницу";
@ -482,10 +484,20 @@
"ui_settings_rating" = "Рейтинг";
"ui_settings_rating_show" = "Показывать";
"ui_settings_rating_hide" = "Скрывать";
"ui_settings_nsfw_content" = "NSFW-контент";
"ui_settings_nsfw_content_dont_show" = "Не показывать в глобальной ленте";
"ui_settings_nsfw_content_blur" = "Только замазывать";
"ui_settings_nsfw_content_show" = "Показывать";
"ui_settings_view_of_posts" = "Вид постов";
"ui_settings_view_of_posts_old" = "Старый";
"ui_settings_view_of_posts_microblog" = "Микроблог";
"ui_settings_main_page" = "Главная страница";
"additional_links" = "Дополнительные ссылки";
"ad_poster" = "Рекламный плакат";
"email_change_confirm_message" = "Чтобы изменение вступило в силу, подтвердите ваш новый адрес электронной почты. Мы отправили инструкции на него.";
/* Two-factor authentication */
"two_factor_authentication" = "Двухфакторная аутентификация";
@ -698,9 +710,6 @@
"support_list" = "Список обращений";
"support_new" = "Новое обращение";
"support_faq_title" = "Для кого этот сайт?";
"support_faq_content" = "Сайт предназначен для поиска друзей и знакомых, а также для просмотра данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке.";
"support_new_title" = "Введите тему вашего обращения";
"support_new_content" = "Опишите проблему или предложение";
@ -890,6 +899,82 @@
"ban_in_support_user_action" = "Заблокировать в поддержке";
"unban_in_support_user_action" = "Разблокировать в поддержке";
/* Admin panel */
"admin" = "Админ-панель";
"admin_ownerid" = "ID владельца";
"admin_author" = "Автор";
"admin_name" = "Имя";
"admin_title" = "Название";
"admin_description" = "Описание";
"admin_first_known_ip" = "Первый IP";
"admin_shortcode" = "Короткий адрес";
"admin_verification" = "Верификация";
"admin_banreason" = "Причина блокировки";
"admin_banned" = "заблокирован";
"admin_actions" = "Действия";
"admin_image" = "Изображение";
"admin_image_replace" = "Заменить изображение?";
"admin_uses" = "Использований";
"admin_uses_reset" = "Сбросить количество использований?";
"admin_limits" = "Ограничения";
"admin_limits_reset" = "Сбросить количество ограничений";
"admin_open" = "Открыть";
"admin_loginas" = "Войти как...";
"admin_commonsettings" = "Общие настройки";
"admin_langsettings" = "Языко-зависимые настройки";
"admin_tab_main" = "Главное";
"admin_tab_ban" = "Блокировка";
"admin_tab_followers" = "Участники";
"admin_overview" = "Обзор";
"admin_overview_summary" = "Сводка";
"admin_content" = "Пользовательский контент";
"admin_user_search" = "Поиск пользователей";
"admin_user_online" = "Онлайн статус";
"admin_user_online_default" = "По-умолчанию";
"admin_user_online_incognito" = "Инкогнито";
"admin_user_online_deceased" = "Покойник";
"admin_club_search" = "Поиск групп";
"admin_club_excludeglobalfeed" = "Не отображать записи в глобальной ленте";
"admin_services" = "Платные услуги";
"admin_newgift" = "Новый подарок";
"admin_price" = "Цена";
"admin_giftset" = "Набор подарков";
"admin_giftsets" = "Наборы подарков";
"admin_giftsets_none" = "Нет наборов подарков. Создайте набор, чтобы создать подарок.";
"admin_giftsets_create" = "Создать набор подарков";
"admin_giftsets_title" = "Внутреннее название набора, которое будет использоваться, если не удаётся найти название на языке пользователя.";
"admin_giftsets_description" = "Внутреннее описание набора, которое будет использоваться, если не удаётся найти название на языке пользователя.";
"admin_price_free" = "бесплатный";
"admin_voucher_rating" = "Рейтинг";
"admin_voucher_serial" = "Серийный номер";
"admin_voucher_serial_desc" = "Номер состоит из 24 символов. Если формат неправильный или поле не заполнено, будет назначен автоматически.";
"admin_voucher_coins" = "Количество голосов";
"admin_voucher_rating" = "Количество рейтинга";
"admin_voucher_usages_desc" = "Количество аккаунтов, которые могут использовать ваучер. Если написать -1, будет бесконечность.";
"admin_voucher_status" = "Состояние";
"admin_voucher_status_opened" = "активен";
"admin_voucher_status_closed" = "закончился";
"admin_settings" = "Настройки";
"admin_settings_tuning" = "Общее";
"admin_settings_appearance" = "Внешний вид";
"admin_settings_security" = "Безопасность";
"admin_settings_integrations" = "Интеграции";
"admin_settings_system" = "Система";
"admin_about" = "Об OpenVK";
"admin_about_version" = "Версия";
"admin_about_instance" = "Инстанция";
"admin_commerce_disabled" = "Коммерция отключена системным администратором";
"admin_commerce_disabled_desc" = "Настройки ваучеров и подарков будут сохранены, но не будут оказывать никакого влияния.";
/* Paginator (deprecated) */
"paginator_back" = "Назад";
@ -948,3 +1033,8 @@
/* User alerts */
"user_alert_scam" = "На этот аккаунт много жаловались в связи с мошенничеством. Пожалуйста, будьте осторожны, особенно если у вас попросят денег.";
/* Cookies pop-up */
"cookies_popup_content" = "Все дети любят печенье, поэтому этот веб-сайт использует Cookies для того, чтобы идентифицировать вашу сессию и ничего более. Ознакомьтесь с нашей <a href='/privacy'>политикой конфиденциальности</a> для получения дополнительной информации.";
"cookies_popup_agree" = "Согласен";