Merge branch 'master' into textarea-refactor

This commit is contained in:
Ilya Prokopenko 2022-02-09 21:32:01 +07:00 committed by GitHub
commit cd484e84dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 955 additions and 236 deletions

View file

@ -48,7 +48,7 @@ ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/ext
ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/
```
4. Import `install/init-static-db.sql` to **same database** you installed Chandler to
4. Import `install/init-static-db.sql` to **same database** you installed Chandler to and import all sqls from `install/sqls` to **same database**
5. Import `install/init-event-db.sql` to **separate database**
6. Copy `openvk-example.yml` to `openvk.yml` and change options
7. Run `composer install` in OpenVK directory
@ -75,11 +75,12 @@ You may reach out to us via:
* [Bug-tracker](https://github.com/openvk/openvk/projects/1)
* [Ticketing system](https://openvk.su/support?act=new)
* Telegram chat: Go to [our channel](https://t.me/openvkch) and open discussion in our channel menu.
* Telegram chat: Go to [our channel](https://t.me/openvkenglish) and open discussion in our channel menu.
* [Reddit](https://www.reddit.com/r/openvk/)
* [Discussions](https://github.com/openvk/openvk/discussions)
* Matrix chat: #openvk:matrix.org
**Attention**: bug tracker and telegram chat are public places. And ticketing system is being served by volunteers. If you need to report something, that shouldn't be immediately disclosed to general public (for instance, vulnerability report), please use contact us directly at this email: **openvk [at] tutanota [dot] com**
**Attention**: bug tracker, telegram and matrix chat are public places. And ticketing system is being served by volunteers. If you need to report something, that shouldn't be immediately disclosed to general public (for instance, vulnerability report), please use contact us directly at this email: **openvk [at] tutanota [dot] com**
<a href="https://codeberg.org/OpenVK/openvk">
<img alt="Get it on Codeberg" src="https://codeberg.org/Codeberg/GetItOnCodeberg/media/branch/main/get-it-on-blue-on-white.png" height="60">

View file

@ -48,7 +48,7 @@ ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/ext
ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/
```
4. Импортируйте `install/init-static-db.sql` в **ту же базу данных**, в которую вы установили Chandler
4. Импортируйте `install/init-static-db.sql` в **ту же базу данных**, в которую вы установили Chandler, и импортируйте все SQL файлы из папки `install/sqls` в **ту же базу данных**
5. Импортируйте `install/init-event-db.sql` в **отдельную базу данных**
6. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры
7. Запустите `composer install` в директории OpenVK
@ -78,8 +78,9 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
* Telegram-чат: Перейдите на [наш канал](https://t.me/openvkch) и откройте обсуждение в меню нашего канала.
* [Reddit](https://www.reddit.com/r/openvk/)
* [Обсуждения](https://github.com/openvk/openvk/discussions)
* Чат в Matrix: #ovk:matrix.org
**Внимание**: баг-трекер и телеграм-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [at] tutanota [dot] com**.
**Внимание**: баг-трекер, телеграм- и matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [at] tutanota [dot] com**.
<a href="https://codeberg.org/OpenVK/openvk">
<img alt="Get it on Codeberg" src="https://codeberg.org/Codeberg/GetItOnCodeberg/media/branch/main/get-it-on-blue-on-white.png" height="60">

57
VKAPI/README.md Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,6 +5,9 @@ use openvk\Web\Models\Entities\Notifications\{RepostNotification, WallPostNotifi
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums};
use Chandler\Database\DatabaseConnection;
use Nette\InvalidStateException as ISE;
use Bhaktaraz\RSSGenerator\Item;
use Bhaktaraz\RSSGenerator\Feed;
use Bhaktaraz\RSSGenerator\Channel;
final class WallPresenter extends OpenVKPresenter
{
@ -83,6 +86,49 @@ final class WallPresenter extends OpenVKPresenter
$this->renderWall($user, true);
}
function renderRSS(int $user): void
{
if(false)
exit("Ошибка доступа: " . (string) random_int(0, 255));
$owner = ($user < 0 ? (new Clubs) : (new Users))->get(abs($user));
if(is_null($this->user)) {
$canPost = false;
} else if($user > 0) {
if(!$owner->isBanned())
$canPost = $owner->getPrivacyPermission("wall.write", $this->user->identity);
else
$this->flashFail("err", tr("error"), "Ошибка доступа");
} else if($user < 0) {
if($owner->canBeModifiedBy($this->user->identity))
$canPost = true;
else
$canPost = $owner->canPost();
} else {
$canPost = false;
}
$posts = iterator_to_array($this->posts->getPostsFromUsersWall($user));
$feed = new Feed();
$channel = new Channel();
$channel->title(OPENVK_ROOT_CONF['openvk']['appearance']['name'])->url(ovk_scheme(true) . $_SERVER["SERVER_NAME"])->appendTo($feed);
foreach($posts as $post) {
$item = new Item();
$item
->title($post->getOwner()->getCanonicalName())
->description($post->getText())
->url(ovk_scheme(true).$_SERVER["SERVER_NAME"]."/wall{$post->getPrettyId()}")
->pubDate($post->getPublicationTime()->timestamp())
->appendTo($channel);
}
header("Content-Type: application/rss+xml");
exit($feed);
}
function renderFeed(): void
{
$this->assertUserLoggedIn();

View file

@ -262,7 +262,7 @@
<a href="/privacy" class="link">{_footer_privacy}</a>
</div>
<p>OpenVK <a href="/about:openvk">{php echo OPENVK_VERSION}</a> | PHP: {phpversion()} | DB: {$dbVersion}</p>
<p n:ifcontent="ifcontent">
<p n:ifcontent>
{php echo OPENVK_ROOT_CONF["openvk"]["appearance"]["motd"]}
</p>
</div>

View file

@ -2,10 +2,17 @@
{block wrap}
<div class="page_wrap">
<div n:ifset="tabs" class="tabs">
<div n:ifset="tabs" n:ifcontent class="tabs">
{include tabs}
</div>
{ifset size}
{include size, x => $dat}
{/ifset}
{ifset specpage}
{include specpage, x => $dat}
{else}
<div class="container_gray">
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
@ -20,24 +27,25 @@
</a>
</td>
<td valign="top" style="width: 100%">
{ifset infoTable}
{include infoTable, x => $dat}
{else}
<a href="{include link, x => $dat}">
<b>
{include name, x => $dat}
</b>
</a>
<br/>
{include description, x => $dat}
{/ifset}
</td>
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px">
<td n:ifset="actions" valign="top" class="action_links" style="width: 150px; text-transform: lowercase;">
{include actions, x => $dat}
</td>
</tr>
</tbody>
</table>
</div>
<div style="padding: 8px;">
{include "components/paginator.xml", conf => (object) [
"page" => $page,
"count" => $count,
@ -45,7 +53,6 @@
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
"atBottom" => true,
]}
</div>
{else}
{ifset customErrorMessage}
{include customErrorMessage}
@ -54,5 +61,10 @@
{/ifset}
{/if}
</div>
{/ifset}
{ifset bottom}
{include bottom}
{/ifset}
</div>
{/block}

View file

@ -5,12 +5,25 @@
{block title}{_notes}{/block}
{block header}
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
{if isset($thisUser) && $thisUser->getId() == $owner->getId()}
{_my_notes}
{else}
<a href="{$owner->getURL()}">
{$owner->getCanonicalName()}</a>
»
{_notes}
{/if}
{/block}
<div n:if="isset($thisUser) && $thisUser->getId() == $owner->getId()" style="float:right;">
{block size}
<div style="padding-bottom: 0px; padding-top: 0;" class="summaryBar">
<div class="summary">
{tr("notes_list", $count)}
<span n:if="isset($thisUser) && $thisUser->getId() == $owner->getId()">
&nbsp;|&nbsp;
<a href="/notes/create">{_create_note}</a>
</span>
</div>
</div>
{/block}
@ -20,18 +33,66 @@
{* BEGIN ELEMENTS DESCRIPTION *}
{block link|strip|stripHtml}
/note{$x->getPrettyId()}
{/block}
{block specpage}
<div class="container_gray" style="background: white; border-top: none;">
{block preview}
<center><img src="/assets/packages/static/openvk/img/note_icon.png" alt="{_note}" style="margin-top: 17px;" /></center>
{/block}
{var data = is_array($iterator) ? $iterator : iterator_to_array($iterator)}
{if sizeof($data) > 0}
{block name}
{$x->getName()}
{/block}
<div n:foreach="$data as $dat">
<div class="profile_thumb">
<a href="{$owner->getURL()}">
<img src="{$owner->getAvatarUrl()}" style="width: 50px;">
</a>
</div>
<article class="note_body" id="userContent" style="width: 540px; display: inline-block; margin-bottom: 35px;">
<div class="note_header">
<div class="note_title">
<div class="note_title">
<a href="/note{$dat->getPrettyId()}">{$dat->getName()}</a>
</div>
</div>
<div class="byline">
<span><a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a></span> {$dat->getPublicationTime()}
<span n:if="$dat->getEditTime() > $dat->getPublicationTime()">({_edited} {$dat->getEditTime()})</span>
</div>
</div>
<div style="margin-left: 6px;">
{$dat->getText(750)|noescape}
</div>
<div class="note_footer" style="margin: 10px 0 0;">
<div class="comments_count">
<a href="/note{$dat->getPrettyId()}">
{block description}
{$x->getPreview(250)}
{if sizeof($dat->getCommentsCount()) > 0}
{_comments} ({$dat->getCommentsCount()})
{else}
{_no_comments}
{/if}
</a>
<span n:if="isset($thisUser) && $thisUser->getId() === $dat->getOwner()->getId()">&nbsp;|&nbsp;
<a id="_noteDelete" href="/note{$dat->getOwner()->getId()}_{$dat->getId()}/delete">{_delete}</a>
&nbsp;|&nbsp;
<a href="/note{$dat->getOwner()->getId()}_{$dat->getVirtualId()}/edit">{_edit}</a>
</span>
</div>
</div>
</article>
</div>
{else}
{if isset($thisUser) && $thisUser->getId() == $owner->getId()}
<div style="padding: 10px 20px 20px;"><h4 style="border: none;padding-bottom: 5px;">{_welcome}</h4>{_notes_start_screen}</div>
{else}
{ifset customErrorMessage}
{include ../customErrorMessage}
{else}
{include ../components/nothing.xml}
{/ifset}
{/if}
{/if}
</div>
{/block}

View file

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

View file

@ -2,39 +2,47 @@
{var iterator = $user->getClubs($page, $admin)}
{var count = $user->getClubCount($admin)}
{block title}{_"groups"}{/block}
{block title}
{_groups}
{/block}
{block header}
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> » {_"groups"}
<div n:if="!is_null($thisUser) && $user->getId() === $thisUser->getId()" style="float:right;">
<span>
<b>
<a href="/groups_create">
{_"create_group"}
</a>
</b>
</span>
</div>
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
{_my_groups}
{else}
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a> » {_groups}
{/if}
{/block}
{* BEGIN ELEMENTS DESCRIPTION *}
{block tabs}
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
<div {if !$admin}id="activetabs"{/if} class="tab">
<a {if !$admin}id="act_tab_a"{/if} href="/groups{$user->getId()}">
<div n:attr='id => ($admin ? false : "activetabs")' class="tab">
<a n:attr='id => ($admin ? false : "act_tab_a")' href="/groups{$user->getId()}">
{_groups}
</a>
</div>
<div {if $admin}id="activetabs"{/if} class="tab">
<a {if $admin}id="act_tab_a"{/if} href="/groups{$user->getId()}?act=managed">
<div n:attr='id => (!$admin ? false : "activetabs")' class="tab">
<a n:attr='id => (!$admin ? false : "act_tab_a")' href="/groups{$user->getId()}?act=managed">
{_managed}
</a>
</div>
{/if}
{/block}
{block size}
<div n:if="!is_null($thisUser) && $user->getId() === $thisUser->getId()" style="padding-bottom: 0px; border-bottom: 0;" class="summaryBar">
<div class="summary">
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
{tr("groups_list", $thisUser->getClubCount())}
{else}
{tr("groups", $user->getClubCount())}
{/if}
</div>
</div>
{/block}
{block link|strip|stripHtml}
{$x->getURL()}
{/block}
@ -43,8 +51,21 @@
<img src="{$x->getAvatarUrl()}" width="75" alt="Фотография группы" />
{/block}
{block name}
{$x->getName()}
{block name}{/block}
{block infoTable}
<table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
<td class="label"><span class="nobold">{_name}: </span></td>
<td class="data"><a href="{$x->getURL()}">{$x->getName()}</a></td>
</tr>
<tr>
<td class="label"><span class="nobold">{_size}:</span></td>
<td class="data"><a href="/club{$x->getId()}/followers">{tr("participants", $x->getFollowersCount())}</a></td>
</tr>
</tbody>
</table>
{/block}
{block description}
@ -53,8 +74,12 @@
{block actions}
{var clubPinned = $thisUser->isClubPinned($x)}
{if $x->canBeModifiedBy($thisUser ?? NULL) && ($clubPinned || $thisUser->getPinnedClubCount() <= 10)}
<a class="profile_link" href="/groups_pin?club={$x->getId()}&hash={rawurlencode($csrfToken)}" id="_pinGroup" data-group-name="{$x->getName()}" data-group-url="{$x->getUrl()}">
{if $x->canBeModifiedBy($thisUser ?? NULL)}
<a style="width: 140px;" class="profile_link" href="{$x->getURL()}">
{_check_community}
</a>
{if ($clubPinned || $thisUser->getPinnedClubCount() <= 10)}
<a style="width: 140px;" class="profile_link" href="/groups_pin?club={$x->getId()}&hash={rawurlencode($csrfToken)}" id="_pinGroup" data-group-name="{$x->getName()}" data-group-url="{$x->getUrl()}">
{if $clubPinned}
{_remove_from_left_menu}
{else}
@ -62,4 +87,34 @@
{/if}
</a>
{/if}
<form action="/setSub/club" method="post">
<input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input style="width: 140px; text-transform: lowercase;" type="submit" id="profile_link" value="{_leave_community}" />
</form>
{/if}
{/block}
{block bottom}
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
<div class="groups_options">
<div id="gp_container" style="width: 200px; margin-right: 40px;">
<h4>{_open_new_group}</h4>
<span>{_open_group_desc}</span>
<form action="/groups_create">
<button class="button">{_create_group}</button>
</form>
</div>
<div id="gp_container" style="width: 344px;">
<h4>{_search_group}</h4>
<span>{_search_group_desc}</span>
<form action="/search">
<input name="type" type="hidden" value="groups">
<input name="query" class="header_search_input" value="" style="background: none; width: 155px; padding-left: 3px;">
<button class="button">{_search_by_groups}</button>
</form>
</div>
</div>
{/if}
{/block}

View file

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

View file

@ -8,9 +8,17 @@
{block header}
<a href="{$user->getURL()}">{$user->getCanonicalName()}</a>
» {_"videos"}
{/block}
<div n:if="isset($thisUser) && $thisUser->getId() == $user->getId()" style="float: right;">
<a href="/videos/upload">{_"upload_video"}</a>
{block size}
<div style="padding-bottom: 0px;border-bottom: 0; padding-top: 0;" class="summaryBar">
<div class="summary">
{tr("videos", $count)}
<span n:if="isset($thisUser) && $thisUser->getId() == $user->getId()">
&nbsp;|&nbsp;
<a href="/videos/upload">{_upload_video}</a>
</span>
</div>
</div>
{/block}

View file

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

View file

@ -1,7 +1,6 @@
<center>
<img src="/assets/packages/static/openvk/img/error.png" alt="Ошибка" />
<h1>{$title}</h1>
<p>
<center style="background: white;border: #DEDEDE solid 1px;">
<span style="color: #707070;margin: 60px 0;display: block;">
<b>{$title}</b><br><br>
{$description}
</p>
</span>
</center>

View file

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

View file

@ -1,7 +1,8 @@
{var $space = 2}
{var $pageCount = ceil($conf->count / $conf->perPage)}
<div n:if="!($conf->page === 1 && $conf->count <= $conf->perPage)" n:class="paginator, ($conf->atBottom ?? false) ? paginator-at-bottom">
<div n:if="!($conf->page === 1 && $conf->count <= $conf->perPage)" style="padding: 8px;">
<div n:class="paginator, ($conf->atBottom ?? false) ? paginator-at-bottom">
{if $conf->page > $space}
<a n:attr="class => ($conf->page === 1 ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => 1]), 'k', '&', PHP_QUERY_RFC3986)}">«</a>
{/if}
@ -13,4 +14,5 @@
{if $conf->page <= $pageCount-$space}
<a n:attr="class => ($conf->page === $pageCount ? 'active')" href="?{http_build_query(array_merge($_GET, ['p' => $pageCount]), 'k', '&', PHP_QUERY_RFC3986)}">»</a>
{/if}
</div>
</div>

View file

@ -8,6 +8,7 @@
<tbody>
<tr>
<td width="54" valign="top">
<a href="{$author->getURL()}">
<img
src="{$author->getAvatarURL()}"
width="{ifset $compact}25{else}50{/ifset}"
@ -17,6 +18,7 @@
{_online}
</span>
{/if}
</a>
</td>
<td width="100%" valign="top">
<div class="post-author">

View file

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

View file

@ -73,6 +73,8 @@ routes:
handler: "User->disableTwoFactorAuth"
- url: "/coins_transfer"
handler: "User->coinsTransfer"
- url: "/increase_social_credits"
handler: "User->increaseRating"
- url: "/id{num}"
handler: "User->view"
- url: "/friends{num}"
@ -105,6 +107,8 @@ routes:
hashTag: ".++"
- url: "/wall{num}"
handler: "Wall->wall"
- url: "/wall{num}/rss"
handler: "Wall->rss"
- url: "/wall{num}/makePost"
handler: "Wall->makePost"
- url: "/wall{num}_{num}"

View file

@ -1832,6 +1832,51 @@ body.scrolled .toTop:hover {
padding: 5px 0px 0px 0px;
}
.groups_options {
padding: 10px 20px 20px;
border-top: #DEDEDE solid 1px;
margin-top: 12px;
margin-left: -12px;
margin-right: -12px;
}
#gp_container {
display: inline-block;
}
#gp_container span {
display: block;
margin: 10px 0 15px;
}
#gp_container h4 {
font-size: 11px;
}
.container_gray .content:last-child {
margin-bottom: 0;
}
.group_info {
padding: 0 0 0 5px !important;
}
.group_info .label {
width: auto !important;
padding-right: 5px;
}
table td[width="120"] {
text-align: right;
}
.profile_thumb {
padding: 0px 10px 0px 0px;
width: 50px;
display: inline-block;
vertical-align: top;
}
.border-block {
box-shadow: inset 0px 0 0px 1px #b6bfca, inset 0px 0 0px 10px #d8dfe7;
width: 300px;

View file

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

View file

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

92
composer.lock generated
View file

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

View file

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

View file

@ -223,6 +223,7 @@
"subscriptions" = "Subscriptions";
"join_community" = "Join community";
"leave_community" = "Leave community";
"check_community" = "View community";
"min_6_community" = "Name of the group must have more that 6 characters";
"participants" = "Participants";
"groups" = "Groups";
@ -240,6 +241,7 @@
"only_administrators" = "Only administrators";
"website" = "Website";
"managed" = "Managed";
"size" = "Size";
"administrators_one" = "$1 administrator";
"administrators_other" = "$1 administrators";
@ -272,10 +274,20 @@
"groups_one" = "$1 group";
"groups_other" = "$1 groups";
"groups_list_zero" = "You are not a participant in any group";
"groups_list_one" = "You are participating in one group";
"groups_list_other" = "You are a participant of $1 groups";
"meetings_zero" = "No meetings";
"meetings_one" = "$1 meeting";
"meetings_other" = "$1 meetings";
"open_new_group" = "Open a new group";
"open_group_desc" = "Can't find the right group? Open your own...";
"search_group" = "Search group";
"search_by_groups" = "Search by groups";
"search_group_desc" = "Here you can browse through the existing groups and choose a group to suit your needs...";
/* Albums */
"create" = "Create";
@ -302,7 +314,7 @@
"note" = "Note";
"name_note" = "Title";
"text_note" = "Content";
"create_note" = "Create note";
"create_note" = "Add note";
"edit_note" = "Edit note";
"actions" = "Actions";
@ -311,6 +323,11 @@
"notes_zero" = "No notes";
"notes_one" = "$1 note";
"notes_other" = "$1 notes";
"notes_start_screen" = "With notes, you can share your events with friends and see what's going on with them.";
"notes_list_zero" = "No notes found";
"notes_list_one" = "$1 note found";
"notes_list_other" = "$1 notes found";
/* Menus */
@ -566,6 +583,21 @@
"receiver_not_found" = "The receiver was not found.";
"you_dont_have_enough_points" = "You don't have enough votes.";
"increase_rating" = "Increase rating";
"increase_rating_button" = "Increase";
"to_whom" = "To whom";
"increase_by" = "Increase by";
"price" = "Price";
"you_have_unused_votes" = "You have $1 unused votes on your balance.";
"apply_voucher" = "Apply voucher";
"failed_to_increase_rating" = "Failed to increase rating";
"rating_increase_successful" = "You have successfully increased rating of <b><a href=\"$1\">$2</a></b> by <b>$3%</b>.";
"negative_rating_value" = "We cannot steal rating from another person, sorry.";
"increased_your_rating_by" = "increased your rating by";
/* Gifts */
"gift" = "Gift";
@ -738,6 +770,8 @@
"invalid_email_address" = "Invalid Email address";
"invalid_email_address_comment" = "The Email you entered is not correct.";
"invalid_real_name" = "Please, enter your real name. It'll be easier for your friends to find you like this.";
"invalid_birth_date" = "Invalid date of birth";
"invalid_birth_date_comment" = "The date of birth you entered is not correct.";

View file

@ -9,6 +9,8 @@
"home" = "Басты бет";
"welcome" = "Қош келдіңіз!";
"to_top" = "Жоғарыға";
/* Login */
"log_in" = "Логин";
@ -36,6 +38,13 @@
"password_reset_error" = "Құпиясөзді қалпына келтіру кезінде күтпеген қате орын алды.";
"password_reset_rate_limit_error" = "Кешіріңіз, бірақ осы әрекетті сонай жиі жасай алмайсыз.";
"email_sent" = "Хат сәтті жіберілді.";
"email_sent_desc" = "Сіз енгізген электрондық пошта бар болса оған нұсқаулар келеді.";
"email_error" = "Электрондық хатты жіберу кезінде күтпеген қате орын алды.";
"email_rate_limit_error" = "Бұл әрекетті жиі жасай алмайсыз.";
"email_verify_success" = "Электрондық поштаңыз расталды. Осы әлеуметтік желіде жақсы уақыт өткізуді тілейміз!";
"registration_disabled_info" = "Жүйе әкімшісі тіркеуді өшірді. Мүмкіндігінше, егер досыңыз сайтта тіркелген болса одан шақыру сілтемесін сұраңыз.";
"registration_closed" = "Тіркелу жабық";
"invites_you_to" = "<strong>$1</strong> сізді $2 желіне шақырады.";
@ -216,6 +225,7 @@
"subscriptions" = "Жазылымдар";
"join_community" = "Топқа кіру";
"leave_community" = "Топтан шығу";
"check_community" = "Топты қарау";
"min_6_community" = "Топтың аты тым болмаса 6 әріптен көп болу керек";
"participants" = "Қатысушылар";
"groups" = "Топтар";
@ -232,6 +242,8 @@
"all_followers" = "Барлық жазылмандар";
"only_administrators" = "Тек әкімшілер";
"website" = "Сайт";
"managed" = "Басқаратын";
"size" = "Көлемі";
"administrators_one" = "$1 әкімші";
"administrators_other" = "$1 әкімші";
@ -245,6 +257,7 @@
"hidden_yes" = "Жасырылған: Ия";
"hidden_no" = "Жасырылған: Жоқ";
"group_allow_post_for_everyone" = "Бәріне жазба жазуға рұқсат ету";
"group_hide_from_global_feed" = "Жаһандық арнада жазбаларды көрсетпеу";
"statistics" = "Статистика";
"group_administrators_list" = "Әкімшілер тізімі";
"group_display_only_creator" = "Тек топты құрған кісіні көрсету";
@ -263,10 +276,20 @@
"groups_one" = "$1 топ";
"groups_other" = "$1 топ";
"groups_list_zero" = "Сіз ешқандай топқа қатыспайсыз.";
"groups_list_one" = "Сіз бір топқа қатысасыз";
"groups_list_other" = "Сіз $1 топтардың қатысушысысыз";
"meetings_zero" = "Кездесулер жоқ";
"meetings_one" = "$1 кездесу";
"meetings_other" = "$1 кездесу";
"open_new_group" = "Жаңа топ ашу";
"open_group_desc" = "Өзіңізге сай топ таба алмай жүрсіз бе? Өзіңіз ашып алыңыз!";
"search_group" = "Топ іздеу";
"search_by_groups" = "Топтардан іздеу";
"search_group_desc" = "Мұнда сіз бар топтарға шолу жасай аласыз және қажеттіліктеріңізге сай топты таңдай аласыз...";
/* Albums */
"create" = "Жасау";
@ -294,10 +317,13 @@
"name_note" = "Тақырыбы";
"text_note" = "Мазмұны";
"create_note" = "Жолжазба жазу";
"edit_note" = "Жолжазбаны өндеу";
"actions" = "Әрекеттер";
"feed" = "Жаңалықтар";
"publish_post" = "Жазба қосу";
"edited" = "Өнделген";
"notes_zero" = "Жолжазбалар жоқ";
"notes_one" = "$1 жолжазба";
"notes_other" = "$1 жолжазба";
@ -337,6 +363,7 @@
"left_menu_donate" = "Қайырымдылық жасау";
"footer_about_instance" = "инстанция туралы";
"footer_blog" = "блог";
"footer_help" = "көмек";
"footer_developers" = "әзірлеушілерге";
@ -367,6 +394,8 @@
"cut" = "Кесілген";
"round_avatars" = "Дөңгелектелген";
"apply_style_for_this_device" = "Стильді тек осы девайс үшін қосу";
"search_for_groups" = "Топтар іздеу";
"search_for_people" = "Кісілер іздеу";
"search_button" = "Іздеу";
@ -391,6 +420,7 @@
"ui_settings_rating_hide" = "Жасыру";
"additional_links" = "Қосымша сілтемелер";
"ad_poster" = "Жарнама постері";
"two_factor_authentication" = "Екі факторлы аутентификация";
"two_factor_authentication_disabled" = "Бұзылудан сенімді қорғаныс қызметін қамтамасыз етеді: парақшаға кіру үшін 2FA қосымшасында алынған кодты енгізу керек.";
@ -637,6 +667,14 @@
"banned_2" = "Мұның себебі қарапайым: <b>$1</b>. Өкінішке орай, осы жолы сізді мәңгілікке блоктауға мәжбүр болдық.";
"banned_3" = "Егер сізді қателесіп блоктады деп ойласаңыз, <a href=\"/support?act=new\">қолдау қызметіне жаза аласыз</a> немесе <a href=\"/logout?hash=$1\">жүйеден шыға аласыз</a>.";
/* Registration confirm */
"ec_header" = "Тіркеуді растау";
"ec_title" = "Рахмет!";
"ec_1" = "<b>$1</b>, тіркелуіңіз аяқталуға жақын. Бірнеше минуттан кейін электрондық поштаңызға растау сілтемесі бар хат келуі керек.";
"ec_2" = "Егер қандай да бір себептермен хат келмесе, спам қалтасын тексеріңіз. Хатты ол жерден таппасаңыз, оны қайта жіберуге болады.";
"ec_resend" = "Қайта жіберу";
/* Discussions */
"discussions" = "Талқылаулар";
@ -718,7 +756,7 @@
"invalid_telegram_name" = "Telegram парақшаның аты қате";
"invalid_telegram_name_comment" = "Сіз енгізген Telegram-дағы парақшаның аты дұрыс емес.";
"token_manipulation_error" = "Токенді өңдеу қатесі";
"token_manipulation_error" = "Токенді өндеу қатесі";
"token_manipulation_error_comment" = "Токен жарамсыз немесе мерзімі өтіп кеткен";
"profile_changed" = "Парақша өзгерілді";
@ -758,6 +796,26 @@
"about_openvk" = "OpenVK туралы";
"about_this_instance" = "Инстанция туралы";
"rules" = "Ережелер";
"most_popular_groups" = "Ең танымал топтар";
"on_this_instance_are" = "Осы инстанцияда:";
"about_users_one" = "<b>1</b> қолданушы";
"about_users_other" = "<b>$1</b> қолданушы";
"about_online_users_one" = "<b>1</b> желідегі пайдаланушы";
"about_online_users_other" = "<b>$1</b> желідегі пайдаланушы";
"about_active_users_one" = "<b>1</b> белсенді пайдаланушы";
"about_active_users_other" = "<b>$1</b> белсенді пайдаланушы";
"about_groups_one" = "<b>1</b> топ";
"about_groups_other" = "<b>$1</b> топ";
"about_wall_posts_one" = "<b>1</b> жазба";
"about_wall_posts_other" = "<b>$1</b> жазба";
/* Dialogs */
"ok" = "ОК";

View file

@ -232,6 +232,7 @@
"subscriptions" = "Подписки";
"join_community" = "Вступить в группу";
"leave_community" = "Выйти из группы";
"check_community" = "Просмотр группы";
"min_6_community" = "Название должно быть не менее 6 символов";
"participants" = "Участники";
"groups" = "Группы";
@ -249,6 +250,7 @@
"only_administrators" = "Только администраторы";
"website" = "Сайт";
"managed" = "Управляемые";
"size" = "Размер";
"administrators_one" = "$1 администратор";
"administrators_few" = "$1 администратора";
@ -286,12 +288,22 @@
"groups_many" = "$1 групп";
"groups_other" = "$1 групп";
"groups_list_zero" = "Вы не состоите ни в одной группе";
"groups_list_one" = "Вы состоите в одной группе";
"groups_list_other" = "Вы состоите в $1 группах";
"meetings_zero" = "Ни одной встречи";
"meetings_one" = "Одна встреча";
"meetings_few" = "$1 встречи";
"meetings_many" = "$1 встреч";
"meetings_other" = "$1 встреч";
"open_new_group" = "Открыть новую группу";
"open_group_desc" = "Не можете найти нужную группу? Откройте свою...";
"search_group" = "Поиск группы";
"search_by_groups" = "Поиск по группам";
"search_group_desc" = "Здесь Вы можете просмотреть существующие группы и выбрать группу себе по вкусу...";
/* Albums */
"create" = "Создать";
@ -320,9 +332,10 @@
"note" = "Заметка";
"name_note" = "Название";
"text_note" = "Содержание";
"create_note" = "Создать заметку";
"create_note" = "Добавить запись";
"edit_note" = "Редактировать заметку";
"actions" = "Действия";
"notes_start_screen" = "С помощью заметок Вы можете делиться событиями из жизни с друзьями, а так же быть в курсе того, что происходит у них.";
"edited" = "Отредактировано";
@ -332,6 +345,12 @@
"notes_many" = "$1 заметок";
"notes_other" = "$1 заметок";
"notes_list_zero" = "Не найдено ни одной заметки";
"notes_list_one" = "Найдена одна заметка";
"notes_list_few" = "Найдено $1 заметки";
"notes_list_many" = "Найдено $1 заметок";
"notes_list_other" = "Найдено $1 заметок";
/* Menus */
"edit_button" = "ред.";
@ -498,7 +517,7 @@
"info_uploaded_by" = "Загрузил";
"info_upload_date" = "Дата загрузки";
"videos_zero" = "Ни одной видеозаписи";
"videos_zero" = "Нет видео";
"videos_one" = "Одна видеозапись";
"videos_few" = "$1 видеозаписи";
"videos_many" = "$1 видеозаписей";
@ -590,6 +609,21 @@
"receiver_not_found" = "Получатель не найден.";
"you_dont_have_enough_points" = "У вас недостаточно голосов.";
"increase_rating" = "Повысить рейтинг";
"increase_rating_button" = "Повысить";
"to_whom" = "Кому";
"increase_by" = "Повысить на";
"price" = "Стоимость";
"you_have_unused_votes" = "У Вас $1 неиспользованных голоса на балансе.";
"apply_voucher" = "Применить ваучер";
"failed_to_increase_rating" = "Не удалось повысить рейтинг";
"rating_increase_successful" = "Вы успешно повысыли рейтинг <b><a href=\"$1\">$2</a></b> на <b>$3%</b>.";
"negative_rating_value" = "Мы не можем украсть рейтинг у другого человека, извините.";
"increased_your_rating_by" = "повысил ваш рейтинг на";
/* Gifts */
"gift" = "Подарок";
@ -773,6 +807,8 @@
"invalid_email_address" = "Неверный Email адрес";
"invalid_email_address_comment" = "Email, который вы ввели, не является корректным.";
"invalid_real_name" = "Пожалуйста, используйте реальные имена. Так вашим тульпам будет легче найти вас.";
"invalid_telegram_name" = "Неверное имя Telegram аккаунта";
"invalid_telegram_name_comment" = "Вы ввели неверное имя аккаунта Telegram.";