diff --git a/CODE_OF_CONFLICT.md b/CODE_OF_CONFLICT.md new file mode 100644 index 00000000..e45656d3 --- /dev/null +++ b/CODE_OF_CONFLICT.md @@ -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." diff --git a/CODE_STYLE.md b/CODE_STYLE.md new file mode 100644 index 00000000..85f5ab74 --- /dev/null +++ b/CODE_STYLE.md @@ -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 `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"; +} +``` diff --git a/Email/change-email.eml.latte b/Email/change-email.eml.latte new file mode 100644 index 00000000..6cff8c11 --- /dev/null +++ b/Email/change-email.eml.latte @@ -0,0 +1,204 @@ + + + + + + Подтверждение изменения Email + + + + + + + + +
+
+ + + + +
+   +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+   +
+

Подтверждение изменения Email

+
+
+ + + + + +
+ + + + +
+
+ +
+ + + + + +
+   +
+ +
+ + + + + +
+   +
+ +

+ Здравствуйте, {$name}! Вы вероятно изменили свой адрес электронной почты в OpenVK. Чтобы изменение вступило в силу, необходимо подтвердить ваш новый Email. +

+ + + + + +
+   +
+ + + + + +
+ + + + +
+
+ Подтвердить Email! +
+
+
+ + + + + +
+   +
+ +

+ Если кнопка не работает, вы можете попробовать скопировать и вставить эту ссылку в адресную строку вашего веб-обозревателя: +

+ + + + + +
+ + http://{$_SERVER['HTTP_HOST']}/settings/change_email?key={$key} + +
+ +

+ Обратите внимание на то, что эту ссылку нельзя: +

+ +
    +
  • Передавать другим людям (даже друзьям, питомцам, соседам, любимым девушкам)
  • +
  • Использовать, если прошло более двух дней с её генерации
  • +
+ + + + + +
+

+ Ещё раз обратите внимание на то, что данную ссылку или письмо ни в коем случае нельзя передавать другим людям! Даже если они представляются службой поддержки.
+ Это письмо предназначено исключительно для одноразового, непосредственного использования владельцем аккаунта. +

+
+ + + + + +
+   +
+ +

+ С уважением, овк-тян. +

+ + + + + +
+   +
+ +
+ + + + + +
+   +
+ +

+ + Вы получили это письмо так как кто-то или вы изменили адрес электронной почты. Это не рассылка и от неё нельзя отписаться. Если вы всё равно хотите перестать получать подобные письма, деактивируйте ваш аккаунт. + +

+
+
+
+ + + + +
+   +
+
+
+ + diff --git a/VKAPI/Handlers/Account.php b/VKAPI/Handlers/Account.php index 7e707cf3..d1722b29 100644 --- a/VKAPI/Handlers/Account.php +++ b/VKAPI/Handlers/Account.php @@ -13,9 +13,9 @@ final class Account extends VKAPIRequestHandler "last_name" => $this->getUser()->getLastName(), "home_town" => $this->getUser()->getHometown(), "status" => $this->getUser()->getStatus(), - "bdate" => "1.1.1970", // TODO - "bdate_visibility" => 0, // TODO - "phone" => "+420 ** *** 228", // TODO + "bdate" => "1.1.1970", # TODO + "bdate_visibility" => 0, # TODO + "phone" => "+420 ** *** 228", # TODO "relation" => $this->getUser()->getMaritalStatus(), "sex" => $this->getUser()->isFemale() ? 1 : 2 ]; @@ -25,12 +25,12 @@ final class Account extends VKAPIRequestHandler { $this->requireUser(); - // Цiй метод є заглушка + # Цiй метод є заглушка return (object) [ "2fa_required" => 0, - "country" => "CZ", // TODO - "eu_user" => false, // TODO + "country" => "CZ", # TODO + "eu_user" => false, # TODO "https_required" => 1, "intro" => 0, "community_comments" => false, @@ -55,7 +55,7 @@ final class Account extends VKAPIRequestHandler { $this->requireUser(); - // Цiй метод є заглушка + # Цiй метод є заглушка return 1; } @@ -73,6 +73,6 @@ final class Account extends VKAPIRequestHandler "messages" => $this->getUser()->getUnreadMessagesCount() ]; - // TODO: Filter + # TODO: Filter } } diff --git a/VKAPI/Handlers/Friends.php b/VKAPI/Handlers/Friends.php index 2e917f2d..760cef21 100644 --- a/VKAPI/Handlers/Friends.php +++ b/VKAPI/Handlers/Friends.php @@ -25,7 +25,7 @@ final class Friends extends VKAPIRequestHandler $usersApi = new Users($this->getUser()); if (!is_null($fields)) { - $response = $usersApi->get(implode(',', $friends), $fields, 0, $count); // FIXME + $response = $usersApi->get(implode(',', $friends), $fields, 0, $count); # FIXME } return (object) [ diff --git a/VKAPI/Handlers/Groups.php b/VKAPI/Handlers/Groups.php index eb5d3230..ff34d562 100644 --- a/VKAPI/Handlers/Groups.php +++ b/VKAPI/Handlers/Groups.php @@ -48,7 +48,7 @@ final class Groups extends VKAPIRequestHandler "name" => "DELETED", "deactivated" => "deleted" ]; - }else if($clbs[$i] == null){ + }else if($clbs[$i] == NULL){ }else{ $rClubs[$i] = (object)[ @@ -95,10 +95,10 @@ final class Groups extends VKAPIRequestHandler $clubs = new ClubsRepo; - if ($group_ids == null && $group_id != null) + if ($group_ids == NULL && $group_id != NULL) $group_ids = $group_id; - if ($group_ids == null && $group_id == null) + if ($group_ids == NULL && $group_id == NULL) $this->fail(100, "One of the parameters specified was missing or invalid: group_ids is undefined"); $clbs = explode(',', $group_ids); @@ -123,7 +123,7 @@ final class Groups extends VKAPIRequestHandler "type" => "group", "description" => "This group was deleted or it doesn't exist" ]; - }else if($clbs[$i] == null){ + }else if($clbs[$i] == NULL){ }else{ $response[$i] = (object)[ diff --git a/VKAPI/Handlers/Likes.php b/VKAPI/Handlers/Likes.php index b002075b..a9f184b6 100644 --- a/VKAPI/Handlers/Likes.php +++ b/VKAPI/Handlers/Likes.php @@ -63,7 +63,7 @@ final class Likes extends VKAPIRequestHandler return (object)[ "liked" => (int) $post->hasLikeFrom($user), - "copied" => 0 // TODO: handle this + "copied" => 0 # TODO: handle this ]; break; default: diff --git a/VKAPI/Handlers/Users.php b/VKAPI/Handlers/Users.php index 3664903f..8aa25b69 100644 --- a/VKAPI/Handlers/Users.php +++ b/VKAPI/Handlers/Users.php @@ -7,9 +7,9 @@ final class Users extends VKAPIRequestHandler { function get(string $user_ids = "0", string $fields = "", int $offset = 0, int $count = 100, User $authuser = null /* костыль(( */): array { - // $this->requireUser(); + # $this->requireUser(); - if($authuser == null) $authuser = $this->getUser(); + if($authuser == NULL) $authuser = $this->getUser(); $users = new UsersRepo; if($user_ids == "0") @@ -34,7 +34,7 @@ final class Users extends VKAPIRequestHandler "last_name" => "", "deactivated" => "deleted" ]; - }else if($usrs[$i] == null){ + }else if($usrs[$i] == NULL){ }else{ $response[$i] = (object)[ @@ -73,21 +73,21 @@ final class Users extends VKAPIRequestHandler case 'photo_200': $response[$i]->photo_50 = $usr->getAvatarURL("normal"); break; - case 'photo_200_orig': // вообще не ебу к чему эта строка ну пусть будет кек + case 'photo_200_orig': # вообще не ебу к чему эта строка ну пусть будет кек $response[$i]->photo_50 = $usr->getAvatarURL("normal"); break; case 'photo_400_orig': $response[$i]->photo_50 = $usr->getAvatarURL("normal"); break; - // Она хочет быть выебанной видя матан - // Покайфу когда ты Виет а вокруг лишь дискриминант + # Она хочет быть выебанной видя матан + # Покайфу когда ты Виет а вокруг лишь дискриминант case 'status': - if($usr->getStatus() != null) + if($usr->getStatus() != NULL) $response[$i]->status = $usr->getStatus(); break; case 'screen_name': - if($usr->getShortCode() != null) + if($usr->getShortCode() != NULL) $response[$i]->screen_name = $usr->getShortCode(); break; case 'friend_status': diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index 7635342b..50677435 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -23,56 +23,21 @@ final class Wall extends VKAPIRequestHandler foreach ($posts->getPostsFromUsersWall((int)$owner_id, 1, $count, $offset) as $post) { $from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId(); - $attachments; - foreach($post->getChildren() as $attachment) - { - if($attachment instanceof \openvk\Web\Models\Entities\Photo) - { + $attachments = []; + foreach($post->getChildren() as $attachment) { + if($attachment instanceof \openvk\Web\Models\Entities\Photo) { + if($attachment->isDeleted()) + continue; + $attachments[] = [ "type" => "photo", "photo" => [ - "album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : null, - "date" => $attachment->getPublicationTime()->timestamp(), - "id" => $attachment->getVirtualId(), + "album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : NULL, + "date" => $attachment->getPublicationTime()->timestamp(), + "id" => $attachment->getVirtualId(), "owner_id" => $attachment->getOwner()->getId(), - "sizes" => array( - [ - "height" => 2560, - "url" => $attachment->getURLBySizeId("normal"), - "type" => "m", - "width" => 2560, - ], - [ - "height" => 130, - "url" => $attachment->getURLBySizeId("tiny"), - "type" => "o", - "width" => 130, - ], - [ - "height" => 604, - "url" => $attachment->getURLBySizeId("normal"), - "type" => "p", - "width" => 604, - ], - [ - "height" => 807, - "url" => $attachment->getURLBySizeId("large"), - "type" => "q", - "width" => 807, - ], - [ - "height" => 1280, - "url" => $attachment->getURLBySizeId("larger"), - "type" => "r", - "width" => 1280, - ], - [ - "height" => 75, // Для временного компросима оставляю статическое число. Если каждый раз обращаться к файлу за количеством пикселов, то наступает пuпuська полная с производительностью, так что пока так - "url" => $attachment->getURLBySizeId("miniscule"), - "type" => "s", - "width" => 75, - ]), - "text" => "", + "sizes" => array_values($attachment->getVkApiSizes()), + "text" => "", "has_tags" => false ] ]; @@ -86,10 +51,10 @@ final class Wall extends VKAPIRequestHandler "date" => $post->getPublicationTime()->timestamp(), "post_type" => "post", "text" => $post->getText(), - "can_edit" => 0, // TODO + "can_edit" => 0, # TODO "can_delete" => $post->canBeDeletedBy($this->getUser()), "can_pin" => $post->canBePinnedBy($this->getUser()), - "can_archive" => false, // TODO MAYBE + "can_archive" => false, # TODO MAYBE "is_archived" => false, "is_pinned" => $post->isPinned(), "attachments" => $attachments, @@ -115,7 +80,7 @@ final class Wall extends VKAPIRequestHandler else $groups[] = $from_id * -1; - $attachments = null; // free attachments so it will not clone everythingg + $attachments = NULL; # free attachments so it will not clone everythingg } if($extended == 1) @@ -170,9 +135,9 @@ final class Wall extends VKAPIRequestHandler ]; } - function getById(string $posts, int $extended = 0, string $fields = "", User $user = null) + function getById(string $posts, int $extended = 0, string $fields = "", User $user = NULL) { - if($user == null) $user = $this->getUser(); // костыли костыли крылышки + if($user == NULL) $user = $this->getUser(); # костыли костыли крылышки $items = []; $profiles = []; @@ -195,7 +160,7 @@ final class Wall extends VKAPIRequestHandler $attachments[] = [ "type" => "photo", "photo" => [ - "album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : null, + "album_id" => $attachment->getAlbum() ? $attachment->getAlbum()->getId() : NULL, "date" => $attachment->getPublicationTime()->timestamp(), "id" => $attachment->getVirtualId(), "owner_id" => $attachment->getOwner()->getId(), @@ -231,7 +196,7 @@ final class Wall extends VKAPIRequestHandler "width" => 1280, ], [ - "height" => 75, // Для временного компросима оставляю статическое число. Если каждый раз обращаться к файлу за количеством пикселов, то наступает пuпuська полная с производительностью, так что пока так + "height" => 75, # Для временного компросима оставляю статическое число. Если каждый раз обращаться к файлу за количеством пикселов, то наступает пuпuська полная с производительностью, так что пока так "url" => $attachment->getURLBySizeId("miniscule"), "type" => "s", "width" => 75, @@ -250,10 +215,10 @@ final class Wall extends VKAPIRequestHandler "date" => $post->getPublicationTime()->timestamp(), "post_type" => "post", "text" => $post->getText(), - "can_edit" => 0, // TODO + "can_edit" => 0, # TODO "can_delete" => $post->canBeDeletedBy($user), "can_pin" => $post->canBePinnedBy($user), - "can_archive" => false, // TODO MAYBE + "can_archive" => false, # TODO MAYBE "is_archived" => false, "is_pinned" => $post->isPinned(), "post_source" => (object)["type" => "vk"], @@ -279,7 +244,7 @@ final class Wall extends VKAPIRequestHandler else $groups[] = $from_id * -1; - $attachments = null; // free attachments so it will not clone everythingg + $attachments = NULL; # free attachments so it will not clone everythingg } } @@ -370,12 +335,12 @@ final class Wall extends VKAPIRequestHandler if($signed == 1) $flags |= 0b01000000; - // TODO: Compatible implementation of this + # TODO: Compatible implementation of this try { - $photo = null; - $video = null; + $photo = NULL; + $video = NULL; if($_FILES["photo"]["error"] === UPLOAD_ERR_OK) { - $album = null; + $album = NULL; if(!$anon && $owner_id > 0 && $owner_id === $this->getUser()->getId()) $album = (new AlbumsRepo)->getUserWallAlbum($wallOwner); diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php index f306a8e6..ffe81ab4 100644 --- a/Web/Models/Entities/Club.php +++ b/Web/Models/Entities/Club.php @@ -99,6 +99,14 @@ class Club extends RowModel { return $this->getRecord()->about; } + + function getDescriptionHtml(): ?string + { + if(!is_null($this->getDescription())) + return nl2br(htmlspecialchars($this->getDescription(), ENT_DISALLOWED | ENT_XHTML)); + else + return NULL; + } function getShortCode(): ?string { @@ -302,8 +310,8 @@ class Club extends RowModel { $manager = (new Managers)->getByUserAndClub($user->getId(), $this->getId()); - if ($ignoreHidden && $manager !== null && $manager->isHidden()) - return null; + if ($ignoreHidden && $manager !== NULL && $manager->isHidden()) + return NULL; return $manager; } diff --git a/Web/Models/Entities/Correspondence.php b/Web/Models/Entities/Correspondence.php index 031b905e..e4b7d96e 100644 --- a/Web/Models/Entities/Correspondence.php +++ b/Web/Models/Entities/Correspondence.php @@ -131,7 +131,7 @@ class Correspondence */ function getPreviewMessage(): ?Message { - $messages = $this->getMessages(1, null, 1); + $messages = $this->getMessages(1, NULL, 1); return $messages[0] ?? NULL; } diff --git a/Web/Models/Entities/EmailChangeVerification.php b/Web/Models/Entities/EmailChangeVerification.php new file mode 100644 index 00000000..e9b92db1 --- /dev/null +++ b/Web/Models/Entities/EmailChangeVerification.php @@ -0,0 +1,15 @@ +getRecord()->new_email; + } +} diff --git a/Web/Models/Entities/Message.php b/Web/Models/Entities/Message.php index f4fcc9af..de840606 100644 --- a/Web/Models/Entities/Message.php +++ b/Web/Models/Entities/Message.php @@ -126,7 +126,7 @@ class Message extends RowModel ], "timing" => [ "sent" => (string) $this->getSendTime()->format("%e %B %G" . tr("time_at_sp") . "%X"), - "edited" => is_null($this->getEditTime()) ? null : (string) $this->getEditTime(), + "edited" => is_null($this->getEditTime()) ? NULL : (string) $this->getEditTime(), ], "text" => $this->getText(), "read" => !$this->isUnread(), diff --git a/Web/Models/Entities/Photo.php b/Web/Models/Entities/Photo.php index 0f223679..71d5efcb 100644 --- a/Web/Models/Entities/Photo.php +++ b/Web/Models/Entities/Photo.php @@ -165,8 +165,11 @@ class Photo extends Media foreach($manifest->Size as $size) $mappings[(string) $size["id"]] = (string) $size["vkId"]; - foreach($sizes as $id => $meta) - $res[$mappings[$id] ?? $id] = $meta; + foreach($sizes as $id => $meta) { + $type = $mappings[$id] ?? $id; + $meta->type = $type; + $res[$type] = $meta; + } return $res; } diff --git a/Web/Models/Entities/Postable.php b/Web/Models/Entities/Postable.php index c3585dc3..b4f8b4c6 100644 --- a/Web/Models/Entities/Postable.php +++ b/Web/Models/Entities/Postable.php @@ -87,7 +87,7 @@ abstract class Postable extends Attachable ])); } - // TODO add pagination + # TODO add pagination function getLikers(): \Traversable { $sel = DB::i()->getContext()->table("likes")->where([ diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index 68ad6a77..7c432561 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -85,6 +85,11 @@ class User extends RowModel { return (bool) $this->getRecord()->microblog; } + + function getMainPage(): int + { + return $this->getRecord()->main_page; + } function getChandlerGUID(): string { @@ -877,6 +882,17 @@ class User extends RowModel return true; } + function changeEmail(string $email): void + { + DatabaseConnection::i()->getContext()->table("ChandlerUsers") + ->where("id", $this->getChandlerUser()->getId())->update([ + "login" => $email + ]); + + $this->stateChanges("email", $email); + $this->save(); + } + function adminNotify(string $message): bool { $admId = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["adminAccount"]; @@ -928,7 +944,7 @@ class User extends RowModel return $this->getRecord()->website; } - // ты устрица + # ты устрица function isActivated(): bool { return (bool) $this->getRecord()->activated; diff --git a/Web/Models/Entities/Video.php b/Web/Models/Entities/Video.php index 7bf1b010..b45072b9 100644 --- a/Web/Models/Entities/Video.php +++ b/Web/Models/Entities/Video.php @@ -63,7 +63,7 @@ class Video extends Media if(!file_exists($this->getFileName())) { if((time() - $this->getRecord()->last_checked) > 3600) { - // TODO notify that video processor is probably dead + # TODO notify that video processor is probably dead } return false; diff --git a/Web/Models/Repositories/EmailChangeVerifications.php b/Web/Models/Repositories/EmailChangeVerifications.php new file mode 100644 index 00000000..0e8c668b --- /dev/null +++ b/Web/Models/Repositories/EmailChangeVerifications.php @@ -0,0 +1,33 @@ +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()); + } +} diff --git a/Web/Models/Repositories/Notes.php b/Web/Models/Repositories/Notes.php index a41c6914..7d95f975 100644 --- a/Web/Models/Repositories/Notes.php +++ b/Web/Models/Repositories/Notes.php @@ -39,7 +39,7 @@ class Notes if(!is_null($note)) return new Note($note); else - return null; + return NULL; } function getUserNotesCount(User $user): int diff --git a/Web/Models/Repositories/Posts.php b/Web/Models/Repositories/Posts.php index 94ce6482..6dde27c8 100644 --- a/Web/Models/Repositories/Posts.php +++ b/Web/Models/Repositories/Posts.php @@ -96,7 +96,7 @@ class Posts if(!is_null($post)) return new Post($post); else - return null; + return NULL; } diff --git a/Web/Models/Repositories/TicketComments.php b/Web/Models/Repositories/TicketComments.php index 4d3c5316..9277218f 100644 --- a/Web/Models/Repositories/TicketComments.php +++ b/Web/Models/Repositories/TicketComments.php @@ -1,8 +1,5 @@ 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 { diff --git a/Web/Models/Repositories/Tickets.php b/Web/Models/Repositories/Tickets.php index 7f00e62b..1e84ebd8 100644 --- a/Web/Models/Repositories/Tickets.php +++ b/Web/Models/Repositories/Tickets.php @@ -50,7 +50,7 @@ class Tickets if(!is_null($requests)) return new Req($requests); else - return null; + return NULL; } diff --git a/Web/Models/Repositories/Topics.php b/Web/Models/Repositories/Topics.php index 23b854d4..1c176310 100644 --- a/Web/Models/Repositories/Topics.php +++ b/Web/Models/Repositories/Topics.php @@ -35,7 +35,7 @@ class Topics { $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; - // Get pinned topics first + # Get pinned topics first $query = "SELECT `id` FROM `topics` WHERE `pinned` = 1 AND `group` = ? AND `deleted` = 0 UNION SELECT `id` FROM `topics` WHERE `pinned` = 0 AND `group` = ? AND `deleted` = 0"; $query .= " LIMIT " . $perPage . " OFFSET " . ($page - 1) * $perPage; diff --git a/Web/Presenters/AboutPresenter.php b/Web/Presenters/AboutPresenter.php index cf4572b8..3caa007d 100644 --- a/Web/Presenters/AboutPresenter.php +++ b/Web/Presenters/AboutPresenter.php @@ -14,7 +14,12 @@ final class AboutPresenter extends OpenVKPresenter { if(!is_null($this->user)) { header("HTTP/1.1 302 Found"); - header("Location: /id" . $this->user->id); + + if($this->user->identity->getMainPage()) + header("Location: /feed"); + else + header("Location: /id" . $this->user->id); + exit; } @@ -85,7 +90,7 @@ final class AboutPresenter extends OpenVKPresenter if(is_null($lg)) $this->throwError(404, "Not found", "Language is not found"); header("Content-Type: application/javascript"); - echo "window.lang = " . json_encode($localizer->export($lang)) . ";"; // привет хардкод :DDD + echo "window.lang = " . json_encode($localizer->export($lang)) . ";"; # привет хардкод :DDD exit; } @@ -120,7 +125,7 @@ final class AboutPresenter extends OpenVKPresenter function renderHumansTxt(): void { - // :D + # :D header("HTTP/1.1 302 Found"); header("Location: https://github.com/openvk/openvk#readme"); diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php index 39845ae4..f35e1081 100644 --- a/Web/Presenters/AdminPresenter.php +++ b/Web/Presenters/AdminPresenter.php @@ -23,7 +23,7 @@ final class AdminPresenter extends OpenVKPresenter private function warnIfNoCommerce(): void { if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"]) - $this->flash("warn", "Коммерция отключена системным администратором", "Настройки ваучеров и подарков будут сохранены, но не будут оказывать никакого влияния."); + $this->flash("warn", tr("admin_commerce_disabled"), tr("admin_commerce_disabled_desc")); } private function searchResults(object $repo, &$count) @@ -70,14 +70,14 @@ final class AdminPresenter extends OpenVKPresenter $user->setLast_Name($this->postParam("last_name")); $user->setPseudo($this->postParam("nickname")); $user->setStatus($this->postParam("status")); - $user->setVerified(empty($this->postParam("verify") ? 0 : 1)); - if($user->onlineStatus() != $this->postParam("online")) $user->setOnline(intval($this->postParam("online"))); if(!$user->setShortCode(empty($this->postParam("shortcode")) ? NULL : $this->postParam("shortcode"))) $this->flash("err", tr("error"), tr("error_shorturl_incorrect")); + $user->changeEmail($this->postParam("email")); + if($user->onlineStatus() != $this->postParam("online")) $user->setOnline(intval($this->postParam("online"))); + $user->setVerified(empty($this->postParam("verify") ? 0 : 1)); + $user->save(); break; - - } } diff --git a/Web/Presenters/CommentPresenter.php b/Web/Presenters/CommentPresenter.php index 41f0bc79..81783a2f 100644 --- a/Web/Presenters/CommentPresenter.php +++ b/Web/Presenters/CommentPresenter.php @@ -60,7 +60,7 @@ final class CommentPresenter extends OpenVKPresenter } } - // TODO move to trait + # TODO move to trait try { $photo = NULL; $video = NULL; diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php index 65d3640f..c239ff22 100644 --- a/Web/Presenters/GroupPresenter.php +++ b/Web/Presenters/GroupPresenter.php @@ -89,7 +89,7 @@ final class GroupPresenter extends OpenVKPresenter $this->template->club = $this->clubs->get($id); $this->template->onlyShowManagers = $this->queryParam("onlyAdmins") == "1"; if($this->template->onlyShowManagers) { - $this->template->followers = null; + $this->template->followers = NULL; $this->template->managers = $this->template->club->getManagers((int) ($this->queryParam("p") ?? 1), !$this->template->club->canBeModifiedBy($this->user->identity)); if($this->template->club->canBeModifiedBy($this->user->identity) || !$this->template->club->isOwnerHidden()) { @@ -99,7 +99,7 @@ final class GroupPresenter extends OpenVKPresenter $this->template->count = $this->template->club->getManagersCount(); } else { $this->template->followers = $this->template->club->getFollowers((int) ($this->queryParam("p") ?? 1)); - $this->template->managers = null; + $this->template->managers = NULL; $this->template->count = $this->template->club->getFollowersCount(); } @@ -116,7 +116,7 @@ final class GroupPresenter extends OpenVKPresenter $user = is_null($this->queryParam("user")) ? $this->postParam("user") : $this->queryParam("user"); $comment = $this->postParam("comment"); $removeComment = $this->postParam("removeComment") === "1"; - $hidden = ["0" => false, "1" => true][$this->queryParam("hidden")] ?? null; + $hidden = ["0" => false, "1" => true][$this->queryParam("hidden")] ?? NULL; //$index = $this->queryParam("index"); if(!$user) $this->badRequest(); diff --git a/Web/Presenters/InternalAPIPresenter.php b/Web/Presenters/InternalAPIPresenter.php index f0551b7f..60c0b550 100644 --- a/Web/Presenters/InternalAPIPresenter.php +++ b/Web/Presenters/InternalAPIPresenter.php @@ -80,11 +80,11 @@ final class InternalAPIPresenter extends OpenVKPresenter if ($postTZ != $sessionOffset || $sessionOffset == null) { Session::i()->set("_timezoneOffset", $postTZ ? $postTZ : 3 * MINUTE ); $this->returnJson([ - "success" => 1 // If it's new value + "success" => 1 # If it's new value ]); } else { $this->returnJson([ - "success" => 2 // If it's the same value (if for some reason server will call this func) + "success" => 2 # If it's the same value (if for some reason server will call this func) ]); } } else { diff --git a/Web/Presenters/MessengerPresenter.php b/Web/Presenters/MessengerPresenter.php index fdcb934e..968c1936 100644 --- a/Web/Presenters/MessengerPresenter.php +++ b/Web/Presenters/MessengerPresenter.php @@ -106,7 +106,7 @@ final class MessengerPresenter extends OpenVKPresenter $messages = []; $correspondence = new Correspondence($this->user->identity, $correspondent); - foreach($correspondence->getMessages(1, $lastMsg === 0 ? null : $lastMsg) as $message) + foreach($correspondence->getMessages(1, $lastMsg === 0 ? NULL : $lastMsg) as $message) $messages[] = $message->simplify(); header("Content-Type: application/json"); diff --git a/Web/Presenters/OpenVKPresenter.php b/Web/Presenters/OpenVKPresenter.php index f4192cce..d9202a7d 100755 --- a/Web/Presenters/OpenVKPresenter.php +++ b/Web/Presenters/OpenVKPresenter.php @@ -118,7 +118,7 @@ abstract class OpenVKPresenter extends SimplePresenter return ($action === "register" || $action === "login"); } - return (bool) $this->user->raw->can($action)->model($model)->whichBelongsTo($context === -1 ? null : $context); + return (bool) $this->user->raw->can($action)->model($model)->whichBelongsTo($context === -1 ? NULL : $context); } protected function assertPermission(string $model, string $action, int $context, bool $throw = false): void @@ -252,7 +252,7 @@ abstract class OpenVKPresenter extends SimplePresenter exit; } - // ето для емейл уже надо (и по хорошему надо бы избавится от повторяющегося кода мда) + # ето для емейл уже надо (и по хорошему надо бы избавится от повторяющегося кода мда) if(!$this->user->identity->isActivated() && !$this->activationTolerant) { header("HTTP/1.1 403 Forbidden"); $this->getTemplatingEngine()->render(__DIR__ . "/templates/@email.xml", [ @@ -290,7 +290,7 @@ abstract class OpenVKPresenter extends SimplePresenter $whichbrowser = new WhichBrowser\Parser(getallheaders()); $mobiletheme = OPENVK_ROOT_CONF["openvk"]["preferences"]["defaultMobileTheme"]; - if($mobiletheme && $whichbrowser->isType('mobile') && Session::i()->get("_tempTheme") == null) + if($mobiletheme && $whichbrowser->isType('mobile') && Session::i()->get("_tempTheme") == NULL) $this->setSessionTheme($mobiletheme); $theme = NULL; @@ -301,7 +301,7 @@ abstract class OpenVKPresenter extends SimplePresenter $theme = Themepacks::i()[Session::i()->get("_sessionTheme", "ovk")]; } else if($this->requestParam("themePreview")) { $theme = Themepacks::i()[$this->requestParam("themePreview")]; - } else if($this->user->identity !== null && $this->user->identity->getTheme()) { + } else if($this->user->identity !== NULL && $this->user->identity->getTheme()) { $theme = $this->user->identity->getTheme(); } diff --git a/Web/Presenters/SearchPresenter.php b/Web/Presenters/SearchPresenter.php index 0fc4611f..a5eaedc7 100644 --- a/Web/Presenters/SearchPresenter.php +++ b/Web/Presenters/SearchPresenter.php @@ -29,7 +29,7 @@ final class SearchPresenter extends OpenVKPresenter if($query != "") $this->assertUserLoggedIn(); - // https://youtu.be/pSAWM5YuXx8 + # https://youtu.be/pSAWM5YuXx8 $repos = [ "groups" => "clubs", "users" => "users" ]; $repo = $repos[$type] or $this->throwError(400, "Bad Request", "Invalid search entity $type."); diff --git a/Web/Presenters/SupportPresenter.php b/Web/Presenters/SupportPresenter.php index 9c2bcc8e..b8d197d6 100644 --- a/Web/Presenters/SupportPresenter.php +++ b/Web/Presenters/SupportPresenter.php @@ -28,6 +28,41 @@ final class SupportPresenter extends OpenVKPresenter $this->assertUserLoggedIn(); $this->template->mode = in_array($this->queryParam("act"), ["faq", "new", "list"]) ? $this->queryParam("act") : "faq"; + if($this->template->mode === "faq") { + $lang = Session::i()->get("lang", "ru"); + $base = OPENVK_ROOT . "/data/knowledgebase/faq"; + if(file_exists("$base.$lang.md")) + $file = "$base.$lang.md"; + else if(file_exists("$base.md")) + $file = "$base.md"; + else + $file = NULL; + + if(is_null($file)) { + $this->template->faq = []; + } else { + $lines = file($file); + $faq = []; + $index = 0; + + foreach($lines as $line) { + if(strpos($line, "# ") === 0) + ++$index; + + $faq[$index][] = $line; + } + + $this->template->faq = array_map(function($section) { + $title = substr($section[0], 2); + array_shift($section); + return [ + $title, + (new Parsedown())->text(implode("\n", $section)) + ]; + }, $faq); + } + } + $this->template->count = $this->tickets->getTicketsCountByUserId($this->user->id); if($this->template->mode === "list") { $this->template->page = (int) ($this->queryParam("p") ?? 1); @@ -79,12 +114,13 @@ final class SupportPresenter extends OpenVKPresenter $act = $this->queryParam("act") ?? "open"; switch($act) { default: + # NOTICE falling through case "open": $state = 0; - break; + break; case "answered": $state = 1; - break; + break; case "closed": $state = 2; } diff --git a/Web/Presenters/TopicsPresenter.php b/Web/Presenters/TopicsPresenter.php index f29c7979..16dd1795 100644 --- a/Web/Presenters/TopicsPresenter.php +++ b/Web/Presenters/TopicsPresenter.php @@ -91,7 +91,7 @@ final class TopicsPresenter extends OpenVKPresenter $topic->setFlags($flags); $topic->save(); - // TODO move to trait + # TODO move to trait try { $photo = NULL; $video = NULL; diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index b9e8b714..c6a41616 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -9,12 +9,15 @@ use openvk\Web\Models\Repositories\Albums; use openvk\Web\Models\Repositories\Videos; use openvk\Web\Models\Repositories\Notes; use openvk\Web\Models\Repositories\Vouchers; +use openvk\Web\Models\Repositories\EmailChangeVerifications; use openvk\Web\Models\Exceptions\InvalidUserNameException; use openvk\Web\Util\Validator; use openvk\Web\Models\Entities\Notifications\{CoinsTransferNotification, RatingUpNotification}; +use openvk\Web\Models\Entities\EmailChangeVerification; use Chandler\Security\Authenticator; use lfkeitel\phptotp\{Base32, Totp}; use chillerlan\QRCode\{QRCode, QROptions}; +use Nette\Database\UniqueConstraintViolationException; final class UserPresenter extends OpenVKPresenter { @@ -132,7 +135,7 @@ final class UserPresenter extends OpenVKPresenter if(!$id) $this->notFound(); - + $user = $this->users->get($id); if($_SERVER["REQUEST_METHOD"] === "POST") { $this->willExecuteWriteAction($_GET['act'] === "status"); @@ -300,7 +303,7 @@ final class UserPresenter extends OpenVKPresenter if(!$id) $this->notFound(); - + if(in_array($this->queryParam("act"), ["finance", "finance.top-up"]) && !OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"]) $this->flashFail("err", tr("error"), tr("feature_disabled")); @@ -312,7 +315,7 @@ final class UserPresenter extends OpenVKPresenter if($this->postParam("old_pass") && $this->postParam("new_pass") && $this->postParam("repeat_pass")) { if($this->postParam("new_pass") === $this->postParam("repeat_pass")) { if($this->user->identity->is2faEnabled()) { - $code = $this->postParam("code"); + $code = $this->postParam("password_change_code"); if(!($code === (new Totp)->GenerateToken(Base32::decode($this->user->identity->get2faSecret())) || $this->user->identity->use2faBackupCode((int) $code))) $this->flashFail("err", tr("error"), tr("incorrect_2fa_code")); } @@ -323,6 +326,46 @@ final class UserPresenter extends OpenVKPresenter $this->flashFail("err", tr("error"), tr("error_new_password")); } } + + if($this->postParam("new_email")) { + if(!Validator::i()->emailValid($this->postParam("new_email"))) + $this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment")); + + if(!Authenticator::verifyHash($this->postParam("email_change_pass"), $user->getChandlerUser()->getRaw()->passwordHash)) + $this->flashFail("err", tr("error"), tr("incorrect_password")); + + if($user->is2faEnabled()) { + $code = $this->postParam("email_change_code"); + if(!($code === (new Totp)->GenerateToken(Base32::decode($user->get2faSecret())) || $user->use2faBackupCode((int) $code))) + $this->flashFail("err", tr("error"), tr("incorrect_2fa_code")); + } + + if($this->postParam("new_email") !== $user->getEmail()) { + if (OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']) { + $request = (new EmailChangeVerifications)->getLatestByUser($user); + if(!is_null($request) && $request->isNew()) + $this->flashFail("err", tr("forbidden"), tr("email_rate_limit_error")); + + $verification = new EmailChangeVerification; + $verification->setProfile($user->getId()); + $verification->setNew_Email($this->postParam("new_email")); + $verification->save(); + + $params = [ + "key" => $verification->getKey(), + "name" => $user->getCanonicalName(), + ]; + $this->sendmail($this->postParam("new_email"), "change-email", $params); #Vulnerability possible + $this->flashFail("succ", tr("information_-1"), tr("email_change_confirm_message")); + } + + try { + $user->changeEmail($this->postParam("new_email")); + } catch(UniqueConstraintViolationException $ex) { + $this->flashFail("err", tr("error"), tr("user_already_exists")); + } + } + } if(!$user->setShortCode(empty($this->postParam("sc")) ? NULL : $this->postParam("sc"))) $this->flashFail("err", tr("error"), tr("error_shorturl_incorrect")); @@ -376,6 +419,9 @@ final class UserPresenter extends OpenVKPresenter if(in_array($this->postParam("nsfw"), [0, 1, 2])) $user->setNsfwTolerance((int) $this->postParam("nsfw")); + + if(in_array($this->postParam("main_page"), [0, 1])) + $user->setMain_Page((int) $this->postParam("main_page")); } else if($_GET['act'] === "lMenu") { $settings = [ "menu_bildoj" => "photos", @@ -400,11 +446,7 @@ final class UserPresenter extends OpenVKPresenter throw $ex; } - $this->flash( - "succ", - "Изменения сохранены", - "Новые данные появятся на вашей странице." - ); + $this->flash("succ", tr("changes_saved"), tr("changes_saved_comment")); } $this->template->mode = in_array($this->queryParam("act"), [ "main", "privacy", "finance", "finance.top-up", "interface" @@ -456,7 +498,7 @@ final class UserPresenter extends OpenVKPresenter $this->template->secret = $secret; } - // Why are these crutch? For some reason, the QR code is not displayed if you just pass the render output to the view + # Why are these crutch? For some reason, the QR code is not displayed if you just pass the render output to the view $issuer = OPENVK_ROOT_CONF["openvk"]["appearance"]["name"]; $email = $this->user->identity->getEmail(); @@ -502,6 +544,9 @@ final class UserPresenter extends OpenVKPresenter $this->assertUserLoggedIn(); $this->willExecuteWriteAction(); + if(!OPENVK_ROOT_CONF["openvk"]["preferences"]["commerce"]) + $this->flashFail("err", tr("error"), tr("feature_disabled")); + $receiverAddress = $this->postParam("receiver"); $value = (int) $this->postParam("value"); $message = $this->postParam("message"); @@ -517,7 +562,7 @@ final class UserPresenter extends OpenVKPresenter $receiver = $this->users->getByAddress($receiverAddress); if(!$receiver) - $this->flashFail("err", tr("failed_to_tranfer_points"), tr("receiver_not_found")); + $this->flashFail("err", tr("failed_to_tranfer_points"), tr("receiver_not_found")); if($this->user->identity->getCoins() < $value) $this->flashFail("err", tr("failed_to_tranfer_points"), tr("you_dont_have_enough_points")); @@ -574,4 +619,24 @@ final class UserPresenter extends OpenVKPresenter $this->flashFail("succ", tr("information_-1"), tr("rating_increase_successful", $receiver->getURL(), htmlentities($receiver->getCanonicalName()), $value)); } + + function renderEmailChangeFinish(): void + { + $request = (new EmailChangeVerifications)->getByToken(str_replace(" ", "+", $this->queryParam("key"))); + if(!$request || !$request->isStillValid()) { + $this->flash("err", tr("token_manipulation_error"), tr("token_manipulation_error_comment")); + $this->redirect("/settings"); + } else { + $request->delete(false); + + try { + $request->getUser()->changeEmail($request->getNewEmail()); + } catch(UniqueConstraintViolationException $ex) { + $this->flashFail("err", tr("error"), tr("user_already_exists")); + } + + $this->flash("succ", tr("changes_saved"), tr("changes_saved_comment")); + $this->redirect("/settings"); + } + } } diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php index 9da0311c..5bdac066 100644 --- a/Web/Presenters/WallPresenter.php +++ b/Web/Presenters/WallPresenter.php @@ -414,7 +414,7 @@ final class WallPresenter extends OpenVKPresenter $post->unpin(); } - // TODO localize message based on language and ?act=(un)pin + # TODO localize message based on language and ?act=(un)pin $this->flashFail("succ", tr("information_-1"), tr("changes_saved_comment")); } } diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index bd8de7a0..6ef482cc 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -17,7 +17,7 @@ {script "js/l10n.js"} {script "js/openvk.cls.js"} - {if $isTimezoned == null} + {if $isTimezoned == NULL} {script "js/timezone.js"} {/if} @@ -26,7 +26,7 @@ {css "css/nsfw-posts.css"} {/if} - {if $theme !== null} + {if $theme !== NULL} {if $theme->inheritDefault()} {css "css/style.css"} {css "css/dialog.css"} @@ -103,7 +103,7 @@
{ifset $thisUser} + {include "components/cookies.xml"} + {script "js/node_modules/msgpack-lite/dist/msgpack.min.js"} {script "js/node_modules/soundjs/lib/soundjs.min.js"} {script "js/node_modules/ky/umd.js"} diff --git a/Web/Presenters/templates/Admin/@layout.xml b/Web/Presenters/templates/Admin/@layout.xml index 5213802c..92de0cc0 100644 --- a/Web/Presenters/templates/Admin/@layout.xml +++ b/Web/Presenters/templates/Admin/@layout.xml @@ -1,3 +1,4 @@ +{var $instance_name = OPENVK_ROOT_CONF['openvk']['appearance']['name']} @@ -6,7 +7,7 @@ {var $css = file_get_contents(OPENVK_ROOT . "/Web/static/js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.css")} {str_replace("fonts/", "/assets/packages/static/openvk/js/node_modules/@atlassian/aui/dist/aui/fonts/", $css)|noescape} - {include title} - Админ-панель {=OPENVK_ROOT_CONF['openvk']['appearance']['name']} + {include title} - {_admin} {$instance_name}
@@ -16,23 +17,15 @@
    - - + +
@@ -46,83 +39,64 @@
@@ -139,11 +113,11 @@

{$flashMessage->msg|noescape}

{/ifset} - + {ifset preHeader} {include preHeader} {/ifset} - +
@@ -167,11 +141,11 @@
- + {script "js/node_modules/jquery/dist/jquery.min.js"} {script "js/node_modules/@atlassian/aui/dist/aui/aui-prototyping.js"} - + {ifset scripts} {include scripts} {/ifset} diff --git a/Web/Presenters/templates/Admin/Club.xml b/Web/Presenters/templates/Admin/Club.xml index 91d7c6c6..a8f3ad50 100644 --- a/Web/Presenters/templates/Admin/Club.xml +++ b/Web/Presenters/templates/Admin/Club.xml @@ -1,191 +1,157 @@ {extends "@layout.xml"} {block title} - Редактировать {$club->getCanonicalName()} + {_edit} {$club->getCanonicalName()} {/block} {block heading} {$club->getCanonicalName()} {/block} - {block content} -{var $isMain = $mode === 'main'} -{var $isBan = $mode === 'ban'} -{var $isFollowers = $mode === 'followers'} + {var $isMain = $mode === 'main'} + {var $isBan = $mode === 'ban'} + {var $isFollowers = $mode === 'followers'} -{if $isMain} - - - -
- -
-
- - - - - - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- isVerified()} checked {/if} /> - -
-
- isHideFromGlobalFeedEnabled()} checked {/if} /> - -
-
-
-
- - -
-
-
-
-{/if} - -{if $isBan} - - - -
- -
-
- - -
-
-
-
- - -
-
-
-
-{/if} - -{if $isFollowers} - - - -{var $followers = iterator_to_array($followers)} - -
- - - - - - - - - - - - -
{$follower->getId()} - + {if $isMain} +
+ +
+
+ + - {$follower->getCanonicalName()} + - - {$follower->getCanonicalName()} - - - заблокирован - -
{$follower->isFemale() ? "Женский" : "Мужской"}{$follower->getShortCode() ?? "(отсутствует)"}{$follower->getRegistrationTime()} - - Редактировать - -
-
- {var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count} - - - ⭁ туда - - - ⭇ сюда - -
-
-{/if} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ isVerified()} checked {/if} /> + +
+
+ isHideFromGlobalFeedEnabled()} checked {/if} /> + +
+
+
+
+ + +
+
+ +
+ {/if} + + {if $isBan} +
+ +
+
+ + +
+
+
+
+ + +
+
+
+
+ {/if} + + {if $isFollowers} + {var $followers = iterator_to_array($followers)} + +
+ + + + + + + + + + + + +
{$follower->getId()} + + + {$follower->getCanonicalName()} + + + + {$follower->getCanonicalName()} + + {_admin_banned} + {$follower->isFemale() ? tr("female") : tr("male")}{$follower->getShortCode() ?? "(" . tr("none") . ")"}{$follower->getRegistrationTime()} + + {_edit} + +
+
+ {var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count} + + « + » +
+
+ {/if} {/block} diff --git a/Web/Presenters/templates/Admin/Clubs.xml b/Web/Presenters/templates/Admin/Clubs.xml index cbe5d80b..d240768b 100644 --- a/Web/Presenters/templates/Admin/Clubs.xml +++ b/Web/Presenters/templates/Admin/Clubs.xml @@ -2,14 +2,16 @@ {var $search = true} {block title} - Группы + {_admin_club_search} {/block} {block heading} - Бутылки + {_groups} {/block} -{block searchTitle}Поиск бутылок{/block} +{block searchTitle} + {include title} +{/block} {block content} {var $clubs = iterator_to_array($clubs)} @@ -18,12 +20,12 @@ - - - - - - + + + + + + @@ -49,11 +51,11 @@ {$user->getCanonicalName()} - + @@ -63,11 +65,7 @@
{var $isLast = ((10 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count} - - ⭁ туда - - - ⭇ сюда - + « + »
{/block} diff --git a/Web/Presenters/templates/Admin/Gift.xml b/Web/Presenters/templates/Admin/Gift.xml index fd05037c..8a20c538 100644 --- a/Web/Presenters/templates/Admin/Gift.xml +++ b/Web/Presenters/templates/Admin/Gift.xml @@ -2,9 +2,9 @@ {block title} {if $form->id === 0} - Новый подарок + {_admin_newgift} {else} - Подарок "{$form->name}" + {_gift} "{$form->name}" {/if} {/block} @@ -16,7 +16,7 @@
{if $form->id === 0} @@ -29,43 +29,39 @@ {/if}
- +
- +
- - + +
@@ -75,13 +71,13 @@
- - + +
- + - +
@@ -94,12 +90,12 @@ {block scripts} diff --git a/Web/Presenters/templates/Admin/GiftCategories.xml b/Web/Presenters/templates/Admin/GiftCategories.xml index 113d45b6..d0fe0f30 100644 --- a/Web/Presenters/templates/Admin/GiftCategories.xml +++ b/Web/Presenters/templates/Admin/GiftCategories.xml @@ -1,7 +1,7 @@ {extends "@layout.xml"} {block title} - Наборы подарков + {_admin_giftsets} {/block} {block headingWrap} @@ -9,7 +9,7 @@ {_create} -

Наборы подарков

+

{_admin_giftsets}

{/block} {block content} @@ -27,12 +27,11 @@
@@ -40,17 +39,13 @@
#ИмяАвторОписаниеКороткий адресДействияID{_admin_title}{_admin_author}{_admin_description}{_admin_shortcode}{_admin_actions}
{$club->getDescription() ?? "(не указано)"}{$club->getDescription() ?? "(" . tr("none") . ")"} {$club->getShortCode()} - Редактировать + {_edit}
- Редактировать + {_edit} - Открыть - Открыть + {_admin_open}
{else}
-

Наборов подарков нету. Чтобы создать подарок, создайте набор.

+

{_admin_giftsets_none}

{/if}
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($categories)) < $count} - - ⭁ туда - - - ⭇ сюда - + « + »
{/block} diff --git a/Web/Presenters/templates/Admin/GiftCategory.xml b/Web/Presenters/templates/Admin/GiftCategory.xml index 936a823f..15494b41 100644 --- a/Web/Presenters/templates/Admin/GiftCategory.xml +++ b/Web/Presenters/templates/Admin/GiftCategory.xml @@ -2,7 +2,7 @@ {block title} {if $form->id === 0} - Создать набор подарков + {_admin_giftsets_create} {else} {$form->languages["master"]->name} {/if} @@ -14,7 +14,7 @@ {block content} -

Общие настройки

+

{_admin_commonsettings}

-
Внутреннее название набора, которое будет использоваться, если не удаётся найти название на языке пользователя.
+
{_admin_giftsets_title}
-
Внутреннее описание набора, которое будет использоваться, если не удаётся найти название на языке пользователя.
+
{_admin_giftsets_description}
-

Языко-зависимые настройки

+

{_admin_langsettings}

{foreach $form->languages as $locale => $data} {continueIf $locale === "master"}
diff --git a/Web/Presenters/templates/Admin/Gifts.xml b/Web/Presenters/templates/Admin/Gifts.xml index b1c9cf90..64cde8f7 100644 --- a/Web/Presenters/templates/Admin/Gifts.xml +++ b/Web/Presenters/templates/Admin/Gifts.xml @@ -9,7 +9,7 @@ {_create} -

Набор "{$cat->getName()}"

+

{_admin_giftset} "{$cat->getName()}"

{/block} {block content} @@ -32,11 +32,11 @@ {$gift->getName()} - бесплатный + {_admin_price_free} - {$gift->getPrice()} голосов + {tr("points_amount", $gift->getPrice())} {$gift->getUsages()} раз @@ -72,11 +72,8 @@
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($gifts)) < $count} - - ⭁ туда - - - ⭇ сюда - + + « + »
{/block} diff --git a/Web/Presenters/templates/Admin/Index.xml b/Web/Presenters/templates/Admin/Index.xml index f860f86f..62019de9 100644 --- a/Web/Presenters/templates/Admin/Index.xml +++ b/Web/Presenters/templates/Admin/Index.xml @@ -1,13 +1,13 @@ {extends "@layout.xml"} {block title} - Сводка + {_admin_overview_summary} {/block} {block heading} - Сводка + {_admin_overview_summary} {/block} {block content} - Да! + ┬─┬︵/(.□.)╯ {/block} diff --git a/Web/Presenters/templates/Admin/User.xml b/Web/Presenters/templates/Admin/User.xml index 205add63..e0f4d905 100644 --- a/Web/Presenters/templates/Admin/User.xml +++ b/Web/Presenters/templates/Admin/User.xml @@ -1,7 +1,7 @@ {extends "@layout.xml"} {block title} - Редактировать {$user->getCanonicalName()} + {_edit} {$user->getCanonicalName()} {/block} {block heading} @@ -10,95 +10,70 @@ {block content}
- -
- - - - + +
+ + + + + - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - isVerified()} checked {/if} /> -
-
- - -
-
-
- -
-
- - +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + isVerified()} checked {/if} /> +
+
+ + +
+
+
+ + +
+
+
{/block} diff --git a/Web/Presenters/templates/Admin/Users.xml b/Web/Presenters/templates/Admin/Users.xml index 51a3ea15..f538d7ad 100644 --- a/Web/Presenters/templates/Admin/Users.xml +++ b/Web/Presenters/templates/Admin/Users.xml @@ -2,14 +2,16 @@ {var $search = true} {block title} - Пользователи + {_admin_user_search} {/block} {block heading} - Пиздюки + {_users} {/block} -{block searchTitle}Поиск пиздюков{/block} +{block searchTitle} + {include title} +{/block} {block content} {var $users = iterator_to_array($users)} @@ -18,12 +20,12 @@ - - - - - - + + + + + + @@ -35,23 +37,21 @@ {$user->getCanonicalName()} - + {$user->getCanonicalName()} - - - заблокирован - + + {_admin_banned} - - + + @@ -61,12 +61,8 @@
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count} - - - ⭁ туда - - - ⭇ сюда - + + « + »
{/block} diff --git a/Web/Presenters/templates/Admin/Voucher.xml b/Web/Presenters/templates/Admin/Voucher.xml index 1de53fe0..712fb7e9 100644 --- a/Web/Presenters/templates/Admin/Voucher.xml +++ b/Web/Presenters/templates/Admin/Voucher.xml @@ -5,45 +5,36 @@ {/block} {block heading} - {_edit} №{$form->token ?? "undefined"} + {_edit} #{$form->token ?? "undefined"} {/block} {block content}
-
- +
- + -
Номер состоит из 24 символов, если формат неправильный или поле не заполнено, будет назначен автоматически.
+
{_admin_voucher_serial_desc}
- +
- +
@@ -55,9 +46,9 @@ {/if} -
Количество аккаунтов, которые могут использовать ваучер. Если написать -1, будет Infinity.
+
{_admin_voucher_usages_desc}
- +
@@ -66,7 +57,6 @@
-
#ИмяПолКороткий адресДата регистрацииДействияID{_admin_name}{_gender}{_admin_shortcode}{_registration_date}{_admin_actions}
{$user->isFemale() ? "Женский" : "Мужской"}{$user->getShortCode() ?? "(отсутствует)"}{$user->isFemale() ? tr("female") : tr("male")}{$user->getShortCode() ?? "(" . tr("none") . ")"} {$user->getRegistrationTime()} - Редактировать + {_edit} {if $thisUser->getChandlerUser()->can("substitute")->model('openvk\Web\Models\Entities\User')->whichBelongsTo(0)} - + {/if}
@@ -77,12 +67,10 @@ {$user->getCanonicalName()} - + {$user->getCanonicalName()} - - - заблокирован - + + {_admin_banned} diff --git a/Web/Presenters/templates/Admin/Vouchers.xml b/Web/Presenters/templates/Admin/Vouchers.xml index 319b8de7..331e0429 100644 --- a/Web/Presenters/templates/Admin/Vouchers.xml +++ b/Web/Presenters/templates/Admin/Vouchers.xml @@ -8,7 +8,7 @@ {_create} - +

{_vouchers}

{/block} @@ -16,13 +16,13 @@
- - - - - - - + + + + + + + @@ -34,28 +34,25 @@
#Серийный номерГолосаРейгтингОсталось использованийСостояниеДействияID{_admin_voucher_serial}{_coins}{_admin_voucher_rating}{_usages_left}{_admin_voucher_status}{_admin_actions}
{$voucher->getRemainingUsages() === INF ? "∞" : $voucher->getRemainingUsages()} {if $voucher->isExpired()} - закончился + {_admin_voucher_status_closed} {else} - активен + {_admin_voucher_status_opened} {/if} - Редактировать + {_edit}

- +
{var $isLast = ((20 * (($_GET['p'] ?? 1) - 1)) + sizeof($vouchers)) < $count} - - ⭁ туда - - - ⭇ сюда - + + « + »
{/block} diff --git a/Web/Presenters/templates/Group/View.xml b/Web/Presenters/templates/Group/View.xml index 5e9acaac..b68fcd2b 100644 --- a/Web/Presenters/templates/Group/View.xml +++ b/Web/Presenters/templates/Group/View.xml @@ -29,7 +29,7 @@ {_"description"}: - {$club->getDescription()} + {$club->getDescriptionHtml()|noescape} {_"website"}: diff --git a/Web/Presenters/templates/Support/Index.xml b/Web/Presenters/templates/Support/Index.xml index 3524da40..8d41fee9 100644 --- a/Web/Presenters/templates/Support/Index.xml +++ b/Web/Presenters/templates/Support/Index.xml @@ -51,9 +51,9 @@ {if $isMain}

{_support_faq}


-
-
{_support_faq_title}
-
{_support_faq_content}
+
+
{$section[0]}
+
{$section[1]|noescape}
{/if} diff --git a/Web/Presenters/templates/User/Settings.xml b/Web/Presenters/templates/User/Settings.xml index e24c1535..1437d1a3 100644 --- a/Web/Presenters/templates/User/Settings.xml +++ b/Web/Presenters/templates/User/Settings.xml @@ -64,7 +64,7 @@ {_"2fa_code"} - + @@ -154,6 +154,38 @@ {$user->getEmail()} + + + {_new_email_address} + + + + + + + + {_password} + + + + + + + + {_"2fa_code"} + + + + + + + + + + + + +
@@ -447,24 +479,35 @@ - NSFW-контент + {_ui_settings_nsfw_content} - Вид постов + {_ui_settings_view_of_posts} + + + + + {_ui_settings_main_page} + + + diff --git a/Web/Presenters/templates/components/cookies.xml b/Web/Presenters/templates/components/cookies.xml new file mode 100644 index 00000000..1b424545 --- /dev/null +++ b/Web/Presenters/templates/components/cookies.xml @@ -0,0 +1,35 @@ + + + \ No newline at end of file diff --git a/Web/Presenters/templates/components/post/microblogpost.xml b/Web/Presenters/templates/components/post/microblogpost.xml index 60cc9e6d..37dfb5da 100644 --- a/Web/Presenters/templates/components/post/microblogpost.xml +++ b/Web/Presenters/templates/components/post/microblogpost.xml @@ -2,7 +2,7 @@ {var $comments = $post->getLastComments(3)} {var $commentsCount = $post->getCommentsCount()} -{var $commentTextAreaId = $post === null ? rand(1,300) : $post->getId()} +{var $commentTextAreaId = $post === NULL ? rand(1,300) : $post->getId()} diff --git a/Web/Presenters/templates/components/textArea.xml b/Web/Presenters/templates/components/textArea.xml index 9d81ac6d..8c4c7f86 100644 --- a/Web/Presenters/templates/components/textArea.xml +++ b/Web/Presenters/templates/components/textArea.xml @@ -1,5 +1,5 @@ {php if(!isset($GLOBALS["textAreaCtr"])) $GLOBALS["textAreaCtr"] = 10;} -{var $textAreaId = ($post ?? NULL) === null ? (++$GLOBALS["textAreaCtr"]) : $post->getId()} +{var $textAreaId = ($post ?? NULL) === NULL ? (++$GLOBALS["textAreaCtr"]) : $post->getId()}
diff --git a/Web/routes.yml b/Web/routes.yml index 35de00fd..7b9f36b8 100644 --- a/Web/routes.yml +++ b/Web/routes.yml @@ -73,6 +73,8 @@ routes: handler: "User->disableTwoFactorAuth" - url: "/settings/reset_theme" handler: "User->resetThemepack" + - url: "/settings/change_email" + handler: "User->emailChangeFinish" - url: "/coins_transfer" handler: "User->coinsTransfer" - url: "/increase_social_credits" diff --git a/Web/static/css/style.css b/Web/static/css/style.css index f8e0f9d0..9db53696 100644 --- a/Web/static/css/style.css +++ b/Web/static/css/style.css @@ -1913,4 +1913,25 @@ table td[width="120"] { border-bottom: 1.5px solid #707070; text-align: center; user-select: none; -} \ No newline at end of file +} + +.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%; +} diff --git a/Web/static/js/notifications.js b/Web/static/js/notifications.js index a45dfe19..f6a9595b 100644 --- a/Web/static/js/notifications.js +++ b/Web/static/js/notifications.js @@ -2,32 +2,12 @@ Function.noop = () => {}; var _n_counter = 0; -var _activeWindow = true; - -const _pageTitle = u("title").nodes[0].innerText; - var counter = 0; -/* this fucking dumb shit is broken :c - -window.addEventListener('focus', () => { - _activeWindow = true; - closeAllNotifications(); +window.addEventListener("focus", () => { + document.title = document.title.replace(/^\([0-9]+\) /, ""); // remove notification counter xD }); -window.addEventListener('blur', () => {_activeWindow = false}); - -function closeAllNotifications() { - var notifications = u(".notifications_global_wrap").nodes[0].children; - for (var i = 0; i < notifications.length; i++) { - setTimeout(() => { - console.log(i); - notifications.item(i).classList.add('disappears'); - setTimeout(() => {notifications.item(i).remove()}, 500).bind(this); - }, 5000).bind(this); - } -} */ - function NewNotification(title, body, avatar = null, callback = () => {}, time = 5000, count = true) { if(avatar != null) { avatar = '' + @@ -62,18 +42,19 @@ function NewNotification(title, body, avatar = null, callback = () => {}, time = } function __closeNotification() { + if(document.visibilityState != "visible") + return setTimeout(() => {__closeNotification()}, time); // delay notif deletion + getPrototype().addClass('disappears'); - setTimeout(() => {getPrototype().remove()}, 500); + return setTimeout(() => {getPrototype().remove()}, 500); } if(count == true) { counter++; - document.title = `(${counter}) ${_pageTitle}`; + document.title = `(${counter}) ${document.title}`; } - /* if(_activeWindow == true) { */ - setTimeout(() => {__closeNotification()}, time); - /* } */ + setTimeout(() => {__closeNotification()}, time); notification.children('notification_title').children('a.close').on('click', function(e) { __closeNotification(); diff --git a/Web/static/js/openvk.cls.js b/Web/static/js/openvk.cls.js index 77677191..668e8ef9 100644 --- a/Web/static/js/openvk.cls.js +++ b/Web/static/js/openvk.cls.js @@ -171,7 +171,7 @@ function repostPost(id, hash) { xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.onload = (function() { if(xhr.responseText.indexOf("wall_owner") === -1) - MessageBox(tr('error'), tr('error_repost_fail'), tr('ok'), [Function.noop]); + MessageBox(tr('error'), tr('error_repost_fail'), [tr('ok')], [Function.noop]); else { let jsonR = JSON.parse(xhr.responseText); NewNotification(tr('information_-1'), tr('shared_succ'), null, () => {window.location.href = "/wall" + jsonR.wall_owner}); diff --git a/bootstrap.php b/bootstrap.php index 70b52244..baaece8d 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -136,7 +136,7 @@ function isLanguageAvailable($lg): bool function getBrowsersLanguage(): array { - if ($_SERVER['HTTP_ACCEPT_LANGUAGE'] != null) return mb_split(",", mb_split(";", $_SERVER['HTTP_ACCEPT_LANGUAGE'])[0]); + if ($_SERVER['HTTP_ACCEPT_LANGUAGE'] != NULL) return mb_split(",", mb_split(";", $_SERVER['HTTP_ACCEPT_LANGUAGE'])[0]); else return array(); } @@ -144,7 +144,7 @@ function eventdb(): ?DatabaseConnection { $conf = OPENVK_ROOT_CONF["openvk"]["credentials"]["eventDB"]; if(!$conf["enable"]) - return null; + return NULL; $db = (object) $conf["database"]; return DatabaseConnection::connect([ @@ -216,8 +216,8 @@ return (function() { setlocale(LC_TIME, "POSIX"); - // TODO: Default language in config - if(Session::i()->get("lang") == null) { + # TODO: Default language in config + if(Session::i()->get("lang") == NULL) { $languages = array_reverse(getBrowsersLanguage()); foreach($languages as $lg) { if(isLanguageAvailable($lg)) setLanguage($lg); @@ -233,7 +233,7 @@ return (function() { else $ver = "Public Technical Preview 3"; - // Unix time constants + # Unix time constants define('MINUTE', 60); define('HOUR', 60 * MINUTE); define('DAY', 24 * HOUR); diff --git a/data/knowledgebase/faq.md b/data/knowledgebase/faq.md new file mode 100644 index 00000000..b1025468 --- /dev/null +++ b/data/knowledgebase/faq.md @@ -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. diff --git a/data/knowledgebase/faq.ru.md b/data/knowledgebase/faq.ru.md new file mode 100644 index 00000000..18414002 --- /dev/null +++ b/data/knowledgebase/faq.ru.md @@ -0,0 +1,2 @@ +# Для кого этот сайт? +Сайт предназначен для поиска друзей и знакомых, а также для просмотра данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке. diff --git a/install/sqls/00023-email-change.sql b/install/sqls/00023-email-change.sql new file mode 100644 index 00000000..70320fb2 --- /dev/null +++ b/install/sqls/00023-email-change.sql @@ -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; diff --git a/install/sqls/00024-main-page-setting.sql b/install/sqls/00024-main-page-setting.sql new file mode 100644 index 00000000..4e36e6d3 --- /dev/null +++ b/install/sqls/00024-main-page-setting.sql @@ -0,0 +1 @@ +ALTER TABLE `profiles` ADD COLUMN `main_page` tinyint(3) unsigned NOT NULL DEFAULT 0 AFTER `microblog`; diff --git a/locales/am.strings b/locales/am.strings index 54985e59..f8140754 100644 --- a/locales/am.strings +++ b/locales/am.strings @@ -9,6 +9,7 @@ "home" = "Գլխավոր"; "welcome" = "Բարի գալուստ"; +"to_top" = "Վերև"; /* Login */ @@ -381,7 +382,6 @@ "left_menu_donate" = "Աջակցել"; - "footer_about_instance" = "հոսքի մասին"; "footer_blog" = "բլոգ"; "footer_help" = "օգնություն"; @@ -410,6 +410,8 @@ "style" = "Ոճ"; "default" = "Սովորական"; + +"arbitrary_avatars" = "Կամայական"; "cut" = "Կտրվածք"; "round_avatars" = "Կլոր ավատար"; @@ -519,6 +521,8 @@ "videos_many" = "$1 տեսանյութ"; "videos_other" = "$1 տեսանյութ"; +"view_video" = "Դիտում"; + /* Notifications */ "feedback" = "Հետադարձ կապ"; @@ -624,6 +628,21 @@ "receiver_not_found" = "Ստացողը չի գտնվել։"; "you_dont_have_enough_points" = "Դուք չունե՛ք բավական ձայն։"; +"increase_rating" = "Բարձրացնել վարկանիշը"; +"increase_rating_button" = "Բարձրացնել"; +"to_whom" = "Ում"; +"increase_by" = "Բարձրացնել"; +"price" = "Արժողություն"; + +"you_have_unused_votes" = "Ձեր մոտ $1 չօգտագործված ձայն կա հաշվի վրա։"; +"apply_voucher" = "Կիրառել վաուչեր"; + +"failed_to_increase_rating" = "Չհաջողվե՛ց բարձրացնել վարկանիշը"; +"rating_increase_successful" = "Դուք հաջողությամբ բարձրացրեցիք Ձեր վարկանիշը $2 $3%-ով։"; +"negative_rating_value" = "Կներե՛ք, մենք չենք կարող գողանալ ուրիշի վարկանիշը։"; + +"increased_your_rating_by" = "բարձրացրել է վարկանիշը"; + /* Gifts */ "gift" = "Նվեր"; @@ -703,6 +722,9 @@ "ticket_changed" = "Տոմսը փոփոխված է"; "ticket_changed_comment" = "Փոփոխությունները ուժի մեջ կմտնեն մի քանի վայրկյանից։"; +"banned_in_support_1" = "Կներե՛ք, $1, բայց հիմա Ձեզ թույլատրված չէ դիմումներ ստեղծել։"; +"banned_in_support_2" = "Դրա պատճառաբանությունը սա է․ $1։ Ցավո՛ք, այդ հնարավորությունը մենք Ձեզնից վերցրել ենք առհավետ։"; + /* Invite */ "invite" = "Հրավիրել"; @@ -711,9 +733,9 @@ /* Banned */ -"banned_title" = "Բլոկավորված եք"; -"banned_header" = "Ձեզ կասեցրել է կառլենի անհաջող բոցը։"; -"banned_alt" = "Օգտատերը բլոկավորված է"; +"banned_title" = "Արգելափակված եք"; +"banned_header" = "Ձեզ կասեցրել է կարլենի անհաջող բոցը։"; +"banned_alt" = "Օգտատերը արգելափակված է"; "banned_1" = "Կներե՛ք, $1, բայց Դուք կասեցված եք։"; "banned_2" = "Պատճառը հետևյալն է․ $1. Ափսոս, բայց մենք ստիպված Ձեզ հավերժ ենք կասեցրել;"; "banned_3" = "Դուք դեռ կարող եք գրել նամակ աջակցության ծառայությանը, եթե համարում եք որ դա սխալմունք է, կամ էլ կարող եք դուրս գալ։"; @@ -835,6 +857,7 @@ "captcha_error" = "Սխալ են գրված սիմվոլները"; "captcha_error_comment" = "Խնդրում ենք համոզվել, որ ճիշտ եք ներմուծել կապտչայի սիմվոլները։"; + /* Admin actions */ "login_as" = "Մտնել ինչպես $1"; @@ -843,7 +866,84 @@ "ban_user_action" = "Բլոկավորել օգտվողին"; "warn_user_action" = "Զգուշացնել օգտվողին"; -/* Paginator (subject to delete) */ + +/* Admin panel */ + +"admin" = "Ադմին-վահանակ"; + +"admin_ownerid" = "Տիրոջ ID"; +"admin_author" = "Հեղինակ"; +"admin_name" = "Անուն"; +"admin_title" = "Անվանում"; +"admin_description" = "Նկարագրություն"; +"admin_first_known_ip" = "Առաջին IP"; +"admin_shortcode" = "Կարճ հասցե"; +"admin_verification" = "Վերիֆիկացիա"; +"admin_banreason" = "Արգելափակման պատճառ"; +"admin_banned" = "արգելափակված է"; +"admin_actions" = "Գործողություններ"; +"admin_image" = "Նկար"; +"admin_image_replace" = "Փոխե՞լ նկարը"; +"admin_uses" = "Օգտագործումներ"; +"admin_uses_reset" = "Զրոյացնե՞լ օգտագործումների քանակը"; +"admin_limits" = "Սահմանափակումներ"; +"admin_limits_reset" = "Զրոյացնել օգտագործումների քանակը"; +"admin_open" = "Բացել"; +"admin_loginas" = "Մուտք գործել ինչպես..."; +"admin_commonsettings" = "Ընդհանուր կարգավորումներ"; +"admin_langsettings" = "Լեզվից կախված կարգավորումներ"; + +"admin_tab_main" = "Գլխավոր"; +"admin_tab_ban" = "Բլոկավորում"; +"admin_tab_followers" = "Մասնակիցներ"; + +"admin_overview" = "Դիտում"; +"admin_overview_summary" = "Ամփոփում"; + +"admin_content" = "Օգտատիրային կոնտենտ"; +"admin_user_search" = "Օգտատերերի որոնում"; +"admin_user_online" = "Օնլայն վիճակ"; +"admin_user_online_default" = "Ըստ նախնականի"; +"admin_user_online_incognito" = "Ինկոգնիտո"; +"admin_user_online_deceased" = "Հանգուցյալ"; +"admin_club_search" = "Խմբերի որոնում"; +"admin_club_excludeglobalfeed" = "Չ՛ցույց տալ գլոբալ ժապավենում"; + +"admin_services" = "Վճարովի ծառայություններ"; +"admin_newgift" = "Նոր նվեր"; +"admin_price" = "Գին"; +"admin_giftset" = "Նվերների հավաքախու"; +"admin_giftsets" = "Նվերների հավաքախուներ"; +"admin_giftsets_none" = "Նվերների հավաքածու չկա։ Ստեղծե՛ք հավաքածու նվեր ավելացնելու համար։"; +"admin_giftsets_create" = "Ստեղծել նվերների հավաքածու"; +"admin_giftsets_title" = "Հավաքածույի ներքին անվանում, եթե չի հաջողվում որոնել այն օգտատիրոջ լեզվով"; +"admin_giftsets_description" = "Հավաքածույի ներքին նկարագրություն, եթե չի հաջողվում որոնել այն օգտատիրոջ լեզվով"; +"admin_price_free" = "անվճար"; +"admin_voucher_rating" = "Վարկանիշ"; +"admin_voucher_serial" = "Սերիական համար"; +"admin_voucher_serial_desc" = "Համարը բաղկացած է 24 նշից։ Եթե Դուք այն սխալ գրեք, այն կտրվի ավտոմատ։"; +"admin_voucher_coins" = "Ձայների քանակ"; +"admin_voucher_rating" = "Վարկանշի քանակ"; +"admin_voucher_usages_desc" = "Վաուչերը օգտագործող ակկաունտների քանակ։ Եթե գրեք -1, ապա այն կլինի օգտագործել անվերջ։"; +"admin_voucher_status" = "Կարգավիճակ"; +"admin_voucher_status_opened" = "ակտիվ է"; +"admin_voucher_status_closed" = "վերջացել է"; + +"admin_settings" = "Կարգավորումներ"; +"admin_settings_tuning" = "Ընդհանուր"; +"admin_settings_appearance" = "Արտաքին տեսք"; +"admin_settings_security" = "Անվտանգություն"; +"admin_settings_integrations" = "Ինտեգրացիաներ"; +"admin_settings_system" = "Համակարգ"; + +"admin_about" = "OpenVK-ի մասին"; +"admin_about_version" = "Վերսիա"; +"admin_about_instance" = "Հոսք"; + +"admin_commerce_disabled" = "Կոմմերցիան անջատված է համակարգային ադմինիստրատորի կողմից"; +"admin_commerce_disabled_desc" = "Վաուչերների և նվերների կարգավորումները կպահպանվեն, բայց ոչ մի ազդեցություն չեն ունենա։"; + +/* Paginator (deprecated) */ "paginator_back" = "Հետ"; "paginator_page" = "$1 էջ"; @@ -857,6 +957,8 @@ "rules" = "Կանոններ"; "most_popular_groups" = "Ամենահայտնի խմբերը"; "on_this_instance_are" = "Այս հոսքում․"; +"about_links" = "Հղումներ"; +"instance_links" = "Հոսքերի հղումներ․"; "about_users_one" = "Մեկ օգտատեր"; "about_users_few" = "$1 օգտատեր"; diff --git a/locales/by.strings b/locales/by.strings index b1e70c1e..ae31a80e 100644 --- a/locales/by.strings +++ b/locales/by.strings @@ -470,3 +470,7 @@ "comment" = "Каментарый"; "sender" = "Адпраўнік"; +/* Cookies pop-up */ + +"cookies_popup_content" = "Усе хлопчыкi любяць пэчыва, таму гэты вэб-сайт выкарыстоўвае Cookies для таго, каб ідэнтыфікаваць вашу сесію і нічога болей. Звяртайцеся да нашай палiтыкi канфiдэнцыальнастi для палучэння поўнай iнфармацыi."; +"cookies_popup_agree" = "Прынiмаю"; diff --git a/locales/en.strings b/locales/en.strings index 44487af0..0432e614 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -444,6 +444,8 @@ "your_page_address" = "Your address page"; "page_address" = "Address page"; "current_email_address" = "Current email address"; +"new_email_address" = "New email address"; +"save_email_address" = "Save email address"; "page_id" = "Page ID"; "you_can_also" = "You can also"; "delete_your_page" = "delete your page"; @@ -454,10 +456,20 @@ "ui_settings_rating" = "Rating"; "ui_settings_rating_show" = "Show"; "ui_settings_rating_hide" = "Hide"; +"ui_settings_nsfw_content" = "NSFW content"; +"ui_settings_nsfw_content_dont_show" = "Don't show in global feed"; +"ui_settings_nsfw_content_blur" = "Just blur"; +"ui_settings_nsfw_content_show" = "Show"; +"ui_settings_view_of_posts" = "View of posts"; +"ui_settings_view_of_posts_old" = "Old"; +"ui_settings_view_of_posts_microblog" = "Microblog"; +"ui_settings_main_page" = "Main page"; "additional_links" = "Additional links"; "ad_poster" = "Ad poster"; +"email_change_confirm_message" = "Please confirm your new email address for the change to take effect. We have sent instructions to it."; + /* Two-factor authentication */ "two_factor_authentication" = "Two-factor authentication"; @@ -661,9 +673,6 @@ "support_list" = "List of tickets"; "support_new" = "New ticket"; -"support_faq_title" = "Who is this website for?"; -"support_faq_content" = "The site is designed to find friends and acquaintances, as well as view user data. It is like a city directory, through which people can quickly find relevant information about a person."; - "support_new_title" = "Enter the topic of your ticket"; "support_new_content" = "Describe the issue or suggestion"; @@ -847,6 +856,84 @@ "ban_in_support_user_action" = "Ban in support"; "unban_in_support_user_action" = "Unban in support"; +/* Admin panel */ + +"admin" = "Admin panel"; + +"admin_ownerid" = "Owner ID"; +"admin_author" = "Author"; +"admin_name" = "Name"; +"admin_title" = "Title"; +"admin_description" = "Description"; +"admin_first_known_ip" = "First known IP"; +"admin_shortcode" = "Short code"; +"admin_verification" = "Verification"; +"admin_banreason" = "Ban reason"; +"admin_banned" = "banned"; +"admin_gender" = "Gender"; +"admin_registrationdate" = "Registration date"; +"admin_actions" = "Actions"; +"admin_image" = "Image"; +"admin_image_replace" = "Replace the image?"; +"admin_uses" = "Uses"; +"admin_uses_reset" = "Reset the number of uses?"; +"admin_limits" = "Limits"; +"admin_limits_reset" = "Reset the number of limits"; +"admin_open" = "Open"; +"admin_loginas" = "Login as..."; +"admin_commonsettings" = "Common settings"; +"admin_langsettings" = "Language-dependent settings"; + +"admin_tab_main" = "General"; +"admin_tab_ban" = "Ban"; +"admin_tab_followers" = "Followers"; + +"admin_overview" = "Overview"; +"admin_overview_summary" = "Summary"; + +"admin_content" = "User-generated content"; +"admin_user_search" = "Search for users"; +"admin_user_online" = "Online status"; +"admin_user_online_default" = "Default"; +"admin_user_online_incognito" = "Incognito"; +"admin_user_online_deceased" = "Deceased"; +"admin_club_search" = "Search for groups"; +"admin_club_excludeglobalfeed" = "Do not display posts in the global feed"; + +"admin_services" = "Paid services"; +"admin_newgift" = "New gift"; +"admin_price" = "Price"; +"admin_giftset" = "Gift set"; +"admin_giftsets" = "Gift sets"; +"admin_giftsets_none" = "There are no gift sets. Create a set to create a gift."; +"admin_giftsets_create" = "Create a gift set"; +"admin_giftsets_title" = "The internal name of the set, which will be used if no name can be found in the user's language."; +"admin_giftsets_description" = "The internal description of the set, which will be used if no name can be found in the user's language."; +"admin_price_free" = "free"; +"admin_voucher_rating" = "Rating"; +"admin_voucher_serial" = "Serial number"; +"admin_voucher_serial_desc" = "The number consists of 24 characters. If the format is incorrect or the field is not filled in, it will be assigned automatically."; +"admin_voucher_coins" = "Number of votes"; +"admin_voucher_rating" = "Number of rating"; +"admin_voucher_usages_desc" = "The number of accounts that can use the voucher. If you type -1, it will be infinity."; +"admin_voucher_status" = "Status"; +"admin_voucher_status_opened" = "active"; +"admin_voucher_status_closed" = "closed"; + +"admin_settings" = "Settings"; +"admin_settings_tuning" = "General"; +"admin_settings_appearance" = "Appearance"; +"admin_settings_security" = "Security"; +"admin_settings_integrations" = "Integrations"; +"admin_settings_system" = "System"; + +"admin_about" = "About OpenVK"; +"admin_about_version" = "Version"; +"admin_about_instance" = "Instance"; + +"admin_commerce_disabled" = "Commerce has been disabled by the system administrator"; +"admin_commerce_disabled_desc" = "The voucher and gift settings will be saved, but will have no effect."; + /* Paginator (deprecated) */ "paginator_back" = "Back"; @@ -895,3 +982,8 @@ /* User alerts */ "user_alert_scam" = "This account has been reported a lot for scam. Please be careful, especially if he asked for money."; + +/* Cookies pop-up */ + +"cookies_popup_content" = "All kids love cookie, so this website uses Cookies to identify your session and nothing more. Check our privacy policy for more information."; +"cookies_popup_agree" = "Accept"; diff --git a/locales/ru.strings b/locales/ru.strings index 88608f41..eb4b0e25 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -472,6 +472,8 @@ "your_page_address" = "Адрес Вашей страницы"; "page_address" = "Адрес страницы"; "current_email_address" = "Текущий адрес"; +"new_email_address" = "Новый адрес"; +"save_email_address" = "Сохранить адрес"; "page_id" = "ID страницы"; "you_can_also" = "Вы также можете"; "delete_your_page" = "удалить свою страницу"; @@ -482,10 +484,20 @@ "ui_settings_rating" = "Рейтинг"; "ui_settings_rating_show" = "Показывать"; "ui_settings_rating_hide" = "Скрывать"; +"ui_settings_nsfw_content" = "NSFW-контент"; +"ui_settings_nsfw_content_dont_show" = "Не показывать в глобальной ленте"; +"ui_settings_nsfw_content_blur" = "Только замазывать"; +"ui_settings_nsfw_content_show" = "Показывать"; +"ui_settings_view_of_posts" = "Вид постов"; +"ui_settings_view_of_posts_old" = "Старый"; +"ui_settings_view_of_posts_microblog" = "Микроблог"; +"ui_settings_main_page" = "Главная страница"; "additional_links" = "Дополнительные ссылки"; "ad_poster" = "Рекламный плакат"; +"email_change_confirm_message" = "Чтобы изменение вступило в силу, подтвердите ваш новый адрес электронной почты. Мы отправили инструкции на него."; + /* Two-factor authentication */ "two_factor_authentication" = "Двухфакторная аутентификация"; @@ -698,9 +710,6 @@ "support_list" = "Список обращений"; "support_new" = "Новое обращение"; -"support_faq_title" = "Для кого этот сайт?"; -"support_faq_content" = "Сайт предназначен для поиска друзей и знакомых, а также для просмотра данных пользователя. Это как справочник города, с помощью которого люди могут быстро найти актуальную информацию о человеке."; - "support_new_title" = "Введите тему вашего обращения"; "support_new_content" = "Опишите проблему или предложение"; @@ -890,6 +899,82 @@ "ban_in_support_user_action" = "Заблокировать в поддержке"; "unban_in_support_user_action" = "Разблокировать в поддержке"; +/* Admin panel */ + +"admin" = "Админ-панель"; + +"admin_ownerid" = "ID владельца"; +"admin_author" = "Автор"; +"admin_name" = "Имя"; +"admin_title" = "Название"; +"admin_description" = "Описание"; +"admin_first_known_ip" = "Первый IP"; +"admin_shortcode" = "Короткий адрес"; +"admin_verification" = "Верификация"; +"admin_banreason" = "Причина блокировки"; +"admin_banned" = "заблокирован"; +"admin_actions" = "Действия"; +"admin_image" = "Изображение"; +"admin_image_replace" = "Заменить изображение?"; +"admin_uses" = "Использований"; +"admin_uses_reset" = "Сбросить количество использований?"; +"admin_limits" = "Ограничения"; +"admin_limits_reset" = "Сбросить количество ограничений"; +"admin_open" = "Открыть"; +"admin_loginas" = "Войти как..."; +"admin_commonsettings" = "Общие настройки"; +"admin_langsettings" = "Языко-зависимые настройки"; + +"admin_tab_main" = "Главное"; +"admin_tab_ban" = "Блокировка"; +"admin_tab_followers" = "Участники"; + +"admin_overview" = "Обзор"; +"admin_overview_summary" = "Сводка"; + +"admin_content" = "Пользовательский контент"; +"admin_user_search" = "Поиск пользователей"; +"admin_user_online" = "Онлайн статус"; +"admin_user_online_default" = "По-умолчанию"; +"admin_user_online_incognito" = "Инкогнито"; +"admin_user_online_deceased" = "Покойник"; +"admin_club_search" = "Поиск групп"; +"admin_club_excludeglobalfeed" = "Не отображать записи в глобальной ленте"; + +"admin_services" = "Платные услуги"; +"admin_newgift" = "Новый подарок"; +"admin_price" = "Цена"; +"admin_giftset" = "Набор подарков"; +"admin_giftsets" = "Наборы подарков"; +"admin_giftsets_none" = "Нет наборов подарков. Создайте набор, чтобы создать подарок."; +"admin_giftsets_create" = "Создать набор подарков"; +"admin_giftsets_title" = "Внутреннее название набора, которое будет использоваться, если не удаётся найти название на языке пользователя."; +"admin_giftsets_description" = "Внутреннее описание набора, которое будет использоваться, если не удаётся найти название на языке пользователя."; +"admin_price_free" = "бесплатный"; +"admin_voucher_rating" = "Рейтинг"; +"admin_voucher_serial" = "Серийный номер"; +"admin_voucher_serial_desc" = "Номер состоит из 24 символов. Если формат неправильный или поле не заполнено, будет назначен автоматически."; +"admin_voucher_coins" = "Количество голосов"; +"admin_voucher_rating" = "Количество рейтинга"; +"admin_voucher_usages_desc" = "Количество аккаунтов, которые могут использовать ваучер. Если написать -1, будет бесконечность."; +"admin_voucher_status" = "Состояние"; +"admin_voucher_status_opened" = "активен"; +"admin_voucher_status_closed" = "закончился"; + +"admin_settings" = "Настройки"; +"admin_settings_tuning" = "Общее"; +"admin_settings_appearance" = "Внешний вид"; +"admin_settings_security" = "Безопасность"; +"admin_settings_integrations" = "Интеграции"; +"admin_settings_system" = "Система"; + +"admin_about" = "Об OpenVK"; +"admin_about_version" = "Версия"; +"admin_about_instance" = "Инстанция"; + +"admin_commerce_disabled" = "Коммерция отключена системным администратором"; +"admin_commerce_disabled_desc" = "Настройки ваучеров и подарков будут сохранены, но не будут оказывать никакого влияния."; + /* Paginator (deprecated) */ "paginator_back" = "Назад"; @@ -948,3 +1033,8 @@ /* User alerts */ "user_alert_scam" = "На этот аккаунт много жаловались в связи с мошенничеством. Пожалуйста, будьте осторожны, особенно если у вас попросят денег."; + +/* Cookies pop-up */ + +"cookies_popup_content" = "Все дети любят печенье, поэтому этот веб-сайт использует Cookies для того, чтобы идентифицировать вашу сессию и ничего более. Ознакомьтесь с нашей политикой конфиденциальности для получения дополнительной информации."; +"cookies_popup_agree" = "Согласен";