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

View file

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

View file

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

View file

@ -63,7 +63,7 @@ final class Likes extends VKAPIRequestHandler
return (object)[ return (object)[
"liked" => (int) $post->hasLikeFrom($user), "liked" => (int) $post->hasLikeFrom($user),
"copied" => 0 // TODO: handle this "copied" => 0 # TODO: handle this
]; ];
break; break;
default: 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 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; $users = new UsersRepo;
if($user_ids == "0") if($user_ids == "0")
@ -34,7 +34,7 @@ final class Users extends VKAPIRequestHandler
"last_name" => "", "last_name" => "",
"deactivated" => "deleted" "deactivated" => "deleted"
]; ];
}else if($usrs[$i] == null){ }else if($usrs[$i] == NULL){
}else{ }else{
$response[$i] = (object)[ $response[$i] = (object)[
@ -73,21 +73,21 @@ final class Users extends VKAPIRequestHandler
case 'photo_200': case 'photo_200':
$response[$i]->photo_50 = $usr->getAvatarURL("normal"); $response[$i]->photo_50 = $usr->getAvatarURL("normal");
break; break;
case 'photo_200_orig': // вообще не ебу к чему эта строка ну пусть будет кек case 'photo_200_orig': # вообще не ебу к чему эта строка ну пусть будет кек
$response[$i]->photo_50 = $usr->getAvatarURL("normal"); $response[$i]->photo_50 = $usr->getAvatarURL("normal");
break; break;
case 'photo_400_orig': case 'photo_400_orig':
$response[$i]->photo_50 = $usr->getAvatarURL("normal"); $response[$i]->photo_50 = $usr->getAvatarURL("normal");
break; break;
// Она хочет быть выебанной видя матан # Она хочет быть выебанной видя матан
// Покайфу когда ты Виет а вокруг лишь дискриминант # Покайфу когда ты Виет а вокруг лишь дискриминант
case 'status': case 'status':
if($usr->getStatus() != null) if($usr->getStatus() != NULL)
$response[$i]->status = $usr->getStatus(); $response[$i]->status = $usr->getStatus();
break; break;
case 'screen_name': case 'screen_name':
if($usr->getShortCode() != null) if($usr->getShortCode() != NULL)
$response[$i]->screen_name = $usr->getShortCode(); $response[$i]->screen_name = $usr->getShortCode();
break; break;
case 'friend_status': 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) { 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(); $from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId();
$attachments; $attachments = [];
foreach($post->getChildren() as $attachment) foreach($post->getChildren() as $attachment) {
{ if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
if($attachment instanceof \openvk\Web\Models\Entities\Photo) if($attachment->isDeleted())
{ continue;
$attachments[] = [ $attachments[] = [
"type" => "photo", "type" => "photo",
"photo" => [ "photo" => [
"album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : null, "album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : NULL,
"date" => $attachment->getPublicationTime()->timestamp(), "date" => $attachment->getPublicationTime()->timestamp(),
"id" => $attachment->getVirtualId(), "id" => $attachment->getVirtualId(),
"owner_id" => $attachment->getOwner()->getId(), "owner_id" => $attachment->getOwner()->getId(),
"sizes" => array( "sizes" => array_values($attachment->getVkApiSizes()),
[ "text" => "",
"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" => "",
"has_tags" => false "has_tags" => false
] ]
]; ];
@ -86,10 +51,10 @@ final class Wall extends VKAPIRequestHandler
"date" => $post->getPublicationTime()->timestamp(), "date" => $post->getPublicationTime()->timestamp(),
"post_type" => "post", "post_type" => "post",
"text" => $post->getText(), "text" => $post->getText(),
"can_edit" => 0, // TODO "can_edit" => 0, # TODO
"can_delete" => $post->canBeDeletedBy($this->getUser()), "can_delete" => $post->canBeDeletedBy($this->getUser()),
"can_pin" => $post->canBePinnedBy($this->getUser()), "can_pin" => $post->canBePinnedBy($this->getUser()),
"can_archive" => false, // TODO MAYBE "can_archive" => false, # TODO MAYBE
"is_archived" => false, "is_archived" => false,
"is_pinned" => $post->isPinned(), "is_pinned" => $post->isPinned(),
"attachments" => $attachments, "attachments" => $attachments,
@ -115,7 +80,7 @@ final class Wall extends VKAPIRequestHandler
else else
$groups[] = $from_id * -1; $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) 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 = []; $items = [];
$profiles = []; $profiles = [];
@ -195,7 +160,7 @@ final class Wall extends VKAPIRequestHandler
$attachments[] = [ $attachments[] = [
"type" => "photo", "type" => "photo",
"photo" => [ "photo" => [
"album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : null, "album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : NULL,
"date" => $attachment->getPublicationTime()->timestamp(), "date" => $attachment->getPublicationTime()->timestamp(),
"id" => $attachment->getVirtualId(), "id" => $attachment->getVirtualId(),
"owner_id" => $attachment->getOwner()->getId(), "owner_id" => $attachment->getOwner()->getId(),
@ -231,7 +196,7 @@ final class Wall extends VKAPIRequestHandler
"width" => 1280, "width" => 1280,
], ],
[ [
"height" => 75, // Для временного компросима оставляю статическое число. Если каждый раз обращаться к файлу за количеством пикселов, то наступает пuпuська полная с производительностью, так что пока так "height" => 75, # Для временного компросима оставляю статическое число. Если каждый раз обращаться к файлу за количеством пикселов, то наступает пuпuська полная с производительностью, так что пока так
"url" => $attachment->getURLBySizeId("miniscule"), "url" => $attachment->getURLBySizeId("miniscule"),
"type" => "s", "type" => "s",
"width" => 75, "width" => 75,
@ -250,10 +215,10 @@ final class Wall extends VKAPIRequestHandler
"date" => $post->getPublicationTime()->timestamp(), "date" => $post->getPublicationTime()->timestamp(),
"post_type" => "post", "post_type" => "post",
"text" => $post->getText(), "text" => $post->getText(),
"can_edit" => 0, // TODO "can_edit" => 0, # TODO
"can_delete" => $post->canBeDeletedBy($user), "can_delete" => $post->canBeDeletedBy($user),
"can_pin" => $post->canBePinnedBy($user), "can_pin" => $post->canBePinnedBy($user),
"can_archive" => false, // TODO MAYBE "can_archive" => false, # TODO MAYBE
"is_archived" => false, "is_archived" => false,
"is_pinned" => $post->isPinned(), "is_pinned" => $post->isPinned(),
"post_source" => (object)["type" => "vk"], "post_source" => (object)["type" => "vk"],
@ -279,7 +244,7 @@ final class Wall extends VKAPIRequestHandler
else else
$groups[] = $from_id * -1; $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) if($signed == 1)
$flags |= 0b01000000; $flags |= 0b01000000;
// TODO: Compatible implementation of this # TODO: Compatible implementation of this
try { try {
$photo = null; $photo = NULL;
$video = null; $video = NULL;
if($_FILES["photo"]["error"] === UPLOAD_ERR_OK) { if($_FILES["photo"]["error"] === UPLOAD_ERR_OK) {
$album = null; $album = NULL;
if(!$anon && $owner_id > 0 && $owner_id === $this->getUser()->getId()) if(!$anon && $owner_id > 0 && $owner_id === $this->getUser()->getId())
$album = (new AlbumsRepo)->getUserWallAlbum($wallOwner); $album = (new AlbumsRepo)->getUserWallAlbum($wallOwner);

View file

@ -99,6 +99,14 @@ class Club extends RowModel
{ {
return $this->getRecord()->about; 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 function getShortCode(): ?string
{ {
@ -302,8 +310,8 @@ class Club extends RowModel
{ {
$manager = (new Managers)->getByUserAndClub($user->getId(), $this->getId()); $manager = (new Managers)->getByUserAndClub($user->getId(), $this->getId());
if ($ignoreHidden && $manager !== null && $manager->isHidden()) if ($ignoreHidden && $manager !== NULL && $manager->isHidden())
return null; return NULL;
return $manager; return $manager;
} }

View file

@ -131,7 +131,7 @@ class Correspondence
*/ */
function getPreviewMessage(): ?Message function getPreviewMessage(): ?Message
{ {
$messages = $this->getMessages(1, null, 1); $messages = $this->getMessages(1, NULL, 1);
return $messages[0] ?? NULL; 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" => [ "timing" => [
"sent" => (string) $this->getSendTime()->format("%e %B %G" . tr("time_at_sp") . "%X"), "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(), "text" => $this->getText(),
"read" => !$this->isUnread(), "read" => !$this->isUnread(),

View file

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

View file

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

View file

@ -85,6 +85,11 @@ class User extends RowModel
{ {
return (bool) $this->getRecord()->microblog; return (bool) $this->getRecord()->microblog;
} }
function getMainPage(): int
{
return $this->getRecord()->main_page;
}
function getChandlerGUID(): string function getChandlerGUID(): string
{ {
@ -877,6 +882,17 @@ class User extends RowModel
return true; 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 function adminNotify(string $message): bool
{ {
$admId = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"]; $admId = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"];
@ -928,7 +944,7 @@ class User extends RowModel
return $this->getRecord()->website; return $this->getRecord()->website;
} }
// ты устрица # ты устрица
function isActivated(): bool function isActivated(): bool
{ {
return (bool) $this->getRecord()->activated; return (bool) $this->getRecord()->activated;

View file

@ -63,7 +63,7 @@ class Video extends Media
if(!file_exists($this->getFileName())) { if(!file_exists($this->getFileName())) {
if((time() - $this->getRecord()->last_checked) > 3600) { 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; 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)) if(!is_null($note))
return new Note($note); return new Note($note);
else else
return null; return NULL;
} }
function getUserNotesCount(User $user): int function getUserNotesCount(User $user): int

View file

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

View file

@ -1,8 +1,5 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories; 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 openvk\Web\Models\Entities\TicketComment;
use Chandler\Database\DatabaseConnection; 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); 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 function get(int $id): ?TicketComment
{ {

View file

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

View file

@ -35,7 +35,7 @@ class Topics
{ {
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; $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 = "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; $query .= " LIMIT " . $perPage . " OFFSET " . ($page - 1) * $perPage;

View file

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

View file

@ -23,7 +23,7 @@ final class AdminPresenter extends OpenVKPresenter
private function warnIfNoCommerce(): void private function warnIfNoCommerce(): void
{ {
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"]) 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) private function searchResults(object $repo, &$count)
@ -70,14 +70,14 @@ final class AdminPresenter extends OpenVKPresenter
$user->setLast_Name($this->postParam("last_name")); $user->setLast_Name($this->postParam("last_name"));
$user->setPseudo($this->postParam("nickname")); $user->setPseudo($this->postParam("nickname"));
$user->setStatus($this->postParam("status")); $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"))) if(!$user->setShortCode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode")))
$this->flash("err", tr("error"), tr("error_shorturl_incorrect")); $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(); $user->save();
break; break;
} }
} }

View file

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

View file

@ -89,7 +89,7 @@ final class GroupPresenter extends OpenVKPresenter
$this->template->club = $this->clubs->get($id); $this->template->club = $this->clubs->get($id);
$this->template->onlyShowManagers = $this->queryParam("onlyAdmins") == "1"; $this->template->onlyShowManagers = $this->queryParam("onlyAdmins") == "1";
if($this->template->onlyShowManagers) { 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)); $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()) { 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(); $this->template->count = $this->template->club->getManagersCount();
} else { } else {
$this->template->followers = $this->template->club->getFollowers((int) ($this->queryParam("p") ?? 1)); $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(); $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"); $user = is_null($this->queryParam("user")) ? $this->postParam("user") : $this->queryParam("user");
$comment = $this->postParam("comment"); $comment = $this->postParam("comment");
$removeComment = $this->postParam("removeComment") === "1"; $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"); //$index = $this->queryParam("index");
if(!$user) if(!$user)
$this->badRequest(); $this->badRequest();

View file

@ -80,11 +80,11 @@ final class InternalAPIPresenter extends OpenVKPresenter
if ($postTZ != $sessionOffset || $sessionOffset == null) { if ($postTZ != $sessionOffset || $sessionOffset == null) {
Session::i()->set("_timezoneOffset", $postTZ ? $postTZ : 3 * MINUTE ); Session::i()->set("_timezoneOffset", $postTZ ? $postTZ : 3 * MINUTE );
$this->returnJson([ $this->returnJson([
"success" => 1 // If it's new value "success" => 1 # If it's new value
]); ]);
} else { } else {
$this->returnJson([ $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 { } else {

View file

@ -106,7 +106,7 @@ final class MessengerPresenter extends OpenVKPresenter
$messages = []; $messages = [];
$correspondence = new Correspondence($this->user->identity, $correspondent); $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(); $messages[] = $message->simplify();
header("Content-Type: application/json"); header("Content-Type: application/json");

View file

@ -118,7 +118,7 @@ abstract class OpenVKPresenter extends SimplePresenter
return ($action === "register" || $action === "login"); 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 protected function assertPermission(string $model, string $action, int $context, bool $throw = false): void
@ -252,7 +252,7 @@ abstract class OpenVKPresenter extends SimplePresenter
exit; exit;
} }
// ето для емейл уже надо (и по хорошему надо бы избавится от повторяющегося кода мда) # ето для емейл уже надо (и по хорошему надо бы избавится от повторяющегося кода мда)
if(!$this->user->identity->isActivated() && !$this->activationTolerant) { if(!$this->user->identity->isActivated() && !$this->activationTolerant) {
header("HTTP/1.1 403 Forbidden"); header("HTTP/1.1 403 Forbidden");
$this->getTemplatingEngine()->render(__DIR__ . "/templates/@email.xml", [ $this->getTemplatingEngine()->render(__DIR__ . "/templates/@email.xml", [
@ -290,7 +290,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$whichbrowser = new WhichBrowser\Parser(getallheaders()); $whichbrowser = new WhichBrowser\Parser(getallheaders());
$mobiletheme = OPENVK_ROOT_CONF["openvk"]["preferences"]["defaultMobileTheme"]; $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); $this->setSessionTheme($mobiletheme);
$theme = NULL; $theme = NULL;
@ -301,7 +301,7 @@ abstract class OpenVKPresenter extends SimplePresenter
$theme = Themepacks::i()[Session::i()->get("_sessionTheme", "ovk")]; $theme = Themepacks::i()[Session::i()->get("_sessionTheme", "ovk")];
} else if($this->requestParam("themePreview")) { } else if($this->requestParam("themePreview")) {
$theme = Themepacks::i()[$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(); $theme = $this->user->identity->getTheme();
} }

View file

@ -29,7 +29,7 @@ final class SearchPresenter extends OpenVKPresenter
if($query != "") if($query != "")
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
// https://youtu.be/pSAWM5YuXx8 # https://youtu.be/pSAWM5YuXx8
$repos = [ "groups" => "clubs", "users" => "users" ]; $repos = [ "groups" => "clubs", "users" => "users" ];
$repo = $repos[$type] or $this->throwError(400, "Bad Request", "Invalid search entity $type."); $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->assertUserLoggedIn();
$this->template->mode = in_array($this->queryParam("act"), ["faq", "new", "list"]) ? $this->queryParam("act") : "faq"; $this->template->mode = in_array($this->queryParam("act"), ["faq", "new", "list"]) ? $this->queryParam("act") : "faq";
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); $this->template->count = $this->tickets->getTicketsCountByUserId($this->user->id);
if($this->template->mode === "list") { if($this->template->mode === "list") {
$this->template->page = (int) ($this->queryParam("p") ?? 1); $this->template->page = (int) ($this->queryParam("p") ?? 1);
@ -79,12 +114,13 @@ final class SupportPresenter extends OpenVKPresenter
$act = $this->queryParam("act") ?? "open"; $act = $this->queryParam("act") ?? "open";
switch($act) { switch($act) {
default: default:
# NOTICE falling through
case "open": case "open":
$state = 0; $state = 0;
break; break;
case "answered": case "answered":
$state = 1; $state = 1;
break; break;
case "closed": case "closed":
$state = 2; $state = 2;
} }

View file

@ -91,7 +91,7 @@ final class TopicsPresenter extends OpenVKPresenter
$topic->setFlags($flags); $topic->setFlags($flags);
$topic->save(); $topic->save();
// TODO move to trait # TODO move to trait
try { try {
$photo = NULL; $photo = NULL;
$video = 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\Videos;
use openvk\Web\Models\Repositories\Notes; use openvk\Web\Models\Repositories\Notes;
use openvk\Web\Models\Repositories\Vouchers; use openvk\Web\Models\Repositories\Vouchers;
use openvk\Web\Models\Repositories\EmailChangeVerifications;
use openvk\Web\Models\Exceptions\InvalidUserNameException; use openvk\Web\Models\Exceptions\InvalidUserNameException;
use openvk\Web\Util\Validator; use openvk\Web\Util\Validator;
use openvk\Web\Models\Entities\Notifications\{CoinsTransferNotification, RatingUpNotification}; use openvk\Web\Models\Entities\Notifications\{CoinsTransferNotification, RatingUpNotification};
use openvk\Web\Models\Entities\EmailChangeVerification;
use Chandler\Security\Authenticator; use Chandler\Security\Authenticator;
use lfkeitel\phptotp\{Base32, Totp}; use lfkeitel\phptotp\{Base32, Totp};
use chillerlan\QRCode\{QRCode, QROptions}; use chillerlan\QRCode\{QRCode, QROptions};
use Nette\Database\UniqueConstraintViolationException;
final class UserPresenter extends OpenVKPresenter final class UserPresenter extends OpenVKPresenter
{ {
@ -132,7 +135,7 @@ final class UserPresenter extends OpenVKPresenter
if(!$id) if(!$id)
$this->notFound(); $this->notFound();
$user = $this->users->get($id); $user = $this->users->get($id);
if($_SERVER["REQUEST_METHOD"] === "POST") { if($_SERVER["REQUEST_METHOD"] === "POST") {
$this->willExecuteWriteAction($_GET['act'] === "status"); $this->willExecuteWriteAction($_GET['act'] === "status");
@ -300,7 +303,7 @@ final class UserPresenter extends OpenVKPresenter
if(!$id) if(!$id)
$this->notFound(); $this->notFound();
if(in_array($this->queryParam("act"), ["finance", "finance.top-up"]) && !OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"]) if(in_array($this->queryParam("act"), ["finance", "finance.top-up"]) && !OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
$this->flashFail("err", tr("error"), tr("feature_disabled")); $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("old_pass") && $this->postParam("new_pass") && $this->postParam("repeat_pass")) {
if($this->postParam("new_pass") === $this->postParam("repeat_pass")) { if($this->postParam("new_pass") === $this->postParam("repeat_pass")) {
if($this->user->identity->is2faEnabled()) { 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))) 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")); $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")); $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"))) if(!$user->setShortCode(empty($this->postParam("sc")) ? NULL : $this->postParam("sc")))
$this->flashFail("err", tr("error"), tr("error_shorturl_incorrect")); $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])) if(in_array($this->postParam("nsfw"), [0, 1, 2]))
$user->setNsfwTolerance((int) $this->postParam("nsfw")); $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") { } else if($_GET['act'] === "lMenu") {
$settings = [ $settings = [
"menu_bildoj" => "photos", "menu_bildoj" => "photos",
@ -400,11 +446,7 @@ final class UserPresenter extends OpenVKPresenter
throw $ex; throw $ex;
} }
$this->flash( $this->flash("succ", tr("changes_saved"), tr("changes_saved_comment"));
"succ",
"Изменения сохранены",
"Новые данные появятся на вашей странице."
);
} }
$this->template->mode = in_array($this->queryParam("act"), [ $this->template->mode = in_array($this->queryParam("act"), [
"main", "privacy", "finance", "finance.top-up", "interface" "main", "privacy", "finance", "finance.top-up", "interface"
@ -456,7 +498,7 @@ final class UserPresenter extends OpenVKPresenter
$this->template->secret = $secret; $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"]; $issuer = OPENVK_ROOT_CONF["openvk"]["appearance"]["name"];
$email = $this->user->identity->getEmail(); $email = $this->user->identity->getEmail();
@ -502,6 +544,9 @@ final class UserPresenter extends OpenVKPresenter
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->willExecuteWriteAction(); $this->willExecuteWriteAction();
if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"])
$this->flashFail("err", tr("error"), tr("feature_disabled"));
$receiverAddress = $this->postParam("receiver"); $receiverAddress = $this->postParam("receiver");
$value = (int) $this->postParam("value"); $value = (int) $this->postParam("value");
$message = $this->postParam("message"); $message = $this->postParam("message");
@ -517,7 +562,7 @@ final class UserPresenter extends OpenVKPresenter
$receiver = $this->users->getByAddress($receiverAddress); $receiver = $this->users->getByAddress($receiverAddress);
if(!$receiver) 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) if($this->user->identity->getCoins() < $value)
$this->flashFail("err", tr("failed_to_tranfer_points"), tr("you_dont_have_enough_points")); $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)); $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(); $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")); $this->flashFail("succ", tr("information_-1"), tr("changes_saved_comment"));
} }
} }

View file

@ -17,7 +17,7 @@
{script "js/l10n.js"} {script "js/l10n.js"}
{script "js/openvk.cls.js"} {script "js/openvk.cls.js"}
{if $isTimezoned == null} {if $isTimezoned == NULL}
{script "js/timezone.js"} {script "js/timezone.js"}
{/if} {/if}
@ -26,7 +26,7 @@
{css "css/nsfw-posts.css"} {css "css/nsfw-posts.css"}
{/if} {/if}
{if $theme !== null} {if $theme !== NULL}
{if $theme->inheritDefault()} {if $theme->inheritDefault()}
{css "css/style.css"} {css "css/style.css"}
{css "css/dialog.css"} {css "css/dialog.css"}
@ -103,7 +103,7 @@
<div n:if="isset($thisUser) ? (!$thisUser->isBanned() XOR !$thisUser->isActivated()) : true" class="header_navigation"> <div n:if="isset($thisUser) ? (!$thisUser->isBanned() XOR !$thisUser->isActivated()) : true" class="header_navigation">
{ifset $thisUser} {ifset $thisUser}
<div class="link"> <div class="link">
<a href="/" title="[Alt+Shift+,]" accesskey=",">{_header_home}</a> <a href="/">{_header_home}</a>
</div> </div>
<div class="link"> <div class="link">
<a href="/search?type=groups">{_header_groups}</a> <a href="/search?type=groups">{_header_groups}</a>
@ -161,7 +161,7 @@
</a> </a>
<a n:if="$thisUser->getLeftMenuItemStatus('notes')" href="/notes{$thisUser->getId()}" class="link">{_my_notes}</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('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} <a href="/notifications" class="link" title="{_my_feedback} [Alt+Shift+N]" accesskey="n">{_my_feedback}
{if $thisUser->getNotificationsCount() > 0} {if $thisUser->getNotificationsCount() > 0}
(<b>{$thisUser->getNotificationsCount()}</b>) (<b>{$thisUser->getNotificationsCount()}</b>)
@ -173,7 +173,7 @@
{var $canAccessHelpdesk = $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)} {var $canAccessHelpdesk = $thisUser->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0)}
{var $menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0 && $thisUser->getLeftMenuItemStatus('links')} {var $menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0 && $thisUser->getLeftMenuItemStatus('links')}
<div n:if="$canAccessAdminPanel || $canAccessHelpdesk || $menuLinksAvaiable" class="menu_divider"></div> <div n:if="$canAccessAdminPanel || $canAccessHelpdesk || $menuLinksAvaiable" class="menu_divider"></div>
<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 <a href="/support/tickets" class="link" n:if="$canAccessHelpdesk">Helpdesk
{if $helpdeskTicketNotAnsweredCount > 0} {if $helpdeskTicketNotAnsweredCount > 0}
(<b>{$helpdeskTicketNotAnsweredCount}</b>) (<b>{$helpdeskTicketNotAnsweredCount}</b>)
@ -280,6 +280,8 @@
</p> </p>
</div> </div>
{include "components/cookies.xml"}
{script "js/node_modules/msgpack-lite/dist/msgpack.min.js"} {script "js/node_modules/msgpack-lite/dist/msgpack.min.js"}
{script "js/node_modules/soundjs/lib/soundjs.min.js"} {script "js/node_modules/soundjs/lib/soundjs.min.js"}
{script "js/node_modules/ky/umd.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> <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head> <head>
@ -6,7 +7,7 @@
{var $css = file_get_contents(OPENVK_ROOT . "/Web/static/js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.css")} {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} {str_replace("fonts/", "/assets/packages/static/openvk/js/node_modules/@atlassian/aui/dist/aui/fonts/", $css)|noescape}
</style> </style>
<title>{include title} - Админ-панель {=OPENVK_ROOT_CONF['openvk']['appearance']['name']}</title> <title>{include title} - {_admin} {$instance_name}</title>
</head> </head>
<body> <body>
<div id="page"> <div id="page">
@ -16,23 +17,15 @@
<div class="aui-header-primary"> <div class="aui-header-primary">
<h1 id="logo" class="aui-header-logo aui-header-logo-textonly"> <h1 id="logo" class="aui-header-logo aui-header-logo-textonly">
<a href="/admin"> <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> </a>
</h1> </h1>
</div> </div>
<div n:if="$search ?? false" class="aui-header-secondary"> <div n:if="$search ?? false" class="aui-header-secondary">
<ul class="aui-nav"> <ul class="aui-nav">
<form class="aui-quicksearch dont-default-focus ajs-dirty-warning-exempt"> <form class="aui-quicksearch dont-default-focus ajs-dirty-warning-exempt">
<input <input id="quickSearchInput" autocomplete="off" class="search" type="text" placeholder="{include searchTitle}" value="{$_GET['q'] ?? ''}" name="q" accesskey="Q" />
id="quickSearchInput" <input type="hidden" value=1 name=p />
autocomplete="off"
class="search"
type="text"
placeholder="{include searchTitle}"
value="{$_GET['q'] ?? ''}"
name="q"
accesskey="Q" />
<input type="hidden" value=1 name=p />
</form> </form>
</ul> </ul>
</div> </div>
@ -46,83 +39,64 @@
<div class="aui-navgroup-inner"> <div class="aui-navgroup-inner">
<div class="aui-navgroup-primary"> <div class="aui-navgroup-primary">
<div class="aui-nav-heading"> <div class="aui-nav-heading">
<strong>Обзор</strong> <strong>{_admin_overview}</strong>
</div> </div>
<ul class="aui-nav"> <ul class="aui-nav">
<li> <li>
<a href="/admin"> <a href="/admin">{_admin_overview_summary}</a>
Сводка
</a>
</li> </li>
</ul> </ul>
<div class="aui-nav-heading"> <div class="aui-nav-heading">
<strong>Пользовательский контент</strong> <strong>{_admin_content}</strong>
</div> </div>
<ul class="aui-nav"> <ul class="aui-nav">
<li> <li>
<a href="/admin/users"> <a href="/admin/users">{_users}</a>
Пользователи
</a>
</li> </li>
<li> <li>
<a href="/admin/clubs"> <a href="/admin/clubs">{_groups}</a>
Группы
</a>
</li> </li>
</ul> </ul>
<div class="aui-nav-heading"> <div class="aui-nav-heading">
<strong>Платные услуги</strong> <strong>{_admin_services}</strong>
</div> </div>
<ul class="aui-nav"> <ul class="aui-nav">
<li> <li>
<a href="/admin/vouchers"> <a href="/admin/vouchers">{_vouchers}</a>
{_vouchers}
</a>
</li> </li>
<li> <li>
<a href="/admin/gifts"> <a href="/admin/gifts">{_gifts}</a>
Подарки
</a>
</li> </li>
</ul> </ul>
<div class="aui-nav-heading"> <div class="aui-nav-heading">
<strong>Настройки</strong> <strong>{_admin_settings}</strong>
</div> </div>
<ul class="aui-nav"> <ul class="aui-nav">
<li> <li>
<a href="/admin/settings/tuning"> <a href="/admin/settings/tuning">{_admin_settings_tuning}</a>
Общие
</a>
</li> </li>
<li> <li>
<a href="/admin/settings/appearance"> <a href="/admin/settings/appearance">{_admin_settings_appearance}</a>
Внешний вид
</a>
</li> </li>
<li> <li>
<a href="/admin/settings/security"> <a href="/admin/settings/security">{_admin_settings_security}</a>
Безопасность
</a>
</li> </li>
<li> <li>
<a href="/admin/settings/integrations"> <a href="/admin/settings/integrations">{_admin_settings_integrations}</a>
Интеграции
</a>
</li> </li>
<li> <li>
<a href="/admin/settings/system"> <a href="/admin/settings/system">{_admin_settings_system}</a>
Система
</a>
</li> </li>
</ul> </ul>
<div class="aui-nav-heading"> <div class="aui-nav-heading">
<strong>Об OpenVK</strong> <strong>{_admin_about}</strong>
</div> </div>
<ul class="aui-nav"> <ul class="aui-nav">
<li> <li>
<a href="/about:openvk"> <a href="/about:openvk">{_admin_about_version}</a>
Версия </li>
</a> <li>
<a href="/about">{_admin_about_instance}</a>
</li> </li>
</ul> </ul>
</div> </div>
@ -139,11 +113,11 @@
<p>{$flashMessage->msg|noescape}</p> <p>{$flashMessage->msg|noescape}</p>
</div> </div>
{/ifset} {/ifset}
{ifset preHeader} {ifset preHeader}
{include preHeader} {include preHeader}
{/ifset} {/ifset}
<header class="aui-page-header"> <header class="aui-page-header">
<div class="aui-page-header-inner"> <div class="aui-page-header-inner">
<div class="aui-page-header-main"> <div class="aui-page-header-main">
@ -167,11 +141,11 @@
</section> </section>
</footer> </footer>
</div> </div>
{script "js/node_modules/jquery/dist/jquery.min.js"} {script "js/node_modules/jquery/dist/jquery.min.js"}
{script "js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.js"} {script "js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.js"}
<script>AJS.tabs.setup();</script> <script>AJS.tabs.setup();</script>
{ifset scripts} {ifset scripts}
{include scripts} {include scripts}
{/ifset} {/ifset}

View file

@ -1,191 +1,157 @@
{extends "@layout.xml"} {extends "@layout.xml"}
{block title} {block title}
Редактировать {$club->getCanonicalName()} {_edit} {$club->getCanonicalName()}
{/block} {/block}
{block heading} {block heading}
{$club->getCanonicalName()} {$club->getCanonicalName()}
{/block} {/block}
{block content} {block content}
{var $isMain = $mode === 'main'} {var $isMain = $mode === 'main'}
{var $isBan = $mode === 'ban'} {var $isBan = $mode === 'ban'}
{var $isFollowers = $mode === 'followers'} {var $isFollowers = $mode === 'followers'}
{if $isMain} {if $isMain}
<div class="aui-tabs horizontal-tabs">
<!-- This main block --> <nav class="aui-navgroup aui-navgroup-horizontal">
<div class="aui-navgroup-inner">
<div class="aui-tabs horizontal-tabs"> <div class="aui-navgroup-primary">
<nav class="aui-navgroup aui-navgroup-horizontal"> <ul class="aui-nav">
<div class="aui-navgroup-inner"> <li class="aui-nav-selected"><a href="?act=main">{_admin_tab_main}</a></li>
<div class="aui-navgroup-primary"> <li><a href="?act=ban">{_admin_tab_ban}</a></li>
<ul class="aui-nav"> <li><a href="?act=followers">{_admin_tab_followers}</a></li>
<li class="aui-nav-selected"><a href="?act=main">Главное</a></li> </ul>
<li><a href="?act=ban">Бан</a></li> </div>
<li><a href="?act=followers">Участники</a></li> </div>
</ul> </nav>
</div> <form class="aui" method="POST">
</div> <div class="field-group">
</nav> <label for="avatar">{_avatar}</label>
<form class="aui" method="POST"> <span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
<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">
<span class="aui-avatar-inner"> <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>
</span> </span>
</div>
<a href="{$follower->getURL()}">{$follower->getCanonicalName()}</a> <div class="field-group">
<label for="id">ID</label>
<span n:if="$follower->isBanned()" class="aui-lozenge aui-lozenge-subtle aui-lozenge-removed"> <input class="text medium-field" type="number" id="id" disabled value="{$club->getId()}" />
заблокирован </div>
</span> <div class="field-group">
</td> <label for="id_owner">{_admin_ownerid}</label>
<td>{$follower->isFemale() ? "Женский" : "Мужской"}</td> <input class="text medium-field" type="text" id="id_owner" name="id_owner" value="{$club->getOwner()->getId()}" />
<td>{$follower->getShortCode() ?? "(отсутствует)"}</td> </div>
<td>{$follower->getRegistrationTime()}</td> <div class="field-group">
<td> <label for="name">{_admin_title}</label>
<a class="aui-button aui-button-primary" href="/admin/users/id{$follower->getId()}"> <input class="text medium-field" type="text" id="name" name="name" value="{$club->getName()}" />
<span class="aui-icon aui-icon-small aui-iconfont-new-edit">Редактировать</span> </div>
</a> <div class="field-group">
</td> <label for="about">{_admin_description}</label>
</tr> <input class="text medium-field" type="text" id="about" name="about" value="{$club->getDescription()}" />
</tbody> </div>
</table> <div class="field-group">
<div align="right"> <label for="shortcode">{_admin_shortcode}</label>
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count} <input class="text medium-field" type="text" id="shortcode" name="shortcode" value="{$club->getShortCode()}" />
</div>
<a n:if="($_GET['p'] ?? 1) > 1" class="aui-button" href="?p={($_GET['p'] ?? 1) - 1}"> <br/>
⭁ туда <div class="group">
</a> <input class="toggle-large" type="checkbox" id="verify" name="verify" value="1" {if $club->isVerified()} checked {/if} />
<a n:if="$isLast" class="aui-button" href="?p={($_GET['p'] ?? 1) + 1}"> <label for="verify">{_admin_verification}</label>
⭇ сюда </div>
</a> <div class="group">
</div> <input class="toggle-large" type="checkbox" id="hide_from_global_feed" name="hide_from_global_feed" value="1" {if $club->isHideFromGlobalFeedEnabled()} checked {/if} />
</div> <label for="hide_from_global_feed">{_admin_club_excludeglobalfeed}</label>
{/if} </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} {/block}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
{extends "@layout.xml"} {extends "@layout.xml"}
{block title} {block title}
Редактировать {$user->getCanonicalName()} {_edit} {$user->getCanonicalName()}
{/block} {/block}
{block heading} {block heading}
@ -10,95 +10,70 @@
{block content} {block content}
<div class="aui-tabs horizontal-tabs"> <div class="aui-tabs horizontal-tabs">
<form class="aui" method="POST"> <form class="aui" method="POST">
<div class="field-group"> <div class="field-group">
<label for="avatar"> <label for="avatar">{_avatar}</label>
Аватарка <span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge">
</label> <span class="aui-avatar-inner">
<span id="avatar" class="aui-avatar aui-avatar-project aui-avatar-xlarge"> <img src="{$user->getAvatarUrl('tiny')}" style="object-fit: cover;"></img>
<span class="aui-avatar-inner"> </span>
<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>
</div> <div class="field-group">
</form> <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> </div>
{/block} {/block}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -64,7 +64,7 @@
<span class="nobold">{_"2fa_code"}</span> <span class="nobold">{_"2fa_code"}</span>
</td> </td>
<td> <td>
<input type="text" name="code" style="width: 100%;" /> <input type="text" name="password_change_code" style="width: 100%;" />
</td> </td>
</tr> </tr>
<tr> <tr>
@ -154,6 +154,38 @@
{$user->getEmail()} {$user->getEmail()}
</td> </td>
</tr> </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> </tbody>
</table> </table>
<br/> <br/>
@ -447,24 +479,35 @@
</tr> </tr>
<tr> <tr>
<td width="120" valign="top"> <td width="120" valign="top">
<span class="nobold">NSFW-контент</span> <span class="nobold">{_ui_settings_nsfw_content}</span>
</td> </td>
<td> <td>
<select name="nsfw"> <select name="nsfw">
<option value="0" {if $user->getNsfwTolerance() === 0}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}>Только замазывать</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}>Показывать</option> <option value="2" {if $user->getNsfwTolerance() === 2}selected{/if}>{_ui_settings_nsfw_content_show}</option>
</select> </select>
</td> </td>
</tr> </tr>
<tr> <tr>
<td width="120" valign="top"> <td width="120" valign="top">
<span class="nobold">Вид постов</span> <span class="nobold">{_ui_settings_view_of_posts}</span>
</td> </td>
<td> <td>
<select name="microblog"> <select name="microblog">
<option value="0" {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}>Микроблог</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> </select>
</td> </td>
</tr> </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 $comments = $post->getLastComments(3)}
{var $commentsCount = $post->getCommentsCount()} {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"> <table border="0" style="font-size: 11px;" n:class="post, !$compact ? post-divider, $post->isExplicit() ? post-nsfw">
<tbody> <tbody>

View file

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

View file

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

View file

@ -1913,4 +1913,25 @@ table td[width="120"] {
border-bottom: 1.5px solid #707070; border-bottom: 1.5px solid #707070;
text-align: center; text-align: center;
user-select: none; 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 _n_counter = 0;
var _activeWindow = true;
const _pageTitle = u("title").nodes[0].innerText;
var counter = 0; var counter = 0;
/* this fucking dumb shit is broken :c window.addEventListener("focus", () => {
document.title = document.title.replace(/^\([0-9]+\) /, ""); // remove notification counter xD
window.addEventListener('focus', () => {
_activeWindow = true;
closeAllNotifications();
}); });
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) { function NewNotification(title, body, avatar = null, callback = () => {}, time = 5000, count = true) {
if(avatar != null) { if(avatar != null) {
avatar = '<avatar>' + avatar = '<avatar>' +
@ -62,18 +42,19 @@ function NewNotification(title, body, avatar = null, callback = () => {}, time =
} }
function __closeNotification() { function __closeNotification() {
if(document.visibilityState != "visible")
return setTimeout(() => {__closeNotification()}, time); // delay notif deletion
getPrototype().addClass('disappears'); getPrototype().addClass('disappears');
setTimeout(() => {getPrototype().remove()}, 500); return setTimeout(() => {getPrototype().remove()}, 500);
} }
if(count == true) { if(count == true) {
counter++; 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) { notification.children('notification_title').children('a.close').on('click', function(e) {
__closeNotification(); __closeNotification();

View file

@ -171,7 +171,7 @@ function repostPost(id, hash) {
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = (function() { xhr.onload = (function() {
if(xhr.responseText.indexOf("wall_owner") === -1) 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 { else {
let jsonR = JSON.parse(xhr.responseText); let jsonR = JSON.parse(xhr.responseText);
NewNotification(tr('information_-1'), tr('shared_succ'), null, () => {window.location.href = "/wall" + jsonR.wall_owner}); 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 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(); else return array();
} }
@ -144,7 +144,7 @@ function eventdb(): ?DatabaseConnection
{ {
$conf = OPENVK_ROOT_CONF["openvk"]["credentials"]["eventDB"]; $conf = OPENVK_ROOT_CONF["openvk"]["credentials"]["eventDB"];
if(!$conf["enable"]) if(!$conf["enable"])
return null; return NULL;
$db = (object) $conf["database"]; $db = (object) $conf["database"];
return DatabaseConnection::connect([ return DatabaseConnection::connect([
@ -216,8 +216,8 @@ return (function() {
setlocale(LC_TIME, "POSIX"); setlocale(LC_TIME, "POSIX");
// TODO: Default language in config # TODO: Default language in config
if(Session::i()->get("lang") == null) { if(Session::i()->get("lang") == NULL) {
$languages = array_reverse(getBrowsersLanguage()); $languages = array_reverse(getBrowsersLanguage());
foreach($languages as $lg) { foreach($languages as $lg) {
if(isLanguageAvailable($lg)) setLanguage($lg); if(isLanguageAvailable($lg)) setLanguage($lg);
@ -233,7 +233,7 @@ return (function() {
else else
$ver = "Public Technical Preview 3"; $ver = "Public Technical Preview 3";
// Unix time constants # Unix time constants
define('MINUTE', 60); define('MINUTE', 60);
define('HOUR', 60 * MINUTE); define('HOUR', 60 * MINUTE);
define('DAY', 24 * HOUR); 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" = "Գլխավոր"; "home" = "Գլխավոր";
"welcome" = "Բարի գալուստ"; "welcome" = "Բարի գալուստ";
"to_top" = "Վերև";
/* Login */ /* Login */
@ -381,7 +382,6 @@
"left_menu_donate" = "Աջակցել"; "left_menu_donate" = "Աջակցել";
"footer_about_instance" = "հոսքի մասին"; "footer_about_instance" = "հոսքի մասին";
"footer_blog" = "բլոգ"; "footer_blog" = "բլոգ";
"footer_help" = "օգնություն"; "footer_help" = "օգնություն";
@ -410,6 +410,8 @@
"style" = "Ոճ"; "style" = "Ոճ";
"default" = "Սովորական"; "default" = "Սովորական";
"arbitrary_avatars" = "Կամայական";
"cut" = "Կտրվածք"; "cut" = "Կտրվածք";
"round_avatars" = "Կլոր ավատար"; "round_avatars" = "Կլոր ավատար";
@ -519,6 +521,8 @@
"videos_many" = "$1 տեսանյութ"; "videos_many" = "$1 տեսանյութ";
"videos_other" = "$1 տեսանյութ"; "videos_other" = "$1 տեսանյութ";
"view_video" = "Դիտում";
/* Notifications */ /* Notifications */
"feedback" = "Հետադարձ կապ"; "feedback" = "Հետադարձ կապ";
@ -624,6 +628,21 @@
"receiver_not_found" = "Ստացողը չի գտնվել։"; "receiver_not_found" = "Ստացողը չի գտնվել։";
"you_dont_have_enough_points" = "Դուք չունե՛ք բավական ձայն։"; "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 */ /* Gifts */
"gift" = "Նվեր"; "gift" = "Նվեր";
@ -703,6 +722,9 @@
"ticket_changed" = "Տոմսը փոփոխված է"; "ticket_changed" = "Տոմսը փոփոխված է";
"ticket_changed_comment" = "Փոփոխությունները ուժի մեջ կմտնեն մի քանի վայրկյանից։"; "ticket_changed_comment" = "Փոփոխությունները ուժի մեջ կմտնեն մի քանի վայրկյանից։";
"banned_in_support_1" = "Կներե՛ք, <b>$1</b>, բայց հիմա Ձեզ թույլատրված չէ դիմումներ ստեղծել։";
"banned_in_support_2" = "Դրա պատճառաբանությունը սա է․ <b>$1</b>։ Ցավո՛ք, այդ հնարավորությունը մենք Ձեզնից վերցրել ենք առհավետ։";
/* Invite */ /* Invite */
"invite" = "Հրավիրել"; "invite" = "Հրավիրել";
@ -711,9 +733,9 @@
/* Banned */ /* Banned */
"banned_title" = "Բլոկավորված եք"; "banned_title" = "Արգելափակված եք";
"banned_header" = "Ձեզ կասեցրել է կառլենի անհաջող բոցը։"; "banned_header" = "Ձեզ կասեցրել է կարլենի անհաջող բոցը։";
"banned_alt" = "Օգտատերը բլոկավորված է"; "banned_alt" = "Օգտատերը արգելափակված է";
"banned_1" = "Կներե՛ք, <b>$1</b>, բայց Դուք կասեցված եք։"; "banned_1" = "Կներե՛ք, <b>$1</b>, բայց Դուք կասեցված եք։";
"banned_2" = "Պատճառը հետևյալն է․ <b>$1</b>. Ափսոս, բայց մենք ստիպված Ձեզ հավերժ ենք կասեցրել;"; "banned_2" = "Պատճառը հետևյալն է․ <b>$1</b>. Ափսոս, բայց մենք ստիպված Ձեզ հավերժ ենք կասեցրել;";
"banned_3" = "Դուք դեռ կարող եք <a href=\"/support?act=new\">գրել նամակ աջակցության ծառայությանը</a>, եթե համարում եք որ դա սխալմունք է, կամ էլ կարող եք <a href=\"/logout?hash=$1\">դուրս գալ</a>։"; "banned_3" = "Դուք դեռ կարող եք <a href=\"/support?act=new\">գրել նամակ աջակցության ծառայությանը</a>, եթե համարում եք որ դա սխալմունք է, կամ էլ կարող եք <a href=\"/logout?hash=$1\">դուրս գալ</a>։";
@ -835,6 +857,7 @@
"captcha_error" = "Սխալ են գրված սիմվոլները"; "captcha_error" = "Սխալ են գրված սիմվոլները";
"captcha_error_comment" = "Խնդրում ենք համոզվել, որ ճիշտ եք ներմուծել կապտչայի սիմվոլները։"; "captcha_error_comment" = "Խնդրում ենք համոզվել, որ ճիշտ եք ներմուծել կապտչայի սիմվոլները։";
/* Admin actions */ /* Admin actions */
"login_as" = "Մտնել ինչպես $1"; "login_as" = "Մտնել ինչպես $1";
@ -843,7 +866,84 @@
"ban_user_action" = "Բլոկավորել օգտվողին"; "ban_user_action" = "Բլոկավորել օգտվողին";
"warn_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_back" = "Հետ";
"paginator_page" = "$1 էջ"; "paginator_page" = "$1 էջ";
@ -857,6 +957,8 @@
"rules" = "Կանոններ"; "rules" = "Կանոններ";
"most_popular_groups" = "Ամենահայտնի խմբերը"; "most_popular_groups" = "Ամենահայտնի խմբերը";
"on_this_instance_are" = "Այս հոսքում․"; "on_this_instance_are" = "Այս հոսքում․";
"about_links" = "Հղումներ";
"instance_links" = "Հոսքերի հղումներ․";
"about_users_one" = "<b>Մեկ</b> օգտատեր"; "about_users_one" = "<b>Մեկ</b> օգտատեր";
"about_users_few" = "<b>$1</b> օգտատեր"; "about_users_few" = "<b>$1</b> օգտատեր";

View file

@ -470,3 +470,7 @@
"comment" = "Каментарый"; "comment" = "Каментарый";
"sender" = "Адпраўнік"; "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"; "your_page_address" = "Your address page";
"page_address" = "Address page"; "page_address" = "Address page";
"current_email_address" = "Current email address"; "current_email_address" = "Current email address";
"new_email_address" = "New email address";
"save_email_address" = "Save email address";
"page_id" = "Page ID"; "page_id" = "Page ID";
"you_can_also" = "You can also"; "you_can_also" = "You can also";
"delete_your_page" = "delete your page"; "delete_your_page" = "delete your page";
@ -454,10 +456,20 @@
"ui_settings_rating" = "Rating"; "ui_settings_rating" = "Rating";
"ui_settings_rating_show" = "Show"; "ui_settings_rating_show" = "Show";
"ui_settings_rating_hide" = "Hide"; "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"; "additional_links" = "Additional links";
"ad_poster" = "Ad poster"; "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" = "Two-factor authentication"; "two_factor_authentication" = "Two-factor authentication";
@ -661,9 +673,6 @@
"support_list" = "List of tickets"; "support_list" = "List of tickets";
"support_new" = "New ticket"; "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_title" = "Enter the topic of your ticket";
"support_new_content" = "Describe the issue or suggestion"; "support_new_content" = "Describe the issue or suggestion";
@ -847,6 +856,84 @@
"ban_in_support_user_action" = "Ban in support"; "ban_in_support_user_action" = "Ban in support";
"unban_in_support_user_action" = "Unban 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 (deprecated) */
"paginator_back" = "Back"; "paginator_back" = "Back";
@ -895,3 +982,8 @@
/* User alerts */ /* User alerts */
"user_alert_scam" = "This account has been reported a lot for scam. Please be careful, especially if he asked for money."; "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" = "Адрес Вашей страницы"; "your_page_address" = "Адрес Вашей страницы";
"page_address" = "Адрес страницы"; "page_address" = "Адрес страницы";
"current_email_address" = "Текущий адрес"; "current_email_address" = "Текущий адрес";
"new_email_address" = "Новый адрес";
"save_email_address" = "Сохранить адрес";
"page_id" = "ID страницы"; "page_id" = "ID страницы";
"you_can_also" = "Вы также можете"; "you_can_also" = "Вы также можете";
"delete_your_page" = "удалить свою страницу"; "delete_your_page" = "удалить свою страницу";
@ -482,10 +484,20 @@
"ui_settings_rating" = "Рейтинг"; "ui_settings_rating" = "Рейтинг";
"ui_settings_rating_show" = "Показывать"; "ui_settings_rating_show" = "Показывать";
"ui_settings_rating_hide" = "Скрывать"; "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" = "Дополнительные ссылки"; "additional_links" = "Дополнительные ссылки";
"ad_poster" = "Рекламный плакат"; "ad_poster" = "Рекламный плакат";
"email_change_confirm_message" = "Чтобы изменение вступило в силу, подтвердите ваш новый адрес электронной почты. Мы отправили инструкции на него.";
/* Two-factor authentication */ /* Two-factor authentication */
"two_factor_authentication" = "Двухфакторная аутентификация"; "two_factor_authentication" = "Двухфакторная аутентификация";
@ -698,9 +710,6 @@
"support_list" = "Список обращений"; "support_list" = "Список обращений";
"support_new" = "Новое обращение"; "support_new" = "Новое обращение";
"support_faq_title" = "Для кого этот сайт?";
"support_faq_content" = "Сайт предназначен для поиска друзей и знакомых, а также для просмотра данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке.";
"support_new_title" = "Введите тему вашего обращения"; "support_new_title" = "Введите тему вашего обращения";
"support_new_content" = "Опишите проблему или предложение"; "support_new_content" = "Опишите проблему или предложение";
@ -890,6 +899,82 @@
"ban_in_support_user_action" = "Заблокировать в поддержке"; "ban_in_support_user_action" = "Заблокировать в поддержке";
"unban_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 (deprecated) */
"paginator_back" = "Назад"; "paginator_back" = "Назад";
@ -948,3 +1033,8 @@
/* User alerts */ /* User alerts */
"user_alert_scam" = "На этот аккаунт много жаловались в связи с мошенничеством. Пожалуйста, будьте осторожны, особенно если у вас попросят денег."; "user_alert_scam" = "На этот аккаунт много жаловались в связи с мошенничеством. Пожалуйста, будьте осторожны, особенно если у вас попросят денег.";
/* Cookies pop-up */
"cookies_popup_content" = "Все дети любят печенье, поэтому этот веб-сайт использует Cookies для того, чтобы идентифицировать вашу сессию и ничего более. Ознакомьтесь с нашей <a href='/privacy'>политикой конфиденциальности</a> для получения дополнительной информации.";
"cookies_popup_agree" = "Согласен";